Documentation Index
Fetch the complete documentation index at: https://docs.mejik.web.id/llms.txt
Use this file to discover all available pages before exploring further.
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:
- Laravel Blade Integration: Ideal for traditional server-rendered applications where you want to “sprinkle” interactivity onto a page generated by Laravel.
- 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:
- NPM Dependencies:
vue, axios, bootstrap-vue, ant-design-vue@^1.7.8, moment.
- Event Bus: A simple
resources/js/EventBus.js file:
import Vue from 'vue';
export const EventBus = new Vue();
- 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:
- Integrasi dengan Laravel Blade: Ideal untuk aplikasi tradisional yang dirender oleh server, di mana Anda ingin menambahkan interaktivitas pada halaman yang dibuat oleh Laravel.
- 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:
- Dependensi NPM:
vue, axios, bootstrap-vue, ant-design-vue@^1.7.8, moment.
- Event Bus: File sederhana
resources/js/EventBus.js:
import Vue from 'vue';
export const EventBus = new Vue();
- 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>