Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Structurer son code jQuery avec Backbone.js : échanges avec le serveur

Compatible JavaScript. Cliquer pour en savoir plus sur les compatibilités.Par dcz.switcher (david CHOLLEZ), le 31 octobre 2013

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)

Prérequis

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