Let's practice creating a CRUD with another one: Task Categories. It will be almost the same as Tasks CRUD, so for the most part, I will just show the code and specify the differences.
Task Categories: DB Model/Migration
First, we prepare the back end.
Create Model, Migration, and Pivot table for task categories:
php artisan make:model TaskCategory -mphp artisan make:migration create_task_task_category_table
Migration:
Schema::create('task_categories', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps();});
Pivot migration:
Schema::create('task_task_category', function (Blueprint $table) { $table->foreignId('task_id')->constrained(); $table->foreignId('task_category_id')->constrained();});
app/Models/Task.php:
use Illuminate\Database\Eloquent\Relations\BelongsToMany; // ... public function taskCategories(): BelongsToMany{ return $this->belongsToMany(TaskCategory::class);}
app/Models/TaskCategory.php
namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsToMany; class TaskCategory extends Model{ protected $fillable = ['name']; public function tasks(): BelongsToMany { return $this->belongsToMany(Task::class); }}
Livewire Components and Routes
Create Livewire components:
php artisan make:livewire TaskCategories/Indexphp artisan make:livewire TaskCategories/Createphp artisan make:livewire TaskCategories/Edit
app/Livewire/TaskCategories/Index.php:
use Livewire\Component;use Illuminate\View\View;use App\Models\TaskCategory; class Index extends Component{ public function delete(int $id): void { $taskCategory = TaskCategory::findOrFail($id); if ($taskCategory->tasks()->count() > 0) { $taskCategory->tasks()->detach(); } $taskCategory->delete(); } public function render(): View { return view('livewire.task-categories.index', [ 'taskCategories' => TaskCategory::withCount('tasks')->paginate(5), ]); }}
app/Livewire/TaskCategories/Create.php:
use Livewire\Component;use Illuminate\View\View;use App\Models\TaskCategory;use Livewire\Attributes\Validate; class Create extends Component{ #[Validate('required|string|max:255')] public string $name = ''; public function save(): void { $this->validate(); TaskCategory::create([ 'name' => $this->name, ]); session()->flash('success', 'Task category successfully created.'); $this->redirectRoute('task-categories.index', navigate: true); } public function render(): View { return view('livewire.task-categories.create'); }}
app/Livewire/TaskCategories/Edit.php:
use Livewire\Component;use Illuminate\View\View;use App\Models\TaskCategory;use Livewire\Attributes\Validate; class Edit extends Component{ #[Validate('required|string|max:255')] public string $name; public TaskCategory $taskCategory; public function mount(TaskCategory $taskCategory): void { $this->taskCategory = $taskCategory; $this->name = $taskCategory->name; } public function save(): void { $this->validate(); $this->taskCategory->update([ 'name' => $this->name, ]); session()->flash('success', 'Task category successfully updated.'); $this->redirectRoute('task-categories.index', navigate: true); } public function render(): View { return view('livewire.task-categories.edit'); }}
And assign them routes.
routes/web.php
use App\Livewire\TaskCategories; // ... Route::middleware(['auth'])->group(function () { // ... Route::get('task-categories', TaskCategories\Index::class)->name('task-categories.index'); Route::get('task-categories/create', TaskCategories\Create::class)->name('task-categories.create'); Route::get('task-categories/{taskCategory}/edit', TaskCategories\Edit::class)->name('task-categories.edit');}); require __DIR__.'/auth.php';
Showing Categories Table
Here's what our table will look like. Again, it is very similar to the Task list.
resources/views/livewire/task-categories/index.blade.php:
<section> <x-alerts.success /> <flux:button href="{{ route('task-categories.create') }}" variant="filled" class="mb-4">{{ __('Create Category') }}</flux:button> <div class="relative overflow-x-auto shadow-md sm:rounded-lg"> <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400"> <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"> <tr> <th scope="col" class="px-6 py-3"> Name </th> <th scope="col" class="px-6 py-3"> Assigned Tasks </th> <th scope="col" class="px-6 py-3"> Actions </th> </tr> </thead> <tbody> @foreach($taskCategories as $category) <tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700 border-gray-200"> <th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white"> {{ $category->name }} </th> <td class="px-6 py-4"> {{ $category->tasks_count }} </td> <td class="px-6 py-4 space-x-2"> <flux:button href="{{ route('task-categories.edit', $category) }}" variant="filled">{{ __('Edit') }}</flux:button> <flux:button wire:confirm="Are you sure you want to delete category with {{ $category->tasks_count }} assigned tasks?" wire:click="delete({{ $category->id }})" variant="danger" type="button">{{ __('Delete') }}</flux:button> </td> </tr> @endforeach </tbody> </table> </div> @if($taskCategories->hasPages()) <div class="mt-5"> {{ $taskCategories->links() }} </div> @endif</section>
Okay, now how do we get to that route to test it? We need a link or button, right?
Adding a Button to Manage Categories
In this case, I won't add a link in the main navigation. Rather, I will add a button at the top of the Tasks List, near the "Create Task" button.
resources/views/livewire/tasks/index.blade.php:
BEFORE:
<section> <x-alerts.success /> <div class="flex flex-grow gap-x-4 mb-4"> <flux:button href="{{ route('tasks.create') }}" variant="filled">{{ __('Create Task') }}</flux:button> </div> // ...
AFTER:
<section> <x-alerts.success /> <div class="flex flex-grow gap-x-4 mb-4"> <flux:button href="{{ route('tasks.create') }}" variant="filled">{{ __('Create Task') }}</flux:button> <flux:button href="{{ route('task-categories.index') }}" variant="filled">{{ __('Manage Task Categories') }}</flux:button> </div>
The visual result:
If we click that button, we see the empty (but working!) table:
Create Category Form
I'm not even sure if I need to comment anything here. It has a totally identical structure to the Create Task form from the previous lesson.
resources/views/livewire/task-categories/create.blade.php:
<section class="max-w-5xl"> <form wire:submit="save" class="flex flex-col gap-6"> <flux:input wire:model="name" :label="__('Name')" required badge="required" /> <div> <flux:button variant="primary" type="submit">{{ __('Save') }}</flux:button> </div> </form></section>
Here's the visual result:
Edit Category Form
Again, very similar to the Task Edit form. Not much more to say.
resources/views/livewire/task-categories/edit.blade.php:
<section class="max-w-5xl"> <form wire:submit="save" class="flex flex-col gap-6"> <flux:input wire:model="name" :label="__('Name')" required badge="required" /> <div> <flux:button variant="primary" type="submit">{{ __('Save') }}</flux:button> </div> </form></section>
Here's the visual result if we click Edit:
Now, we don't have category selection in the Task Create/Edit forms. We will add that in the next lesson.
The repository for this starter kit project section is here on GitHub.
No comments yet…