Multi Form dengan InertiaJS

banner image

InertiaJS membuat pembuatan fullstack web development menjadi lebih modern. Dengan InertiaJS kita bisa menggabungkan framework JS yang intuitif dengan teknologi backend seperti Laravel. InertiaJS memfasilitasi komunikasi antara frontend dengan backend, sehingga kita tidak perlu lagi membuat API.

InertiaJS menyerhanakan workflow yang kompleks menjadi lebih seamless. Seperti misalkan kasus dalam pembuatan form. Dalam tulisan ini saya akan mendemonstrasikan bagaimana kita bisa membuat sebuah form yang bisa merima multiple input dalam sekali form request dengna menggunakan Laravel, Vue3 dan InertiaJS 2.

Pertama kita akn setup sebuah proyek Laravel dengan menggunakan composer:

composer create-project laravel/laravel inertiajs-demo

Untuk menggunakan InertiaJS dengan Vue di dalam proyek Laravel, kita akan menggunakan Laravel Breeze. Laravel Breeze adalah starter kit untuk mempercepat scafold aplikasi. Secara otomatis, dia akan mensetupkan Laravel dengan fitur seperti authentication, dengan piliahn techstack yang kita inginkan. Saya akan memilih Vue dengan InertiaJS.

cd inertiajs-demo
composer require laravel/breeze --dev
php artisan breeze:install

Prompt setup akan muncul dan pilih Vue with Inertia.

Menyiapkan Basis Data

Next, kita akan menyiapkan sebuah migration file. Dalam demo aplikasi ini kita akan menggunakan contoh input data barang:

php artisan make:model Barang -m

Pada model Barang, kita akan sesuaikan nama $table dan property $fillable.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Barang extends Model
{
    protected $table = 'barang';
    protected $fillable = ['nama_barang', 'stok', 'harga'];
}

Pada migration file kita definisikan kolom pada tabel barang.

<?php

namespace App\\Models;

use Illuminate\\Database\\Eloquent\\Model;

class Barang extends Model
{
    protected $table = 'barang';
    protected $fillable = ['nama_barang', 'stok', 'harga'];
}

Run migration untuk membuat table barang:

php artisan migrate

Membuat Route

Buat sebuah route baru pada routes/web.php :

Route::get('/barang', [BarangController::class, 'index'])->name('barang.index');
Route::post('/barang', [BarangController::class, 'store'])->name('barang.store');
Route::delete('/barang/{barang}', [BarangController::class, 'destroy'])->name('barang.destroy');
Route::post('/barang/delete-selected', [BarangController::class, 'deleteSelected'])->name('barang.deleteSelected');

Selanjutnya kita buat controller dan handler function untuk route GET barang

php artisan make:controller BarangController
<?php

namespace App\\Http\\Controllers;

use App\\Models\\Barang;
use Illuminate\\Http\\Request;
use Inertia\\Inertia;

class BarangController extends Controller
{
    public function index()
    {
        $barang = Barang::all();
        return Inertia::render('Barang/Index', [
            'barang' => $barang
        ]);
    }

    public function store(Request $request)
    {
        $request->validate([
            'barang.*.nama_barang' => 'required',
            'barang.*.stok' => 'required',
            'barang.*.harga' => 'required'
        ]);

        foreach ($request->barang as $value) {
            Barang::create([
                'nama_barang' => $value['nama_barang'],
                'stok' => $value['stok'],
                'harga' => $value['harga']
            ]);
        }

        return to_route('barang.index');
    }

    public function destroy(Barang $barang)
    {
        $barang->delete();
        return to_route('barang.index');
    }

    public function deleteSelected(Request $request)
    {
        Barang::whereIn('id', $request->barang)->delete();
        return to_route('barang.index');
    }
}

Selanjutnya kita akan membuat sebuah page dengan menggunakan Vue pada resources/js/Pages:

mkdir resources/js/Pages/Barang
touch resources/js/Pages/Barang/Index.vue

Isikan dengan code berikut:

<script setup>
import { ref, reactive } from 'vue';
import { router } from '@inertiajs/vue3';

const props = defineProps({
    barang: Object
})
const newBarang = reactive([]);
const barangToDelete = ref([]);

const tambahBarang = () => {
    const id = newBarang.length + 1;
    newBarang.push({
        id: id,
        nama_barang: '',
        harga: '',
        stok: ''
    });

    // set focus to the last input
    setTimeout(() => {
        const inputs = document.querySelectorAll('input[type="text"]');
        inputs[inputs.length - 1].focus();
    }, 100);
}

const simpan = () => {
    router.post(route('barang.store'), {
        barang: newBarang
    }, {
        preserveState: true,
        preserveScroll: true,
        onSuccess: () => {
            // Clear neBarang once saved
            newBarang.length = 0;
        }
    });
}

const deleteBarang = (id) => {
    router.delete(route('barang.destroy', id));
}

const removeItem = (index) => {
    newBarang.splice(index, 1);
}

const addOrRemoveFromBarangToDelete = (id) => {
    if (barangToDelete.value.includes(id)) {
        barangToDelete.value = barangToDelete.value.filter(item => item !== id);
    } else {
        barangToDelete.value.push(id);
    }
}

const deleteSelected = () => {
    console.log(barangToDelete.value);
    router.post(route('barang.deleteSelected'), {
        barang: barangToDelete.value
    }, {
        onSuccess: () => {
            barangToDelete.value.length = 0;
        }
    });
}

</script>

<template>
    <div class="max-w-3xl mx-auto py-6 sm:px-6 lg:px-8 border rounded-md shadow-lg my-5 p-4 ">
        <h1 class="text-xl font-bold">Barang</h1>
        <div class="border-b-gray-200 my-2 border"></div>

        <form @submit.prevent="simpan">
            <table class="table-auto w-full">
                <thead>
                    <tr>
                        <th class="px-4 py-2 max-w-[2px]">

                        </th>
                        <th class="px-4 py-2 max-w-[2px]">No</th>
                        <th class="px-4 py-2">Nama Barang</th>
                        <th class="px-4 py-2">Harga</th>
                        <th class="px-4 py-2">Stok</th>
                        <th class="px-4 py-2"></th>
                    </tr>

                </thead>

                <tbody>
                    <tr v-for="(barang, index) in barang" :key="barang.id">
                        <td>
                            <input type="checkbox" @change="addOrRemoveFromBarangToDelete(barang.id)"  />
                        </td>
                        <td class="px-4 py-2">{{ index + 1 }}</td>
                        <td class="px-4 py-2">{{ barang.nama_barang }}</td>
                        <td class="px-4 py-2">{{ barang.harga }}</td>
                        <td class="px-4 py-2">{{ barang.stok }}</td>
                        <td class="px-4 py-2">
                            <button class="hover:underline text-gray-400 hover:text-red-500" @click="deleteBarang(barang.id)" type="button">Hapus</button>
                        </td>
                    </tr>
                    <tr v-for="(barang, index) in newBarang" :key="index">
                        <td class="px-4 py-2"></td>
                        <td class="px-4 py-2"></td>
                        <td class="px-4 py-2">
                            <input type="text" class="w-full rounded" v-model="barang.nama_barang" placeholder="Nama Barang" :class="{ 'border border-red-500': $page.props.errors && $page.props.errors['barang.'+index+'.nama_barang'] }">
                        </td>
                        <td class="px-4 py-2">
                            <input type="number" class="w-full rounded" v-model="barang.harga" placeholder="Harga"  :class="{ 'border border-red-500': $page.props.errors && $page.props.errors['barang.'+index+'.harga'] }">
                        </td>
                        <td class="px-4 py-2 ">
                            <input type="number" class="w-full rounded" v-model="barang.stok" placeholder="Stok"  :class="{ 'border border-red-500': $page.props.errors && $page.props.errors['barang.'+index+'.stok'] }">
                        </td>
                        <td class="px-4 py-2">
                            <button class="hover:underline text-gray-400 hover:text-red-500" tabindex="-1" type="button" @click="removeItem(index)">Hapus</button>
                        </td>

                    </tr>
                </tbody>
                <tfoot>
                    <tr>
                        <td class="px-4 py-2" colspan="6">
                            <button @click="tambahBarang" type="button"
                                class="block w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
                                Tambah Barang
                            </button>
                            <button v-if="newBarang.length > 0" click="simpan" type="submit"
                                class="mt-5 block w-full bg-green-500 hover:bg-gren-700 text-white font-bold py-2 px-4 rounded">
                                Simpan
                            </button>
                            <button v-if="barangToDelete.length > 0" @click="deleteSelected" type="button"
                                class="mt-5 block w-full bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
                                Hapus Terpilih
                            </button>
                        </td>
                    </tr>
                </tfoot>
            </table>
        </form>
    </div>
</template>

Sampai di sini jalankan dev server dengan php artisan serve dan npm run dev.

Download Project:

Anda bisa mendownload project di atas via Github repository berikut:

https://github.com/budasuyasa/multiform-inertiajs-demo

Previous Article

Delete Automated Moodle Backup

Next Article

Replikasi MySQL dengan Metode Master Slave

Write a Comment

Leave a Comment

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