Structurer son code jQuery avec Backbone.js : échanges avec le serveur
Après le premier tutoriel qui vous a permis de découvrir les principales “classes” de Backbone.js (BB) et la souplesse qu'offre la séparation de responsabilités entre la vue et les models, nous ici allons aborder une autre aspect qui fait la richesse de ce framework : la synchronisation des données avec le serveur
En clair, vous allez pouvoir connecter vos models à la base de données pour les opérations CRUD (Create, Read, Update et Delete)
Avoir suivi le premier tuto est nécessaire,
vous devez posséder quelques connaissances en HTML, JS et PHP
et devez également disposer d'un serveur web sur votre poste pour la partie PHP.
Note : pour simplifier, nous n'utiliserons pas de base de données.
Préparation
Nous allons partir du code du tuto 1 en lui apportant quelques changements.
Voici le code de la page HTML modifiée
<body> <div class="content"> <h1>Tutoriel Mediabox - Backbone.js part 2</h1> <div id="user-view"> <form id="form-ajout" action="#"> <input type="text" name="nom" id="user-nom" required> <input type="submit" value="ajouter"> </form> <div> <ul id="user-ul"></ul> </div> </div> </div> <!-- TEMPLATES --> <script type="text/template" id="user-row-tpl"> <li data-id="<%= id %>"><%= nom %> ( <%= role %> )</li> </script> <!-- SCRIPTS --> <script type="text/javascript" src="js/libs/jquery.min.js"></script> <script type="text/javascript" src="js/libs/underscore-min.js"></script> <script type="text/javascript" src="js/libs/backbone-min.js"></script> <script type="text/javascript" src="js/views/index.js"></script> </body>
On a défini un conteneur (div) user-view qui servira de référence à notre vue BB
On y a ajouté un formulaire pour la création de nouveau “user”
Chargement liste (client)
On va maintenant reprendre le code javascript du fichier index.js
Dans notre Collection, on va ajouter l'attribut url qui indique quelle adresse sera appelée à chaque opération sur les Models de notre Collection : création, mises à jour, suppression, lecture
Le Model quant à lui ne change pas
// le model var User = Backbone.Model.extend({ defaults : { "role" : "invité", "age" : 0 } }) ; // la collection var UserCollection = Backbone.Collection.extend({ model : User, url : 'api/user.php' }) ;
Passons à la vue. Comme expliqué plus haut, l'élément HTML auquel la vue est rattachée est maintenant la div portant l'ID user-view
// la vue var UserView = Backbone.View.extend({ el : '#user-view', initialize : function () { // on garde une référence au template de la liste this.template = _.template($('#user-row-tpl').html()); // quelques références pour éviter de rechercher à chaque fois dans le DOM this.$userList = this.$el.find('#user-ul'); this.$userNom = this.$el.find('#user-nom'); // permet de faire référence à la vue dans la fonction appelée en dessous var that = this; // on va charger les données de la collection en interrogeant le serveur userColl.fetch({ success : function (collection) { // la collection est chargée, on va écouter les futurs évènement sur les models that.addListeners(); // on affiche la liste dans la vue that.render(); } }); }, events : { }, addListeners : function () { // à chaque fois qu'on ajoute un user dans la Collection => rechargement de la vue this.listenTo(userColl, 'add', this.render); }, render : function () { // affichage de la liste des user de la collection dans la vue var html = '', that = this; userColl.each(function (user) { html += that.template(user.attributes); }); this.$userList.html(html); return this; } });
Normalement, vous devez comprendre la plupart des lignes écrites, focalisons sur la nouveauté :
userColl.fetch({ success : function (collection) { // la collection est chargée, on va écouter les futurs évènement sur les models that.addListeners(); // on affiche la liste dans la vue that.render(); } });
Ici, je vais charger la Collection par un appel au serveur.
Cette manière de faire n'est pas idéale puisqu'on ajoute un appel serveur inutile.
Il est conseillé de plutôt utiliser la technique du bootstrap du tuto 1, par exemple en PHP, vous pouvez interroger la base puis créer un objet javascript :
<script> var users_bootstrap = <?php echo json_encode($users); ?> ; </script>
Mais puisqu'on est là pour présenter les choses, autant voir d'autres manières de faire.
Donc, une fois que les données seront arrivées, on appellera la fonction addListeners() pour poser un écouteur sur la collection
addListeners : function () { // à chaque fois qu'on ajoute un user dans la Collection => rechargement de la vue this.listenTo(userColl, 'add', this.render); }
Puis, comme dans le tutoriel 1, nous affichons la liste avec la fonction render()
Chargement liste (serveur)
Pour commencer on ajoute dans notre arborescence un répertoire api/ dans lequel on crée un fichier user.php
On se retrouve alors avec l'arborescence suivante
. index.html | api . user.php | js | libs . backbone-min.js . jquery.min.js . underscore-min.js | views . index.js
Voici le code du script user.php
<?php session_start(); // permet la mise en place d'échange REST avec l'application backboneJS // en production, on utilisera un framework comme Slim // on simule l'utilisation d'une base de données en stockant les users en SESSION if (!isset($_SESSION['users'])) { $_SESSION['users'] = array(); } // recuperation des paramétres $postData = file_get_contents('php://input'); $postData = json_decode($postData, true ); // test de la méthode envoyée switch ($_SERVER['REQUEST_METHOD']) { case 'GET' : echo getUsers(); break; case 'POST' : echo createUser($postData); break; case 'PUT' : echo updateUser($postData); break; case 'DELETE' : echo deleteUser(); break; default : die("REQUEST METHOD inconnue ..."); } /** * retourne la liste des users */ function getUsers () { return json_encode($_SESSION['users']); } /** * creation d'un nouveau user */ function createUser ($postData) {} /** * mise à jour d'un user */ function updateUser ($postData) {} /** * suppression d'un user */ function deleteUser () {} ?>
Si vous avez déjà codé un peu de PHP, vous ne devriez pas être perdu.
Vous remarquez qu'on utilise les sessions pour la persistance des données, ce qui permet de s'affranchir d'une base de données.
En revanche, vous n'avez peut-être encore jamais rencontré l'instruction qui suit :
$postData = file_get_contents('php://input');
Ce code permet de récupérer les paramètres envoyés au serveur par notre javascript.
Vient enfin la partie la plus importante, le test de la méthode de communication employée.
switch ($_SERVER['REQUEST_METHOD']) { ... }
C'est en fonction de la méthode qu'on détermine l'action demandée :
- GET = lecture
- POST = création
- PUT = modification
- DELETE = suppression
Pour revenir à notre cas, l'appel (en javascript) du fetch() sur notre collection va générer une requête GET au serveur
On retourne alors l'ensemble des users stockés en session
return json_encode($_SESSION['users']);
Pour tester, affichez les outils pour développeurs de votre navigateur et rafraichissez la page, vous pouvez voir l'appel au serveur ainsi que la réponse retournée (un tableau vide pour l'instant)
Ajout user (client)
Pour ajouter un nouveau user, nous commençons par poser dans la vue un écouteur sur la soumission du formulaire avec l'attribut events et appelons la fonction formAjoutSubmitHandler()
events : { 'submit #form-ajout' : 'formAjoutSubmitHandler' }, formAjoutSubmitHandler : function (e) { e.preventDefault(); // si vous utilisez un ancien navigateur qui ne prend pas en charge // l'attribut "required" du tag "input", vous devez ajouter ici un test // sur le valeur du champ "nom" var that = this; userColl.create( {nom : this.$userNom.val()}, { wait : true, success : function () { that.$userNom.val(''); } } ); },
Dans la fonction formAjoutSubmitHandler() nous utilisons la fonction create de la Collection.
Cette fonction équivaut à faire :
* création d'un nouveau model User * enregistrement du model sur le serveur * ajout du model dans la collection
soit :
var newUser = new User(), that = this; newUser.save( {nom : this.$userNom.val()}, {wait : true, success : function (model) { userColl.add(model); } }
Il bien évidemment possible d'utiliser cette dernière méthode mais ça nous oblige à ajouter un attribut url dans le Model User pour pouvoir échanger avec le serveur.
En effet, la Collection possède l'attribut url, dès lors, tous les modèles appartenant à la Collection en héritent.
Or, notre nouveau Model n'appartient pas encore à la Collection, il est donc nécessaire de lui indiquer quelle url appeler.
Bref, je vous invite à plutôt utiliser Collection.create()
Ajout user (serveur)
Si après avoir rafraichi la page index.html vous enregistrez un nouveau user, vous pouvez remarquer que cette fois-ci la méthode utilisée est POST (et non GET).
Et des données sont envoyées au serveur !
Il va donc falloir ajouter dans notre script PHP le code pour créer un nouveau user
/** * creation d'un nouveau user */ function createUser ($postData) { //attribution d'un ID en prenant le prochain disponible $lastID = 0; foreach ($_SESSION['users'] as $user) { if ($user['id'] > $lastID) $lastID = $user['id']; } $postData['id'] = $lastID + 1; $_SESSION['users'][] = $postData; return json_encode($postData); }
La fonction createUSer() va chercher dans la liste des users existant celui qui possède l'ID le plus élevé On donne l'ID suivant à notre nouveau user puis on l'enregistre en SESSION Enfin, on retourne le nouveau user
Et voilà, le nouveau user vient enrichir notre liste !
Supprimer user (client)
Pour pouvoir supprimer un user, on a besoin d'un bouton “supprimer”, on va donc le rajouter sur chaque ligne en modifiant simplement le template !
Vous comprenez à présent l'intérêt des templates ?
Remplacer le template par celui-ci
<script type="text/template" id="user-row-tpl"> <li data-id="<%= id %>"> <div class="user-row-wrapper"> <%= nom %> ( <%= role %> ) <span class="user-delete" data-id="<%= id %>">delete</span> </div> </li> </script>
Bien entendu, il conviendra de mettre en forme avec un peu de CSS notre ligne pour avoir un icône à la place du texte et de placer ce dernier à droite de la ligne, mais là n'est pas l'objet de ce tutoriel.
On écoute dans la vue le click sur le bouton de suppression
events : { 'submit #form-ajout' : 'formAjoutSubmitHandler', 'click .user-delete' : 'userDeleteClickHandler' }, userDeleteClickHandler : function (e) { // avec l'id du template on récupère dans la collection le Model var userToDelete = userColl.get($(e.target).data('id')); if (confirm('Supprimer le user ' + userToDelete.get('nom') + ' ?')) { // suppression du Model sur le serveur userToDelete.destroy({wait : true}); } },
On commence par récupérer l'id du Model qui se trouve dans le template.
Avec cet id, on récupère le Model dans la Collection avec la fonction Collection.get(id)
Enfin, si la demande de suppression est confirmée, on supprime le Model par la fonction Model.destroy
Avant de tester, on va écouter les suppressions de Model dans notre Collection pour mettre à jour la vue.
Dans la fonction addListener(), on ajoute donc :
addListeners : function () { this.listenTo(userColl, 'add', this.render); this.listenTo(userColl, 'destroy', this.render); },
A présent, si vous confirmez la suppression d'un user, vous verrez l'appel au serveur avec la méthode DELETE
Mais plus important, l'id du user a supprimer n'est pas passé au serveur de la même manière que pour la création : il est passé dans l'URL !
/api/user.php/1 // l'id a supprimer est le 1
Il faudra le prendre en compte dans le script PHP
Supprimer user (serveur)
Comme expliqué plus haut, nous devons récupérer l'id passé dans l'URL.
Il y a plusieurs manières de faire, en voici une.
case 'DELETE' : //l'ID est passé avec l'URL : http://path_to_my_script.php/ID $arRequestURI = explode( "/", $_SERVER['REQUEST_URI'] ) ; $id = array_pop($arRequestURI); echo deleteUser($id); break;
Reste à supprimer le user de la SESSION
/** * suppression d'un user */ function deleteUser ($deleteID) { for ($i = 0, $ii = count($_SESSION['users']); $i < $ii; $i++) { if ($_SESSION['users'][$i]['id'] == $deleteID) { array_splice($_SESSION['users'], $i, 1); break; } } return true; }
Félicitation, vous pouvez maintenant supprimer les users de la liste !
Conclusion
Et la mise à jour me direz-vous ?
Et bien, vous avez toutes les clés en main pour réaliser vous même cette dernière étape
Bien sûr, en cas de blocage, n'hésitez pas à poster sur le Forum.
Dans un prochain tutoriel nous aborderons la dernière principale fonctionnalité apportée par Backbone.js : le Router
Bien connu des flasheurs et flexeurs sous le nom deep linking, il permet d'utiliser les flèches précédente et suivante du navigateur ainsi que d'ajouter une “page” de votre application dans les favoris.
Bon dev
Les sources
