Blog tech

Présentation de backbone.js

Rédigé par Martin Catty | 12 avril 2012

Présentation de Backbone.js

Backbone.js est un cadre applicatif pour les applications à forte teneur en javascript.

Backbone.js peut se définir comme un framework MVC mais pas au sens classique du terme. Ici le C représente des Collections d’objets. Le M et le V désignent respectivement les modèles et les vues.

Backbone.js ne possède qu’une dépendance ferme, il s’agit de underscore.js, qui est maintenu par la même équipe.

Le rôle d’underscore.js est de vous apporter tout un tas de méthodes pour vous simplifier la vie (enumerable, binding, template, comparaison…) sans faire 500ko.

Généralement jQuery vient compléter la liste des dépendances finales de l’application, car il y a fort à parier que vous aurez à minima besoin de manipuler le DOM.

Quand utiliser Backbone.js

Backbone.js est un excellent choix pour les applications dites «single page application», c’est à dire une page principale avec un nombre important d’interactions utilisateur.

Plutôt que d’avoir une navigation classique par page (ou le serveur envoie toute la page à chaque URL), la navigation, l’envoi de formulaire et toutes les actions classiques se gèrent en javascript.

Avant, quand on souhaitait faire ce genre de choses, on écrivait son propre javascript, sans aucune convention, ầ grand coup d’AJAX et on se retrouvait vite avec du spaghetti code, complexe à maintenir et à tester.

Backbone.js nous permet de cadrer cela en définissant modèle, vue et collections. On va pouvoir définir des événements sur des changements de valeurs de nos modèles et ainsi rafraichir automatiquement nos vues.

Pour cela Backbone.js possède son propre routeur. On peut faire correspondre des actions à des URL.

Le code créé avec Backbone.js peut être testé via jasmine.

Ce que Backbone.js ne fait pas

Backbone.js est uniquement une couche client, il n’a aucune vocation à gérer la persistence de vos données sur un serveur.

Mais rassurez vous cela se fait très bien, il suffit d’indiquer à nos modèles une URL et d’avoir une API JSON Restful pour être capable de réaliser du CRUD très simplement.

Apprentissage par la pratique

L’objectif de cette première partie est de créer une application qui va lister les catégories récupérées depuis le serveur.

L’application backbone.js est disponible sur github.

Le tag 0.1 correspond à cette partie.

Aperçu rapide de la partie serveur

Pour la persistence des données nous utilisons une application Ruby on Rails qui agit uniquement comme une API.

Nous avons une migration pour créer des catégories qui ont un nom, ainsi que quelques seeds. Une fois l’application clonée il vous reste à configurer votre base de données (database.yml) et lancer le chargement:

  bin/rake db:setup

Notre contrôleur categories renvoie uniquement du JSON.

class CategoriesController < ApplicationController
  respond_to :json

  def index
    @categories = Category.all
    respond_with(@categories)
  end
end

On va donc pouvoir récupérer nos catégories sur /categories.json

La partie client avec Backbone.js et coffeescript

Pour son côté pratique et maintenant par habitude, le code est en coffeescript. Jetez un œil à notre introduction à coffeescript si vous ne connaissez pas encore. Vous ne serez pas trop dépaysés.

Le fichier principal est app.coffee, il est généré dans app.js Si vous souhaitez faire des changements en local pour tester, vous pouvez utiliser le Guardfile fournit via:

  bin/guard

Il s’occupera de compiler votre coffeescript à la volée.

Explication du code Backbone.js

Logiquement notre application devrait être découplée en plusieurs fichiers, en séparant modèle, vue etc…

Ici la simplicité de l’application fait que tout est en un seul fichier. Commençons à décortiquer le code.

@app = window.app ? {}

Afin que les fichiers javascript puissent avoir connaissance les uns des autres on utilise l’objet global windows et le namespace app.

_.templateSettings = {
    interpolate: /\{\{\=(.+?)\}\}/g,
    evaluate: /\{\{(.+?)\}\}/g
};

Underscore.js possède un système de template, dont la syntaxe par défaut est la même que celle des templates ERB.

Nous changeons cela pour qu’ERB n’essaie pas de les interpréter, ce sera le rôle de Backbone.js.

Pour cela nous modifions les paramètres du template pour avoir une syntaxe proche de celle de mustache.

On va donc passer de <% %> à .

Définition du modèle

  class Category extends Backbone.Model

Notre catégorie va simplement étendre le modèle de base de Backbone.js pour hériter de toutes ces méthodes (save…).

Ici on utilise directement le mécanisme de coffeescript. Par convention le modèle est au singulier.

En javascript on aurait plutôt:

  var Category = Backbone.Model.extend({})

Une fois notre classe créée nous la mettons dans le namespace. Nous le ferons systématiquement pour les autres classes, quelque soit leur fonction.

Définition de la collection

  class Categories extends Backbone.Collection
    url: "/categories"
    model: app.Category
  @app.Categories = Categories

La collection se trouve à mi chemin entre la vue et le modèle. Elle concerne un modèle dont elle contiendra une liste, dont le but sera d’aller peupler une vue.

Par convention la collection est au pluriel.

Partant de là nous sommes déjà en mesure de récupérer les données de l’API. Vous pouvez tester depuis la console de votre navigateur:

  categories = new window.app.Categories()
  categories.fetch()
  categories.models
  # [Category, Category]

Définition des vues

Regardons comment définir un template dans nos vues HTML.

  <script type="text/template" id="category">
    
  </script>

Les templates sont définis dans le head de notre HTML, de base le navigateur n’interprétera rien. Comme mentionné plus tôt nous utilisons la syntaxe de mustache.

Le but pour ces templates est d’être remplacé par le HTML correspondant à ce qu’on souhaite afficher par Backbone.js.

Par exemple #category représentera un élément LI contenant le nom de la catégorie.

Voyons comment est définie notre classe du côté de Backbone.js:

class CategoryView extends Backbone.View
  tagName: 'li'
  template: _.template($('#category').html())

  initialize: ->
    _.bindAll(@, 'render')

  render: ->
    @.$el.html @template(@model.toJSON())
    @
@app.CategoryView = CategoryView

Nous spécifions donc le tagName qui servira à l’enrobage HTML. Il existe d’autres options comme la classe CSS…

Ensuite nous indiquons quel est le template à utiliser (#category). C’est cette définition qui justifie l’enrobage $(document).ready, afin que le DOM soit prêt pour l’initialisation des templates.

Autrement on peut toujours les initialiser dans le constructeur de la vue mais cela impliquera un calcul à chaque instanciation d’objet, donc ce n’est pas recommandé.

Dans le constructeur nous allons nous assurer que le rendu de la vue se fera toujours dans le contexte de cet objet (this).

En effet le render pourra être appelé depuis d’autres endroits, notamment le modèle.

Dans ce cas nous voulons nous assurer que le this courant s’apparentera toujours à cette vue. Nous utilisons pour cela bindAll.

Enfin la méthode render va se servir du modèle que nous lui aurons passé pour générer notre LI.

Plutôt que d’appeler $(@el) on utilise @.$el qui permet d’éviter d’accéder à chaque fois au DOM en stockant une référence à l’élément.

Render doit toujours retourner this afin de pouvoir chainer les appels.

Un petit test en console:

  // creation de la categorie
  var category = new window.app.Category({ name: "Synbioz" })
  // creation de la vue
  var view = new window.app.CategoryView({ model: category })

  var content = view.render()
  content.el
  // <li>Synbioz</li>

Regardons maintenant la vue qui listera l’ensemble des catégories:

class CategoriesView extends Backbone.View
  tagName: 'ul'
  template: _.template($('#categories').html())

  initialize: ->
    _.bindAll(@, 'render')
    @collection.bind 'reset', @render

  render: ->
    @.$el.html(@template())
    node = @.$('.categories')
    collection = @collection
    @collection.each (category) ->
      view = new CategoryView
        model: category
        collection: collection
      node.append view.render().el
    @

Avec le binding du reset sur la collection nous introduisons toute la puissance de Backbone.js. Ce code va permettre de surveiller la collection, et de faire automatiquement un render de la vue associée.

Dans la méthode render nous allons utiliser la vue précédente pour générer chaque catégorie et l’ajouter à la liste.

Plutôt que de rechercher .categories dans tout le DOM nous limitons la portée au template, ce qui est nettement plus efficace.

Définition du routeur

Le routeur va nous permettre de définir les routes de notre application. Il définit les routes en GET en faisant matcher l’URL courante sur une action.

class MetricsRouter extends Backbone.Router
  routes:
    '': 'dashboard'

  initialize: ->
    window.categories = new app.Categories()
    @categoriesView = new app.CategoriesView
      collection: categories

  dashboard: ->
    window.categories.fetch()
    $("#container").html(@categoriesView.el);

@app.MetricsRouter = new MetricsRouter()

Dès que nous serons sur la home, la méthode dashboard sera appelée. Dans celle ci on récupère les catégories, ce qui exécute directement un rendering de la vue associée.

Il ne reste qu’à l’ajouter dans le DOM.

Lancement de l’application

Maintenant que tout est en place, lançons l’application:

  Backbone.history.start()

Nous n’avons pas encore de navigation mais cela n’en demeure pas moins indispensable.

Par défaut les URL sont gérées comme celles de twitter, avec un #. Mais il existe une option (pushState) qui permet d’utiliser les possibilités d’HTML5 pour mettre à jour l’historique en javascript.

Ainsi pour votre navigateur c’est complètement transparent, la navigation s’apparente à une navigation classique.

Conclusion

Le rapport puissance / taille du code du Backbone.js est assez impressionnant. Il permet de créer des applications plus fluides et réactives en limitant les aller / retour avec le serveur.

Ce cadre applicatif permet d’avoir du code bien organisé, qui plus est testable avec jasmine.

Dans la prochaine partie nous allons gérer toute la partie événementielle et formulaire afin de pouvoir interagir avec la page et naviguer comme dans une application classique.

L’équipe Synbioz.

Libres d’être ensemble.