BackboneJS

Coder proprement en JS, c'est possible

Par Charles-Édouard Coste / @charlycoste

3615 Mavie...

Charles-Édouard Coste

  • Développeur fullstack
  • Consultant en logiciel libre
  • Consultant en accessibilité web
  • Ingénieur qualité
Certification Opquast avancé

Me contacter...

Le bon vieux mail : contact@ccoste.fr

Droits d'auteurs

Cette présentation est sous licence CC BY-SA 4.0

Vous êtes autorisés à la...
Partager
Adapter
Selon les conditions suivantes :
Attribution
Partage dans les Mêmes Conditions

jQuery

Quelques bonnes pratiques...

Isolez votre code !

Mauvaise pratique

function test() {
  alert("Je suis chargée");
};

// Quand la page est chargée...
$(function(){
  test();
});

Bonne pratique

(function($, window, document) { // <= il ressortira par là

  function test() {
    alert("Je suis chargée");
  };

  // Quand la page est chargée...
  $(function(){
    test();
  });

})(window.jQuery, window, document); // <= il est entré par ici

Cachez vos variables !

Mauvaise pratique

$('.main li .content').css('border', '1px solid red');

$('.main li .content').on('click', function(){
    $('.main li .content .message').hide();
});

$('.main li .content')
  .html('<div class="message">Au revoir</div>');

Bonne pratique

var content = $('.main li .content');

content
  .css('border', '1px solid red');
  .on('click', function(){
    content.find('.message').hide();
  })
  .html('
Au revoir
');

Utilisez la délégation !

Mauvaise pratique

$('li').click(function(){
 
  $(this).closest('ul').addClass('checked');

});

Bonne pratique

$('ul').on('click', 'li', function(){

  $(this).addClass('checked');

});

Utilisez les promesses !

Non mais sérieux !?

Qu'est-ce qui ne tourne pas rond chez vous ?

$(function(){
  function affichePage(title) {
    $.ajax({
      url: "/article/"+title,
      type: "get",
      success: function(data) {
        $(".main .content")
        .html("

"+data.title+"

"); $("button").click(function(){ affichePage($(".main .content h1").text()); }); } }); } affichePage("Helloworld"); });

Bonne pratique

var title = "Helloworld";
var $el;

function load() {
  var request = $.ajax({url: "/article/"+title, type: "get"});

  request.done(function(data){title = data.title});

  request.done(function(data){
    $el.html("

"+data.title+"

"); }); } $(function(){ $el = $(".main .content"); $el.on('click', 'button', load); load(); });

Résumé

4 bonnes pratiques à connaître

  • Les IIFE
  • Les variables de cache
  • La délégation d'événements
  • Les promesses

Qu'est-ce que BackboneJS ?

Une bibliothèque poids plume

15Ko en plus de jQuery

Une philosophie

“Get your truth out of the DOM”
Jeremy Ashkenas

Hash vs DOM

[
    {id:'23', titre: 'Les œufs brouillés', statut:'brouillon'},
    {id:'24', titre: 'Les œufs à la coque', statut:'publié'}
]
<div>
    

Les œufs brouillés

Les œufs à la coque

</div>

Un découpage MVC

4 classes pratiques

  • Backbone.Model
  • Backbone.Collection
  • Backbone.View
  • Backbone.Router

Backbone n'est pas un framework

Rappel sur Javascript

Les "classes" en JS

var Menu = (function() {
  function Menu($el) {
    this.$el = $el;
  }
  Menu.prototype.open = function() {
    this.$el.show();
  }
  Menu.prototype.close = function() {
    this.$el.hide();
  }
  return Menu;
})();

"Héritage" en JS

var extend = function(child, parent) {
  for (var key in parent) {
    if (hasProp.call(parent, key)) child[key] = parent[key];
  }

  function ctor() {
    this.constructor = child;
  }
  ctor.prototype = parent.prototype;
  child.prototype = new ctor();
  child.__super__ = parent.prototype;
  return child;
},
var hasProp = {}.hasOwnProperty;

var MyMenu = (function(superClass) {
  extend(MyMenu, superClass);

  function MyMenu() {
    return MyMenu.__super__.constructor.apply(this, arguments);
  }

  MyMenu.prototype.open = function() {
    return this.$el.show();
  };

  return MyMenu;

})(Menu);

Version Coffeescript

class MyMenu extends Menu
  open: ->
    @$el.show()

Modèles

Objets temporaires

Option 1

var Classe = Backbone.Model.extend({});
var objet = {id:'23', title: 'Les œufs brouillés', statut:'publié'}
objet = new Classe(objet);
objet.toJSON(); // -> {id:'23', title: 'Les œufs...

Option 2

var Classe = Backbone.Model.extend({
  default : {
    title: 'Mon titre',
    statut:'brouillon'
  }
});
var objet = new Classe();

Objets persistants

var Article = Backbone.Model.extend({
  urlRoot: '/articles'
});

var objet = new Article({id: 12});

objet.fetch(); // => GET /articles/12

Synchronisation serveur


var objet = new Article();

objet.save(); // => POST /articles

objet.get('id'); // => 1

objet.set({title: 'Machin'});

objet.save() // => PUT /articles/1

objet.destroy(); // => DELETE /articles/1

Chacune de ces fonctions retourne l'objet $.ajax()

Accesseurs

en lecture

objet.get('titre');

en écriture

objet.set({statut: 'publié'});

Événements

objet1.on(<event>, <method>)

objet2.listenTo(objet1, <event>, <method>)

objet1.trigger(<event>, <arg1>, <arg2>, ...)

objet1.save()
  .done(function(){...})
  .fail(function(){...});

objet1
  .on('sync', function(){})
  .on('error', function(){});

objet1.save();

Événements standards

change Quand un attribut est modifié
change:<attr> Quand <attr> est modifié
destroy Quand un modèle est détruit
sync Quand une synchronisation réussit
error Quand une validation ou une sauvegarde échoue
all N'importe quel événement

Collections

Temporaires

var Articles = Backbone.Collection.extend({
  model: Article
})

var collection = new Articles([
  {title:'Lorem', status:'brouillon'},
  {title:'Ipsum', status:'publié'}
]);

Persistantes

var Articles = Backbone.Collection.extend({
  model: Article,
  url: '/articles'
})

var collection = new Articles();
collection.fetch();

46 fonctions itératives

articles.forEach(...);
articles.map(...);
articles.find(...);
articles.size();
articles.toArray();
...

Événements

add Quand un objet est ajouté
remove Quand un objet est retiré

Vues

J'avais pas de vues

(Oui, c'est nul...)

De jQuery à Backbone

var el = document.getElementsById('#content');

//$(el) ~= $('#content');
el == $('#content').get(0);

var vue = new Vue({el: '#content'});

vue.el // => équivalent à document.getElementsById('#content')
vue.$el // => équivalent à $('#content')

Sur un noeud existant

var Menu = Backbone.View.extend({
  events: {
    "click button": "cacher",
    "mouseover": "afficher"
  },
  cacher: function(e){
    this.$el.children().hide();
  },
  afficher: function(e){
    this.$el.children().show();
  }
});
var menu = new Menu({
  el: '.menu'
});

Création d'un nouveau noeud

var Menu = Backbone.View.extend({
  ...
  tagName: "h3",
  className: ".menu",
  title: "ceci est un menu"
});
var menu = new Menu();
menu.$el.appendTo('body');
//<body>
//   <h3 class="menu" title="ceci est un menu"></h3>
//</body>

Synchronisation modèle/vue

var o1 = new Backbone.Model.extend({});
var View = new Backbone.View.extend({
  // ...
  render: function(){
    this.$el.html("

"+this.model.get('titre')+"

"); } }); var v1 = new View({model: o1}); v1.listenTo('o1', 'change', v1.render); o1.set('titre', 'Lorem ipsum'); // => change:titre

Utilisation de templates

var RecetteView = Backbone.View.extend({
  // ...
  template: _.template('<%= titre %>') // moteur de underscore.js
  render: function(){
    var attributes = this.model.toJSON();
    this.$el.html(this.template(attributes));
  }
});

Quelques moteurs de templates

Underscore.js

<h3><%= description %></h3>

Mustache.js

<h3>{{ description }}</h3>

Haml-js

%h3= description

Eco

<h3><%= description %></h3>

la fin de la foire aux écouteurs

Avant


function alertClick(e){
  alert("Un click a eu lieu !");
}
$('h3').click(alertClick); // s'applique sur tous les h3
            

Avec Backbone


var RecetteView = Backbone.View.extend({
  events: {
    'click h3': 'alertClick' // restreint aux descendants de `el`
  },
  alertClick: function(e){
    alert("Un click a eu lieu !");
  }
});
            

Dans les coulisses...

this.$el.on(<event>, <selector>, <callback>)

Pause questions...

Qu'est-ce que MarionetteJS ?

Un complément à BackboneJS

Des vues de collections

Une implémentation par défaut

Une bonne gestion mémoire

Démo

Merci à tous

- backbonejs.org
- marionettejs.com
- Marionette Wires
- Anatomy of Backbone.js sur CodeSchool