Laravel datatable with Grid.js

Laravel datatable with Grid.js

Published: March 23, 2021

Updated: August 17, 2021

laravel

gridjs

grid

datatable

Update:

A typo was fixed when generating the Single Action Controller. Also as the article mentions in the beginning we already assume you have a Laravel project, so in order to keep the tutorial short we didn't go through the required steps to scaffold a new project and seed the data. As for the final design ( on the screenshot ) its based on Tabler, which is a free dashboard template for Bootstrap 5. Although not covered in this article (as the focus is not styling) Grid.js itself is highly customizable as explained in their docs.

In some cases it's a trivial task to display large datasets to the user. Since Bootstrap moved away from jQuery, we cannot use the popular DataTables plugin to display large chunks of data to the user, so we looked for alternatives. Our decision was also influenced by the fact that most of our projects provide custom design by using TailwindCSS, so we looked for a vanilla JavaScript implementation which will allow us to customize it to our needs.

During our search we found Grid.js and in this tutorial we will show you how to set it up with server side rendering. As they explain it in their website, Grid.js is a Free and open-source JavaScript table plugin. It works with most JavaScript frameworks, including React, Angular, Vue and VanillaJs.

We are assuming you already have a Laravel 8 project so we will skip that part and jump right to installing.

npm install gridjs

In our project we have a Clients model where we have more than 100 records and we will use Grid.js to display all that data to the frontend ( You can also use a seeder for this ).

First I will make a single action controller that will fetch all the clients and return a json response that we can use in frontend.

To make the invokable controller we can do so with this command:

php artisan make:controller Actions\\FetchClientsController --invokable

This will store our new invokable controller in an Actions folder in order to keep our controllers clean and organized.

Here is our FetchClientController

<?php

namespace App\Http\Controllers\Actions;

use App\Http\Controllers\Controller;
use \Illuminate\Http\JsonResponse;
use App\Models\Client;

class FetchClientsController extends Controller
{
    public function __invoke(): JsonResponse
    {
        $clients = Client::all()
            ->transform(function($client){
                return [
                    'id' => $client->id,
                    'full_name' => "$client->name $client->surname",
                    'number' => $client->number,
                    'street' => $client->street,
                    'edit_url' => route('clients.edit', $client->id)
                ];
            });

        return response()->json($clients);
    }
}

Here on the controller we get all our Clients, and then using the transform function we only return the needed fields in the frontend. Note the last line for edit_url, we will use it to automatically get the url for editing data so we can use it in the table.

We navigate to the routes folder and we access web.php file to reference our new controller like so:

<?php

use Illuminate\Support\Facades\Route;
use \App\Http\Controllers\ClientController;
use \App\Http\Controllers\Actions\FetchClientsController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Auth::routes();

Route::group(['middleware' => 'auth'], function () {

    // Single Action Controllers
    Route::get('/clients/fetch', FetchClientsController::class)->name('clients.fetch');
    
    // Resource Controllers
    Route::resource('clients', ClientController::class);
});

Now all we have to do is configure our index file in order to render our table from JavaScript.

In our resource/views/clients/index.blade.php we have the following code:

@extends('layouts.app', ['title' => 'Clients'])

@section('content')
    <div class="row">
        <div class="col-lg-12">
            <div js-hook-url="{{ route('clients.fetch') }}" js-hook-table-client></div>
        </div>
    </div>
@endsection

In this example we use two html attributes that we will use in our JavaScript file, the first one is for retrieving our url where we fetch all the clients, and we are going to use the second one as a reference to render our table from JavaScript.

In our app.js file under resources/js/app.js we need to add the following code.

import { Grid, html } from "gridjs";
import "gridjs/dist/theme/mermaid.css";

const TABLE_CLIENTS = '[js-hook-table-client]'

// Get the table element
const table_clients_wrapper = document.querySelector(TABLE_CLIENTS);

// Get the url attribute
const table_clients_url = table_clients_wrapper.getAttribute('js-hook-url');

if (table_clients_wrapper) {
    const table_clients = new Grid({
        columns: [
            {
                name: 'Full Name'
            },
            {
                name: 'Number'
            },
            {
                name: 'Street'
            },
            {
                name: 'Actions',
                // Here we inject our route edit
                formatter: (_, row) => html(`<a href='${row.cells[3].data}'>Edit</a>`)
            }
        ],
        search: {
            enabled: true
        },
        server: {
            // Here we give the URL we passed in the hook
            url: table_clients_url,
            then: data => data.map(table => [table.full_name, table.number, table.street, table.edit_url]),
            handle: (res) => {
                // no matching records found
                if (res.status === 404) return {data: []};
                if (res.ok) return res.json();

                throw Error('oh no :(');
            },
        },
        pagination: {
            enabled: true,
            limit: 10,
            summary: false
        },
    }).render(table_clients_wrapper);
}

So, what are we doing here? We instantiate a new Grid Class and pass down the options as object. The columns array represents all the table columns, and the last item is used to inject our edit route. In the server object, we give the url parameter that we called earlier, and after the data has been loaded, we just map each data that's coming from the server with their respective columns.

The pagination part is self explanatory. We have enabled pagination and set the limit of rows to be displayed at 10.

You can read more about this in the official Grid.JS documentation which covers a lot of use cases.

After this you have to run npm run dev in order to compile all the assets. If you did everything correctly then you should see the following screen:

Tip: You can use the following library to cache your models and supercharge your loading times. We highly recommended for large data: laravel-model-caching