Initialize Vue App Laravel
August 8, 2025 · AI Conversations
but now want to initials creatApp once for all 3 pages
### ✅ Wrap all pages with a single Blade layout
Create a common layout (e.g., `resources/views/layouts/app.blade.php`) where you mount the Vue app once.
—
### ✅ Step-by-step Setup
#### 1. **Create a Vue entry point**
Your entry file might be like:
`resources/js/survey-app.js`
import { createApp } from 'vue';
import SurveyIndex from './components/surveys/SurveyIndex.vue';
import SurveyCreate from './components/surveys/SurveyCreate.vue';
import SurveyEdit from './components/surveys/SurveyEdit.vue';
const app = createApp({});
const pageComponent = document.getElementById('vue-page')?.dataset.component;
switch (pageComponent) {
case 'SurveyIndex':
app.component('survey-index', SurveyIndex);
break;
case 'SurveyCreate':
app.component('survey-create', SurveyCreate);
break;
case 'SurveyEdit':
app.component('survey-edit', SurveyEdit);
break;
}
app.mount('#vue-app');
—
#### 2. **Create one app div in Blade layout**
In `layouts/app.blade.php`:
<!DOCTYPE html>
<html>
<head>
<title>Survey Management</title>
@vite('resources/js/survey-app.js')
</head>
<body>
<div id="vue-app">
@yield('content')
</div>
</body>
</html>
—
#### 3. **Load pages with dynamic component flags**
In each view:
**index.blade.php**
@extends('layouts.app')
@section('content')
<div id="vue-page" data-component="SurveyIndex">
<survey-index></survey-index>
</div>
@endsection
**create.blade.php**
@extends('layouts.app')
@section('content')
<div id="vue-page" data-component="SurveyCreate">
<survey-create></survey-create>
</div>
@endsection
**edit.blade.php**
@extends('layouts.app')
@section('content')
<div id="vue-page" data-component="SurveyEdit">
<survey-edit :survey-id="{{ $survey->id }}"></survey-edit>
</div>
@endsection
—
This pattern allows you to:
– Mount Vue only once per request.
– Dynamically render the appropriate component.
– Maintain clean separation of Blade views and Vue components.
Let me know if you’re using **Inertia**, or want to refactor to a **SPA**, the setup would change slightly.
Here’s how to do that properly:
—
### ✅ Goal
**Mount the Vue app once**, and based on the page, load and render the right component inside a placeholder div.
—
### ✅ Suggested Setup
#### 1. **One Vue Entry Point** (`resources/js/survey-app.js`)
import { createApp } from 'vue';
import SurveyIndex from './components/surveys/SurveyIndex.vue';
import SurveyCreate from './components/surveys/SurveyCreate.vue';
import SurveyEdit from './components/surveys/SurveyEdit.vue';
const components = {
SurveyIndex,
SurveyCreate,
SurveyEdit,
};
document.addEventListener('DOMContentLoaded', () => {
const el = document.querySelector('[data-vue-survey]');
if (el) {
const componentName = el.dataset.vueSurvey;
const props = JSON.parse(el.dataset.vueProps || '{}');
const app = createApp(components[componentName], props);
app.mount(el);
}
});
—
#### 2. **Include the Vue App Once**
In `app.blade.php` or just inside your page footer (loaded on all pages that might use Vue):
@vite('resources/js/survey-app.js')
—
#### 3. **Use a Shared Mounting Point**
In your views:
**index.blade.php**
<div data-vue-survey="SurveyIndex"></div>
**create.blade.php**
<div data-vue-survey="SurveyCreate"></div>
**edit.blade.php**
<div
data-vue-survey="SurveyEdit"
data-vue-props='@json(["surveyId" => $survey->id])'>
</div>
—
### 🔥 Summary
– You include the Vue script **once**.
– You only mount the app **if the `[data-vue-survey]` attribute exists**.
– You dynamically **load the Vue component based on the attribute value**.
—
Want to load components lazily (via `import()`), or support multiple Vue zones per page? Let me know — I can help expand it too.
### ✅ Goal:
Initialize Vue **once globally**, and mount only if a Vue container exists in the current page (for example, a `#survey-app` div).
—
### ✅ Clean Setup for Section-Based Vue Mounting
#### 1. **Vue Entry File** (`resources/js/survey-app.js`)
import { createApp } from 'vue'
import SurveyIndex from './components/surveys/SurveyIndex.vue'
import SurveyCreate from './components/surveys/SurveyCreate.vue'
import SurveyEdit from './components/surveys/SurveyEdit.vue'
const mountEl = document.getElementById('survey-app')
if (mountEl) {
const component = mountEl.dataset.component
const props = JSON.parse(mountEl.dataset.props || '{}')
const components = {
SurveyIndex,
SurveyCreate,
SurveyEdit,
}
const app = createApp(components[component], props)
app.mount('#survey-app')
}
—
#### 2. **Include the Vue Script Once**
In your master Blade layout (like `layouts/app.blade.php`) or directly at the bottom of the 3 survey Blade views:
@vite('resources/js/survey-app.js')
—
#### 3. **Use the Mounting Div Where Needed**
**In `survey/index.blade.php`:**
<div id="survey-app" data-component="SurveyIndex"></div>
**In `survey/create.blade.php`:**
<div id="survey-app" data-component="SurveyCreate"></div>
**In `survey/edit.blade.php`:**
<div id="survey-app"
data-component="SurveyEdit"
data-props='@json(["surveyId" => $survey->id])'>
</div>
—
### ✅ What This Does
– ✅ Vue is **included only once**, globally.
– ✅ Vue **only initializes** if the `#survey-app` is present.
– ✅ The correct Vue component is loaded dynamically.
– ✅ Keeps Blade structure clean and minimal.
—
Let me know if you want to **lazy-load components**, or use multiple mount points on the same page!
@extends('layouts.admin')
@section('plugins.Summernote', true)
@section('plugins.icheckBootstrap', true)
@section('plugins.TempusDominusBs4', true)
{{– Customize layout sections –}}
@section('subtitle', __('Edit') . '-' . __('Customer'))
{{– Content body: main page content –}}
@section('content_body')
{{ __('Edit Customer') }}
@if ($errors->any())
@foreach ($errors->all() as $error)
{{ $error }}
@endforeach
@endif
@if (session()->has('message.level'))
{!! session('message.content') !!}
@endif
{{– tabs –}}
{{ __('Customer Details') }}
{{ __('Survey') }}
{{ __('Participants') }}
{{–
Survey Results
–}}
{{– tabs end –}}
{{– tab content –}}
@csrf
@method('patch')
@php
$salutations = config('const.salutations');
$industries=config('const.industries');
$langs=getLanguages();
@endphp
{{ __('Select Salutation') }}
@foreach ($salutations as $key => $val)
salutation) == $key ? 'selected' : '' }}>
{{ $val }}
@endforeach
{{ __('Select Industry') }}
@foreach ($industries as $key => $val)
industry) ? 'selected' : '' }}>
{{ $val }}
@endforeach
{{ __('Select Language') }}
@foreach ($langs as $key => $val)
code == old('language', $client->language) ? 'selected' : '' }}>
{{ $val->title }}
@endforeach
{{ __('URL') }}
{{ route('client.url', $client->url_id) }}
{{ __('QRCODE') }}
{{ __('Cancel') }}
{{ __('Update') }}
{{ __('Send Email') }}
{{– form end –}}
{{– survey –}}
@php
$survey = $current_survey?->survey;
@endphp
{{ __('Survey') }}
{{– @if (!$current_survey)
{{ __('Assign Survey') }}
@endif –}}
@if (session()->has('message.level'))
{!! session('message.content') !!}
@endif
{{ __('Survey Name') }}
{{ __('Start Date') }}
{{ __('Duration') }}
{{ __('Action') }}
@csrf
Select Survey
@foreach ($surveys as $k => $v)
survey_id ?? '') ? 'selected' : '' }}>
{{ $v }}
@endforeach
@php
$config = ['format' => 'YYYY-MM-DD'];
@endphp
{{–
–}}
Select Duration
survey_duration ?? '') ? 'selected' : '' }}>
3
survey_duration ?? '') ? 'selected' : '' }}>
5
survey_duration ?? '') ? 'selected' : '' }}>
7
@if ($disable_survey_btn)
{{ __('Note') }}:
{{ __('You can no longer update survey start date and duration after it has started') }}.
@endif
{{– survey end –}}
{{– participants –}}
@php
$participants = $client->participants;
@endphp
Participants
{{ __('Add Participants') }}
Salutation
First Name
Last Name
Email
Position
Language
Survey Status
Action
@if ($participants->isNotEmpty())
@foreach ($participants as $data)
{{ $data->salutation }}
{{ $data->first_name }}
{{ $data->last_name }}
{{ $data->email }}
{{ $data->position }}
{{ $data->language == 'en' ? 'English' : 'German' }}
@php
$surveyData = AppServicesClientService::surveyStatusByParticipant( $client->id,$data->id,);
@endphp
{{ $surveyData['status'] }}
@endforeach
@else
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
@endif
{{– participants end –}}
{{– tab content end –}}
@stop
@push('css')
<style>
.copy-icon {
cursor: pointer;
font-size: 24px;
color: #222;
}
</style>
@endpush
{{– Push extra scripts –}}
@push('js')
<script>
const client = "{{ $client->url_id }}";
const activeTabID = `activeTab-${client}`;
$(document).on('shown.bs.tab', 'a[data-toggle="tab"]', function(e) {
localStorage.setItem(activeTabID, e.target.id);
});
$(document).ready(function() {
$('.nav-link').removeClass('active');
var activeTab = localStorage.getItem(activeTabID);
console.log(activeTab)
var tab = "{{ $tab }}";
if(tab){
$(`#${tab}`).tab('show');
}else{
if (activeTab) {
$(`#${activeTab}`).tab('show');
} else {
$(`#client-tab`).tab('show');
}
}
});
$('#survey_save').on('click', function() {
var validated = validateSurvey();
if (!validated) return false;
$('#survey_save').prop('disabled', true);
var surveyId = $(this).data('surveyid');
var startDate = $('#survey_start_date').val();
var duration = $('#survey_duration').val();
$.ajax({
url: "/admin/survey/order-save",
type: "POST",
data: {
_token: "{{ csrf_token() }}",
survey_id: surveyId,
start_date: startDate,
survey_duration: duration
},
success: function(data) {
$('#survey_message').removeClass('text-success text-danger');
$('#survey_message').empty();
if (data.success) {
$('#survey_message').addClass('text-success');
$('#survey_message').text(data.message);
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
$('#survey_message').addClass('text-danger');
$('#survey_message').text(data.message);
}
$('#survey_save').prop('disabled', false);
}
});
});
function validateSurvey() {
var startDate = $('#survey_start_date').val();
var duration = $('#survey_duration').val();
if (!startDate || !duration) {
$('#survey_message').addClass('text-danger');
$('#survey_message').text('Please select survey start date and duration.');
return false;
} else {
return true;
}
}
jQuery(document).ready(function () {
jQuery(document).on('click', '.show-alert-delete-box', function (event) {
var id = jQuery(this).data('itemid');
var that = this;
event.preventDefault();
Swal.fire({
title: "{{ __('Are you sure?') }}",
text: "{{ __('You won't be able to revert this!') }}",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Delete',
cancelButtonText: 'Cancel'
}).then((result) => {
if (result.isConfirmed) {
jQuery.ajax({
url: "{{ route('admin.participants.destroy', ['__id__']) }}".replace('__id__', id),
type: "DELETE",
dataType: "json",
data: {
"_token": "{{ csrf_token() }}"
},
beforeSend: function () {
jQuery('.loader-section').show();
},
complete: function () {
jQuery('.loader-section').hide();
},
success: function (data) {
if (data.result == "success") {
jQuery('.row_' + id).remove();
Swal.fire({
title: data.message,
icon: 'success'
});
} else {
Swal.fire({
title: "{{ __('Error Occurred: Please try again.') }}",
color: '#dc3545',
icon: 'error'
});
}
},
error: function () {
Swal.fire({
title: "{{ __('Error Occurred: Please try again.') }}",
icon: 'error'
});
}
});
}
});
});
});
function copyURL(event) {
const id = event.target.dataset['id'];
const ele = document.getElementsByClassName('url_input_' + id)[0];
const url = ele.value;
navigator.clipboard.writeText(url);
Swal.fire({
title: 'URL Copied',
icon: 'success'
});
}
function editSurvey() {
const editUrl = "{{ route('admin.survey.create.order', $client->url_id) }}";
window.location = editUrl;
}
const disableSurveyControls = "{{ $disable_survey_btn }}";
</script>
@endpush
@extends('layouts.admin')
@section('plugins.Summernote', true)
@section('plugins.icheckBootstrap', true)
@section('plugins.TempusDominusBs4', true)
{{– Customize layout sections –}}
@section('subtitle', __('Edit – Survey'))
{{– Content body: main page content –}}
@section('content_body')
{{ __('Edit Survey') }}
@if($errors->any())
@foreach ($errors->all() as $error)
{{ $error }}
@endforeach
@endif
@if(session()->has('message.level'))
{!! session('message.content') !!}
@endif
Loading…
{{ __('Loading survey creation…') }}
1. Survey
2. Pillers
3. Questions
{{ __('Create Survey') }}
Name
@{{ surveyError }}
{{ __('Allow comments on Survey') }}
{{ __('Allow comments on Pillers') }}
{{ __('Mark as External') }}
{{ __('Save and Next') }}
{{ __('Edit Pillers') }}
{{ __('Piller') }} @{{ index + 1 }}
{{ __('Title') }} (@{{ lang.code.toUpperCase() }})
{{ __('Weight') }}
{{ __('Position') }}
{{ __('Add More Pillers') }}
{{ __('Save and Next') }}
{{ __('Add Questions') }}
{{ __('Select Piller') }}
{{ __('– Select Piller –') }}
@{{ pill.titles.en }}
@{{ getPillerTitle(selectedPillerId) }}
{{ __('Title') }} (@{{ lang.code.toUpperCase() }})
{{ __('Position') }}
{{ __('Add More Question') }}
{{ __('Submit All Questions') }}
@stop
{{– Push extra scripts –}}
@push('js')
<script>
window.surveyData = @json($survey);
console.log(window.surveyData);
window.pillarsData = @json($pillars);
console.log(window.pillarsData);
window.questionsData = @json($questions);
console.log(window.questionsData);
window.availableLanguages = @json(getLanguages());
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
</script>
<script>
import { createApp } from 'https://unpkg.com/vue@3.5.13/dist/vue.esm-browser.prod.js'
document.addEventListener('DOMContentLoaded', () => {
const app = createApp({
data() {
return {
message: 'Hello Vue!',
loading: false,
step: 1,
survey: {
name: window.surveyData.name || '',
options: []
},
surveyError: '',
survey_id: window.surveyData.id || null,
availableLanguages: window.availableLanguages || [],
pillers: window.pillarsData || [{
titles: {},
weight: '',
position: ''
}],
selectedPillerId: '',
questions: window.questionsData || {},
}
},
mounted() {
// Initialize options from booleans
if (window.surveyData?.allow_comments_survey) {
this.survey.options.push('allow_comments');
}
if (window.surveyData?.allow_comments_pillars) {
this.survey.options.push('allow_comments_on_pillers');
}
if (window.surveyData?.external) {
this.survey.options.push('mark_as_external');
}
// Init first piller titles
if (this.pillers.length > 0) {
this.pillers.forEach(piller => {
this.availableLanguages.forEach(lang => {
if (!piller.titles[lang.code]) {
piller.titles[lang.code] = '';
}
});
});
}
},
methods: {
onPillerChange() {
if (this.selectedPillerId && !this.questions[this.selectedPillerId]) {
this.questions[this.selectedPillerId] = [];
}
// If there are no questions yet for this pillar, initialize with one
if (!this.questions[this.selectedPillerId] || this.questions[this.selectedPillerId].length === 0) {
const newQuestion = {
id: null, // in case you support editing
titles: {},
position: ''
};
this.availableLanguages.forEach(lang => {
newQuestion.titles[lang.code] = '';
});
this.questions[this.selectedPillerId] = [newQuestion];
}
},
getPillerTitle(id) {
const piller = this.pillers.find(p => p.id == id);
return piller ? piller.titles.en : '';
},
addQuestion() {
if (!this.questions[this.selectedPillerId]) {
this.questions[this.selectedPillerId] = [];
}
if (this.questions[this.selectedPillerId].length >= 10) return;
const newQuestion = {
titles: {},
position: ''
};
this.availableLanguages.forEach(lang => {
newQuestion.titles[lang.code] = '';
});
this.questions[this.selectedPillerId].push(newQuestion);
},
removeQuestion(index) {
this.questions[this.selectedPillerId].splice(index, 1);
},
goToStep(step) {
if (step === 2 && !this.survey_id) return;
if (step === 3 && this.pillers.every(p => !p.id)) return;
this.step = step;
if (step === 3 && !this.selectedPillerId && this.pillers.length > 0) {
this.selectedPillerId = this.pillers[0].id;
// Initialize questions array for selected pillar if not already present
if (!this.questions[this.selectedPillerId]) {
this.questions[this.selectedPillerId] = [];
}
}
},
addPiller() {
if (this.pillers.length >= 5) return;
const newPiller = {
titles: {},
weight: '',
position: ''
};
this.availableLanguages.forEach(lang => {
newPiller.titles[lang.code] = '';
});
this.pillers.push(newPiller);
},
removePiller(index) {
this.pillers.splice(index, 1);
},
async submitSurvey() {
this.surveyError = '';
if (!this.survey.name) {
this.surveyError = 'Survey name is required.';
this.survey.name = window.surveyData?.name || '';
return;
}
const data = {
name: this.survey.name,
allow_comments: this.survey.options.includes('allow_comments') ? 1 : 0,
allow_comments_on_pillers: this.survey.options.includes('allow_comments_on_pillers') ? 1 : 0,
mark_as_external: this.survey.options.includes('mark_as_external') ? 1 : 0,
};
this.loading = true;
try {
fetch(`{{ route('admin.surveys.update', ['survey' => ':id']) }}`.replace(':id', this.survey_id), {
method: "PUT",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": csrfToken
},
body: JSON.stringify(data)
})
.then(res => res.json().then(json => ({ status: res.status, body: json })))
.then(({ status, body }) => {
if (status === 200 && body.success) {
this.survey_id = body.survey_id;
Swal.fire({
icon: 'success',
title: "{{ __('Survey saved successfully!') }}",
showConfirmButton: false,
timer: 1500
});
this.step = 2;
} else {
Swal.fire({
icon: 'error',
title: "{{ __('Validation Error') }}",
text: body.message || "{{ __('Please check your input.') }}"
});
}
})
.catch(err => {
Swal.fire({
icon: 'error',
title: "{{ __('Unexpected Error') }}",
text: "{{ __('Something went wrong while saving the survey.') }}"
});
});
} catch (error) {
Swal.fire({
icon: 'error',
title: "{{ __('Unexpected Error') }}",
text: "{{ __('Something went wrong while saving the survey.') }}"
});
} finally {
this.loading = false;
}
},
submitPillers() {
if (this.pillers.length !== 5) {
Swal.fire('Validation Error', 'You must add exactly 5 Pillers to continue.', 'warning');
return;
}
// Validate each piller
for (let [i, piller] of this.pillers.entries()) {
for (let lang of this.availableLanguages) {
if (!piller.titles[lang.code]) {
Swal.fire('Validation Error', `Piller ${i + 1}: Title required for ${lang.title}`, 'error');
return;
}
}
if (!piller.weight || !piller.position) {
Swal.fire('Validation Error', `Piller ${i + 1}: Weight and Position are required`, 'error');
return;
}
}
const data = {
pillars: this.pillers
};
this.loading = true;
try {
fetch(`{{ route('admin.pillars.updateAll', ['survey' => ':id']) }}`.replace(':id', this.survey_id), {
method: 'PUT',
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": csrfToken
},
body: JSON.stringify(data)
})
.then(res => res.json().then(json => ({ status: res.status, body: json })))
.then(({ status, body }) => {
if (status === 200 && body.success) {
// Save returned piller_ids if needed
this.pillers = body.pillers;
this.selectedPillerId = this.pillers[0].id;
Swal.fire({
icon: 'success',
title: "{{ __('Pillers saved successfully!') }}",
showConfirmButton: false,
timer: 1500
});
this.step = 3;
} else {
Swal.fire({
icon: 'error',
title: "{{ __('Validation Error') }}",
text: body.message || "{{ __('Please check your input.') }}"
});
}
})
.catch(() => {
Swal.fire({
icon: 'error',
title: "{{ __('Unexpected Error') }}",
text: "{{ __('Something went wrong while saving the Pillers.') }}"
});
});
} catch (error) {
Swal.fire({
icon: 'error',
title: "{{ __('Unexpected Error') }}",
text: "{{ __('Something went wrong while saving the Pillers.') }}"
});
} finally {
this.loading = false;
}
},
submitQuestions() {
const payload = {
questions: []
};
for (let piller of this.pillers) {
const list = this.questions[piller.id] || [];
if (list.length < 1) {
Swal.fire('Validation Error', `Please add at least 1 question for "${piller.titles.en}"`, 'warning');
return;
}
for (let q of list) {
for (let lang of this.availableLanguages) {
if (!q.titles[lang.code]) {
Swal.fire('Validation Error', `All languages required for a question in "${piller.titles.en}"`, 'error');
return;
}
}
if (!q.position) {
Swal.fire('Validation Error', `Position is required for a question in "${piller.titles.en}"`, 'error');
return;
}
payload.questions.push({
id: q.id || null,
piller_id: piller.id,
titles: q.titles,
position: q.position
});
}
}
fetch("{{ route('admin.questions.updateAll', ['survey' => ':id']) }}".replace(':id', this.survey_id), {
method: "PUT",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": csrfToken
},
body: JSON.stringify(payload)
})
.then(res => res.json().then(json => ({ status: res.status, body: json })))
.then(({ status, body }) => {
if (status === 200 && body.success) {
Swal.fire({
icon: 'success',
title: "{{ __('Questions saved successfully!') }}",
showConfirmButton: false,
timer: 1500
});
window.location.href = "{{ route('admin.surveys.index') }}";
} else {
Swal.fire({
icon: 'error',
title: "{{ __('Validation Error') }}",
text: body.message || "{{ __('Please check your input.') }}"
});
}
})
.catch(() => {
Swal.fire({
icon: 'error',
title: "{{ __('Unexpected Error') }}",
text: "{{ __('Something went wrong while saving the questions.') }}"
});
});
},
deletePiller(id, index) {
if (!id) {
// Not saved to backend yet, just remove from local list
this.pillers.splice(index, 1);
return;
}
Swal.fire({
title: `{{ __('Are you sure?') }}`,
text: `{{ __('This will permanently delete the pillar and its questions!') }}`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
confirmButtonText: `{{ __('Delete') }}`,
cancelButtonText: `{{ __('Cancel') }}`
}).then((result) => {
if (result.isConfirmed) {
fetch(`{{ route('admin.pillars.destroy', ['pillar' => ':id']) }}`.replace(':id', id), {
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': csrfToken
}
})
.then(res => res.json())
.then(data => {
if (data.success) {
this.pillers.splice(index, 1);
delete this.questions[id]; // clean up related questions
Swal.fire({
icon: 'success',
title: `{{ __('Deleted!') }}`,
text: data.message,
showConfirmButton: false,
timer: 1500
});
} else {
Swal.fire({
icon: 'error',
title: `{{ __('Error') }}`,
text: data.message || 'Failed to delete pillar.',
showConfirmButton: false,
timer: 1500
});
}
})
.catch(() => {
Swal.fire('Error', 'Something went wrong.', 'error');
});
}
});
},
deleteQuestion(id, index) {
if (!id) {
// Not saved to backend yet, just remove from local list
this.questions[this.selectedPillerId].splice(index, 1);
return;
}
Swal.fire({
title: `{{ __('Are you sure?') }}`,
text: `{{ __('This will permanently delete the question!') }}`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
confirmButtonText: `{{ __('Delete') }}`,
cancelButtonText: `{{ __('Cancel') }}`
}).then((result) => {
if (result.isConfirmed) {
fetch(`{{ route('admin.questions.destroy', ['question' => ':id']) }}`.replace(':id', id), {
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': csrfToken
}
})
.then(res => res.json())
.then(data => {
if (data.success) {
this.questions[this.selectedPillerId].splice(index, 1);
Swal.fire({
icon: 'success',
title: `{{ __('Deleted!') }}`,
text: data.message,
showConfirmButton: false,
timer: 1500
});
} else {
Swal.fire({
icon: 'error',
title: `{{ __('Error') }}`,
text: data.message || `{{ __('Failed to delete question.') }}`,
showConfirmButton: false,
timer: 1500
});
}
})
.catch(() => {
Swal.fire('Error', `{{ __('Something went wrong.') }}`, 'error');
});
}
});
},
}
});
app.mount('#surveyApp');
// Hide loading spinner after mount
const loadingDiv = document.getElementById('vue-loading');
if (loadingDiv) {
loadingDiv.classList.add('fade-out');
setTimeout(() => loadingDiv.remove(), 500); // fully remove from DOM after fade
}
});
</script>
@endpush
—
### ✅ Recommended Setup
#### 1. **Global Vue Entry File**
Create one global Vue entry file — e.g., `resources/js/survey-section.js`:
import { createApp } from 'vue'
import SurveyEditComponent from './components/surveys/Edit.vue'
import SurveyCreateComponent from './components/surveys/Create.vue'
// import SurveyIndexComponent from './components/surveys/Index.vue' if needed
const mountEl = document.getElementById('surveyApp');
if (mountEl) {
const component = mountEl.dataset.component;
const props = mountEl.dataset.props ? JSON.parse(mountEl.dataset.props) : {};
const components = {
SurveyEditComponent,
SurveyCreateComponent,
// SurveyIndexComponent
};
const app = createApp(components[component], props);
app.mount('#surveyApp');
}
—
#### 2. **Include Once in Blade**
Push it to all pages that might need it via `@vite('resources/js/survey-section.js')` or inline in the layout with:
@push('js')
@vite('resources/js/survey-section.js')
@endpush
—
#### 3. **Use Smart Blade Mounting**
In your Blade where Vue is needed, just do this:
<div id="surveyApp" data-component="SurveyEditComponent" data-props='@json($yourProps)'></div>
or for create:
<div id="surveyApp" data-component="SurveyCreateComponent"></div>
You **don’t need to write `createApp()` logic inside the Blade anymore** — your main file handles it centrally.
—
### ✅ Result
– One `createApp()` call, shared by all pages.
– No repeated logic in every Blade file.
– Blade only provides the mount target + dynamic component + optional props.
– No wasteful duplication — super clean separation.
—
Let me know if you want to extract `Edit.vue` logic from that inline “ block into a proper `.vue` component — I can help modularize that too.
we are using resources/views folder
—
### ✅ Your Goal:
You want to avoid repeating `createApp()` in every page like `edit`, `create`, `index`. You want a **single global Vue initialization script**, but still work with different views/blades.
—
### ✅ Solution: Use a **generic global script** in your layout and load inline components conditionally per page.
—
### ✅ Step-by-Step
#### 1. ✅ Create a shared global Vue loader (example: `resources/views/vendor/includes/vue-loader.blade.php`)
<script type="module">
import { createApp } from 'https://unpkg.com/vue@3.5.13/dist/vue.esm-browser.prod.js'
document.addEventListener('DOMContentLoaded', () => {
const el = document.querySelector('[data-vue-app]')
if (!el) return;
const componentName = el.dataset.component
const props = JSON.parse(el.dataset.props || '{}')
const components = {
SurveyAppComponent: {
// You can optionally load inline component here
template: el.innerHTML,
data() { return props }
}
}
const app = createApp(components[componentName])
app.mount('[data-vue-app]')
})
</script>
—
#### 2. ✅ In your Blade layout (`layouts/admin.blade.php` or wherever), include it once:
@include('vendor.includes.vue-loader')
—
#### 3. ✅ On your `edit.blade.php`, inject the target like this:
<div data-vue-app data-component="SurveyAppComponent" data-props='@json([
"survey_id" => $survey->id,
"pillars" => $pillars,
"questions" => $questions,
"availableLanguages" => getLanguages()
])'>
<!-- Your Vue template here -->
</div>
> Now this “ acts like a lightweight inline component.
—
### 💡 BONUS: Move inline “ logic into separate Blade include if it's big
<div data-vue-app data-component="SurveyAppComponent" data-props='...'>
@include('admin.survey.partials.vue-survey-template')
</div>
—
### ✅ Result:
– Only one place initializes Vue (`vue-loader.blade.php`)
– Each page only defines a `div` with the Vue template + props
– Clean and DRY — no repeating `createApp()` logic
—
Let me know if you'd like to go **one step further** and dynamically load `.vue` files from CDN too — it’s possible with `defineAsyncComponent`.
I think we need to create like add a seperate layout for survey and then include it in all create/edit/index
You’re thinking **exactly in the right direction**:
✅ **Create a reusable Blade layout specifically for Survey pages** — that way you **only initialize `createApp()` once**, and reuse it for all Survey views like `create`, `edit`, and `index`.
Let me show you how to do it clearly and cleanly:
—
## ✅ Step-by-Step: Use a Shared Survey Layout
### 1. ✅ Create `resources/views/layouts/survey.blade.php`
{{-- layouts/survey.blade.php --}}
@extends('layouts.admin')
@section('content_body')
{{-- Shared Vue mount --}}
<div id="surveyApp"
data-component="{{ $vueComponent }}"
data-props='@json($vueProps ?? [])'>
@yield('survey_vue_template')
</div>
@endsection
@push('js')
<script type="module">
import { createApp } from 'https://unpkg.com/vue@3.5.13/dist/vue.esm-browser.prod.js';
document.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('surveyApp');
if (!el) return;
const componentName = el.dataset.component;
const props = JSON.parse(el.dataset.props || '{}');
const components = {
SurveyEditComponent: {
template: el.innerHTML,
data() { return props }
},
SurveyCreateComponent: {
template: el.innerHTML,
data() { return props }
},
// Add more if needed
};
const app = createApp(components[componentName]);
app.mount('#surveyApp');
});
</script>
@endpush
—
### 2. ✅ In your `edit.blade.php`
@extends('layouts.survey')
@php
$vueComponent = 'SurveyEditComponent';
$vueProps = [
'survey_id' => $survey->id,
'pillars' => $pillars,
'questions' => $questions,
'availableLanguages' => getLanguages(),
];
@endphp
@section('subtitle', __('Edit Survey'))
@section('survey_vue_template')
{{-- Vue inline template goes here --}}
@include('admin.survey.partials.vue-template')
@endsection
—
### 3. ✅ Similarly for `create.blade.php`:
@extends('layouts.survey')
@php
$vueComponent = 'SurveyCreateComponent';
$vueProps = [
'availableLanguages' => getLanguages(),
];
@endphp
@section('subtitle', __('Create Survey'))
@section('survey_vue_template')
@include('admin.survey.partials.vue-template')
@endsection
—
## ✅ Summary:
| Benefit | Outcome |
|——–|———|
| 🔄 DRY | You initialize Vue only once in `layouts/survey.blade.php` |
| 🎯 Clean | Each page just sets `@section('survey_vue_template')` and props |
| ⚙️ Scalable | Easy to extend: add more components in `components` map later |
—
Would you like help splitting your current `edit.blade.php` Vue logic into a partial Blade template (`vue-template.blade.php`) for use with this setup?
—
## ❓ Problem:
You’re using a **common layout and single Vue mount**, but:
– `create`, `edit`, and `index` have **different Vue logic/steps/data/methods**.
– So how do we make sure **each page handles its own Vue operations independently**?
—
## ✅ Solution:
You still use **one Vue app mount**, but pass a **different inline Vue component** per page (like `SurveyCreateComponent`, `SurveyEditComponent`, etc.), each with its own:
– `data()`
– `methods`
– `template`
– `mounted()`
And this is all still **inside Blade** — no `.vue` files, no Vite.
—
### 💡 Option 1: Define Vue components inline per page
#### 🔁 In `layouts/survey.blade.php` (already done):
<div id="surveyApp"
data-component="{{ $vueComponent }}"
data-props='@json($vueProps ?? [])'>
@yield('survey_vue_template')
</div>
—
#### ✨ In `edit.blade.php`
@extends('layouts.survey')
@php
$vueComponent = 'SurveyEditComponent';
$vueProps = [
'survey_id' => $survey->id,
'pillars' => $pillars,
'questions' => $questions,
'availableLanguages' => getLanguages(),
];
@endphp
@section('survey_vue_template')
<script type="text/x-template">
<div>
<h4>Edit Survey</h4>
<input v-model="survey_name" class="form-control" placeholder="Survey Name">
<button @click="save">Save</button>
</div>
</script>
@endsection
@push('js')
<script type="module">
const SurveyEditComponent = {
template: document.querySelector('[data-component="SurveyEditComponent"]').innerHTML,
data() {
return {
survey_name: this.survey_id ? 'Loaded Survey ' + this.survey_id : '',
survey_id: this.survey_id
}
},
methods: {
save() {
console.log('Saving survey', this.survey_id, this.survey_name)
// Add fetch/axios logic here
}
}
}
window.SurveyEditComponent = SurveyEditComponent;
</script>
@endpush
—
#### 🔥 In `create.blade.php`, you just swap:
$vueComponent = 'SurveyCreateComponent';
and define:
@section('survey_vue_template')
<script type="text/x-template">
<div>
<h4>Create Survey</h4>
<input v-model="name" class="form-control" placeholder="New Survey Name">
<button @click="create">Create</button>
</div>
</script>
@endsection
@push('js')
<script type="module">
const SurveyCreateComponent = {
template: document.querySelector('[data-component="SurveyCreateComponent"]').innerHTML,
data() {
return {
name: ''
}
},
methods: {
create() {
console.log('Creating survey', this.name)
// Add AJAX logic
}
}
}
window.SurveyCreateComponent = SurveyCreateComponent;
</script>
@endpush
—
### ✅ Why this works
| Problem | Solved By |
|——–|———–|
| Different logic per page | Each page defines its own component name + code |
| Only one `createApp()` | Handled in the shared layout |
| No duplication | You DRY up the app logic, but keep custom behavior |
—
If you prefer, I can help you refactor your current `edit` survey page into this pattern with working code.
Want that?
@section('plugins.Datatables', true)
@section('plugins.Sweetalert2', true)
{{– Customize layout sections –}}
@section('subtitle', 'Surveys')
{{– Content body: main page content –}}
@section('content_body')
{{ __('Surveys') }}
{{ __('Add Survey') }}
@if ($errors->any())
@foreach ($errors->all() as $error)
{{ $error }}
@endforeach
@endif
@if (session()->has('message.level'))
{!! session('message.content') !!}
@endif
@if (session()->get('success'))
{{ session()->get('success') }}
@endif
@if (session()->get('danger'))
{{ session()->get('danger') }}
@endif
{{ __('Survey Title') }}
{{ __('Allow comments on Survey') }}
{{ __('Allow comments on Pillars') }}
{{ __('Mark as External') }}
{{ __('Action') }}
@{{ survey.name }}
@{{ survey.allow_comments_survey == 1 ? 'Yes' : 'No' }}
@{{ survey.allow_comments_pillars == 1 ? 'Yes' : 'No' }}
@{{ survey.external == 1 ? 'Yes' : 'No' }}
@foreach ($surveys as $data)
{{ $data->name }}
{{ $data->allow_comments_survey == 1 ? 'Yes' : 'No' }}
{{ $data->allow_comments_pillars == 1 ? 'Yes' : 'No' }}
{{ $data->external == 1 ? 'Yes' : 'No' }}
@if($data->id != 1)
@endif
@endforeach
{{ $surveys->links() }}
@stop
{{– Push extra scripts –}}
@push('js')
<script>
let surveyData = @json($surveys);
console.log(surveyData);
import { createApp } from 'https://unpkg.com/vue@3.5.13/dist/vue.esm-browser.prod.js'
document.addEventListener('DOMContentLoaded', () => {
const app = createApp({
data() {
return {
surveys: surveyData
}
}
});
app.mount('#surveyApp');
// Hide loading spinner after mount
const loadingDiv = document.getElementById('vue-loading');
if (loadingDiv) {
loadingDiv.classList.add('fade-out');
setTimeout(() => loadingDiv.remove(), 500); // fully remove from DOM after fade
}
})
</script>
@endpush
> ✅ Use a **shared layout** or setup so the Vue app is **mounted once**, but still handles different logic per page — like listing, creating, or editing surveys — without rewriting `createApp()` logic every time.
—
## ✅ Final Plan (Tailored for Your Setup)
You're already **injecting Vue inline**, so we'll refactor your current setup to:
1. Use a **shared layout** (like `layouts/survey.blade.php`).
2. Pass a **component name** (`SurveyIndexComponent`, `SurveyEditComponent`, etc.).
3. Write page-specific Vue logic in the individual views.
4. Mount Vue only once and load the correct component per view.
—
## ✅ Step-by-Step Implementation
—
### ✅ 1. Create a Shared Layout `layouts/survey.blade.php`
{{-- layouts/survey.blade.php --}}
@extends('layouts.admin')
@section('content_body')
<div id="surveyApp"
data-component="{{ $vueComponent }}"
data-props='@json($vueProps ?? [])'>
@yield('survey_vue_template')
</div>
@endsection
@push('js')
<script type="module">
import { createApp } from 'https://unpkg.com/vue@3.5.13/dist/vue.esm-browser.prod.js';
document.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('surveyApp');
if (!el) return;
const componentName = el.dataset.component;
const props = JSON.parse(el.dataset.props || '{}');
const component = window[componentName];
if (!component) {
console.error(`Vue component "${componentName}" not found.`);
return;
}
const app = createApp(component, props);
app.mount('#surveyApp');
});
</script>
@endpush
—
### ✅ 2. Update Your `index.blade.php` for Surveys
Replace `@extends('layouts.admin')` with:
@extends('layouts.survey')
@php
$vueComponent = 'SurveyIndexComponent';
$vueProps = [
'surveys' => $surveys
];
@endphp
Then replace the inline Vue logic with:
@section('survey_vue_template')
<div class="row">
<div class="col-sm-12">
<h1>{{ __('Surveys') }}</h1>
</div>
<div class="col-sm-12 my-4">
<a href="{{ route('admin.surveys.create') }}" class="btn btn-success">{{ __('Add Survey') }}</a>
</div>
</div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>{{ __('Survey Title') }}</th>
<th class="text-center">{{ __('Allow comments on Survey') }}</th>
<th class="text-center">{{ __('Allow comments on Pillars') }}</th>
<th class="text-center">{{ __('Mark as External') }}</th>
<th class="text-center">{{ __('Action') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="survey in surveys" :key="survey.id">
<td>@{{ survey.name }}</td>
<td class="text-center">@{{ survey.allow_comments_survey ? 'Yes' : 'No' }}</td>
<td class="text-center">@{{ survey.allow_comments_pillars ? 'Yes' : 'No' }}</td>
<td class="text-center">@{{ survey.external ? 'Yes' : 'No' }}</td>
<td class="text-center">
<a :href="`/admin/surveys/${survey.encrypted_id}/edit`" class="btn btn-info btn-sm">
<i class="fas fa-pencil-alt"></i>
</a>
<a v-if="survey.id != 1" href="javascript:void(0);" class="btn btn-danger btn-sm" @click="deleteSurvey(survey.id)">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
</tbody>
</table>
@endsection
@push('js')
<script type="module">
window.SurveyIndexComponent = {
props: ['surveys'],
methods: {
deleteSurvey(id) {
Swal.fire('Delete clicked for ID ' + id);
// you can add AJAX here
}
}
}
</script>
@endpush
—
## ✅ Result
| Feature | Benefit |
|——–|———|
| 🔁 One Vue mount | Done in `layouts.survey` layout |
| 🔀 Per-page logic | Each Blade defines its own `window.ComponentName` |
| ⚙️ Blade + Vue harmony | Props passed from PHP, UI handled by Vue |
—
Would you like me to show the `edit.blade.php` refactored the same way?