Step 01: running artisan commands for creating project, installing bootstrap, scaffolding auth and installing livewire
composer create-project laravel/laravel livewire composer require laravel/ui php artisan ui bootstrap php artisan ui bootstrap --auth npm install
composer require livewire/livewire
Step 02: Setting up vite.config.js like following
import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import path from 'path'; export default defineConfig({ plugins: [ laravel({ input: [ 'resources/scss/app.scss', 'resources/js/app.js', ], refresh: true, }), ], resolve: { alias: { '~bootstrap': path.resolve(__dirname, 'node_modules/bootstrap'), } } });
Step 03: now importing bootstrap css inside resources/scss/app.scss
@import "~bootstrap/scss/bootstrap";
Step 04: Setting up resources/views/components/layouts/app.blade.php adding $slot variable, @livewireStyles, @livewireScripts for livewire component, vite directive with css and js at the top inside head tag like below
<!DOCTYPE html> <html lang="en"> <head> <title>Clients Issues Crud with Livewire</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> @vite(['resources/scss/app.scss', 'resources/js/app.js']) @livewireStyles </head> <body> <div class="p-5 bg-primary text-white text-center"> <h1>Clients Issues Crud with livewire</h1> </div> <nav class="navbar navbar-expand-sm bg-dark navbar-dark"> <div class="container"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link active" href="/">Home</a> </li> <li class="nav-item"> <a class="nav-link" href="{{ route('issues') }}">Issues</a> </li> <li class="nav-item"> <a class="nav-link" href="{{ route('issues.create') }}">Create an Issue</a> </li> </ul> </div> </nav> <div class="container mt-5"> <div class="row"> <div class="col-sm-12"> {{ $slot }} </div> </div> </div> <div class="mt-5 p-4 bg-dark text-white text-center"> <p>Footer</p> </div> @livewireScripts </body> </html>
Step 05: Seeding clients and issues data
Following this link seeding clients and issues data will be implemented.
Step 06: Now, Issues component list with livewire
// with artisan command creating first livewire for showing issue list php artisan make:livewire Issues/IssueList // inside routes/web.php adding routes for issues list // at the top use App\Livewire\Issues\IssueList; Route::get('issues', IssueList::class)->name('issues'); //Inside app/Livewire/Issues/IssueList making changes like below namespace App\Livewire\Issues; use App\Models\Issue; use Livewire\Component; use Illuminate\Contracts\View\View; class IssueList extends Component { public $page; protected $queryString = ['page']; public function render(): View { $issues = Issue::with('client')->latest()->paginate(); //dd($this->page); session('submit_redirect', ''); // creating $this->page variable using public property $page
// and value of protected property $queryString
if( $this->page > 0) { // if page query string has value greater than 0 session()->put('submit_redirect', $_SERVER['REQUEST_URI']); //getting current query string from url and saving it to session variable } return view('livewire.issues.issue-list', compact('issues')); } public function delete(Issue $issue): void { $issue->delete(); $redirectTo = session()->get('submit_redirect'); // deleting and redirecting to the page from where deletion is happening which may be not in 1 but in 2,3,4 etc if( !empty( $redirectTo ) ) { redirect(url('').$redirectTo)->with( 'success', 'Issue deleted successfully' ); } } } ?>
// now into livewire.issues.issue-list.blade.php file with edit and delete link for issues <div> <h2 class="text-xl font-semibold leading-tight text-gray-800"> Issues List </h2> <table class="table"> <thead class="thead-dark"> <tr> <th>ID</th> <th>Client</th> <th>Title</th> <th>Description</th> <th>Created</th> <th>Actions</th> </tr> </thead> <tbody> @forelse($issues as $issue) <tr > <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> {{ $issue->id }} </td> <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> {{ $issue->client->name }} </td> <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> {{ $issue->title }} </td> <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> {{ substr($issue->description, 0, 50) . '...' }} </td> <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> {{ $issue->created_at->toDateString() }} </td> <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> <a href="{{ route('issues.edit', $issue->id) }}" class="btn btn-sm btn-primary"> Edit </a> <button wire:click="delete({{ $issue }})" class="btn btn-sm btn-danger"> Delete </button> </td> </tr> @empty <tr> <td colspan="4" class="px-6 py-4 text-center leading-5 text-gray-900 whitespace-no-wrap"> No issues were found. </td> </tr> @endforelse </tbody> </table> <div class="d-flex justify-content-center mt-3"> {!! $issues->links() !!} </div> </div> ?>Now for bootstrap links the html will be broken. To fix this we need to take the help of AppServiceProvider.php and Paginator facade like below
// at the top use Illuminate\Pagination\Paginator; public function boot(): void { // ... Paginator::useBootstrap(); }Now bootstrap navigation html link will be shown correctly. But it will be shown left aligned. To make it center aligned we need to use class d-flex and justify-content-center inside div class value like above.
Step 07: Now for creating and editing issues with client_id as foreign key
// first livewire component php artisan make:livewire Issues/IssueForm // then adding routes for issues create and edit page // at the top use App\Livewire\Issues\IssueForm; Route::get('issues/create', IssueForm::class)->name('issues.create'); Route::get('issues/{issue}', IssueForm::class)->name('issues.edit'); // then inside app/Livewire/Issues/IssueForm.php itself
// adding mount(), save(), render() and rules() method like below use App\Models\Client; use App\Models\Issue; use Illuminate\Http\RedirectResponse; use Livewire\Features\SupportRedirects\Redirector; use Livewire\Component; use Illuminate\Contracts\View\View; class IssueForm extends Component { public ?Issue $issue = null; public string $title= ''; public string|null $description = ''; public int|null $client_id = null; public bool $editing = false; // tracking form whether it is in create or edit mode public $clients; // using this method to load issue first if the form is in edit mode public function mount(Issue $issue): void { $this->issue = $issue; //dd($this->issue); if ($this->issue->exists) { $this->editing = true; $this->title = $issue->title; $this->description = $issue->description; $this->client_id = $issue->client_id; } $this->clients = Client::orderBy('name','asc')->get(); } // this will be default load view method for this livewire component public function render(): View { return view('livewire.issues.issue-form'); } // this method will be used after form is submitted in livewire component public function save(): Redirector|RedirectResponse { $this->validate(); //dd('inside save'); if ( !$this->editing ) { $this->issue = Issue::create($this->only(['title', 'description', 'client_id'])); } else { $this->issue->update($this->only(['title', 'description', 'client_id'])); } return to_route('issues'); } // rules that will be used in livewire component during form submission protected function rules(): array { return [ 'title' => [ 'string', 'required', ], 'description' => [ 'string', 'required', ], 'client_id' => [ 'required', ] ]; } }
// Now changing issue-form.blade.php like below for creating or editing issue <div> <h1>{{ $editing ? 'Edit Client Issue ' : 'Create Client Issue' }}</h1> <div class="card mt-3"> <div class="card-body"> <form wire:submit="save"> <div class="mb-3"> <label for="title">Issue title</label> <input wire:model="title" id="title" class="form-control" type="text" name="title" required /> @if ($errors->has('title')) <span class="text-danger">{{ $errors->first('title') }}</span> @endif </div> <div class="mb-3"> <label for="email">Description:</label> <textarea class="form-control" rows="5" id="description" name="description" wire:model="description"></textarea> @if ($errors->has('description')) <span class="text-danger">{{ $errors->first('description') }}</span> @endif </div> <div class="mb-3"> <label for="pwd">Client:</label> <select id="client_id" class="form-control" wire:model="client_id"> <option value="" selected>-- Choose client --</option> @foreach( $clients as $client ) <option value="{{ $client->id }}" >{{ $client->name }}</option> @endforeach </select> @if ($errors->has('client_id')) <span class="text-danger">{{ $errors->first('client_id') }}</span> @endif </div> <button type="submit" class="btn btn-primary cursor-not-allowed"> <span>Save</span> </button> </form> </div> </div> </div>
So, now after implementiation of all the steps above if arisan ( php artisan serve ) and npm commands ( npm run dev ) are run we will be able to see client issues crud with livewire inside this url http://localhost:8000
This tutorial's github repo can be checked in this url.