Courses

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

avatar

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

avatar

You can see in lesson 5

avatar

ups! Didn't read the title :( thanks mate

avatar

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.

avatar

Maybe you should re-run npm run dev to compile new CSS classes?

avatar

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.

avatar

In the 'routes/web.php:' you need to enter 'use App\Http\Livewire\CategoriesList;'

👍 5
avatar

Thank you @Povilas, copy-pasting makes learning less stressful when it works. It's always good to acknowledge when something works well.

👍 1
avatar

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:

public function definition(): array
    {
        $country = fake()->unique()->country();

        return [
            'name' => $country,
            'slug' => Str::slug($country),
        ];
    }

Open DatabaseSeeder.php and amend:

public function run(): void
    {
        User::factory(10)->create();
        Category::factory(11)->create();
    }

Now seed: php artisam migrate:fresh --seed

👍 5
avatar

use Illuminate\Support\Str;

avatar

Pagination buttons not work even after running npm run build.

avatar

What do you mean by "not work"? Do you see any JavaScript error in the browser console?

avatar

None

avatar

Francis, can you make repo to reproduce your problem?

avatar

Same here. Links are displayed, but nothing happens when clicked. UPDATE: Place {!! $categories->links() !!} emediately after

avatar

Yeah... it's in the tutorial...

avatar

It seems to be placed att the last row of the script...

avatar

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