Veuillez patienter...

Symfony : Intégration tabulator.js

Bonjour,

Je vais vous montrer comment rendre un tableau de données intéractif, dans un projet Symfony, via Tabulator.js.

Qu'est ce que Tabulator.js ?

Tabulator.js est une librarie Javascript permettant de gérer de manière intéractive un tableau de données.

Création du tableau de données

Pour l'exemple, nous devons créer une entité permettant de stocker différentes données en base, que nous avons au préalable créée et configurée avec Doctrine.

Nous allons la nommer Member, elle contiendra, pour l'exemple, les champs suivants :

  • Id (auto-généré),
  • Username (string(50) non null),
  • Firstname (string(255) non null),
  • Lastname (string(255) non null),
  • Age (integer null).

La manière la plus simple est d'utiliser le composant Maker, via une ligne de commande intéractive :

php bin/console maker:entity

Cette dernière nous permet de créer les objets (Entity et Repository) dans notre projet. Puis de les créer en base de données via une migration Doctrine.

On va modifier le Repository, en lui ajoutant la méthode qui permet de gérer la récupération des données, via un système de pagination :

// ...
class MemberRepository extends ServiceEntityRepository
{
    // ...
    public function getDatas(int $limit, int $offset, array $sorters = [], bool $count = false)
    {
        $q = $this->createQueryBuilder('m');

        if ($count)
        {
            $q->select('count(m.id)');
            return $q->getQuery()->getSingleScalarResult();
        }

        if (0 !== count($sorters))
        {
            foreach($sorters as $sorter)
            {
                $q->orderBy($sorter['field'], $sorter['dir']);
            }
        }

        $q->setMaxResults($limit);
        $q->setFirstResult($offset);

        return $q->getQuery()->getResult();
    }
    // ...
}

De la même manière, créons un Controller :

php bin/console make:controller MemberController

On va le modifier pour l'adapter à notre besoin :

// ...
#Route['/members', name: 'app_members_']
class MemberController extends AbstractController
{
    #[Route('/index', name: 'index')]
    public function index(Request $request)
    {
        return $this->render('member/index.html.twig', []);
    }

    #[Route('/list', name: 'list')]
    public function list(Request $request, MemberRepository $repo): JsonResponse 
    {
        $page = $request->get('page', 1);
        $limit = $request->get('size', null);
        $offset = ($page * $limit) - $limit;

        $sorters = $request->get('sort', []);

        $members = $repo->getDatas($limit, $offset, $sorters);
        $membersTotal = $repo->getDatas($limit, $offset, $sorters, true);

        $datas = [];
        $response = new JsonResponse();

        foreach ($members as $member)
        {
            $datas[] = [
                'm.id' => $member->getId(),
                'm.username' => $member->getUsername(),
                'm.firstname' => $member->getFirstname(),
                'm.lastname' => $member->getLastname(),
                'm.age' => $member->getAge()
            ];
        }

        $final['data'] = $datas;
        $final['last_page'] = ceil($membersTotal / $limit);
        $final['nb_row'] = $membersTotal;
        $final['last_row'] = $membersTotal;
        $final['size'] = sizeof($datas);

        $response->setContent(json_encode($final));
        return $response;
    }
}

Créons les templates, en commençant par le layout :

<!DOCTYPE html>
<html>
    <head>
        <!-- ... -->
        {% block stylesheets %}
        <link rel="stylesheet" href="{{ asset('assets/css/tabulator.min.css') }}">
        {% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}
        <script src="{{ asset('assets/js/tabulator.min.js') }}"></script>
        {% endblock %}
    </body>
</html>

Nous allons ensuite créer la page d'index :

{% extends 'layout.html.twig' %}

{% block stylesheets %}
    {{ parent() }}
    <style type="text/css">

    </style>
{% endblock %}

{% block body %}
<div class="wrapper">
    <div class="content">
        <h1>Tabulator intégration</h1>
        <hr />
        <div id="tabulator">

        </div>
        <div id="pagination"></div>
        <div id="page-count"></div>
    </div>
</div>
{% endblock %}

{% block javascripts %}
    {{ parent() }}
    <script type="text/javascript">
        var table = new Tabulator("#tabulator", {
            layout: "fitDataFill",
            renderHorizontal: "virtual",
            maxHeight: "85vh",
            sortMode: "remote",
            movableColumns: true,
            columnsHeaderVertAlign: "middle",
            ajaxUrl: "{{ path('app_members_list') }}", 
            initialSort: {
                { column: "m.username", dir: "asc" },
            },
            columnsDefaults: {
                headerWordWrap: true,
                vertAlign: "middle",
                resizable: true,
            },
            pagination: true,
            paginationSize: 15,
            paginationMode: "remote",
            paginationCounter: "rows",
            paginationCounterElement: "#page-count",
            columns: {
                { title: "ID", field: "m.id", sorter: "number", visible: true },
                { title: "Username", field: "m.username", sorter: "string", visible: true},
                { title: "Firstname", field: "m.firstname", sorter: "string", visible:true },
                { title: "Lastname", field: "m.lastname", sorter: "string", visible:true },
                { title: "Age", field: "m.age", sorter: "number", visible:true },
            }
        });
    </script>
{% endblock %}