Utiliser le Datagrid de EasyUI

Pour initier ce blog, je vais vous présenter une bibliothèque jQuery bien sympathique, il s’agit d’easyui. Vous trouverez cette bibliothèque sur le site officiel : jQuery EasyUI

Un des composants que j’apprécie le plus est l’usage des tableaux (datagrid) et de l’arbre-tableau (treegrid). Sans rentrer dans les détails du paramétrage (ils sont nombreux !), voici un cas d’usage simple :

Afficher un tableau, avec une pagination modulable, une barre de filtre, et la capacité de « styler » les lignes en fonction du contenu. Les données devront être récupérées dynamiquement en AJAX.

Jusqu’à présent, et nous l’avons tous fait, je codais entièrement mes tableaux « à la main » en HTML, pour ensuite gérer mes requêtes AJAX de pagination, etc. etc. Certes le code est réutilisable, mais avec une librairie c’est bien plus pratique.

Notre tutorial va donc se diviser en 2 parties : le codage côté serveur et le codage côté client.

Nous allons utiliser une table simple dans un premier temps, puis nous verrons comment travailler avec des tables liées.

Prérequis

Vous devez maitriser les concepts de base : création d’une table MySQL, déclaration des tables dans CakePHP, création d’un controller, création d’une vue, d’un layout, etc.

Nous utiliserons pour l’instant CakePHP 2.6, mais l’usage est sensiblement le même pour CakePHP v3.

Le tutoriel implique que vous avez déjà paramétré CakePHP dans l’environnement de développement : framework téléchargé et installé, accès à la base de donnée configuré, etc.

Vous devez également télécharger et installer jQuery et jQuery EasyUI dans le webroot/js de votre projet.

Pour en savoir plus sur jQuery, il y’a cet ouvrage qui est pas mal, c’est un mémento de toutes les commandes disponibles.

Codage côté serveur

Etape 1 – Création de la table

Nous créerons une table « produits » simple. Cette table comportera 3 champs :

  • L’index id qui sert de clef primaire, type integer AI
  • un champ VARCHAR(255) intitulé nom
  • un champ DECIMAL(10,2) intitulé prix

Nous verrons par la suite une solution un peu plus compliquée avec des tables liées.

C’est parti ! à vous de créer cette table « produits », vous devriez avoir quelque chose comme ça :

mysql> describe produits;
+----------+---------------+------+-----+---------+----------------+

| Field    | Type          | Null | Key | Default | Extra          |

+----------+---------------+------+-----+---------+----------------+

| id       | int(11)       | NO   | PRI | NULL    | auto_increment |

| nom      | varchar(255)  | NO   |     | NULL    |                |

| prix     | decimal(10,2) | NO   |     | NULL    |                |

| created  | datetime      | NO   |     | NULL    |                |

| modified | datetime      | NO   |     | NULL    |                |

+----------+---------------+------+-----+---------+----------------+

Tu nous as dit 3 champs, mais il y’en a 5 ?!

Vieux réflexe de routier : je rajoute toujours les champs « created » et « modified » pour avoir un timestamp des enregistrements créés et/ou modifiés (comportement par défaut dans CakePHP v2, à rajouter dans CakePHP v3). Vous pouvez les rajouter ou pas, cela ne changera rien à la suite du tuto.

Quand vous codez un projet, je vous conseille de rajouter systématiquement ces 2 champs, ils sont bien pratiques.

Maintenant nous allons insérer 12 enregistrements dans cette table, histoire d’avoir un jeu de données à travailler :

INSERT INTO `produits` (`id`, `nom`, `prix`, `created`, `modified`) VALUES
(1, 'Un livre sur le PHP', '10.50', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(2, 'Un livre sur jQuery', '25.20', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(3, 'Un bon bouquin sur le HTML 5', '5.00', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(4, 'Un ouvrage sur AJAX', '10.40', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(5, 'Quelque chose à lire sur JAVA', '58.50', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(6, 'N''oublions pas SWIFT', '150.80', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(7, 'Go sur C++', '1.00', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(8, 'Vive l''ASM', '0.50', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(9, 'Windev - les arcanes', '200.10', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(10, 'VB.Net sans un point', '8.90', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(11, 'FLASH t''es mort', '0.01', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(12, 'L''algo sans les agglos', '8.30', '2015-05-14 00:00:00', '2015-05-14 00:00:00');

Etape 2 – Création d’une fonction publique dans le model

Dans le fichier Produit.php, nous rajoutons le code qui interprétera la requête émise par le composant Datagrid présent dans les fichiers clients.

Pour l’instant, nous n’allons traiter que les requêtes de pagination. EasyUI envoie 2 paramètres lorsque nous paginons : « rows » et « page ».

  • « rows » détermine le nombre d’enregistrements à afficher, c’est le « limit » du SQL
  • « page » indique le numéro de la page à récupérer

Nous allons utiliser les options ‘limit’ et ‘page’ du find de CakePHP pour filtrer nos enregistrements.

Donc, dans notre code du model, cela donnerait ceci :

/**
 * Renvoi les données selon les paramètres de pagination d'EasyUI
 * @param Array $request Tableau du request->data
 * @return array|null Tableau de valeur, ou null si aucun enregistrement est trouvé
 */
public function get_data($request)
{
    $page = $request['page'];
    $limit = $request['rows'];
    // retour des données
    return $this->find('all', [
        'limit' => $limit,
        'page'  => $page,
    ]);
}

Normalement, il faudrait rajouter les vérifications d’usage des variables (isset, etc.), mais pour l’exemple on laisse en l’état

Voilà, la partie requête est terminé, nous allons maintenant travailler sur le controller qui, comme son nom l’indique, va opérer le contrôle et l’interface entre la requête cliente et le model.

Etape 3 – Création de l’action dans le controller

Créez un controller typique « ProduitsController.php » (par typique j’entends un fichier Controller selon les normes CakePHP, avec les entêtes qui vont bien et tout et tout).

Dans ce controller, nous allons créer 2 actions :

  • Une action « index » qui se chargera d’afficher notre page principale, avec juste le tableau « datagrid » de EasyUI
  • Une action « get_data » qui ne sera autorisée qu’en AJAX pour permettre de récupérer les données de la table

Voici le code de base généré, avec les commentaires inclus pour faciliter la compréhension :

class ProduitsController extends Controller
{

    /**
     * Action d'affichage de la vue index.ctp
     */
    public function index()
    {

    }

    /**
     * Fonction appelée en AJAX pour fournir les données "produits" à la vue index.ctp et au composant EasyUI
     */
    public function get_data()
    {
        // si la requête n'est pas de type AJAX nous refusons le traitement et on affiche un message d'erreur
        if (!$this->request->is('ajax')) {
            throw new ForbiddenException(__('Accès direct non autorisé'));
        }
    }
}

Nous allons récupérer les informations envoyées par POST et les fournir à notre fonction publique du model.

Le résultat obtenu de la fonction publique sera renvoyé vers le client au format JSON. Comme nous n’utilisons pas de vues pour le retour AJAX, nous forgeons directement une réponse au niveau du controller.

Pour information, EasyUI s’attend à avoir les enregistrements dans la clef « rows » du tableau. Une clef « total » est également nécessaire, cette clef contient le total des enregistrements de la table « produits » (C’est parce que nous demandons à EasyUI de paginer, sinon on peut renvoyer les enregistrements tel quel).

Nous avons donc créé dans notre model Produit.php une fonction get_count() qui retourne un $this->find(‘count’).

Voici le code modifié :

/**
 * Fonction appelée en AJAX pour fournir les données "produits" à la vue index.ctp et au composant EasyUI
 */
public function get_data()
{
    // si la requête n'est pas de type AJAX nous refusons le traitement et on affiche un message d'erreur
    if (!$this->request->is('ajax')) {
        throw new ForbiddenException(__('Accès direct non autorisé'));
    }
    // envoi des paramètres au model Produits et récupération du résultat
    $rows = $this->Produit->get_data($this->request->data);
    $total = $this->Produit->get_count();
    // retour du résultat vers le client au format JSON
    return new CakeResponse([
        'type' => 200,
        'body' => json_encode(['rows'  => $rows,
                               'total' => $total
        ])
    ]);
}

Le type 200 correspond au retour du serveur WEB vers le client. Il existe plusieurs codes de retour, le plus connu étant le fameux 404… page not found. Vous pouvez trouver une liste de codes sur Wikipedia.

Voilà, le plus difficile est fait. Codons maintenant la partie cliente.

Codage côté client

Etape 1 – Téléchargement et installation de la librairie

Les librairies jQuery et EasyUI sont inscrites dans votre layout.ctp. N’oubliez pas de mettre les CSS et les images au bon endroit, sinon ça sera tout moche. Il existe plusieurs thèmes pour EasyUI, dans notre cas j’ai utilisé le thème par défaut.

Etape 2 – Création de la vue

Le (presque) plus compliqué c’est de connaitre la syntaxe et les options de configuration d’EasyUI, mais la documentation étant relativement claire, ce n’est pas trop compliqué, SAUF pour les paramètres envoyés au serveur et les paramètres attendus par la librairie.

J’ai dû jouer avec le débuggeur du navigateur pour connaitre les paramètres « rows » et « page », j’écrirai un petit snippet pour vous expliquer comment débugger avec Chrome, c’est très bien fichu.

Notre vue s’appelle index.ctp. Nous allons juste afficher une table, c’est dans cette table que nous afficherons notre datagrid.

Ensuite dans la partie <script></script> nous initialisons la librairie EasyUI, avec les options qui vont bien. Les 3 seules options qui nous intéresse pour l’instant sont :

  • url : nous indiquons l’url qui nous renverra les données au format JSON, nous utiliserons l’helper HTML pour générer l’url correspondant à notre action « get_data »
  • pagination : booléen à TRUE pour indiquer que nous utiliserons la pagination EasyUI
  • columns : pour définir les colonnes à afficher, nous faisons le plus simple possible

Voici le code de notre vue :

<script>
$(function () {
        $('#dg').datagrid({
            url: '<?= $this->Html->url(['controller'=>'produits','action'=>'get_data'])?>',
            pagination: true,
            columns: [[
                {field: 'id', title: 'id', width: 100},
                {field: 'nom', title: 'Nom', width: 100},
                {field: 'prix', title: 'Prix', width: 100, align: 'center'}
            ]]
        })
    })
</script>
<table id="dg"></table>

Si vous avez tout bien fait, en allant sur la page /produits/ vous verrez cette page :

datagrid-1

 

Bizarre non ? il y’a 12 lignes affichées, mais elles sont vides !

Pourquoi ?

C’est une subtilité CakePHP v2.6 / EasyUI. Je dis CakePHP v2.6, car en v3 nous n’avons plus ce problème. Que se passe-t’il en fait ?

En CakePHP v2.6 (et antérieur), quand vous faites une requête sur une table, vous avez un tableau de retour indexé par le nom du modèle. Dans notre cas, la requête :

return $this->find('all', [
'limit' => $limit,
'page' => $page,
]);

retourne un tableau [‘Produit’].

Or EasyUI veut un tableau « plat », non indexé.

Donc nous avons 2 solutions :

  • Traiter le tableau avant de le renvoyer en JSON (une boucle for each suffit)
  • Dire à EasyUI de traiter les données en entrée

Pour ce tutorial, c’est la 2eme solution que nous choisirons. Pour ce faire, il faut indiquer à EasyUI que nous allons filtrer les données avant l’affichage. Et ça tombe bien, c’est prévu dans EasyUI ! c’est la fonction loadFilter. Elle prend en paramètre les données récupérées en AJAX.

Mais avant nous allons légèrement modifier notre code du controller. En effet, EasyUI veut dans le tableau JSON une clef « total » qui est le total des enregistrements de la table consultée, et une clef « rows » qui contiendra les données.

Voici le code modifié dans le controller :

/**
 * Fonction appelée en AJAX pour fournir les données "produits" à la vue index.ctp et au composant EasyUI
 */
public function get_data()
{
    // si la requête n'est pas de type AJAX nous refusons le traitement et on affiche un message d'erreur
    if (!$this->request->is('ajax')) {
        throw new ForbiddenException(__('Accès direct non autorisé'));
    }
    // envoi des paramètres au model Produits et récupération du résultat
    $rows = $this->Produit->get_data($this->request->data);

    // retour du résultat vers le client au format JSON
    return new CakeResponse([
        'type' => 200,
        'body' => json_encode(['rows'  => $rows,
                               'total' => $total
        ])
    ]);
}

Voici le code final :


<script>
$(function () {
        $('#dg').datagrid({
            url: '<?= $this->Html->url(['controller'=>'produits','action'=>'get_data'])?>',
            pagination: true,
            loadFilter: function (data) {
                var $rows = Array();
                $.each(data.rows, function (index, value) {
                    $rows[$rows.length] = {
                        id: value.Produit.id,
                        nom: value.Produit.nom,
                        prix: value.Produit.prix,
                    };
                });
                output = {rows: $rows, total: data.total};
                return output;
            },
            columns: [[
                {field: 'id', title: 'id', width: 100},
                {field: 'nom', title: 'Nom', width: 100},
                {field: 'prix', title: 'Prix', width: 100, align: 'center'}
            ]]
        })
    })
</script>
<table id="dg"></table>

Voici comment s’explique le code :

Nous traitons chaque entrée de data.rows (qui correspond à notre tableau « jsonencodé » ‘rows’=>$rows), car en Javascript, un tableau c’est un objet, d’où la notation en point (data.rows, qui équivaut à data[‘rows’]).

Pour chaque entrée, nous récupérons le champ voulu (id, nom ou prix) et nous construisons un tableau « à plat » ($rows[$rows.length] = …).

C’est cette astuce qui nous permettra de travailler avec des modèles liés.

Et voilà ! maintenant notre tableau s’affiche correctement, et la pagination s’effectue par le composant EasyUI qui fait une requête AJAX à chaque changement de page 🙂

datagrid-2
Cela peut paraître compliqué, mais c’est très simple en fait. Et une fois que l’on a gouté à EasyUI on a du mal à s’en passer. Il est même possible de rajouter des boutons supplémentaires, des filtres, etc.

Mais tout cela fera parti d’un autre article à ce sujet.

C’est tout pour aujourd’hui, si vous avez des questions, n’hésitez pas à aller sur le forum Cakephp-fr et je me ferai un plaisir de vous répondre.

Comments: 3

  1. Alexis says:

    Merci super.

    J’ai quelques questions :
    – extends Controller ==> pourquoi ce n’est pas extends AppController ?
    – il ne manquerait pas une fonction get_count dans le model ?

  2. Alexis says:

    Je me demande s’il n’est pas plus judicieux d’éviter le loadFilter dans le script et de sortir les données du modèle tels qu’attendues par easyui.

    En effet, si dans cakephp 3 le pb ne se pose plus, ce serait bien à cakephp de sortir les données au format attendu. Comme ça si on migre cake de 2 à 3, il n’y a que lui à changer (et en particulier que la couche modèle ).

    J’aurai mis ça à la fin de la fonction get_data du modèle (la boucle foreach, pas dans le controller mais dans le model)
    $retour = array();
    foreach ($this->find(‘all’, $options) as $row){
    array_push($retour,$row[‘CebSite’]);
    }
    return $retour;

    PS : on peut préférer la syntaxe [] au lieu d’array mais j’ai une version ancienne de php sur au moins un de mes sites (qui m’interdit d’ailleurs le passage en 3.0)

  3. romeo says:

    merci pour le tutos mais si après l’affichage j’aimerai pouvoir modifier un champ (par exemple le prix pour un livre)comment je procède?

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *