CakePHP et JQuery/AJAX

Une des requêtes les plus courantes que je peux lire sur le forum francophone, c’est : « comment rafraichir ma page avec un appel AJAX ? », ou « comment récupérer mes données du serveur pour mettre à jour une liste déroulante ? »

Nous allons, dans cet article, parler de 2 cas de figure d’une utilisation AJAX avec CakePHP : « afficher une ressource HTML » et « utiliser un résultat JSON pour interpréter les données obtenues ». Dans le cadre de ces 2 exercices, nous utiliserons la table « produits » que nous avions créée pour cet article. Cette table contient une liste de livre avec les prix associés.

Dans le 1er cas, nous afficherons une zone d’entrée de texte (input) dans laquelle l’utilisateur pourra indiquer le prix maximum des livres qu’il veut afficher. La liste des livres sera affichée dynamiquement dans un div en-desous de l’input.

Dans le 2eme exercice, il y’a toujours cette zone d’input, mais cette fois-ci ce ne sera pas un div qui sera affiché, mais une listbox (SELECT) HTML.

Prérequis

Une connaissance relative de jQuery. Télécharger et installer jQuery dans le répertoire /webroot/js.

Paramétrer votre layout « default.ctp » pour y inclure la librairie jQuery :

<?= $this->Html->script('jquery.min');?>

Un site CakePHP v3 fonctionnel, avec base de donnée paramétrée et accessible.

Dans votre base de donnée, vous pourrez y injecter la table que nous avions créée dans un autre article :

CREATE TABLE `produits` (
  `id` int(11) NOT NULL,
  `nom` varchar(255) NOT NULL,
  `prix` decimal(10,2) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

--
-- Dumping data for table `produits`
--

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 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 sans tâches', '0.01', '2015-05-14 00:00:00', '2015-05-14 00:00:00'),
(12, 'Algo et les agglos', '8.30', '2015-05-14 00:00:00', '2015-05-14 00:00:00');

--
-- Indexes for dumped tables
--

--
-- Indexes for table `produits`
--
ALTER TABLE `produits`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT for table `produits`
--
ALTER TABLE `produits`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=13;

Cas N°1 – AJAX et HTML

Notre premier « usecase » est l’affichage d’un champ FORM INPUT et l’affichage dynamique d’un DIV.

Création des vues

Nous avons besoin de 2 vues : la vue principale « index.ctp » (par exemple), et la vue pour notre DIV dynamique que nous appellerons « liste.ctp ». Ces 2 vues seront associées au controller ProduitsController.php. Nous les créons donc dans le répertoire « Templates/Produits ».

La vue index.ctp contient un simple formulaire, avec un champ input. Nous plaçons en-dessous de notre formulaire un div identifié par « listeDiv », c’est dans ce DIV que nous afficherons notre tableau de résultats :

<?= $this->Form->create(NULL); ?>
<?= $this->Form->input('prix', [
    'type'  => 'number',
    'step'  => '0.1',
    'label' => __('Prix maximum')
]); ?>
<?= $this->Form->end() ?>
<div id="listeDiv"></div>

Le code est très simple, petite subtilité nous n’indiquons pas de Table dans la création du FORM. Le champ INPUT est un champ classique de type NUMBER, les paramètres possibles du type NUMBER sont détaillés sur cette page W3.

Nous créons ensuite la vue qui doit être insérée dans le div « listeDiv ». Cette vue sera nommée « liste.ctp » et contiendra une simple définition de table qui affichera nos produits.

Notez l’usage de la notation simplifiée PHP qui facilite grandement la rapidité de codage, quand on y goûte on a du mal à revenir en arrière :

<table>
    <thead>
    <tr>
        <th>#</th>
        <th>Titre</th>
        <th>Prix</th>
    </tr>
    </thead>
    <tbody>
    <?php foreach ($produits as $produit):; ?>
        <tr>
            <td><?= $produit->id; ?></td>
            <td><?= $produit->nom; ?></td>
            <td><?= $produit->prix; ?></td>
        </tr>
    <?php endforeach; ?>
    </tbody>
</table>

L’usage de CakePHP nous permet également d’interroger directement le getter d’un objet, ce qui rend le code plus lisible que la consultation d’un tableau, comme nous y avait habitué CakePHP v2.

Vue index.ctp dynamique avec jQuery

Ecrite comme ça, notre vue index.ctp ne fait rien, elle n’est pas dynamique. Nous allons maintenant rajouter « l’intelligence » du code. Nous allons spécifier à notre document, avec l’aide de jQuery, que nous « écouterons » les changements de l’input « prix ». Et lorsque l’utilisateur rentrera une valeur, nous demanderons au serveur de nous afficher la table prix au bon endroit dans le div spécifié (listeDiv).

Tout d’abord nous déclarons notre fonction d’écoute sur modification de l’input :

$("#prix").bind('input', function () {
            console.log($("#prix").val());
        });

Il y’a 2 types d’événements d’écoute possible : « change » et « input ». Nous utilisons « input » car cet événement est déclenché dés que l’utilisateur appuie sur une touche du clavier. Si nous avions utilisé « change », il aurait fallu que l’utilisateur appuie sur « entrée » pour que cet événement se déclenche.

Nous déclarons ensuite l’écoute de l’évènement « input » dés que la page est chargée :

    $(function () {
        $("#prix").bind('input', function () {
            console.log($("#prix").val());
        });
    })

Rentrons maintenant dans l’AJAX pur et dur, et faisons un appel AJAX lorsque l’évènement est déclenché, nous utiliserons une configuration de base, avec des paramètres simples :

<script>
    $(function () {
        $("#prix").bind('input', function () {
            $.ajax({
                url: "/produits/liste",
                data: {
                    prix: $("#prix").val()
                },
                dataType: 'html',
                type: 'post',
                success: function (html) {
                    $("#listeDiv").html(html);
                }
            })
        });
    })
</script>

les paramètres AJAX sont les suivants :

  • url : l’adresse URL de la requête, j’ai codé en dur l’adresse URL, mais le mieux serait de faire quelque chose comme ci-après, de telle façon que l’url sera calculé par le framework CakePHP :
url: "<?= $this->Url->build(['controller'=>'produits','action'=>'liste'])?>"
  • data : les données à envoyer dans la requête, ce qui nous intéresse, c’est de renvoyer le contenu de l’input « PRIX », nous récupérons donc le contenu de l’input et le plaçons dans la variable « prix », cette variable sera récupérée dans notre controller
  • dataType : nous indiquons le type de retour que nous attendons du controller, ce peut être json, jsonp, xml ou html (notre cas)
  • type : le type de requête envoyée (GET ou POST)
  • success : la fonction de retour (callback) qui sera appelée par AJAX en cas de succès de la requête (c’est à dire si le serveur renvoie le code de retour 200), nous définissons une fonction anonyme qui récupère juste la 1ère variable, que nous nommerons « html »

La requête de succès (callback success) fait l’opération suivante : nous récupérons le retour de l’url (qui est stocké dans la variable « html »), et nous plaçons ce retour comme contenu HTML du div « listeDiv » :

$("#listeDiv").html(html);

Le plus difficile est fait, désormais nous exécutons notre requête AJAX sur notre serveur, et le contenu HTML renvoyé par le serveur est placé dans la balise div. Occupons-nous maintenant de faire renvoyer par le controlleur du contenu exploitable.

Création des actions

L’opération est très très simple. Nous allons créer une action « liste » dans le controller « Produits ». Cette action « liste » va récupérer les données envoyées par la requête AJAX (notre fameuse variable « prix »), récupérer les enregistrements concernés, et afficher le résultat.

Avant tout, nous devons rajouter, et c’est très important, le composant RequestHandler dans la déclaration de notre controller :

public function initialize()
        {
            parent::initialize();
            $this->loadComponent('Flash');
            $this->loadComponent('RequestHandler');

        }

Ce composant est essentiel car il va détecter le type de requête que va recevoir notre serveur, puis récupérer les informations envoyées dans la requête pour que ces données soient accessibles dans le tableau $this->request->data.

Ensuite nous pouvons créer notre action « liste » comme suit :

public function liste()
        {
            $prix = $this->request->data['prix'];
            $produits = $this->Produits->find('all')
                                       ->where(['prix <' => $prix]);
            $this->set('produits', $produits);
        }

La solution est toute simple quand on la comprends, on récupère la variable « prix » dans notre action, on construit notre requête de recherche et on envoie le tout dans la vue liste.ctp que nous avions créée précédemment. Comme nous avons rajouté le RequestHandler, seule la vue sera affichée, sans le layout. Cette vue simple en HTML est ensuite injectée dans le div de notre page HTML.

Voilà vous avez réussi avec succès votre fonction AJAX avec CakePHP 🙂

Résultat

Avec le CSS par défaut de CakePHP, voilà ce que cela donne lorsque l’on entre le prix maximum dans la zone de texte :

AJAX-CAKEPHP-1

 

Notre tableau se mets à jour automatiquement !

Cas N°2 – AJAX et JSON

Le 2eme cas est légèrement plus complexe, mais pas trop. C’est un dérivé du 1er cas, la différence est que nous ne voulons pas afficher un contenu HTML dans un div, mais remplir un SELECT avec les titres des livres trouvés.

Vous l’aurez deviné, nous utiliserons le format de donnée « JSON ».

Création des vues

Nous reprenons notre vue index.ctp, et nous remplaçons le div « listeDiv » par un SELECT que nous incluons dans la déclaration de notre FORM :

<?= $this->Form->create(NULL); ?>
<?= $this->Form->input('prix', [
    'type'  => 'number',
    'step'  => '0.1',
    'label' => __('Prix maximum')
]); ?>
<?= $this->Form->input('livres', ['type' => 'select','label'=>__('Liste des livres')]); ?>
<?= $this->Form->end() ?>

Notre SELECT est par défaut vide, son « id » est par défaut est fixé à « livres » (convention CakePHP v3, bien pratique par rapport à la v2).

Le plus délicat se passe dans la balise SCRIPT, puisque nous demanderons à l’action de fournir les résultats au format JSON, puis nous interpréterons les résultats pour alimenter notre SELECT, le code est documentée pour être compréhensible :

<script>
    $(function () {
        $("#prix").bind('input', function () {
            $.ajax({
                url: "/produits/liste",
                data: {
                    prix: $("#prix").val()
                },
                dataType: 'json',
                type: 'post',
                success: function (json) {
                    $("#livres").empty(); // nous vidons le SELECT
                    $("#livres").append('<option value="0"><?=__('Selectionnez un livre')?></option>'); // Nous rajoutons une option "vide" qu SELECT qui indique à l'utilisateur de choisir un livre
                    $.each(json, function (clef, valeur) { // pour chaque élément du tableau JSON, on récupère la clef et la valeur
                        // on ajoute l'option dans la liste
                        $("#livres").append('<option value="' + clef + '">' + valeur + '</option>');
                    });
                }
            })
        });
    })
</script>

Il y’a une différence dans le dataType qui passe de ‘html’ à ‘json’. Ensuite nous traitons le json pour alimenter notre SELECT via jQuery. C’est le plus difficile à manipuler au pire. Sinon c’est aussi simple que cela ! il est même possible de s’amuser à chainer les change sur les SELECT pour alimenter d’autres SELECT via AJAX, etc.

Reste maintenant à voir comment nous générons notre JSON dans le controller.

Création des actions

Avant tout il est important de déclarer notre Table Livres dans CakePHP, ça se passe dans /Model/Table, nous indiquons à CakePHP que la valeur affichée par défaut (le label) est le champ « nom » :

<?php
    namespace App\Model\Table;

    use Cake\ORM\Table;

    class ProduitsTable extends Table
    {
        public function initialize(array $config)
        {
            $this->addBehavior('Timestamp');
            $this->displayField('nom');
        }
    }

    ?>

Nous pouvons ensuite nous occuper de notre controller, je vous propose 2 types d’actions : un type de fainéant et un type de rigoureux, vous choisirez celle qui vous convient le mieux.

Comme c’est un tutorial (j’espère !) de qualité, on va commencer par le rigoureux, c’est à dire dans les règles de CakePHP : nous allons créer une vue JSON dédiée pour notre action « liste ».

1ère option, méthode « rigueur »

Cette vue json dédiée va se créer dans le répertoire /Template/Produits/json/liste.ctp, elle contiendra ce code :

<?= json_encode($produits); ?>

cela va juste récupérer les données de l’action « liste » et les encoder en json.

Ensuite dans notre action « liste » du controller « produits », nous écrivons le code suivant :

public function liste()
        {
            $prix = $this->request->data['prix'];
            $produits = $this->Produits->find('list')
                                       ->where(['prix <' => $prix]);
            $this->set('produits', $produits);
        }

En remplaçant find(‘all’) par find(‘list’) nous permettons à CakePHP de ne récupérer que les champs « id » et « nom » (car nous avions défini que le champ à afficher par défaut « displayField » est « nom »).

Cette requête produit donc un tableau sous la forme [‘id1’=>’nom1′,’id2’=>’nom2’], etc. Donc une clef = une valeur, exactement ce dont notre script d’alimentation du SELECT à besoin !

L’encodage de ce tableau sous format json (json_encode) est retranscrit de cette manière dans le flux renvoyé par le serveur :

{« 7″: »Go sur C++ », »8″: »Vive ASM », »11″: »FLASH sans tâches »}

2eme option, méthode fainéant

La méthode fainéant nous épargne la création du fichier /json/liste.ctp puisque nous renverrons directement l’encodage json à partir de l’action du controller. Cela se fait de cette façon :

        public function liste()
        {
            $prix = $this->request->data['prix'];
            $produits = $this->Produits->find('list')
                                       ->where(['prix <' => $prix]);
            return new Response([
                'status' => 200,
                'body'   => json_encode($produits)
            ]); // nous forgeons directement une réponse
        }

Le contenu renvoyé est exactement le même que la 1ère méthode, mais cela nous épargne le codage d’une vue supplémentaire. Après tout nous voulons renvoyer du bête JSON qui sera interprété par notre script AJAX, alors à quoi bon ?

C’est vous qui voyez 🙂

Résultat

Le résultat est que lorsque vous entrez le prix maximum d’un livre, automatiquement la liste se rafraichit avec le contenu des livres :

AJAX-CAKEPHP-2

 

Note

Evidemment, nous aurions très bien pu utiliser la méthode HTML pour afficher un SELECT réactualisé. Pour cela nous aurions remplacé tout le code « <table>…</table> » par notre SELECT tout neuf, les 2 options sont possibles, mais je trouve que l’usage du javascript permet de rajouter une couche de séparation supplémentaire entre la vue cliente et l’application côté serveur. Si demain on change de framework (ce que je n’espère pas ;), nous n’aurions pas à changer la vue car le code est compatible avec tout les frameworks PHP, du moment que les données JSON soient encodées à l’identique.

Fin

J’espère que cet article vous a plu, et qu’il a répondu à vos questions sur l’utilisation d’AJAX et de CakePHP. Si vous avez des questions, je suis disponible sur le forum de cakephp-fr.org.

A bientôt pour un prochain article !

Comments: 3

  1. Digaweba says:

    Bonjour,
    Ce tuto date un peu mais je tente ma chance.
    Je cherche à utiliser un formulaire de type radio plutôt que le Input mais rien ne s’affiche dans la console.
    Auriez vous une piste à m’offrir ?
    Merci

  2. gb85220 says:

    Bonjour,

    Je suis tombé sur ce site par hasard, merci pour la qualité du style, très clair et très pédagogique, ce qui n’est en soi pas si courant.
    Dans ce genre de tuto il serait très utile pour le lecteur de connaître la version de cake utilisée.

    Je fais des sites web gratuitement avec cakephp, par plaisir.
    Je suis retraité de l’informatique, je me recycle régulièrement (j’en ai le temps) mais je n’ai jamais retrouvé la qualité d’un langage comme Delphi mais je dois dire que les communautés de développeurs procurent à tous des outils formidables.

    Merci à vous

    Cordialement

  3. armand boutchouang says:

    merci pour ce tutoriel une version vidéo ne serai pas mal je suis débutant avec cake PHP

Laisser un commentaire

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