In this lesson, we will create a table of Categories, powered by Livewire. For now, without too many dynamic elements, but one step at a time.
The final result of this lesson will look like this:
First, we will create a Livewire component.
php artisan make:livewire CategoriesList
It will serve instead of Laravel Controller, as a full-page Livewire component, so we will attach the route directly to the Component, and our project will not have Controllers at all.
So, let's register the route in the middleware group, with the name of categories.index
, and add a link to the navigation.
routes/web.php:
Route::middleware('auth')->group(function () { Route::get('categories', CategoriesList::class)->name('categories.index'); Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');});
resources/views/layouts/navigation.blade.php:
<!-- Navigation Links --><div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex"> <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')"> {{ __('Dashboard') }} </x-nav-link> <x-nav-link :href="route('categories.index')" :active="request()->routeIs('categories.index')"> {{ __('Categories') }} </x-nav-link> </div>
Of course, we need Model and Migrations:
php artisan make:model Category -m
database/migrations/xxxx_create_categories_table.php:
return new class extends Migration{ public function up() { Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('slug'); $table->boolean('is_active')->default(true); $table->timestamps(); }); }};
app/Models/Category.php:
class Category extends Model{ use HasFactory; protected $fillable = ['name', 'slug', 'is_active']; }
Now in resources/views/livewire/categories-list.blade.php
we will add this Blade code for the table structure, with "hardcoded" one row to show you the future behavior visually.
<div> <x-slot name="header"> <h2 class="text-xl font-semibold leading-tight text-gray-800"> {{ __('Categories') }} </h2> </x-slot> <div class="py-12"> <div class="mx-auto max-w-7xl sm:px-6 lg:px-8"> <div class="overflow-hidden bg-white shadow-sm sm:rounded-lg"> <div class="p-6 bg-white border-b border-gray-200"> <x-primary-button class="mb-4"> Add Category </x-primary-button> <div class="overflow-hidden overflow-x-auto mb-4 min-w-full align-middle sm:rounded-md"> <table class="min-w-full border divide-y divide-gray-200"> <thead> <tr> <th class="px-6 py-3 w-10 text-left bg-gray-50"> </th> <th class="px-6 py-3 text-left bg-gray-50"> <span class="text-xs font-medium tracking-wider leading-4 text-gray-500 uppercase">Name</span> </th> <th class="px-6 py-3 text-left bg-gray-50"> <span class="text-xs font-medium tracking-wider leading-4 text-gray-500 uppercase">Slug</span> </th> <th class="px-6 py-3 text-left bg-gray-50"> <span class="text-xs font-medium tracking-wider leading-4 text-gray-500 uppercase">Active</span> </th> <th class="px-6 py-3 text-left bg-gray-50 w-56"> </th> </tr> </thead> <tbody class="bg-white divide-y divide-gray-200 divide-solid"> <tr class="bg-white"> <td class="px-6"> <button> <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> <path fill="none" d="M0 0h256v256H0z" /> <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M156.3 203.7 128 232l-28.3-28.3M128 160v72M99.7 52.3 128 24l28.3 28.3M128 96V24M52.3 156.3 24 128l28.3-28.3M96 128H24M203.7 99.7 232 128l-28.3 28.3M160 128h72" /> </svg> </button> </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> Category name </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> Category slug </td> <td class="px-6"> <div class="inline-block relative mr-2 w-10 align-middle transition duration-200 ease-in select-none"> <input type="checkbox" name="toggle" class="block absolute w-6 h-6 bg-white rounded-full border-4 appearance-none cursor-pointer focus:outline-none toggle-checkbox" /> <label for="toggle" class="block overflow-hidden h-6 bg-gray-300 rounded-full cursor-pointer toggle-label"></label> </div> </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> <x-primary-button> Edit </x-primary-button> <button class="px-4 py-2 text-xs text-red-500 uppercase bg-red-200 rounded-md border border-transparent hover:text-red-700 hover:bg-red-300"> Delete </button> </td> </tr> </tbody> </table> </div> </div> </div> </div> </div></div>
Result:
Now, let's fill our table with the real data, from Livewire component.
Let's start by showing categories in the table, and paginating them. In the CategoriesList
Livewire component we need to add public properties and make a query to get Categories.
app/Livewire/CategoriesList.php:
use App\Models\Category; class CategoriesList extends Component{ use WithPagination; public function render(): View { $categories = Category::paginate(10); return view('livewire.categories-list', [ 'categories' => $categories, ]); }}
And in the resources/views/livewire/categories-list.blade.php
in the <tbody>
tag we can loop through all categories to show them and paginate.
<tbody class="bg-white divide-y divide-gray-200 divide-solid"> @foreach($categories as $category) <tr class="bg-white"> <td class="px-6"> <button> <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> <path fill="none" d="M0 0h256v256H0z" /> <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M156.3 203.7 128 232l-28.3-28.3M128 160v72M99.7 52.3 128 24l28.3 28.3M128 96V24M52.3 156.3 24 128l28.3-28.3M96 128H24M203.7 99.7 232 128l-28.3 28.3M160 128h72" /> </svg> </button> </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> Category name {{ $category->name }} </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> Category slug {{ $category->slug }} </td> <td class="px-6"> <div class="inline-block relative mr-2 w-10 align-middle transition duration-200 ease-in select-none"> <input type="checkbox" name="toggle" class="block absolute w-6 h-6 bg-white rounded-full border-4 appearance-none cursor-pointer focus:outline-none toggle-checkbox" /> <label for="toggle" class="block overflow-hidden h-6 bg-gray-300 rounded-full cursor-pointer toggle-label"></label> </div> </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> <x-primary-button> Edit </x-primary-button> <button class="px-4 py-2 text-xs text-red-500 uppercase bg-red-200 rounded-md border border-transparent hover:text-red-700 hover:bg-red-300"> Delete </button> </td> </tr> @endforeach </tbody> </table></div> {!! $categories->links() !!}
After manually adding at least 11 categories you should see a similar result, with a working pagination:
Hey guys, I can't get how it works the toggle checkbox? is it a tailwind class? or another component? Can't see the thing :(
Any help?
Thanks
You can see in lesson 5
ups! Didn't read the title :( thanks mate
Some problems difficult to solve for my level:
pagination links are not seen anywhere. Also, modal for create Category appears embedded in a horrible look inside the whole form. And button CREATE INSIDE the modal, is not seen. It is white over white. I think I am having trouble with tailwind changes I make. With the original code posted here it does not work also.
Sure some is my fault. Any help will be appreciated.
Maybe you should re-run
npm run dev
to compile new CSS classes?Thank you very much, Povilas.
Everything was corrected running npm run build. The colours and look of the pages and buttons, and also the modal is now acting as modal.
Tks again.
In the 'routes/web.php:' you need to enter 'use App\Http\Livewire\CategoriesList;'
Thank you @Povilas, copy-pasting makes learning less stressful when it works. It's always good to acknowledge when something works well.
For those who want to seed the database, can following these steps:
Execute on your terminal:
php artisan make:factory CategoryFactory
Open
CategoryFactory.php
and amend:Open
DatabaseSeeder.php
and amend:Now seed:
php artisam migrate:fresh --seed
use Illuminate\Support\Str;
Pagination buttons not work even after running npm run build.
What do you mean by "not work"? Do you see any JavaScript error in the browser console?
None
Francis, can you make repo to reproduce your problem?
Same here. Links are displayed, but nothing happens when clicked. UPDATE: Place {!! $categories->links() !!} emediately after
Yeah... it's in the tutorial...
It seems to be placed att the last row of the script...
Something worth to note for @povilas
At the section: And in the resources/views/livewire/categories-list.blade.php in the
tag we can loop through all categories to show them and paginate.You forgot to highlight in green for the closing of the @endforeach loop
Thanks.