Laravel Livewire: Datatable, with sort fields and search using QueryString

- Andrés Cruz

En español
Laravel Livewire: Datatable, with sort fields and search using QueryString

A table of type datatable, is the term that we use to indicate a table in basic HTML with the data with the CRUD type operations for each one of these data; this table; It is a table that allows us to define a set of useful options for our user such as pagination, search fields, column ordering, and of course, options to edit, delete, and all the others you need; In this entry we are only going to create the table, with the corresponding options for it, using tailwind.css for our interface, although you can use anything else like native CSS or even the tables we have for Bootstrap.

A DataTable can store data of any type, including text, numbers, dates, images, among other types. It is commonly used in web applications to display data in a table in an ordered and structured way. For example, if there is information from a list of publications, in this supposed list the options to delete, create, edit, see the detail, and filter the post may appear.

In the end, a Datatable is a table on steroids in which you define multiple management, filter, and sort options.

In Laravel Livewire, thanks to all the reactive behavior and communication with the server directly from the client, we can create these types of tables very easily.

Creating the Datatable in Laravel Livewire

As we mentioned before, there is some management component in the tables, therefore we have to define the model, the view and the component.

Define Model

First things first, and we need a data source, our user model, which is the one that comes free with Laravel Livewire, is quite convenient for us, but you can use any other; in our model of a lifetime for users with the traits that we have by default:

class User extends Authenticatable #implements MustVerifyEmail
{
    use HasApiTokens;
    use HasFactory;
    use HasProfilePhoto;
    use HasTeams;
    use Notifiable;
    use TwoFactorAuthenticatable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password'
    ];
}

Define trait for search fields using queryString

Now we go to our component, for that we are going to use a Livewire component that we can create with:

php artisan make:livewire UserList

And remember that with this, 3 elements are created, our Server Side Component and our view; now with the component created, we have to define some characteristics to be able to use the search; we use the property called queryString, to indicate which properties we are going to use for the search; This is useful because if our user refreshes the page, the data that the user is typing is saved in the application's URL and the search would be maintained:

use WithPagination;

protected $queryString = ['name'];
public $name = "";

Definir columnas y función de ordenación:

    public $sortColumn = "id";
    public $sortDirection = "asc";

    public $columns = [
        'id',
        'name',
        'email'
    ];

    public function sort($column)
    {
        $this->sortColumn = $column;
        $this->sortDirection = $this->sortDirection == 'asc' ? 'desc' : 'asc';
    }

For the rest, we define some more properties to indicate the columns that we are going to use, their names and also a function and properties that will allow us to search by columns, and the type of ordering, ascending or descending.

Process data for listing, render function

Now, the heart of our component, in which we build the query or the query to the database, as well as map the properties that we want to use to filter our data, which in this case is the name/name; otherwise we use pagination and return to the view we want to use:

    public function render()
    {
        $users = User::orderBy($this->sortColumn, $this->sortDirection);

        if ($this->name) {
            $users->where('name', 'like', '%' . $this->name . '%')
                ->orWhere('email', 'like', '%' . $this->name . '%');
        }

        $users = $users->paginate(10);

        return view('livewire.users-list', ['users' => $users]); //->layout('layouts.app');
    }

Full project code

Finally, here you can see the source code of the component used previously, in which we have the columns, as well as the columns to indicate the filter, ordering, in the render function, we indicate the view as well as apply the corresponding filters, remember that each of these properties used in the render function is used at the view level as models in the fields, thanks to the communication between the backend and the frontend, the communication is done directly and this is the secret and most important point of this project:

namespace App\Http\Livewire;

use App\Models\User;
//use Illuminate\View\Component;
use Livewire\Component;
use Livewire\WithPagination;

class UsersList extends Component
{

    use WithPagination;

    //public $users;

    public $columns = [
        'id',
        'name',
        'email'
    ];

    public $name = "";
    public $sortColumn = "id";
    public $sortDirection = "asc";

    protected $queryString = ['name'];

    public function render()
    {
        $users = User::orderBy($this->sortColumn, $this->sortDirection);

        if ($this->name) {
            $users->where('name', 'like', '%' . $this->name . '%')
                ->orWhere('email', 'like', '%' . $this->name . '%');
        }

        $users = $users->paginate(10);

        return view('livewire.users-list', ['users' => $users]); //->layout('layouts.app');
    }

    public function sort($column)
    {
        $this->sortColumn = $column;
        $this->sortDirection = $this->sortDirection == 'asc' ? 'desc' : 'asc';
    }

    public function cleanFilter()
    {
        $this->name = "";
    }

    public function delete(User $user)
    {
        $user->delete();
    }
}

View code

The view, it's really simple; As we mentioned, we are using Tailwind, we have a block of inputs referencing the properties of the previous model through the wire:model, which is to define the fields of the form to filter and then we have a block for the table, which already has all the logic implemented in the component from Laravel, we are dedicated to painting the data and columns without any condition:

<div class="flex mt-1 ">
                        <x-jet-input wire:model="name" class="block w-full" />

                        <x-jet-secondary-button class="ml-2" wire:click="cleanFilter">

                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                                    d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
                            </svg>

                        </x-jet-secondary-button>
                    </div>

                    <table class="table-auto w-full">
                        <thead>
                            <tr>

                                @foreach ($columns as $c)
                                    <th class="p-3" wire:click="sort('{{ $c }}')">
                                        <button>
                                            {{ $c }}
                                            @if ($sortColumn == $c)
                                                @if ($sortDirection == 'asc')
                                                    &uarr;
                                                @else
                                                    &darr;
                                                @endif
                                            @endif
                                        </button>
                                    </th>
                                @endforeach

                                <th class="p-3">Acciones</th>
                            </tr>
                        </thead>
                        <tbody>
                            @forelse ($users as $u)
                                <tr>
                                    <td class="border p-3">{{ $u->id }}</td>
                                    <td class="border p-3">{{ $u->name }}</td>
                                    <td class="border p-3">{{ $u->email }}</td>
                                    <td class="border p-3 flex justify-center">
                                        <x-a class="p-1 bg-blue-600 mr-1" href="{{ route('user.edit', $u) }}">
                                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
                                                stroke="currentColor">
                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                                                    d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
                                            </svg>
                                        </x-a>

                                        <x-jet-danger-button class="p-sm-button" wire:click="delete({{ $u->id }})">

                                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
                                                stroke="currentColor">
                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                                                    d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
                                            </svg>
                                        </x-jet-danger-button>
                                    </td>
                                </tr>
                            @empty
                                <tr>
                                    <td colspan="3">
                                        <p>No hay registros</p>
                                    </td>
                                </tr>
                            @endforelse

                        </tbody>
                    </table>

                    <br>

                    {{ $users->links() }}

Important points is to note the call to the functions through the click events that allow us to call functions of the component from the view, such as delete or order/sort to perform the ordering.

Remember that this is a small part of everything we see in the course of Basic Laravel in my course.

Project source code.

Andrés Cruz

Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz In Udemy

I agree to receive announcements of interest about this Blog.