Envie de découvrir Backbone.js, un framework JavaScript des plus tendance ? TodoMVC, un projet Open source, vise a comparer l’implémentation d’un même cas d’école (la gestion d’une Todo-list en JavaScript) via différents frameworks ou librairies. Je vous propose donc, via ce dernier projet, la découverte du framework Backbone.js.
Introduction
Aujourd’hui de nombreuses solutions s’offrent à nous pour faire du MVC [MVC : Modèle Vue Controlleur]] (ou MVP ou MVVM…) et construire des applications performantes et maintenables en JavaScript. Oui mais alors, quand on n’y connait rien, on choisit quoi ?
[TodoMVC est là pour ça ! Ce projet Open source, hébergé sur GitHub, a pour principe de juxtaposer la réalisation d’une même application par différents frameworks ou librairies JavaScript. Notamment, 16 alternatives sont déjà stables, et plus d’une trentaine sont en cours de développement.
Très bien, mais s’il faut regarder le code source de chaque application avant de faire son choix, ça peut prendre du temps… Je vous rassure tout de suite, nous allons vous aider en publiant prochainement sur ce blog un état des lieux des solutions MVV | MVC JavaScript disponibles.
En attendant et pour vous y préparer, je vous propose de découvrir une de ces implémentations : Backbone.js – un framework qui a le vent en poupe. Ainsi, si vous ne connaissez pas les frameworks JavaScript, cela vous fera une première approche. Et si vous en connaissez, vous pourrez directement comparer cette implémentation à celle de votre framework / librairie préféré.
Présentation de Backbone.js
Backbone.js, projet Open source également hébergé sur GitHub, permet, comme tout framework MV*, de dissocier les responsabilités afin d’augmenter la maintenabilité de votre application.
Il se compose de 4 classes principales : Model, Collection, View et Router, via lesquelles il se veut un framework MVC.
MVC, oui, mais attention.. les termes sont trompeurs.
- Model : contient les données d’un objet – associations clés/valeurs.
- View : une vue est reponsable d’un bloc HTML. Elle gère son affichage dynamiquement et déclenche des évènements à partir des interactions utilisateur sur ce bloc. On peut donc considérer que la vue au sens de Backbone.js est un élément central : c’est un contrôleur (tel que connu dans les implémentations côté serveur, comme par exemple pour Ruby on Rails).
- Collection : contient une liste d’objets, ainsi que leur logique de tri, filtrage, etc. Une collection est un intermédiaire bien pratique entre la vue/contrôleur et le modèle.
- HTML Template : c’est la vue telle qu’on la définit dans le modèle MVC : du code HTML contenant des variables à remplacer. Backbone.js vous laisse le choix d’utiliser votre propre librairie de templating.
- Router : le routeur est un peu en dehors du modèle MVC. Il déclenche principalement des actions en fonction de l’URL et/ou de l’ancre.
Pour résumer, rien ne vaut un dessin :
TodoMVC : l’implémentation Backbone.js
Ouvrons maintenant le capot, et pour continuer à découvrir Backbone.js, je vous propose de plonger directement dans l’exemple de TodoMVC. Cette application, comme son nom l’indique, a pour but l’organisation d’une todo-list, c’est-à-dire la gestion des tâches à faire : ajout, suppression, statut, tri de la liste…
Voici l’application :
– Application TodoMVC implémentée avec Backbone.js
– Code source de l’application TodoMVC implémentée avec Backbone.js
Commençons par considérer notre fichier app.js… ah non, pardon, ça fait bien longtemps que pour les applications JavaScript – maintenables et extensibles – il est conseillé de les structurer en plusieurs fichiers et dossiers !
Commençons donc par créer la structure de notre application :
collections
– todos.js // contient la liste des taches
lib
– underscorejs + jquery + backbonejs
models
– todo.js // modèle d’une tache
routers
– router.js // routeur : peu utilisé dans cette application (pour le filtrage)
views
– app.js // gestion de la vue d’ensemble
– todo.js // gestion de la vue pour une tâche
app.js // nom de domaine et variables globales
On notera que Backbone.js nécessite obligatoirement la librairie Underscore.js (tout plein d’utilitaires qui simplifient la vie et que je vous encourage à essayer), et sollicite fortement l’utilisation de jQuery – ou Zepto.js, une alternative light à jQuery – pour la manipulation du DOM.
Bien sûr, en production, on compacte/minifie l’application pour améliorer les performances.
Créons d’abord le modèle
Une tâche est composée d’au moins un titre, et un statut :
// model/todo.js
app.Todo = Backbone.Model.extend(// valeurs par défault si non renseigné
defaults: {
title: '',
completed: false
...
}
// exemple de création d’une instance de tâche :
var todo = new app.Todo(title : « nourrir le chat ») ;
// l’object todo contient donc le contenu :
// title : « nourrir le chat », completed: false
// et hérite de toutes les fonctions de app.Todo,
// et donc de Backbone.Model
Plutôt que de vous présenter la création des classes une à une, plongeons directement dans quelques exemples de fonctionnement de l’application de bout-en-bout.
Changer le statut d’une tâche
Lorsque l’on clique sur la gauche d’une tâche, son statut est changé. La responsabilité de l’affichage d’une tâche repose sur à la classe TodoView contenue dans view/todo.js. Chaque instance de tâche possède une vue associée.
La logique est la suivante :
-# La vue attend l’évènement click pour lancer l’execution de l’action toggle sur la tâche qu’elle contient.
-# L’action toggle de la tâche inverse son statut (en cours -> complété, et réciproquement). La sauvegarde (save) déclenche l’évènement change.
-# Lorsque le modèle change, la vue écoute l’évènement et se met à jour.
Facile ? Compliquons alors un peu les choses !
Logique de création d’une tâche
-# Lorsque la touche Enter est pressée, la vue ajoute une tâche à la collection.
-# L’ajout d’une tâche à la collection est gérée automatiquement par Backbone.js (surchargeable si besoin). Cette création déclenche notamment l’évènement add.
-# La vue écoute l’évènement add de la collection et reconstruit alors l’affichage de toute la liste : chaque élément est affiché par une instance de TodoView, qui elle-même utilise un template qui est contenu dans le code source de la page.
Un dessin peut-être ?
Cela peut paraître complexe, car toutes les classes ont ici un rôle à jouer. Mais c’est justement là que se situe l’intérêt d’une telle implémentation :la répartition des rôles est décentralisée, chacun a ses propres responsabilités, et évite autant que possible d’interférer avec les attributions des autres.
Le routeur
Le routeur permet à la fois la modification des URL, et le déclenchement d’évènements si modification de l’URL (ou encore au chargement de la page). Dans cet exemple, il est utilisé pour le filtrage de notre liste de tâches.
Lorsque l’on clique sur un filtre, l’ancre de l’URL est changée. C’est ce changement qui est intercepté par le routeur, qui va être à l’origine du filtrage/rafraîchissement de la liste.
Quelques comparaisons
On pourrait écrire un livre si on voulait comparer les différentes implémentations en profondeur. J’ai choisi ici seulement quelques angles de comparaison très rapides.
Jquery
Si vous jetez un oeil à l’implémetation jQuery, vous verrez que l’application se contente de 158 lignes regroupées en un seul fichier !
Oui, mais jQuery n’est pas un framework. Une telle implémentation est possible, mais atteint rapidement ses limites.
Un objet App contient presque l’ensemble des fonctions de l’application :
var App = init: function() {
this.ENTER_KEY = 13;
this.todos = Utils.store('todos-jquery');
this.cacheElements();
this.bindEvents();
this.render();
,
cacheElements: function() this.todoTemplate = Handlebars.compile( $('#todo-template').html() );
this.footerTemplate = Handlebars.compile( $('#footer-template').html() );
this.$todoApp = $('#todoapp');
...
,
bindEvents: function() var list = this.$todoList;
this.$newTodo.on( 'keyup', this.create );
this.$toggleAll.on( 'change', this.toggleAll );
...
,
render: function() ...
,
...
toggleAll: function() ...
,
activeTodoCount: function() ...
,
destroyCompleted: function() ,
getTodo: function( elem, callback ) ...
,
create: function(e) ...
,
toggle: function() ...
,
edit: function() ...
,
...
};
Aucun mal à cela à priori, l’application fonctionne très bien. Par contre, il va par exemple être difficile de rajouter un deuxième modèle (par exemple des personnes que l’on associe aux tâches). Si l’application doit grossir, la liste des éléments et des évènements va rapidement nécessiter un cachet d’aspirine.
Une telle solution comporte des listes en « en vrac » difficiles à maintenir :
cacheElements: function() this.todoTemplate = Handlebars.compile( $('#todo-template').html() );
this.footerTemplate = Handlebars.compile( $('#footer-template').html() );
this.$todoApp = $('#todoapp');
this.$newTodo = $('#new-todo');
this.$toggleAll = $('#toggle-all');
this.$main = $('#main');
this.$todoList = $('#todo-list');
this.$footer = this.$todoApp.find('#footer');
this.$count = $('#todo-count');
this.$clearBtn = $('#clear-completed');
,
bindEvents: function() var list = this.$todoList;
this.$newTodo.on( 'keyup', this.create );
this.$toggleAll.on( 'change', this.toggleAll );
this.$footer.on( 'click', '#clear-completed', this.destroyCompleted );
list.on( 'change', '.toggle', this.toggle );
list.on( 'dblclick', 'label', this.edit );
list.on( 'keypress', '.edit', this.blurOnEnter );
list.on( 'blur', '.edit', this.update );
list.on( 'click', '.destroy', this.destroy );
,
...
Batman.js
Batman.js, c’est du tout-en-un : pas d’import de Underscore.js ou de jQuery, tout est compris, il y a même moteur de templating puissant et complet.
Ici, pas de collection. Un controller et un modèle suffisent. Mais la grande différence, c’est l’importance donnée au template, qui est capable de contenir de la logique d’éxecution :
cacheElements: function() this.todoTemplate = Handlebars.compile( $('#todo-template').html() );
this.footerTemplate = Handlebars.compile( $('#footer-template').html() );
this.$todoApp = $('#todoapp');
this.$newTodo = $('#new-todo');
this.$toggleAll = $('#toggle-all');
this.$main = $('#main');
this.$todoList = $('#todo-list');
this.$footer = this.$todoApp.find('#footer');
this.$count = $('#todo-count');
this.$clearBtn = $('#clear-completed');
,
bindEvents: function() var list = this.$todoList;
this.$newTodo.on( 'keyup', this.create );
this.$toggleAll.on( 'change', this.toggleAll );
this.$footer.on( 'click', '#clear-completed', this.destroyCompleted );
list.on( 'change', '.toggle', this.toggle );
list.on( 'dblclick', 'label', this.edit );
list.on( 'keypress', '.edit', this.blurOnEnter );
list.on( 'blur', '.edit', this.update );
list.on( 'click', '.destroy', this.destroy );
,
...
Ce genre de chose peut s’avérer intéressant, notamment si la logique du template doit pouvoir être changée par le développeur back-end, tout en évitant de modifier le fichier JavaScript utilisé par les développeurs front.
YUI
Yahoo! fait également dans l’autosuffisance. Ce framework contient de nombreux modules (ne chargeant que les dépendances nécessaires). Pour l’implémetation de TodoMVC, on retrouve à première vue un modèle, une collection (appelée liste) et une vue. Mais ici, il y a bien une sorte de controller en plus (app.js) pour gérer le comportement de l’application. Notamment, c’est également lui qui joue le rôle de routeur.
Conclusion
Après la lecture de cet article, vous devriez avoir un aperçu des possibilités de Backbone.js. Ce qu’il faut retenir ici, c’est que Backbone.js est un framework parmi d’autres, et que presque tous permettent la distribution des responsabilité, et donc la modularité de votre application.
Je pourrais continuer les comparaisons, mais c’est une tâche sans fin… Maintenant, c’est à vous de creuser en comparant toutes les implémentations de TodoMVC afin de trouver celle qui vous convient… Sinon, vous pouvez aussi attendre notre état des lieux des solutions MVV | MVC JavaScript disponibles, très prochainement et ici même !