Membuat CRUD Laravel 8 & Jetstream Livewire

Membuat CRUD Laravel 8 & Jetstream Livewire

Pendahuluan

Laravel hadir dengan versi baru beserta teknologinya yang baru, sebut saja Jetstream dengan opsi yang ditawarkannya, yakni: Livewire dan Inertia. Melalui artikel ini kita akan membahas bagaimana membuat CRUD menggunakan Jetstream dengan opsi Livewire.

Apa itu Livewire? Jadi, Livewire adalah sebuah library sederhana untuk interface yang moder, reaktif dan dinamik menggunakan Laravel Blade yang sebagaimana kita ketahui berperan sebagai templating language. Teknologi ini bisa menjadi pilihan jika Anda ingin membuat aplikasi yang dinamis dan reaktif tapi tidak mahir menggunakan framework full Javascript seperti Vue.js.

Seri ini akan membahas bagaimana mengelola data pelanggan (anggota) dengan menggunakan Laravel 8 beserta Jetstream dengan Livewire. Tujuan kita adalah untuk memahami bagimana membuat fitur CRUD.

laravel livewire crud

laravel livewire crud

Baca Juga: Membuat Login & Register Laravel 8

Install Laravel 8 & Jetstream

Langkah pertama akan kita mulai dengan meng-install Laravel 8 sehingga kita sama-sama memulai dari awal.

composer create-project --prefer-dist laravel/laravel laravel8-crud

Kemudian masuk ke dalam project anda dengan command cd laravel8-crud dan install Jetstream Livewire dengan menggunakan bantuan composer.

composer require laravel/jetstream

Bagian selanjutnya adalah membuat fungsi authentication, seperti basic login, register dan email verification. Jalankan command

php artisan jetstream:install livewire

Lalu install node js package

npm install

Setelah proses ini selesai, jalankan fungsi untuk compile.

npm run dev

Konfigurasi Database

Untuk menghubungkan project Laravel kita dengan database, buka file .env dan modifikasi bagian berikut

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel-8
DB_USERNAME=root
DB_PASSWORD=toor

Kemudian jalankan command migration untuk me-generate seluruh table dari migration yang ada.

php artisan migrate

Secara otomatis kita sudah memiliki beberapa table, diantaranya: failed_jobs, migrations, password_resets, personal_access_tokens, sessions dan users.

Manajemen Pelanggan (Anggota)

Tiba saatnya bagi kita untuk membuat fungsi CRUD dalam mengelola data anggota, pertama, buat model beserta migration-nya dengan command

php artisan make:model Member -m

Buka file migration yang baru saja di-generate, yakni file migration yang paling bawah pada folder database/migrations dan modifikasi menjadi

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateMembersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('members', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->string('phone_number', 15);
            $table->char('status', 1);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('members');
    }
}

Eksekusi migration di atas dengan command php artisan migrate.

Kita harus mengizinkan setiap field / colum yang anda agar fungsi mass-assignment bisa berjalan, buka model Member.php yang berada di dalam folder app\Models dan tambahkan code berikut

protected $fillable = ['name', 'email', 'phone_number', 'status'];

Tugas berikutnya adalah membuat Member component dari Livewire, pada command line, jalankan command berikut

php artisan make:livewire members

Ada beberapa method yang akan kita kerjakan guna menunjang fungsi CRUD, diantaranya: render(), create(), openModal(), closeModal(), resetFields(), store(), edit(), delete(). Buka file app/Http/Livewire/Members.php yang baru saja di-generate dan modifikasi menjadi

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Member;

class Members extends Component
{
    public $members, $name, $email, $phone_number, $status, $member_id;
    public $isModal = 0;

  	//FUNGSI INI UNTUK ME-LOAD VIEW YANG AKAN MENJADI TAMPILAN HALAMAN MEMBER
    public function render()
    {
        $this->members = Member::orderBy('created_at', 'DESC')->get(); //MEMBUAT QUERY UNTUK MENGAMBIL DATA
        return view('livewire.members'); //LOAD VIEW MEMBERS.BLADE.PHP YG ADA DI DALAM FOLDER /RESOURSCES/VIEWS/LIVEWIRE
    }

    //FUNGSI INI AKAN DIPANGGIL KETIKA TOMBOL TAMBAH ANGGOTA DITEKAN
    public function create()
    {
        //KEMUDIAN DI DALAMNYA KITA MENJALANKAN FUNGSI UNTUK MENGOSONGKAN FIELD
        $this->resetFields();
        //DAN MEMBUKA MODAL
        $this->openModal();
    }

    //FUNGSI INI UNTUK MENUTUP MODAL DIMANA VARIABLE ISMODAL KITA SET JADI FALSE
    public function closeModal()
    {
        $this->isModal = false;
    }

    //FUNGSI INI DIGUNAKAN UNTUK MEMBUKA MODAL
    public function openModal()
    {
        $this->isModal = true;
    }

    //FUNGSI INI UNTUK ME-RESET FIELD/KOLOM, SESUAIKAN FIELD APA SAJA YANG KAMU MILIKI
    public function resetFields()
    {
        $this->name = '';
        $this->email = '';
        $this->phone_number = '';
        $this->status = '';
        $this->member_id = '';
    }

    //METHOD STORE AKAN MENG-HANDLE FUNGSI UNTUK MENYIMPAN / UPDATE DATA
    public function store()
    {
        //MEMBUAT VALIDASI
        $this->validate([
            'name' => 'required|string',
            'email' => 'required|email|unique:members,email,' . $this->member_id,
            'phone_number' => 'required|numeric',
            'status' => 'required'
        ]);

        //QUERY UNTUK MENYIMPAN / MEMPERBAHARUI DATA MENGGUNAKAN UPDATEORCREATE
        //DIMANA ID MENJADI UNIQUE ID, JIKA IDNYA TERSEDIA, MAKA UPDATE DATANYA
        //JIKA TIDAK, MAKA TAMBAHKAN DATA BARU
        Member::updateOrCreate(['id' => $this->member_id], [
            'name' => $this->name,
            'email' => $this->email,
            'phone_number' => $this->phone_number,
            'status' => $this->status,
        ]);

        //BUAT FLASH SESSION UNTUK MENAMPILKAN ALERT NOTIFIKASI
        session()->flash('message', $this->member_id ? $this->name . ' Diperbaharui': $this->name . ' Ditambahkan');
        $this->closeModal(); //TUTUP MODAL
        $this->resetFields(); //DAN BERSIHKAN FIELD
    }

    //FUNGSI INI UNTUK MENGAMBIL DATA DARI DATABASE BERDASARKAN ID MEMBER
    public function edit($id)
    {
        $member = Member::find($id); //BUAT QUERY UTK PENGAMBILAN DATA
        //LALU ASSIGN KE DALAM MASING-MASING PROPERTI DATANYA
        $this->member_id = $id;
        $this->name = $member->name;
        $this->email = $member->email;
        $this->phone_number = $member->phone_number;
        $this->status = $member->status;

        $this->openModal(); //LALU BUKA MODAL
    }

    //FUNGSI INI UNTUK MENGHAPUS DATA
    public function delete($id)
    {
        $member = Member::find($id); //BUAT QUERY UNTUK MENGAMBIL DATA BERDASARKAN ID
        $member->delete(); //LALU HAPUS DATA
        session()->flash('message', $member->name . ' Dihapus'); //DAN BUAT FLASH MESSAGE UNTUK NOTIFIKASI
    }
}

Membuat Blade Files

Fungsi yang terdiri dari logika serta perintah untuk meng-handle data anggota sudah selesai, maka tugas kita berikutnya adalah membuat tampilan yang dimana terdiri menjadi 2 bagian. Pertama adalah tampilan berupa table untuk menampilkan data anggota yang sudah ada di database dan bagian kedua adalah form untuk mengisi data anggota.

Buka file resources/views/livewire/members.blade.php dan modifikasi menjadi

<x-slot name="header">
    <h2 class="font-semibold text-xl text-gray-800 leading-tight">
        Data Anggota
    </h2>
</x-slot>
<div class="py-12">
    <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
        <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg px-4 py-4">
            @if (session()->has('message'))
                <div class="bg-teal-100 border-t-4 border-teal-500 rounded-b text-teal-900 px-4 py-3 shadow-md my-3" role="alert">
                    <div class="flex">
                        <div>
                            <p class="text-sm">{{ session('message') }}</p>
                        </div>
                    </div>
                </div>
            @endif

            <button wire:click="create()" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded my-3">Tambah Anggota</button>
            
            @if($isModal)
                @include('livewire.create')
            @endif

            <table class="table-fixed w-full">
                <thead>
                    <tr class="bg-gray-100">
                        <th class="px-4 py-2">Nama</th>
                        <th class="px-4 py-2">Email</th>
                        <th class="px-4 py-2">Telp</th>
                        <th class="px-4 py-2 w-20">Status</th>
                        <th class="px-4 py-2">Action</th>
                    </tr>
                </thead>
                <tbody>
                    @forelse($members as $row)
                        <tr>
                            <td class="border px-4 py-2">{{ $row->name }}</td>
                            <td class="border px-4 py-2">{{ $row->email }}</td>
                            <td class="border px-4 py-2">{{ $row->phone_number }}</td>
                            <td class="border px-4 py-2">{!! $row->status_label !!}</td>
                            <td class="border px-4 py-2">
                                <button wire:click="edit({{ $row->id }})" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Edit</button>
                                <button wire:click="delete({{ $row->id }})" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">Hapus</button>
                            </td>
                        </tr>
                    @empty
                        <tr>
                            <td class="border px-4 py-2 text-center" colspan="5">Tidak ada data</td>
                        </tr>
                    @endforelse
                </tbody>
            </table>
        </div>
    </div>
</div>

Ada beberapa hal yang perlu diperhatikan, diantaranya sebagai berikut:

  1. Tombol tambah anggota, dimana kita menggunakan fungsi wire:click="create()", kode create() adalah method yang sudah kita definisikan sebelumnya pada file app/Http/Livewire/Members.php.
  2. Tombol edit, dimana kita menggunakan fungsi wire:click="edit({{ $row->id }})". Jika kita mengingat kembali method edit() yang sudah kita buat, dimana method tersebut meminta parameter $id berdasarkan id user yang akan di-edit. Maka kita perlu memberikan data tersebut dengan menambahkan code $row->id di dalam kurung method edit().
  3. Cara kerja tombol hapus sama dengan tombol edit, hanya beda method yang digunakan.

Masih dengan file yang sama, perhatikan code

@if($isModal)
    @include('livewire.create')
@endif

Penjelasannya adalah jika variable $isModal yang sudah kita set sebelumnya bernilai true, maka fungsi include untuk me-load file create.blade.php akan dijalankan. Jadi tugas berikutnya adalah membuat file create.blade.php di dalam folder resources/views/livewire dan tambahkan code.

<div class="fixed z-10 inset-0 overflow-y-auto ease-out duration-400">
    <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
        <div class="fixed inset-0 transition-opacity">
            <div class="absolute inset-0 bg-gray-500 opacity-75"></div>
        </div>
        <!-- This element is to trick the browser into centering the modal contents. -->
        <span class="hidden sm:inline-block sm:align-middle sm:h-screen"></span>​
        
        <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" role="dialog" aria-modal="true" aria-labelledby="modal-headline">
            <form>
                <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
                    <div class="">
                        <div class="mb-4">
                            <label for="formName" class="block text-gray-700 text-sm font-bold mb-2">Nama:</label>
                            <input type="text" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="formName" wire:model="name">
                            @error('name') <span class="text-red-500">{{ $message }}</span>@enderror
                        </div>
                        <div class="mb-4">
                            <label for="formEmail" class="block text-gray-700 text-sm font-bold mb-2">Email:</label>
                            <input type="email" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="formEmail" wire:model="email">
                            @error('email') <span class="text-red-500">{{ $message }}</span>@enderror
                        </div>
                        <div class="mb-4">
                            <label for="formPhone" class="block text-gray-700 text-sm font-bold mb-2">Telp:</label>
                            <input type="text" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="formPhone" wire:model="phone_number">
                            @error('phone_number') <span class="text-red-500">{{ $message }}</span>@enderror
                        </div>
                        <div class="mb-4">
                            <label for="formStatus" class="block text-gray-700 text-sm font-bold mb-2">Status</label>
                            <select class="form-control" wire:model="status" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
                                <option value="">Pilih</option>
                                <option value="1">Premium</option>
                                <option value="0">Free</option>
                            </select>
                            @error('status') <span class="text-red-500">{{ $message }}</span>@enderror
                        </div>
                    </div>
                </div>
    
                <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
                    <span class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
                        <button wire:click.prevent="store()" type="button" class="inline-flex justify-center w-full rounded-md border border-transparent px-4 py-2 bg-green-600 text-base leading-6 font-medium text-white shadow-sm hover:bg-green-500 focus:outline-none focus:border-green-700 focus:shadow-outline-green transition ease-in-out duration-150 sm:text-sm sm:leading-5">
                        Save
                        </button>
                    </span>
                    <span class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
                        
                        <button wire:click="closeModal()" type="button" class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-base leading-6 font-medium text-gray-700 shadow-sm hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue transition ease-in-out duration-150 sm:text-sm sm:leading-5">
                        Cancel
                        </button>
                    </span>
                </form>
            </div>
        </div>
    </div>
</div>

Bagian yang perlu diperhatikan pada code di atas adalah sebagai berikut

  1. Directive wire:model="name" dimana name adalah public property yang sudah kita tetapkan pada file app/Http/Livewire/Members.php. Jika kamu sudah pernah menggunakan Vue.js, maka kita asumsikan bahwa wire:model itu hampir sama dengan v-model.
  2. Kode @error('name') akan mengecek jika terdapat error validasi terkait properti yang sedang dituju, yakni, name. Maka alert error validasi akan ditampilkan.

Baca Juga: 6 Cara Mendefinisikan Component di Vue.js

Membuat Routing

Langkah terakhir adalah mendefinisikan routing dari halaman member agar bisa diakses melalui browser. Buka file routes/web.php dan modifikasi menjadi

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Livewire\Members; //Load class Members 

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::group(['middleware' => ['auth:sanctum', 'verified']], function() {
    Route::get('/dashboard', function() {
        return view('dashboard');
    })->name('dashboard');

    Route::get('member', Members::class)->name('member'); //Tambahkan routing ini
});

Sebagai penutup, tambahkan code di bawah ini pada bagian navigasi agar bisa diakses melalui menu navigasi. Buka file navigation-dropdown.blade.php lalu tempatkan di bawah menu dashboard.

<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
    <x-jet-nav-link href="{{ route('member') }}" :active="request()->routeIs('member')">
        Member
    </x-jet-nav-link>
</div>

Category:
Share:

Comments