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.
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:
- Tombol tambah anggota, dimana kita menggunakan fungsi
wire:click="create()"
, kodecreate()
adalah method yang sudah kita definisikan sebelumnya pada fileapp/Http/Livewire/Members.php
. - Tombol edit, dimana kita menggunakan fungsi
wire:click="edit({{ $row->id }})"
. Jika kita mengingat kembali methodedit()
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 methodedit()
. - 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
- Directive
wire:model="name"
dimananame
adalah public property yang sudah kita tetapkan pada fileapp/Http/Livewire/Members.php
. Jika kamu sudah pernah menggunakan Vue.js, maka kita asumsikan bahwawire:model
itu hampir sama denganv-model
. - 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>
Comments