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 collectionUser::query() ->withAggregate('country', 'name') -> ...
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 changespublic function updated($property): void{ if (! is_array($property) && $property != "") { $this->resetPage(); }}
You could improve the clear()
method as well.
// Clear filterspublic function clear(): void{ $this->reset(); $this->resetPage(); $this->success('Filters cleared.', position: 'toast-bottom');}
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.
<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 docsIf 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.