Back to Course |
Practical Livewire 3: Order Management System Step-by-Step

Categories Table with Livewire

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:

paginated categories

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:

starter categories list

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:

paginated categories