Listing users

As you can see on the existing users/index.blade.php example component you can already sort and filter, but the data is hardcoded. Let's fix it now!

Table component

Table docs

The x-table is a powerful component. You can easily display data, paginate, customize rows using slots, or make it sortable, clickable, selectable or expandable.

<x-table :headers="$headers" :rows="$users" />

Let's replace entirely the users() method to make it use an Eloquent query.

use Illuminate\Database\Eloquent\Builder;
 
public function users(): Collection
{
return User::query()
->with(['country'])
->when($this->search, fn(Builder $q) => $q->where('name', 'like', "%$this->search%"))
->orderBy(...array_values($this->sortBy))
->get();
}

After this you can see all the users from the database. Notice the users.age column is empty because we have removed it from migrations. Let's fix it on the next topic.

Sorting

As you noticed on example source code, we have a $sortBy property to control the sorting column and its direction. It works automatically when you click on the table headers.

<x-table :headers="$headers" :rows="$users" :sort-by="$sortBy" ... />

In our $headers property let's replace the age column for country.name and refresh the page to see the result. Nice! Table component works with dot notation.

-['key' => 'age', 'label' => 'Age', 'class' => 'w-20'],
+['key' => 'country.name', 'label' => 'Country'],

But, if you try to sort by the country column you will get an error. Let's fix this by using an Eloquent trick.

// It will add an extra column `country_name` on User collection
User::query()
->withAggregate('country', 'name')
-> ...
Finally, adjust the headers property to sort by the new custom column.
-['key' => 'country.name', 'label' => 'Country'],
+['key' => 'country_name', 'label' => 'Country'],

Pagination

As described on Laravel docs you need to adjust your tailwind.config.js

content: [
// Add this
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
],

Go back to users/index.blade and use the WithPagination trait from Livewire itself, as described in Livewire docs.

use Livewire\WithPagination;
 
new class extends Component
{
use WithPagination;
}

Add the with-pagination property on the x-table component.

<x-table ... with-pagination>

Finally, make some changes to use an Eloquent paginated query. Notice that the return type is changed.

use Illuminate\Pagination\LengthAwarePaginator;
 
public function users(): LengthAwarePaginator
{
return User::query()
->withAggregate('country', 'name')
->when($this->search, fn(Builder $q) => $q->where('name', 'like', "%$this->search%"))
->orderBy(...array_values($this->sortBy))
->paginate(5); // No more `->get()`
}

Clear filters

There is a "bug" on pagination...

  • Go to page 9.
  • Filter by any name you see on that page.
  • The list goes empty!

Actually it is not a bug in itself, but a Livewire pagination particularity (not maryUI) you must be aware when you change filters.

Let's fix it by using Livewire lifecycle hooks in order to reset the pagination when any component property changes. Add the following method to the example component.

// Reset pagination when any component property changes
public function updated($property): void
{
if (! is_array($property) && $property != "") {
$this->resetPage();
}
}

You could improve the clear() method as well.

// Clear filters
public function clear(): void
{
$this->reset();
$this->resetPage();
$this->success('Filters cleared.', position: 'toast-bottom');
}
Pro tip: You could create a trait like ClearsFilters with those methods above to reuse the logic.

Table CSS

You can apply CSS on the table headers and make it responsive just like that. Check it at mobile size.

-['key' => 'country_name', 'label' => 'Country'],
+['key' => 'country_name', 'label' => 'Country', 'class' => 'hidden lg:table-cell'],

You can even decorate rows and cells with custom CSS, in addition to use custom slots to override cells. You can do even more with maryUI tables. Check the docs for more.

Header component

Header docs

Check the example's source code to see how useful thex-header component is. It includes a progress indicator, has built-in layout and is responsive. Check it at mobile size.

{{-- Let's change the title --}}
<x-header title="Users" separator progress-indicator ... />

Toast component

Toast docs

The maryUI installer already set up x-toast for you.

Let's replace entirely the delete() method.

public function delete(User $user): void
{
$user->delete();
$this->warning("$user->name deleted", 'Good bye!', position: 'toast-bottom');
}

Drawer component

Drawer docs

The x-drawer component is a great way to avoid interrupting the users flow when it is necessary to quickly execute a secondary action. It comes with a handy close button and a default layout.

<x-drawer wire:model="drawer" title="Filters" right separator with-close-button ... />

Let's add a new filter by country.

use App\Models\Country;
 
new class extends Component {
...
 
// Create a public property.
public int $country_id = 0;
 
// Add a condition to filter by country
public function users(): LengthAwarePaginator
{
...
->when(...)
->when($this->country_id, fn(Builder $q) => $q->where('country_id', $this->country_id))
...
}
 
// Add a new property
public function with(): array
{
return [
'users' => $this->users(),
'headers' => $this->headers(),
'countries' => Country::all(),
];
}
}

Finally, place an x-select component inside the drawer, with a small CSS grid to make it look even better.

Select docs
<x-drawer ...>
...
<div class="grid gap-5">
<x-input placeholder="Search..." ... />
<x-select placeholder="Country" wire:model.live="country_id" :options="$countries" icon="o-flag" placeholder-value="0" />
</div>
 
</x-drawer>

Challenge

Button docs

If you are using a drawer you will probably have a few more filter options. In order to have a better UX it would be nice to display how many filters the user have selected.

Tip: use the button badge property and an extra method on your component to count how many filters are filled.

Before proceeding, we recommend that you make a local commit in order to keep track of what is going on.

maryUI
Sponsor