Skip to main content

Implementation Guide: Reusable In-Place & Modal Editing Components

This guide explains how to implement a powerful set of Vue components for in-place and modal-based editing within a web application. We will cover two primary integration methods:
  1. Laravel Blade Integration: Ideal for traditional server-rendered applications where you want to “sprinkle” interactivity onto a page generated by Laravel.
  2. Full Vue Component: Suitable for Single Page Applications (SPAs) or complex front-end sections where Vue manages the entire state and rendering.

Component Files

This system relies on a set of Vue components. Ensure you have the full code for these files (as provided in previous answers) and they are placed in your resources/js/components/ directory.
  • DynamicFormInputs.vue: The core component that renders different form inputs.
  • InPlaceEdit.vue: The component for inline (in-place) editing.
  • ModalTrigger.vue: The clickable element that opens the editing modal.
  • EditModalContainer.vue: The modal itself, which contains the editing form.

Prerequisites

Before you begin, ensure your project is set up with:
  1. NPM Dependencies: vue, axios, bootstrap-vue, ant-design-vue@^1.7.8, moment.
  2. Event Bus: A simple resources/js/EventBus.js file:
    import Vue from 'vue';
    export const EventBus = new Vue();
    
  3. Main JavaScript (resources/js/app.js): Your entry point must be configured to initialize Vue and register the necessary components and plugins.


Part 1: Laravel Blade Integration

This is the recommended approach when your table structure is rendered by a Blade template. Vue will attach to the existing HTML and make it interactive.

Step 1: Prepare Data in Laravel Controller

Your controller should fetch the attendance data and prepare any options needed for select inputs. AttendanceController.php
class AttendanceController extends Controller
{
    public function show()
    {
        $attendanceData = // ... Eloquent query to get your student attendance data ...

        $yesNoOptions = [
            ['value' => 'YA', 'text' => 'YA'],
            ['value' => 'TIDAK', 'text' => 'TIDAK'],
        ];

        return view('attendance', [
            'attendanceData' => $attendanceData,
            'yesNoOptions' => $yesNoOptions,
        ]);
    }
}

Step 2: Render the Table in Blade View

In your attendance.blade.php, loop through the data and place the Vue component tags directly in the table cells. resources/views/attendance.blade.php
@extends('layouts.app')

@section('content')
<div class="container mt-4" id="app"> {{-- Vue's root element --}}

    {{-- The modal container is needed ONCE for the modal editing to work --}}
    <edit-modal-container></edit-modal-container>

    <h1 class="mb-4">Attendance Table (In-Place Editing)</h1>
    <table class="table table-bordered">
        <thead>
            <tr>
                <th>NO ABSEN</th>
                <th>NAMA</th>
                <th>TERLAMBAT (MENIT)</th>
                <th>KELAS SUSULAN</th>
            </tr>
        </thead>
        <tbody>
            @foreach ($attendanceData as $student)
                <tr>
                    <td><code>{{ $student->no_absen }}</code></td>
                    <td>{{ $student->nama }}</td>
                    <td>
                        {{-- In-Place Editor for a number --}}
                        <in-place-edit
                            type="number"
                            name="terlambat|{{ $student->no_absen }}"
                            :dbid="{{ $student->no_absen }}"
                            :initial-value="{{ $student->terlambat ?? 'null' }}"
                            saveurl="/api/update-kehadiran"
                        ></in-place-edit>
                    </td>
                    <td>
                        {{-- In-Place Editor for a select dropdown --}}
                        <in-place-edit
                            type="select"
                            name="kelas_susulan|{{ $student->no_absen }}"
                            :dbid="{{ $student->no_absen }}"
                            initial-value="{{ $student->kelas_susulan }}"
                            :options='@json($yesNoOptions)'
                            saveurl="/api/update-kehadiran"
                        ></in--place-edit>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>

    <h1 class="mt-5 mb-4">Attendance Table (Modal Editing)</h1>
    <table class="table table-bordered">
        {{-- ... table header ... --}}
        <tbody>
            @foreach ($attendanceData as $student)
                <tr>
                    <td><code>{{ $student->no_absen }}</code></td>
                    <td>{{ $student->nama }}</td>
                    <td>
                        {{-- Modal Trigger for a number --}}
                        <modal-trigger
                            type="number"
                            name="terlambat|{{ $student->no_absen }}"
                            :dbid="{{ $student->no_absen }}"
                            :initial-value="{{ $student->terlambat ?? 'null' }}"
                            saveurl="/api/update-kehadiran"
                        ></modal-trigger>
                    </td>
                    <td>
                        {{-- Modal Trigger for a select dropdown --}}
                        <modal-trigger
                            type="select"
                            name="kelas_susulan|{{ $student->no_absen }}"
                            :dbid="{{ $student->no_absen }}"
                            initial-value="{{ $student->kelas_susulan }}"
                            :options='@json($yesNoOptions)'
                            saveurl="/api/update-kehadiran"
                        ></modal-trigger>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>
</div>
@endsection
Key Blade Syntax:
  • :prop="...": The colon binds the prop, evaluating the content as a JavaScript expression. Use it for numbers, booleans, or objects.
  • prop="...": No colon means the prop is passed as a static string.
  • @json($array): The correct and safe way to pass a PHP array to a Vue prop.
  • ?? 'null': A safeguard for numeric props that might be null in the database.

Step 3: Initialize Vue in app.js

Your main JavaScript file boots Vue and registers the components used in the Blade template. resources/js/app.js
import Vue from 'vue';
import { BootstrapVue } from 'bootstrap-vue';
import Antd from 'ant-design-vue';

// Import our custom components and EventBus
import { EventBus } from './EventBus';
import InPlaceEdit from './components/InPlaceEdit.vue';
import ModalTrigger from './components/ModalTrigger.vue';
import EditModalContainer from './components/EditModalContainer.vue';

// Import CSS
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue/dist/bootstrap-vue.css';
import 'ant-design-vue/dist/antd.css';

Vue.use(BootstrapVue);
Vue.use(Antd);

const app = new Vue({
    el: '#app',
    components: {
        InPlaceEdit,
        ModalTrigger,
        EditModalContainer,
    },
    created() {
        // Optional: Listen for modal saves to show a success toast.
        EventBus.$on('edit-save-success', ({ name, newValue }) => {
            this.$bvToast.toast(`Successfully updated ${name.split('|')[0]}.`, {
                title: 'Saved!', variant: 'success', solid: true,
            });
        });
    }
});


Part 2: Full Vue Component Implementation

This approach is used when an entire section of your page, including the table, is managed by Vue. AttendanceTable.vue
<template>
  <div class="container mt-4">
    <edit-modal-container v-if="editStyle === 'modal'" />
    <h1 class="mb-4">KEHADIRAN (Managed by Vue)</h1>
    <b-form-group label="Select Editing Style:">
      <b-form-radio-group v-model="editStyle" :options="['inline', 'modal']"></b-form-radio-group>
    </b-form-group>
    <b-table striped hover :items="tableData" :fields="tableFields" row-key="no_absen">
      <template #cell(terlambat)="data">
        <in-place-edit
          v-if="editStyle === 'inline'"
          v-model="data.item.terlambat"
          :name="'terlambat|' + data.item.no_absen"
          :dbid="data.item.no_absen"
          type="number"
          mode="ajax"
          saveurl="/api/update-kehadiran"
        />
        <modal-trigger
          v-if="editStyle === 'modal'"
          :initial-value="data.item.terlambat"
          :name="'terlambat|' + data.item.no_absen"
          :dbid="data.item.no_absen"
          type="number"
          saveurl="/api/update-kehadiran"
        />
      </template>
      <template #cell(kelas_susulan)="data">
        <in-place-edit
          v-if="editStyle === 'inline'"
          v-model="data.item.kelas_susulan"
          :name="'kelas_susulan|' + data.item.no_absen"
          :dbid="data.item.no_absen"
          type="select"
          mode="ajax"
          :options="yesNoOptions"
          saveurl="/api/update-kehadiran"
        />
        <modal-trigger
          v-if="editStyle === 'modal'"
          :initial-value="data.item.kelas_susulan"
          :name="'kelas_susulan|' + data.item.no_absen"
          :dbid="data.item.no_absen"
          type="select"
          :options="yesNoOptions"
          saveurl="/api/update-kehadiran"
        />
      </template>
    </b-table>
  </div>
</template>

<script>
import InPlaceEdit from '../components/InPlaceEdit.vue';
import EditModalContainer from '../components/EditModalContainer.vue';
import ModalTrigger from '../components/ModalTrigger.vue';
import { EventBus } from '../EventBus.js';
import axios from 'axios';

export default {
  name: 'AttendanceTable',
  components: { InPlaceEdit, EditModalContainer, ModalTrigger },
  data() {
    return {
      editStyle: 'inline',
      tableFields: [
        { key: 'no_absen', label: 'NO ABSEN' },
        { key: 'nama', label: 'NAMA' },
        { key: 'terlambat', label: 'TERLAMBAT (MENIT)' },
        { key: 'kelas_susulan', label: 'KELAS SUSULAN' },
      ],
      tableData: [],
      yesNoOptions: [
        { value: 'YA', text: 'YA' },
        { value: 'TIDAK', text: 'TIDAK' },
      ],
    };
  },
  created() {
    this.fetchAttendanceData();
    EventBus.$on('edit-save-success', this.updateDataFromModal);
  },
  beforeDestroy() {
    EventBus.$off('edit-save-success', this.updateDataFromModal);
  },
  methods: {
    async fetchAttendanceData() {
      // In a real app, you would fetch data from your API
      // const response = await axios.get('/api/attendance');
      // this.tableData = response.data;
      this.tableData = [ /* Paste mock data here for testing */ ];
    },
    updateDataFromModal({ name, newValue }) {
      const [fieldName, id] = name.split('|');
      const record = this.tableData.find(item => item.no_absen === id);
      if (record) {
        record[fieldName] = newValue;
      }
    },
  },
};
</script>

[Bahasa Indonesia]

Panduan Implementasi: Komponen Vue Edit In-Place & Modal

Panduan ini menjelaskan cara mengimplementasikan satu set komponen Vue untuk fungsionalitas edit di tempat (in-place) dan berbasis modal. Kami akan membahas dua metode integrasi utama:
  1. Integrasi dengan Laravel Blade: Ideal untuk aplikasi tradisional yang dirender oleh server, di mana Anda ingin menambahkan interaktivitas pada halaman yang dibuat oleh Laravel.
  2. Komponen Vue Penuh: Cocok untuk Single Page Applications (SPA) atau bagian front-end yang kompleks di mana Vue mengelola seluruh state dan proses rendering.

File Komponen

Sistem ini bergantung pada satu set komponen Vue. Pastikan Anda memiliki kode lengkap untuk file-file ini (seperti yang diberikan di jawaban sebelumnya) dan letakkan di direktori resources/js/components/.
  • DynamicFormInputs.vue: Komponen inti yang merender berbagai input form.
  • InPlaceEdit.vue: Komponen untuk edit langsung di tempat (inline).
  • ModalTrigger.vue: Elemen yang dapat diklik untuk membuka modal edit.
  • EditModalContainer.vue: Komponen modal itu sendiri, yang berisi form edit.

Persyaratan

Sebelum memulai, pastikan proyek Anda telah disiapkan dengan:
  1. Dependensi NPM: vue, axios, bootstrap-vue, ant-design-vue@^1.7.8, moment.
  2. Event Bus: File sederhana resources/js/EventBus.js:
    import Vue from 'vue';
    export const EventBus = new Vue();
    
  3. JavaScript Utama (resources/js/app.js): File entri Anda harus dikonfigurasi untuk menginisialisasi Vue dan mendaftarkan komponen serta plugin yang diperlukan.


Bagian 1: Integrasi dengan Laravel Blade

Ini adalah pendekatan yang disarankan ketika struktur tabel Anda dirender oleh template Blade. Vue akan “menempel” pada HTML yang ada dan membuatnya interaktif.

Langkah 1: Siapkan Data di Controller Laravel

Controller Anda harus mengambil data kehadiran dan menyiapkan opsi yang diperlukan untuk input select. AttendanceController.php
class AttendanceController extends Controller
{
    public function show()
    {
        $attendanceData = // ... Query Eloquent untuk mendapatkan data kehadiran siswa ...

        $yesNoOptions = [
            ['value' => 'YA', 'text' => 'YA'],
            ['value' => 'TIDAK', 'text' => 'TIDAK'],
        ];

        return view('attendance', [
            'attendanceData' => $attendanceData,
            'yesNoOptions' => $yesNoOptions,
        ]);
    }
}

Langkah 2: Render Tabel di View Blade

Di file attendance.blade.php Anda, lakukan looping data dan letakkan tag komponen Vue langsung di dalam sel tabel. resources/views/attendance.blade.php
@extends('layouts.app')

@section('content')
<div class="container mt-4" id="app"> {{-- Elemen root untuk Vue --}}

    {{-- Container modal ini hanya diperlukan SATU KALI agar edit modal berfungsi --}}
    <edit-modal-container></edit-modal-container>

    <h1 class="mb-4">Tabel Kehadiran (Edit In-Place)</h1>
    <table class="table table-bordered">
        <thead>
            <tr>
                <th>NO ABSEN</th>
                <th>NAMA</th>
                <th>TERLAMBAT (MENIT)</th>
                <th>KELAS SUSULAN</th>
            </tr>
        </thead>
        <tbody>
            @foreach ($attendanceData as $student)
                <tr>
                    <td><code>{{ $student->no_absen }}</code></td>
                    <td>{{ $student->nama }}</td>
                    <td>
                        {{-- Editor In-Place untuk angka --}}
                        <in-place-edit
                            type="number"
                            name="terlambat|{{ $student->no_absen }}"
                            :dbid="{{ $student->no_absen }}"
                            :initial-value="{{ $student->terlambat ?? 'null' }}"
                            saveurl="/api/update-kehadiran"
                        ></in-place-edit>
                    </td>
                    <td>
                        {{-- Editor In-Place untuk select dropdown --}}
                        <in-place-edit
                            type="select"
                            name="kelas_susulan|{{ $student->no_absen }}"
                            :dbid="{{ $student->no_absen }}"
                            initial-value="{{ $student->kelas_susulan }}"
                            :options='@json($yesNoOptions)'
                            saveurl="/api/update-kehadiran"
                        ></in-place-edit>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>

    <h1 class="mt-5 mb-4">Tabel Kehadiran (Edit Modal)</h1>
    <table class="table table-bordered">
        {{-- ... header tabel ... --}}
        <tbody>
            @foreach ($attendanceData as $student)
                <tr>
                    <td><code>{{ $student->no_absen }}</code></td>
                    <td>{{ $student->nama }}</td>
                    <td>
                        {{-- Pemicu Modal untuk angka --}}
                        <modal-trigger
                            type="number"
                            name="terlambat|{{ $student->no_absen }}"
                            :dbid="{{ $student->no_absen }}"
                            :initial-value="{{ $student->terlambat ?? 'null' }}"
                            saveurl="/api/update-kehadiran"
                        ></modal-trigger>
                    </td>
                    <td>
                        {{-- Pemicu Modal untuk select dropdown --}}
                        <modal-trigger
                            type="select"
                            name="kelas_susulan|{{ $student->no_absen }}"
                            :dbid="{{ $student->no_absen }}"
                            initial-value="{{ $student->kelas_susulan }}"
                            :options='@json($yesNoOptions)'
                            saveurl="/api/update-kehadiran"
                        ></modal-trigger>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>
</div>
@endsection
Sintaks Penting di Blade:
  • :prop="...": Tanda titik dua (:) melakukan binding pada prop, mengevaluasi isinya sebagai ekspresi JavaScript. Gunakan untuk angka, boolean, atau objek.
  • prop="...": Tanpa titik dua berarti prop dilewatkan sebagai string statis.
  • @json($array): Cara yang benar dan aman untuk melewatkan array PHP ke prop Vue.
  • ?? 'null': Pengaman untuk prop numerik yang mungkin bernilai null di database.

Langkah 3: Inisialisasi Vue di app.js

File JavaScript utama Anda akan mem-boot Vue dan mendaftarkan komponen yang digunakan dalam template Blade. resources/js/app.js
import Vue from 'vue';
import { BootstrapVue } from 'bootstrap-vue';
import Antd from 'ant-design-vue';

// Impor komponen kustom dan EventBus
import { EventBus } from './EventBus';
import InPlaceEdit from './components/InPlaceEdit.vue';
import ModalTrigger from './components/ModalTrigger.vue';
import EditModalContainer from './components/EditModalContainer.vue';

// Impor CSS
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue/dist/bootstrap-vue.css';
import 'ant-design-vue/dist/antd.css';

Vue.use(BootstrapVue);
Vue.use(Antd);

const app = new Vue({
    el: '#app',
    components: {
        InPlaceEdit,
        ModalTrigger,
        EditModalContainer,
    },
    created() {
        // Opsional: Dengarkan event save dari modal untuk menampilkan notifikasi.
        EventBus.$on('edit-save-success', ({ name, newValue }) => {
            this.$bvToast.toast(`Berhasil memperbarui ${name.split('|')[0]}.`, {
                title: 'Tersimpan!', variant: 'success', solid: true,
            });
        });
    }
});


Bagian 2: Implementasi Komponen Vue Penuh

Pendekatan ini digunakan ketika seluruh bagian halaman, termasuk tabel, dikelola oleh Vue. AttendanceTable.vue
<template>
  <div class="container mt-4">
    <edit-modal-container v-if="editStyle === 'modal'" />
    <h1 class="mb-4">KEHADIRAN (Dikelola oleh Vue)</h1>
    <b-form-group label="Pilih Gaya Edit:">
      <b-form-radio-group v-model="editStyle" :options="['inline', 'modal']"></b-form-radio-group>
    </b-form-group>
    <b-table striped hover :items="tableData" :fields="tableFields" row-key="no_absen">
      <template #cell(terlambat)="data">
        <in-place-edit
          v-if="editStyle === 'inline'"
          v-model="data.item.terlambat"
          :name="'terlambat|' + data.item.no_absen"
          :dbid="data.item.no_absen"
          type="number"
          mode="ajax"
          saveurl="/api/update-kehadiran"
        />
        <modal-trigger
          v-if="editStyle === 'modal'"
          :initial-value="data.item.terlambat"
          :name="'terlambat|' + data.item.no_absen"
          :dbid="data.item.no_absen"
          type="number"
          saveurl="/api/update-kehadiran"
        />
      </template>
      <template #cell(kelas_susulan)="data">
        <in-place-edit
          v-if="editStyle === 'inline'"
          v-model="data.item.kelas_susulan"
          :name="'kelas_susulan|' + data.item.no_absen"
          :dbid="data.item.no_absen"
          type="select"
          mode="ajax"
          :options="yesNoOptions"
          saveurl="/api/update-kehadiran"
        />
        <modal-trigger
          v-if="editStyle === 'modal'"
          :initial-value="data.item.kelas_susulan"
          :name="'kelas_susulan|' + data.item.no_absen"
          :dbid="data.item.no_absen"
          type="select"
          :options="yesNoOptions"
          saveurl="/api/update-kehadiran"
        />
      </template>
    </b-table>
  </div>
</template>

<script>
import InPlaceEdit from '../components/InPlaceEdit.vue';
import EditModalContainer from '../components/EditModalContainer.vue';
import ModalTrigger from '../components/ModalTrigger.vue';
import { EventBus } from '../EventBus.js';
import axios from 'axios';

export default {
  name: 'AttendanceTable',
  components: { InPlaceEdit, EditModalContainer, ModalTrigger },
  data() {
    return {
      editStyle: 'inline',
      tableFields: [
        { key: 'no_absen', label: 'NO ABSEN' },
        { key: 'nama', label: 'NAMA' },
        { key: 'terlambat', label: 'TERLAMBAT (MENIT)' },
        { key: 'kelas_susulan', label: 'KELAS SUSULAN' },
      ],
      tableData: [],
      yesNoOptions: [
        { value: 'YA', text: 'YA' },
        { value: 'TIDAK', text: 'TIDAK' },
      ],
    };
  },
  created() {
    this.fetchAttendanceData();
    EventBus.$on('edit-save-success', this.updateDataFromModal);
  },
  beforeDestroy() {
    EventBus.$off('edit-save-success', this.updateDataFromModal);
  },
  methods: {
    async fetchAttendanceData() {
      // Di aplikasi nyata, Anda akan mengambil data dari API
      // const response = await axios.get('/api/attendance');
      // this.tableData = response.data;
      this.tableData = [ /* Tempel data mock di sini untuk pengujian */ ];
    },
    updateDataFromModal({ name, newValue }) {
      const [fieldName, id] = name.split('|');
      const record = this.tableData.find(item => item.no_absen === id);
      if (record) {
        record[fieldName] = newValue;
      }
    },
  },
};
</script>