Laravel Jetstream CRUD | Laravel 10 Livewire 3 CRUD with Alpine JS & Tailwind CSS Example

Raviya Technical
6 min readDec 10, 2023
Laravel Jetstream CRUD | Laravel 10 Livewire 3 CRUD with Alpine JS & Tailwind CSS Example

Setup Fresh Laravel 10 Project and Install Jetstream

composer create-project --prefer-dist laravel/laravel jetstream
cd jetstream
php artisan jetstream:install livewire --dark --verification

Creating a Components for Table Elements

php artisan make:component table --view
php artisan make:component table.header --view
php artisan make:component table.row --view
php artisan make:component table.cell --view
php artisan make:component modals.form --view

Path:- resources/views/components/table.blade.php

<div class="align-middle min-w-full overflow-x-auto shadow overflow-hidden sm:rounded-lg">
<table class="w-full divide-y divide-gray-200">
@if (!empty($header))
<thead class="table-head">
{{ $header }}
</thead>
@endif
<tbody class="bg-white divide-y divide-gray-200">
{{ $body ?? '' }}
</tbody>
@if (!empty($footer))
<tfoot class="table-foot">
{{ $footer }}
</tfoot>
@endif
</table>
</div>

Path:- resources/views/components/table/header.blade.php

@props([
'sortable' => null,
'direction' => null,
])

<th {{ $attributes->merge(['class' => 'px-6 py-3 bg-gray-50'])->only('class') }}>

@unless ($sortable)
<span class="text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
{{ $slot }}
</span>
@else
<button {{ $attributes->except('class') }}
class="flex items-center space-x-1 text-left text-xs leading-4 font-medium">
<span>
{{ $slot }}
</span>
<span>
@if ($direction === 'asc')
<i class="fas fa-sort-up"></i> ↑
@elseif ($direction === 'desc')
<i class="fas fa-sort-down"></i> ↓
@else
<i class="text-muted fas fa-sort"></i> ↑↓
@endif
</span>
</button>
@endunless
</th>

Path:- resources/views/components/table/row.blade.php

<tr {{ $attributes->merge(['class' => 'bg-white']) }}>
{{ $slot }}
</tr>

Path:- resources/views/components/table/cell.blade.php

<td {{ $attributes->merge(['class' => 'px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900 text-center']) }}>
{{ $slot }}
</td>

Path:- resources/views/components/modals/form.blade.php

@props(['id' => null, 'maxWidth' => null, 'submit' => null])

<x-modal :id="$id" :maxWidth="$maxWidth" {{ $attributes }}>
<form wire:submit="{{ $submit }}">
<div class="px-6 py-4">
<div class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ $title }}
</div>

<div class="mt-4 text-sm text-gray-600 dark:text-gray-400">
{{ $content }}
</div>
</div>

<div class="flex flex-row justify-end px-6 py-4 bg-gray-100 dark:bg-gray-800 text-end">
<x-secondary-button wire:click.prevent="closeFormModal" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-secondary-button>

<x-button class="ms-3" wire:click.prevent="save" wire:loading.attr="disabled">
{{ __('Save') }}
</x-button>
</div>
</form>
</x-modal>

Creating Users using Livewire Path:- resources/views/livewire/users.blade.php

php artisan make:livewire Users

Change In Users Class Path:- app/Livewire/Users.php

<?php

namespace App\Livewire;

use Illuminate\Support\Facades\Hash;
use Livewire\Component;
use Livewire\WithPagination;
use App\Models\User;

class Users extends Component
{
use WithPagination;

public $perPage = 10;
public $sortField = 'name';
public $sortDirection = 'asc';

protected $queryString = ['sortField', 'sortDirection'];

//Form Field
public $rId = null;
public $isFormOpen = false;
public $name, $email, $password;
//Action
public $dId = '';
public $isDeleteModalOpen = false;

public function sortBy($field)
{
$this->sortDirection = $this->sortField === $field ?
$this->sortDirection = $this->sortDirection == 'asc' ? 'desc' : 'asc'
: 'asc';
$this->sortField = $field;
}

// For Delete Feature Start
public function deleteId($id)
{
$this->dId = $id;
$this->isDeleteModalOpen = true;
}

public function closeDelete()
{
$this->dId = '';
$this->isDeleteModalOpen = false;
}

public function delete()
{
try {
$user = User::find($this->dId);
if ($user) {
$user->delete();
}
$this->closeDelete();
session()->flash('success', 'Record deleted successfully!!');
} catch (\Exception $ex) {
session()->flash('success', 'Something goes wrong!!');
}
}
// For Delete Feature End

// Create and Update Feature Start
public function edit($id = null)
{
try {
$this->rId = $id;
if (!empty($this->rId)) {
$user = User::find($this->rId);
if ($user) {
$this->name = $user->name;
$this->email = $user->email;
}
}
$this->isFormOpen = true;
} catch (\Exception $ex) {
session()->flash('success', 'Something goes wrong!!');
}
}

public function save()
{
$ruleFields = [
'name' => 'required',
'email' => 'required|email',
'password' => ($this->rId) ? 'nullable' : 'required',
];
$validatedData = $this->validate($ruleFields);
$validatedData['user_id'] = auth()->id();

if (!empty($validatedData['password'])) {
$validatedData['password'] = Hash::make($validatedData['password']);
} else {
unset($validatedData['password']);
}
try {
$userQuery = User::query();
if (!empty($this->rId)) {
$user = $userQuery->find($this->rId);
if ($user) {
$user->update($validatedData);
}
} else {
$userQuery->create($validatedData);
}
$this->closeFormModal();
} catch (\Exception $ex) {
session()->flash('success', 'Something goes wrong!!');
}
}

public function closeFormModal()
{
$this->isFormOpen = false;
$this->reset();
}
// Create and Update Feature End

public function render()
{
return view('livewire.users', [
'records' => User::orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage)
])->layout('layouts.app');
}
}

Change In Users Class Path:- resources/views/livewire/users.blade.php

<div>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Users') }}
</h2>
</x-slot>

<div>
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<div class="flex mb-5">
<x-button wire:click.prevent="edit" class="float-right">
{{ __('Create User') }}
</x-button>
</div>
<x-table wire:loading.class="opacity-75">
<x-slot name="header">
<x-table.header>No.</x-table.header>
<x-table.header sortable wire:click.prevent="sortBy('name')" :direction="$sortField === 'name' ? $sortDirection : null">Name</x-table.header>
<x-table.header sortable wire:click.prevent="sortBy('email')"
:direction="$sortField === 'email' ? $sortDirection : null">Email</x-table.header>
<x-table.header>Action</x-table.header>
</x-slot>
<x-slot name="body">
@php
$i = (request()->input('page', 1) - 1) * $perPage;
@endphp
@forelse ($records as $key => $record)
<x-table.row>
<x-table.cell> {{ ++$i }}</x-table.cell>
<x-table.cell>{{ $record->name }}</x-table.cell>
<x-table.cell> {{ $record->email }}</x-table.cell>
<x-table.cell>
<button wire:click="edit('{{ $record->id }}')"
class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Edit</button>
<button wire:click="deleteId('{{ $record->id }}')"
class="font-medium text-red-600 dark:text-red-500 hover:underline">Delete</button>
</x-table.cell>
</x-table.row>
@empty
<x-table.row>
<x-table.cell colspan=4>
<div class="flex justify-center items-center">
<span class="font-medium py-8 text-gray-400 text-xl">
No data found...
</span>
</div>
</x-table.cell>
</x-table.row>
@endforelse
</x-slot>
</x-table>
@if ($records->hasPages())
<div class="p-3">
{{ $records->links() }}
</div>
@endif
</div>
</div>

<!-- Add/Edit Modal -->
<x-modals.form wire:model.live="isFormOpen">
<x-slot name="title">
{{ __('Add/Edit Record') }}
</x-slot>

<x-slot name="content">
<div class="col-span-6 sm:col-span-4">
<x-label for="name" value="{{ __('Name') }}" />
<x-input id="name" type="text" class="mt-1 block w-full" wire:model="name" />
<x-input-error for="name" class="mt-2" />
</div>
<div class="col-span-6 sm:col-span-4">
<x-label for="email" value="{{ __('Email') }}" />
<x-input id="email" type="text" class="mt-1 block w-full" wire:model="email" />
<x-input-error for="email" class="mt-2" />
</div>
<div class="col-span-6 sm:col-span-4">
<x-label for="password" value="{{ __('Password') }}" />
<x-input id="password" type="password" class="mt-1 block w-full" wire:model="password" />
<x-input-error for="password" class="mt-2" />
</div>
</x-slot>
</x-modals.form>
<!-- Delete Confirmation Modal -->
<x-confirmation-modal wire:model.live="isDeleteModalOpen">
<x-slot name="title">
{{ __('Delete Record') }}
</x-slot>

<x-slot name="content">
{{ __('Are you sure you would like to delete this record?') }}
</x-slot>

<x-slot name="footer">
<x-secondary-button wire:click.prevent="closeDelete">
{{ __('Cancel') }}
</x-secondary-button>

<x-danger-button class="ms-3" wire:click.prevent="delete" wire:loading.attr="disabled">
{{ __('Delete') }}
</x-danger-button>
</x-slot>
</x-confirmation-modal>
</div>
Laravel Jetstream CRUD | Laravel 10 Livewire 3 CRUD with Alpine JS & Tailwind CSS Form Example
Laravel Jetstream CRUD | Laravel 10 Livewire 3 CRUD with Alpine JS & Tailwind CSS Delete Functionality Example

--

--