Initialize Vue App Laravel

User
We have a section in laravel for survey and we have applied vue on edit,create and index pages
but now want to initials creatApp once for all 3 pages
Assistant
To initialize a single Vue app instance across multiple Laravel Blade views (like `create`, `edit`, and `index` of a survey section), the best practice is to:

### ✅ 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.

User
No
Assistant
Got it — you're **not** using a full-page Blade layout like Inertia or SPA. You're applying Vue **only on specific sections** of the `create`, `edit`, and `index` pages of the Survey module, and now you want to **initialize `createApp()` only once globally**, not per page.

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.

User
No we are not usning wholes vue just a section of survey in vuew
Assistant
Perfect — thanks for the clarification! You’re using **Vue only in a specific section** of the `survey` pages (`create`, `edit`, `index`) — not a full Vue SPA, just enhancing a part of the Blade views with Vue.

### ✅ 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!

User
we have like this
@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

Assistant
User is enhancing only the "survey" section of their Laravel admin views with Vue.js, without converting the whole view to Vue or SPA.
Assistant
Thanks for the detailed context — now it's crystal clear! Since you're only enhancing the **"Survey" section** with Vue (not the full page), and you want to **initialize Vue just once**, here’s how to **standardize your setup** for all pages (`create`, `edit`, and potentially `index`) without redundant `createApp()` calls:

### ✅ 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.

User
PLease understand we are not using resource/js folder
we are using resources/views folder
Assistant
Ahh — thank you, Neeraj, now I fully understand. You're not using Vue via Vite or the `resources/js` structure. You're simply writing Vue **inline via “ in Blade**, and using it **only inside `resources/views`**, for a section (like `#surveyApp`) — **no Vite, no SPA, no component files** — just a classic Blade + Vue enhancement.

### ✅ 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`.

User
not understand
I think we need to create like add a seperate layout for survey and then include it in all create/edit/index
Assistant
Ah — got it now! You're absolutely right, Neeraj 👌

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?

User
what about different operations in each page
Assistant
That’s a great question, Neeraj — and you're absolutely thinking ahead. Let's address it clearly:

## ❓ 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?

User
@extends('layouts.admin')
@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

Assistant
Thanks for sharing the full context, Neeraj — this makes everything clear. You're mixing Blade-rendered content with Vue-enhanced sections (specifically the survey list), and now the goal is:

> ✅ 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?

Leave a Comment

Your email address will not be published. Required fields are marked *