> ## Documentation Index
> Fetch the complete documentation index at: https://docs.doman.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Inline Editing

> Reusable In-Place & Modal Editing Components

## 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:
   ```javascript theme={null}
   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`**

```php theme={null}
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`**

```php theme={null}
@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`**

```javascript theme={null}
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`**

```vue theme={null}
<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`:
   ```javascript theme={null}
   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`**

```php theme={null}
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`**

```php theme={null}
@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`**

```javascript theme={null}
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`**

```vue theme={null}
<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>
```
