Je me suis plongé pendant quelques jours sur la document de backbone , en particulier sur l'exemple de TODO List réalisé par Jérôme Gravel-Niquet.
Reuven Lerner, éditorialiste de la revue Linux Journal (US) a consacré deux series d'article sur le sujet (juin num 206 et juillet: num 207).
On trouve ici et là d'autres articles.
Conclusion : j'ai failli me noyer.
Les principes sont pourtant assez simples: La notion de modèle recouvre l'accès aux données. Ces données sont récupérées par l'appel d'une URL du serveur applicatif. La partie vue correspond à la partie visuelle: la portion de HTML.
La partie controleur est responsable de la gestion des 'routes'. Dans les applications massivement en AJAX, l'utilisateur voit plusieurs pages mais reste physiquement sur une page. Aussi le developpeur peut utiliser les fragments d'url (/#monFragment) pour différentier artificiellement les pages pour la gestion des signets. Ce système fait appel aux 'routes' du controleur.
La partie controleur est réduite au minimum dans les petites applications javascript.
En revanche, le dialogue entre les couches est à prendre en charge par le développeur. A ce dialogue s'ajoute les interractions avec jquery. Au final l'investissement initial n'est pas neutre.
La couche modèle.
Backbone propose une structure pour définir et manipuler des éléments unitaires ou des collections d'élément.
Exemple :
masuite.bind("add", function(v) {
alert("Ahoy " + v.get("valeur") + "!");
});
var a= new UnElement({valeur:1});
var b= new UnElement({valeur:2});
masuite.add(a);
masuite.add(b,{silent:true});
Je commence par définir une fonction qui se déclenchera lors de chaque ajout d'un élément à une collection. L'option 'silent' bloque la remontée de l'évènement.
La collection est la colonne vertébrale de votre application. L'utilisation de la librairie : underscore.js ajoute une vingtaine de méthodes supplémentaires dont le each , le tri, la comparaison entre deux états. La couche modèle est responsable des échanges de donnée avec le serveur.
La couche vue.
Il y aura en principe autant de vue que de portion d'affichage. Dans mon exemple, j'ai deux vues, une pour afficher l'élément unitaire de la suite, l'autre pour les informations relatives à la suite de nombre (la collection).
Tout changement dans le modèle sera repercuté dans la vue.
Comment brancher le modèle à sa vue ? : les avis divergent. Doit on réferencer la vue dans le modèle , ou l'inverse ou les deux à la fois ?.
J'ai suivi l'exemple du TODOS liste.
Cela donne le code suivant:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// javascript // | |
$(function() { | |
// modele pour un element | |
// Un element ne comporte qu'un attribut sa valeur | |
window.UnElement = Backbone.Model.extend({valeur: null}); | |
// modele pour la suite des elements | |
// Définition d'une collection d'element de la suite. | |
// On ajoute un attribut cumul à cette collection | |
// l'attribut 'model' fait le lien entre le modele d'un element | |
// et le modele de la collection | |
window.UneSuite = Backbone.Collection.extend({ | |
model: UnElement, | |
initialize: function(models, options) { | |
this.cumul= 0; | |
}, | |
}); | |
// Instanciation de la collection | |
masuite= new UneSuite; | |
// mise en place de la vue pour UneSuite | |
// A la création de la vue, la connexion du modele (collection) uneSuite avec cette vue | |
// est réalisée par passage de parametre au moment de l'appel. | |
// La vue encapsule des evenements navigateurs mais aussi des evenements sur les modèles (ajout d un élément) , des handlers d'evenements et des portions d'ecran (render) | |
// l'attribut 'el' indique sur quel type d'element du DOM on accroche la vue. | |
// Cette notion est importante pour l'interception des evenements navigateur :click | |
window.UneSuiteView= Backbone.View.extend({ | |
el: $('body'), | |
events: { | |
'click #add_element' : 'addnumber', | |
'click #supp_element' : 'deletelast', | |
}, | |
// addnumber et deletelast sont les callback qui sont appellés sur les clics | |
deletelast: function() { | |
var killme= this.model.last(); | |
this.model.remove(killme); | |
// Le remove de l'element de la collection déclenche un evement 'remove' qui est lié à un callback | |
}, | |
addnumber: function() { | |
nouveau = new UnElement({valeur: this.$('#new-element').val()}); | |
this.model.add(nouveau); | |
// Le add de l'element de la collection déclenche un evement 'add' qui est lié à un callback | |
}, | |
initialize: function() { | |
_.bindAll(this,'removeOne', 'addOne','render'); | |
this.model.bind('all', this.render); | |
this.model.view = this; | |
this.render(); | |
this.model.bind('add', this.addOne); | |
this.model.bind('remove', this.removeOne); | |
}, | |
addOne: function(UnElement) { | |
//lien entre la vue de la collection et la vue du modele UnElement | |
var view = new ElementView({model: UnElement}); | |
// this.$("#suite").append(view.render().el); | |
// cette notation doit se lire comme ceci $(selecteur,this.el) | |
$("#suite").append(view.render().el); | |
//view.render().el retourne une span (el) identifiée par le numero de l'élément (cid) | |
this.model.cumul += Number(UnElement.get('valeur')); | |
}, | |
removeOne: function(UnElement) { | |
var tmp= "#" +UnElement.cid; | |
this.model.cumul -= Number(UnElement.get('valeur')); | |
$(tmp).remove(); | |
}, | |
// html de la vue pour les informations relatifs à la suite | |
render: function(){ | |
$('#nombre_element').text(this.model.length); | |
$('#cumul_element').text(this.model.cumul); | |
return this; | |
}, | |
}); | |
// Vue relative à un nombre de la suite | |
// Le lien entre la vue et un élément est réalisé dans addOne : | |
window.ElementView = Backbone.View.extend({ | |
tagName:'span', | |
initialize: function() { | |
_.bindAll(this, 'render'); | |
this.model.view = this; | |
}, | |
render: function() { | |
$(this.el).append('=> ' +this.model.get("valeur")); | |
$(this.el).attr('id',this.model.cid); | |
return this; | |
}, | |
}); | |
// appel general , instanciation de la vue avec l'objet qui contient la suite des nombres(collection) | |
window.App = new UneSuiteView({model:masuite}); | |
}); |
Le html est très simple:
A chaque ajout d'un élément dans la collection toutes les vues se mettent à jour:
Cette application n'a pas besoin d'un vrai controleur.
Par comparaison j'ai réalisé cette application sans backbone.js , juste avec jquery.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$(function() { | |
var suite= new Array; | |
var cumul=0; | |
var nb=0; | |
$('#nombre_element').text(nb); | |
$('#cumul_element').text(cumul); | |
$('#add_element').click(function() { | |
var input = $('#new-element').val(); | |
cumul+= Number(input); | |
nb+=1; | |
$('#nombre_element').text(nb); | |
$('#cumul_element').text(cumul); | |
$('#suite').append("<span id=c"+ nb+ "> " + input +" </span>"); | |
suite[nb-1]= input; | |
}); | |
$('#supp_element').click(function() { | |
var tmp= suite[suite.length-1]; | |
cumul -= tmp; | |
var tmpspan="#c"+ nb; | |
nb-=1; | |
$('#nombre_element').text(nb); | |
$('#cumul_element').text(cumul); | |
$(tmpspan).remove(); | |
suite =suite.slice(0,nb) ; | |
}); | |
}); |
La comparaison est sans appel : c'est plus compliqué et long à faire avec backbone.js. Mais en ajoutant des deux cotés des nouvelles fonctions, très vite je me suis rendu compte que l'ajout est plus simple à réaliser avec backbone.js.
Attention à l'ordre d'insertion des librairies javascript : jquery, underscore,backbone,mon_script.
Les sources sont sur github.
5 commentaires:
Oui ok pour un serveur RESTFUL, mais existe-t-il une solution propre pour pouvoir repondre uniquement aux verbes GET POST (les seuls autorisés sur les hebergements mutualisés) tout en gardant un sens/esprit REST ?
Enregistrer un commentaire