PageRenderTime 4ms CodeModel.GetById 3ms app.highlight 83ms RepoModel.GetById 1ms app.codeStats 0ms

/07-VUES-ET-TEMPLATING.md

https://github.com/fabiansky/backbone.en.douceur
Markdown | 1715 lines | 1303 code | 412 blank | 0 comment | 0 complexity | cb7d95dc003eae06695dc3a228f59272 MD5 | raw file
   1#Vues & Templating
   2
   3>*Sommaire*
   4
   5>>- *1ère vue*
   6>>- *Mise à jour automatique de l’affichage*
   7>>- *Sous-vues*
   8>>- *Templating*
   9>>- *évènements*
  10
  11>*Nous avons joué avec les données dans le chapitre précédent, nous allons maintenant voir comment les afficher dynamiquement dans notre page web.*
  12
  13
  14Le composant View de Backbone est peut-être celui qui génère le plus de polémiques. Est-ce vraiment une vue ? Ne serait-ce pas plutôt un contrôleur ? Il se trouve que dans une version plus ancienne de Backbone, le composant Controller existait, aujourd’hui il est le devenu le composant Router que nous verrons par la suite … Cependant, un routeur est-il réellement un contrôleur ?... Mais, rappelez-vous que l’on est dans un contexte client (navigateur) et que le concept MVC « classique » n’est pas forcément « portable » en l’état. L’essentiel est que cela fonctionne, et si les contrôleurs vous manquent à ce point, nous verrons comment en créer quelques chapitres plus loin.
  15
  16##Préparons le terrain
  17
  18Pour repartir sur de bonnes bases, nous allons supprimer la base de données avec laquelle nous avons déjà bien joué. Donc supprimez le fichier `blog.db` de la racine de votre application.
  19Ensuite, modifiez le code javascript de la page `index.html` dans le répertoire `/public`, donc dans la partie `<script></script>`, pour instancier une collection : (on ajoute : `window.blogPosts = new Posts();`)
  20
  21*Instancier une collection :*
  22
  23```javascript
  24$(function() {
  25
  26  window.Post = Backbone.Model.extend({
  27  urlRoot: "/blogposts"
  28
  29  });
  30
  31  window.Posts = Backbone.Collection.extend({
  32  model: Post,
  33  all: function() {
  34    this.url = "/blogposts";
  35    return this;
  36  },
  37  query: function(query) {
  38    this.url = "/blogposts/query/" + query;
  39    return this;
  40  }
  41  });
  42
  43  window.blogPosts = new Posts();
  44});
  45```
  46
  47Sauvegardez, puis relancez votre application (`node app.js ou nodemon app.js`), dans le navigateur accédez à la page principale ([http://localhost:3000/](http://localhost:3000/)), pour enfin ouvrir la console de votre navigateur. Nous allons créer des modèles, que nous ajouterons à la collection blogposts.
  48
  49###Création et sauvegarde des modèles
  50
  51Commencez par saisir ceci dans la console du navigateur :
  52
  53*Ajouter des modèles à la collection :*
  54
  55```javascript
  56  var messages = [
  57  "Maecenas sed diam eget risus varius blandit sit amet non magna.",
  58  "Integer posuere erat a ante venenatis dapibus posuere velit aliquet.",
  59  "Donec id elit non mi porta gravida at eget metus.",
  60  "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
  61  "Cras mattis consectetur purus sit amet fermentum.",
  62  "Nulla vitae elit libero, a pharetra augue."];
  63
  64  blogPosts.add([
  65  new Post({
  66  title: "Premier Message",
  67  message: messages[0],
  68  date: new Date(2012, 10, 23, 7, 4, 0, 0),
  69  author: "bob"
  70  }),
  71  new Post({
  72  title: "Backbone ???",
  73  message: messages[1],
  74  date: new Date(2012, 10, 23, 7, 5, 0, 0),
  75  author: "bob"
  76  }),
  77  new Post({
  78  title: "Les Modèles",
  79  message: messages[2],
  80  date: new Date(2012, 10, 23, 7, 6, 0, 0),
  81  author: "sam"
  82  }),
  83  new Post({
  84  title: "Les Vues",
  85  message: messages[3],
  86  date: new Date(2012, 10, 23, 7, 7, 0, 0),
  87  author: "sam"
  88  }),
  89  new Post({
  90  title: "Les Routes",
  91  message: messages[4],
  92  date: new Date(2012, 10, 23, 7, 8, 0, 0),
  93  author: "bob"
  94  }),
  95  new Post({
  96  title: "Mais où sont les contrôleurs ?",
  97  message: messages[5],
  98  date: new Date(2012, 10, 23, 7, 9, 0, 0),
  99  author: "bob"
 100  })
 101
 102  ])
 103```
 104
 105>>**Remarque** : en javascript, pour les dates, le chiffre 10 correspond à Novembre (faire +1)
 106
 107Nous avons donc maintenant 5 Posts dans notre collection. Pour ne pas avoir à tout re-saisir à chaque fois, sauvegardez vos posts (toujours dans la console du navigateur) :
 108
 109*Sauvegarder les modèles en base :*
 110
 111```javascript
 112blogPosts.each(function(post) {
 113  post.save({}, {
 114  success: function(post) {
 115    console.log(post.get("title"), " sauvegardé");
 116  },
 117  error: function() {
 118    console.log("Oupss");
 119  }
 120  });
 121})
 122```
 123
 124Vous devriez au final obtenir ceci :
 125
 126![BB](RSRC/07_01_VIEWS.png)\
 127
 128
 129Pour vérifier que la sauvegarde a bien fonctionné, raffraichissez votre page et lancez ce code dans la console du navigateur :
 130
 131*Charger la collection avec les modèles sauvegardés en base :*
 132
 133```javascript
 134blogPosts.all()
 135  .fetch({
 136  success: function(result) {
 137  console.log(result);
 138  }
 139})
 140```
 141
 142Si tout va bien (il n’y a pas de raison), vous devriez obtenir ceci :
 143
 144![BB](RSRC/07_02_VIEWS.png)\
 145
 146
 147Ainsi, quoiqu’il se passe, vous disposez de tous vos messages et ne serez plus obligés de les ressaisir pour la suite des exercices. Nous pouvons donc entrer dans le vif du sujet.
 148
 149
 150##1ère vue
 151
 152Un objet Vue dans Backbone (`Backbone.View`) et généralement composé (au minimum) par convention de :
 153
 154- une propriété `el` : c'est l'élément du DOM (la partie de votre page html à laquelle on rattache l'objet `View`)
 155- une méthode `initialize` (déclenchée à l’instanciation de la vue)
 156- une méthode `render` (chargée d’afficher les données liées à la vue)
 157
 158
 159>>**Remarque** : Libre à vous de vous faire vos propres bonnes pratiques concernant les responsabilités de l’objet View afin de rendre votre code lisible et maintenable … Vous trouverez toujours quelqu’un pour les discuter mais c’est comme cela que l’on apprend et s’améliore … Et vous pouvez aussi avoir raison :-).
 160
 161Dans notre page `index.html` nous allons ajouter un tag `<div id="posts_list"></div>` comme ceci :
 162
 163```html
 164<div class="container">
 165  <div class="hero-unit">
 166  <h1>Backbone rocks !!!</h1>
 167  </div>
 168  <div id="posts_list"></div>
 169</div>
 170```
 171
 172Et modifier le code javascript de la manière suivante : entre la définition de la collection et son instanciation, ajoutez le code de notre première vue :
 173
 174*1ère vue pour afficher le contenu de la collection :*
 175
 176```javascript
 177window.PostsListView = Backbone.View.extend({
 178  el: $("#posts_list"),
 179  initialize: function(data) {
 180  this.collection = data;
 181  },
 182  render: function() {
 183  var html = "";
 184  $(this.el).html(""); //on vide le div
 185  this.collection.each(function(model) {
 186
 187    html += [
 188    '<h1>' + model.get("title") + '</h1><hr>',
 189    '<b>par : ' + model.get("author") + '</b> le : ' + model.get("date") + '<br>',
 190    '<p>' + model.get("message") + '</p>'].join("");
 191
 192  });
 193
 194  $(this.el).append(html);
 195
 196  }
 197});
 198```
 199
 200###Explications & utilisation
 201
 202Notre vue `PostsListView` est reliée au tag `<div id="posts_list"></div>` par la propriété `el` qui n’est ni plus ni moins un objet **jQuery**. La méthode `initialize` (qui sera appelée à l’instanciation de la vue), prend en paramètre les données que nous souhaitons afficher, et les affecte à la propriété `collection` de la vue. La méthode `render`, vide le contenu du tag `<div id="posts_list"></div>`, parcourt la collection de données pour construire le code html, et enfin affiche celui-ci par la commande `$(this.el).append(html)`. Mais utilisons directement notre code, ce sera plus « parlant ».
 203
 204Sauvegardez, rafraichissez la page et en mode console, passez les étapes qui suivent :
 205
 206*1] Chargez les données de la collection :*
 207
 208```javascript
 209blogPosts.all()
 210  .fetch({
 211  success: function(result) {
 212  console.log(result);
 213  }
 214})
 215```
 216
 217*2] Instanciez la vue en lui passant la collection en paramètre :*
 218
 219```javascript
 220postsListView = new PostsListView(blogPosts)
 221```
 222
 223*3] Appelez la méthode render de la vue :*
 224
 225```javascript
 226postsListView.render()
 227```
 228
 229Et vous obtenez la liste de vos messages :
 230
 231![BB](RSRC/07_03_VIEWS.png)\
 232
 233
 234Souvenez vous, dans les chapitres précédents nous avions « donné » aux collections la possibilité de faire des requêtes sur les données avant de lancer un `fetch`. Essayez donc ceci dans la console de votre navigateur :
 235
 236*Je ne veux que les posts de l’auteur "Sam" :*
 237
 238```javascript
 239blogPosts.query('{"author" : "sam"}')
 240  .fetch({
 241  success: function(result) {
 242  console.log(result);
 243  }
 244})
 245```
 246
 247Puis faite à nouveau un :
 248
 249```javascript
 250postsListView.render()
 251```
 252
 253Et là, l’affichage s’actualise automatiquement :
 254
 255![BB](RSRC/07_04_VIEWS.png)\
 256
 257
 258##Maintenant, un peu de magie ...
 259
 260###S'abonner aux événements
 261
 262Modifions une nouvelle fois notre vue en ajoutans le code suivant à la méthode `initialize` :
 263
 264```javascript
 265_.bindAll(this, 'render');
 266this.collection.bind('reset', this.render);
 267```
 268
 269Nous venons d’expliquer que tous les évènement déclarés déclencheront la méthode `render` de la vue. Et ensuite nous avons expliqué que la méthode `reset` de la collection déclenchera la méthode `render` de la vue.
 270
 271>>**Remarque** : Une collection Backbone déclenche un `reset` lors de l’appel d’un `fetch`. La méthode `reset` vide la collection.
 272
 273  //TODO: faire un chapitre à part sur `_.bindAll`
 274
 275Le code de notre vue doit donc ressembler à ceci :
 276
 277*PostListView :*
 278
 279```javascript
 280window.PostsListView = Backbone.View.extend({
 281  el: $("#posts_list"),
 282  initialize: function(data) {
 283  this.collection = data;
 284
 285  _.bindAll(this, 'render');
 286  this.collection.bind('reset', this.render);
 287
 288  },
 289  render: function() {
 290  var html = "";
 291  $(this.el).html(""); //on vide le div
 292  this.collection.each(function(model) {
 293
 294    html += [
 295    '<h1>' + model.get("title") + '</h1><hr>',
 296    '<b>par : ' + model.get("author") + '</b> le : ' + model.get("date") + '<br>',
 297    '<p>' + model.get("message") + '</p>'].join("");
 298
 299  });
 300
 301  $(this.el).append(html);
 302
 303  }
 304});
 305```
 306
 307Sauvegardez ensuite la page, puis retournez dans le navigateur, rafraichissez la page et retournez dans la console du navigateur pour instancier une nouvelle vue :
 308
 309```javascript
 310postsListView = new PostsListView(blogPosts)
 311```
 312
 313Puis essayez ceci :
 314
 315```javascript
 316blogPosts.all()
 317  .fetch({
 318  success: function(result) {
 319  console.log(result);
 320  }
 321})
 322```
 323
 324
 325
 326et cela :
 327
 328```javascript
 329blogPosts.query('{"author" : "sam"}')
 330  .fetch({
 331  success: function(result) {
 332  console.log(result);
 333  }
 334})
 335```
 336
 337Vous remarquez que votre vue se met à jour automatiquement à chaque changement, sans avoir à rappeler la méthode render de la vue.
 338Mais il est possible de faire ceci aussi avec les changements sur les modèles.
 339
 340###S’abonner à d’autres évènements (modèles)
 341
 342Toujours dans la méthode `initialize` de la vue, ajoutez le code suivant :
 343
 344```javascript
 345this.collection.bind('change', this.render);
 346this.collection.bind('add', this.render);
 347this.collection.bind('remove', this.render);
 348```
 349
 350Maintenant, si vous changez la valeur d'un attribut d'un modèle, que vous ajoutez ou supprimez un modèle de la collection, la vue sera réactualisée à chaque fois.
 351
 352Sauvegardez la page, puis retournez dans le navigateur, rafraichissez la page et retournez dans la console du navigateur pour instancier une nouvelle vue et charger les données de la collection :
 353
 354```javascript
 355postsListView = new PostsListView(blogPosts)
 356blogPosts.all()
 357  .fetch({
 358  success: function(result) {
 359  console.log(result);
 360  }
 361})
 362```
 363
 364Puis changez le titre du 1er post :
 365
 366```javascript
 367blogPosts.at(0).set("title","BACKBONE ???!!!")
 368```
 369
 370ou ajoutez un post :
 371
 372```javascript
 373blogPosts.add(new Post({title:"HELLO",message : "salut", author : "k33g"}))
 374```
 375
 376ou encore supprimez un post :
 377
 378```javascript
 379blogPosts.remove(blogPosts.at(0));
 380```
 381
 382Là encore, votre page s’actualise instantanément.
 383
 384###Amélioration & Finalisation du code
 385
 386Avant de passer à l’utilisation des templates dans les vue, nous allons apporter quelques modifications et améliorer un peu notre code pour nous préparer à la suite.
 387
 388Dans ses dernières versions, Backbone a hérité d’un raccourcis concernant la propriété `el` de la vue qui consiste à remplacer (avec pour objectif l’optimisation d’exécution de code) le sélecteur `$(this.el)` par `this.$el`.
 389
 390De plus, vous devez savoir qu’il n’est pas obligatoire de déclarer l’affectation de la collection dans la méthode initialize de la vue, mais que l’on peut faire ceci directement en paramètre du constructeur à l’instanciation de la vue. Comme ceci :
 391
 392```javascript
 393new PostsListView({collection : blogPosts})
 394```
 395
 396On pourrait faire de même avec `el`, et utiliser ceci :
 397
 398```javascript
 399new PostsListView({el : $("#posts_list"), collection : blogPosts})
 400```
 401
 402En fait tout dépend de vos besoins (et de vos habitudes).
 403
 404En ce qui nous concerne, modifions le code de notre vue de la manière suivante :
 405
 406*PostsListView :*
 407
 408```javascript
 409window.PostsListView = Backbone.View.extend({
 410  el: $("#posts_list"),
 411  initialize: function() {
 412
 413  _.bindAll(this, 'render');
 414  this.collection.bind('reset', this.render);
 415  this.collection.bind('change', this.render);
 416  this.collection.bind('add', this.render);
 417  this.collection.bind('remove', this.render);
 418
 419  },
 420  render: function() {
 421  var html = "";
 422  this.$el.html(""); //on vide le div
 423  this.collection.each(function(model) {
 424
 425    html += [
 426    '<h1>' + model.get("title") + '</h1><hr>',
 427    '<b>par : ' + model.get("author") + '</b> le : ' + model.get("date") + '<br>',
 428    '<p>' + model.get("message") + '</p>'].join("");
 429
 430  });
 431
 432  this.$el.append(html);
 433
 434  }
 435});
 436```
 437
 438Puis à la fin du code javascript, ajoutez le code qui instancie la vue, ainsi que le code qui « charge » la collection (on se souvient que le render de la vue sera déclenché automatiquement une fois le `fetch` de la collection terminé.) :
 439
 440```javascript
 441window.postsListView = new PostsListView({
 442  collection: blogPosts
 443})
 444
 445blogPosts.all().fetch({
 446  success: function(result) {
 447  console.log(result);
 448  }
 449});
 450```
 451
 452Le code final du script dans la page devrait ressembler à ceci :
 453
 454*Code final :*
 455
 456```html
 457<!-- === code applicatif === -->
 458<script>
 459$(function() {
 460
 461  window.Post = Backbone.Model.extend({
 462  urlRoot: "/blogposts"
 463
 464  });
 465
 466  window.Posts = Backbone.Collection.extend({
 467  model: Post,
 468  all: function() {
 469    this.url = "/blogposts";
 470    return this;
 471  },
 472  query: function(query) {
 473    this.url = "/blogposts/query/" + query;
 474    return this;
 475  }
 476
 477  });
 478
 479  window.PostsListView = Backbone.View.extend({
 480  el: $("#posts_list"),
 481  initialize: function() {
 482
 483    _.bindAll(this, 'render');
 484    this.collection.bind('reset', this.render);
 485    this.collection.bind('change', this.render);
 486    this.collection.bind('add', this.render);
 487    this.collection.bind('remove', this.render);
 488
 489  },
 490  render: function() {
 491    var html = "";
 492    this.$el.html(""); //on vide le div
 493    this.collection.each(function(model) {
 494
 495    html += [
 496      '<h1>' + model.get("title") + '</h1><hr>',
 497      '<b>par : ' + model.get("author") + '</b> le : ' + model.get("date") + '<br>',
 498      '<p>' + model.get("message") + '</p>'].join("");
 499
 500    });
 501
 502    this.$el.append(html);
 503
 504  }
 505  });
 506
 507  window.blogPosts = new Posts();
 508
 509  window.postsListView = new PostsListView({
 510  collection: blogPosts
 511  })
 512
 513  blogPosts.all().fetch({
 514  success: function(result) {
 515    //ça marche !!!
 516  }
 517  });
 518});
 519</script>
 520```
 521
 522Nous avons fait un peu de magie, passons donc à la sorcellerie ;) ...
 523
 524##Utilisation du templating ... 1ère fois
 525
 526Vous vous souvenez ? Je vous avez parlé d'underscore avec les templates ? Eh bien il est temps de les mettre en œuvre.
 527
 528###Définition de notre 1er template
 529
 530Dans la partie HTML de notre page, juste avant  `<div id="posts_list"></div>`, ajoutez le code ci-dessous (ce sera notre template) :
 531
 532```html
 533<!-- template pour les posts -->
 534<script type="text/template" id="posts_list_template">
 535
 536<% _.each(posts ,function(post){ %>
 537  <h1><%= post.get("title") %></h1><hr>
 538  <b>par : <%= post.get("author") %></b> le : <%= post.get("date") %><br>
 539  <p><%= post.get("message") %></p>
 540<% }); %>
 541
 542</script>
 543```
 544
 545>>**Remarque** : le fait de définir le template à l'intérieur de `<script type="text/template"></script>` fait que le modèle de template ne sera pas affiché dans la page.
 546
 547En fait (grace à underscore), nous venons de définir le template dont la vue backbone va se servir pour afficher les données. Il faudra lui passer pour cela un **tableau** de posts.
 548Modifions donc notre vue de la façon suivante :
 549
 550```javascript
 551window.PostsListView = Backbone.View.extend({
 552  el: $("#posts_list"),
 553  initialize: function() {
 554  this.template = _.template($("#posts_list_template").html());
 555
 556  _.bindAll(this, 'render');
 557  this.collection.bind('reset', this.render);
 558  this.collection.bind('change', this.render);
 559  this.collection.bind('add', this.render);
 560  this.collection.bind('remove', this.render);
 561
 562  },
 563  render: function() {
 564  var renderedContent = this.template({
 565    posts: this.collection.models
 566  });
 567
 568  this.$el.html(renderedContent);
 569  }
 570});
 571```
 572
 573Nous avons inséré dans la méthode `initialize` : `this.template = _.template($("#posts_list_template").html());`, où nous expliquons que la définition du template se trouve dans la zone ayant `posts_list_template` pour `id`. Nous avons aussi simplifié grandement le contenu de la méthode render, où nous faisons générer le contenu HTML à partir du template et des données.
 574
 575>>**Remarque** : notez bien que `this.collection.models` est un tableau de modèles.
 576
 577Vous pouvez sauvegarder et rafraichir, les résultats sont identiques aux précédent, mais il est beaucoup plus facile de créer et modifier vos templates html.
 578
 579![BB](RSRC/07_05_VIEWS.png)\
 580
 581
 582##Sous-vue(s)
 583
 584Il est possible de faire des sous vues pour gérer différentes parties de votre page web. En fait il s’agit de vues encapsulées dans une autre vue, ce qui peut être pratique en termes d’organisation, mais aussi dans les cas où les comportements de chacunes des vues dépendent les uns des autres.
 585
 586Nous allons donc profiter des possibilités de Twitter Bootstrap pour revoir un peu la mise en page de notre « site » et du même coup mettre en œuvre le concept de sous-vue.
 587
 588###Réorganisation du code html
 589
 590Modifions notre code html de la manière suivante :
 591
 592```html
 593<div class="navbar navbar-fixed-top">
 594  <div class="navbar-inner">
 595    <div class="container">
 596      <a class="brand">Mon Blog</a>
 597    </div>
 598  </div>
 599</div>
 600
 601<div class="container-fluid">
 602
 603  <div class="row-fluid">
 604
 605    <div class="span3">
 606      <script type="text/template" id="blog_sidebar_template">
 607        <h2>Les 3 derniers :</h2>
 608        <ul>
 609        <% _.each(posts ,function(post){ %>
 610          <li><%= post.get("title") %></li>
 611        <% }); %>
 612        </ul>
 613      </script>
 614      <div class="sidebar" id="blog_sidebar">
 615        <!-- Last 3 posts -->
 616      </div>
 617    </div>
 618
 619    <div class="span9">
 620      <div class="hero-unit">
 621        <h1>Backbone rocks !!!</h1>
 622      </div>
 623
 624      <!-- template pour les posts -->
 625      <script type="text/template" id="posts_list_template">
 626
 627        <% _.each(posts ,function(post){ %>
 628          <h1><%= post.get("title") %></h1><hr>
 629          <b>par : <%= post.get("author") %></b> le : <%= post.get("date") %><br>
 630          <p><%= post.get("message") %></p>
 631        <% }); %>
 632
 633      </script>
 634      <div class="row-fluid" id="posts_list"></div>
 635    </div>
 636  </div>
 637</div>
 638```
 639
 640>>**Explications** : nous avons donc 2 templates, un pour afficher les 3 derniers posts (`id="blog_sidebar_template"`), un pour afficher tous les posts (`id="posts_list_template"`).
 641
 642###Création & Modification des vues
 643
 644Nous allons créer une vue principale (`MainView`) qui se chargera de "piloter" 2 sous-vues (`SidebarView` et `PostsListView`) :
 645
 646*1] Simplifions PostsListView :*
 647
 648```javascript
 649window.PostsListView = Backbone.View.extend({
 650  el: $("#posts_list"),
 651  initialize: function() {
 652    this.template = _.template($("#posts_list_template").html());
 653  },
 654  render: function() {
 655    var renderedContent = this.template({
 656      posts: this.collection.models
 657    });
 658    this.$el.html(renderedContent);
 659  }
 660});
 661```
 662
 663*2] Création de SidebarView :*
 664
 665```javascript
 666window.SidebarView = Backbone.View.extend({
 667  el: $("#blog_sidebar"),
 668  initialize: function() {
 669    this.template = _.template($("#blog_sidebar_template").html());
 670  },
 671  render: function() {
 672    var renderedContent = this.template({
 673      posts: this.collection.models
 674    });
 675    this.$el.html(renderedContent);
 676  }
 677});
 678```
 679
 680*3] Création de la vue principale :*
 681
 682```javascript
 683window.MainView = Backbone.View.extend({
 684  initialize: function() {
 685
 686    _.bindAll(this, 'render');
 687    this.collection.bind('reset', this.render);
 688    this.collection.bind('change', this.render);
 689    this.collection.bind('add', this.render);
 690    this.collection.bind('remove', this.render);
 691
 692    this.sidebarView = new SidebarView();
 693    this.postsListView = new PostsListView({
 694      collection: blogPosts
 695    });
 696
 697  },
 698  render: function() {
 699    this.sidebarView.collection = new Posts(this.collection.first(3));
 700    this.sidebarView.render();
 701    this.postsListView.render();
 702  }
 703});
 704```
 705
 706C'est donc maintenant la vue `MainView` qui s'abonne aux changements de la collection et déclenche le rendu des 2 autres vues.
 707
 708Et enfin instancions la collection ainsi que la vue principale (qui se chargera d’instancier les deux sous-vues). Donc à la place de :
 709
 710```javascript
 711window.blogPosts = new Posts();
 712window.postsListView = new PostsListView({
 713  collection: blogPosts
 714})
 715```
 716
 717**Nous aurons ceci :**
 718
 719```javascript
 720window.blogPosts = new Posts();
 721window.mainView = new MainView({
 722  collection: blogPosts
 723});
 724```
 725
 726Vous pouvez sauvegarder votre code et raffraichir votre page :
 727
 728![BB](RSRC/07_06_VIEWS.png)\
 729
 730
 731Et si vous faites ceci en mode console :
 732
 733```javascript
 734blogPosts.at(0).set("title","Bonjour à tous !")
 735```
 736
 737Vous verrez que les modifications sont bien propagées dans les 2 vues simultanément.
 738
 739###Un dernier petit réglage : tri des collections
 740
 741Nous souhaitons avant d’aller plus loin trier la collection de posts pour avoir les message en ordre décroissant. Pour cela nous allons créer ce que l’on appelle un **« comparator »** dans la méthode `initialize` de la vue principale `MainView` :
 742
 743*Trier la collection par ordre décroissant de date :*
 744
 745```javascript
 746this.collection.comparator = function(model) {
 747  return -(new Date(model.get("date")).getTime());
 748}
 749```
 750
 751Donc le code final de MainView sera celui-ci :
 752
 753*Vue principale :*
 754
 755```javascript
 756window.MainView = Backbone.View.extend({
 757  initialize: function() {
 758
 759    this.collection.comparator = function(model) {
 760      return -(new Date(model.get("date")).getTime());
 761    }
 762
 763    _.bindAll(this, 'render');
 764    this.collection.bind('reset', this.render);
 765    this.collection.bind('change', this.render);
 766    this.collection.bind('add', this.render);
 767    this.collection.bind('remove', this.render);
 768
 769    this.sidebarView = new SidebarView();
 770    this.postsListView = new PostsListView({
 771      collection: this.collection
 772    });
 773
 774  },
 775  render: function() {
 776
 777    this.sidebarView.collection = new Posts(this.collection.first(3));
 778    this.sidebarView.render();
 779    this.postsListView.render();
 780  }
 781});
 782```
 783
 784Et le rendu dans le navigateur devrait vous donner ceci :
 785
 786![BB](RSRC/07_07_VIEWS.png)\
 787
 788
 789  //TODO : faire un paragraphe sur le comparator dans le chapitre sur les collections
 790
 791##Utilisation d’autre(s) moteur(s) de template
 792
 793Vous lirez souvent que Backbone est "framwork agnostic", donc vous pouvez par exemple l'utiliser avec **zepto**, plutôt que **jQuery** en ce qui concerne la gestion du DOM et des appels Ajax. Il en est de même avec le moteur de template. Rien ne vous oblige à utiliser celui d’**Underscore**.
 794Un des plus utilisé est Mustache.js ([http://mustache.github.com/](http://mustache.github.com/)). Vous pouvez récupérer le code ici : [https://github.com/janl/mustache.js/](https://github.com/janl/mustache.js/). En fait, plus précisément,  enregistrez le fichier  [https://raw.github.com/janl/mustache.js/master/mustache.js](https://raw.github.com/janl/mustache.js/master/mustache.js) dans votre répertoire `public/libs/vendors`. Puis faites y référence dans votre page `index.html` :
 795
 796```javascript
 797<script src="libs/vendors/mustache.js"></script>
 798```
 799
 800Et nous allons une fois de plus "casser" notre code html.
 801
 802###Redéfinissons donc nos templates
 803
 804*1] Avant pour la partie concernant la vue `SidebarView` nous avions ceci :*
 805
 806```html
 807<script type="text/template" id="blog_sidebar_template">
 808  <h2>Les 3 derniers :</h2>
 809  <ul>
 810  <% _.each(posts ,function(post){ %>
 811      <li><%= post.get("title") %></li>
 812  <% }); %>
 813  </ul>
 814</script>
 815```
 816
 817*2] Que vous allez remplacer par ceci :*
 818
 819```html
 820<script type="text/template" id="blog_sidebar_template">
 821  <h2>Les 3 derniers :</h2>
 822
 823  <ul>{{#posts}}
 824    <li>{{title}}</li>
 825  {{/posts}}</ul>
 826
 827</script>
 828```
 829
 830*3] En ce qui concerne le template lié à la vue PostsListView, remplacez :*
 831
 832```html
 833<script type="text/template" id="posts_list_template">
 834
 835  <% _.each(posts ,function(post){ %>
 836    <h1><%= post.get("title") %></h1><hr>
 837    <b>par : <%= post.get("author") %></b> le : <%= post.get("date") %><br>
 838    <p><%= post.get("message") %></p>
 839  <% }); %>
 840
 841</script>
 842```
 843
 844*4] Par :*
 845
 846```html
 847<script type="text/template" id="posts_list_template">
 848
 849  {{#posts}}
 850    <h1>{{title}}</h1>
 851    <b>par : {{author}}</b> le : {{date}}<br>
 852    <p>{{message}}</p>
 853  {{/posts}}
 854
 855</script>
 856```
 857
 858Nous obtenons donc des templates html plus lisibles, utilisable moyennant une petite modification de nos vues :
 859
 860*5] Avant (avec le moteur de template d’underscore) :*
 861
 862```javascript
 863window.PostsListView = Backbone.View.extend({
 864  el: $("#posts_list"),
 865  initialize: function() {
 866    this.template = _.template($("#posts_list_template").html());
 867  },
 868  render: function() {
 869    var renderedContent = this.template({
 870      posts: this.collection.models
 871    });
 872    this.$el.html(renderedContent);
 873  }
 874});
 875
 876window.SidebarView = Backbone.View.extend({
 877  el: $("#blog_sidebar"),
 878  initialize: function() {
 879    this.template = _.template($("#blog_sidebar_template").html());
 880  },
 881  render: function() {
 882    var renderedContent = this.template({
 883      posts: this.collection.models
 884    });
 885    this.$el.html(renderedContent);
 886  }
 887});
 888```
 889
 890*6] Après (en utilisant Mustache) nous aurons ceci :*
 891
 892```javascript
 893window.PostsListView = Backbone.View.extend({
 894  el: $("#posts_list"),
 895  initialize: function() {
 896    this.template = $("#posts_list_template").html();
 897  },
 898  render: function() {
 899    var renderedContent = Mustache.to_html(
 900    this.template, {
 901      posts: this.collection.toJSON()
 902    });
 903
 904    this.$el.html(renderedContent);
 905  }
 906});
 907
 908window.SidebarView = Backbone.View.extend({
 909  el: $("#blog_sidebar"),
 910  initialize: function() {
 911    this.template = $("#blog_sidebar_template").html();
 912  },
 913  render: function() {
 914    var renderedContent = Mustache.to_html(
 915    this.template, {
 916      posts: this.collection.toJSON()
 917    });
 918
 919    this.$el.html(renderedContent);
 920  }
 921});
 922```
 923
 924Vous noterez l'utilisation de `this.collection.toJSON()` plutôt que `this.collection.models`. En effet Mustache a besoin d’objets au format JSON, et (cela tombe bien), les collections Backbone dispose d’une méthode d’exportation/mise en forme au format JSON.
 925
 926Sauvegardez, lancez, il n’y a pas de changement, l'affichages est identique (heureusement), vous avez juste utilisé une autre façon de travailler.
 927
 928##Gestion des événements dans les vues
 929
 930Les objets de type Backbone.View peuvent aussi gérer les évènements (mouseover, click, etc. …). Nous allons donc profiter de ce paragraphe pour mettre en œuvre un système d’authentification dans notre application, qui utilisera donc cette possibilité. Il est temps de retourner travailler côté serveur quelques instants.
 931
 932Si vous voulez en savoir plus sur les événements dans les vues Backbone, je vous engage fortement à lire la documentation : [http://backbonejs.org/#View-delegateEvents](http://backbonejs.org/#View-delegateEvents).
 933
 934Donc ...
 935
 936##Authentification (côté serveur) : les utilisateurs
 937
 938>>*Ce paragraphe ne parle pas des vues, mais est nécessaire pour la mise en place des paragraphes suivants.*
 939
 940Nous aurons besoin d’une liste des utilisateurs connectés (je vous le rappelle, nous sommes côté serveur, donc dans le fichier `app.js`) que nous représenterons sous la forme d’un tableau de variables (ou d’objets) :
 941
 942```javascript
 943var connectedUsers = [];
 944```
 945
 946Nous aurons besoin d’ajouter des utilisateurs dans notre base de données, donc nous allons nous créer de quoi rajouter au moins une fois quelques utilisateurs pour notre application :
 947
 948*Ajouter un utilisateur en base :*
 949
 950```javascript
 951function addUser(user) {
 952  users.save(null, user, function(err, key) {
 953    if (err) {
 954      console.log("Erreur : ", err);
 955    } else {
 956      user.id = key;
 957      console.log(user);
 958    }
 959  });
 960}
 961```
 962
 963Nous appellerons n fois cette fonctions pour ajouter des utilisateurs :
 964
 965*Ajouter des utilisateurs :*
 966
 967```javascript
 968function addUsers() {
 969  addUser({
 970    email     : "bob@morane.com",
 971    password  : "backbone",
 972    isAdmin   : true,
 973    firstName : "Bob",
 974    lastName  : "Morane"
 975  });
 976  addUser({
 977    email     : "sam@lepirate.com",
 978    password  : "underscore",
 979    isAdmin   : false,
 980    firstName : "Sam",
 981    lastName  : "Le Pirate"
 982  });
 983
 984  //etc. ...
 985}
 986```
 987
 988Et pour déclencher l’ajout des utilisateurs, nous créeons une « route » `addusers` :
 989
 990```javascript
 991app.get('/addusers', function(req, res) {
 992  addUsers();
 993  res.json({
 994    MESSAGE: "Users added."
 995  });
 996});
 997```
 998
 999Qu’il suffira d’appeler comme ceci dans le navigateur : [http://localhost:3000/addusers/](http://localhost:3000/addusers/)
1000
1001>>**Remarque** : notez bien que mon système d’authentification est très « léger ». En production, il vous faudrait quelque chose de plus abouti, mais ce n’est pas le propos de cet ouvrage. Nous avions besoin de quelque chose de simple.
1002
1003###S’authentifier – Se déconnecter
1004
1005Nous aurons besoin de nous authentifier. Il nous faut donc d’abord une fonction « utilitaire » qui nous permette de vérifier si l’email de l’utilisateur n’est pas déjà pris (utilisateur déjà connecté sous une autre session) :
1006
1007*Vérifier si un utilisateur est déjà connecté :*
1008
1009```javascript
1010function findUserByMail(email) {
1011  /*
1012    Permet de vérifier si un utilisateur est déjà loggé
1013  */
1014  return connectedUsers.filter(function(user) {
1015    return user.email == email;
1016  })[0];
1017}
1018```
1019
1020Nous allons donc créer une route `authenticate` avec le code suivant :
1021
1022*Code pour s’authentifier :*
1023
1024```javascript
1025app.post('/authenticate', function(req, res) {
1026  console.log("POST authenticate ", req.body);
1027  //Je récupère les information de connexion de l'utilisateur
1028  var user = req.body;
1029
1030  //est ce que l'email est déjà utilisé ?
1031  if (findUserByMail(user.email)) {
1032    res.json({
1033      infos: "Utilisateur déjà connecté"
1034    })
1035  } else { //si l'email n'est pas utilisé
1036    //Je cherche l'utilisateur dans la base de données
1037    users.find({
1038      email: user.email,
1039      password: user.password
1040    },
1041
1042    function(err, results) {
1043      if (err) {
1044        res.json({
1045          error: "Oups, Houson, on a un problème"
1046        });
1047      } else {
1048        //J'ai trouvé l'utilisateur
1049        var key = Object.keys(results)[0],
1050          authenticatedUser = results[key];
1051
1052        //Je rajoute l'id de session à l'objet utilisateur
1053
1054        authenticatedUser.key = key;
1055        authenticatedUser.sessionID = req.sessionID;
1056
1057        //Ajouter l'utilisateur authentifié à la liste des utilisateurs connectés
1058        connectedUsers.push(authenticatedUser);
1059
1060        //Je renvoie au navigateur les informations de l'utilisateur
1061        // ... sans le mot de passe bien sûr
1062        res.json({
1063          email: authenticatedUser.email,
1064          firstName: authenticatedUser.firstName,
1065          lastName: authenticatedUser.lastName,
1066          isAdmin: authenticatedUser.isAdmin
1067        });
1068      }
1069    });
1070  }
1071
1072});
1073```
1074
1075>>**Remarque** : La « bienséance » (d’un point de vue architecture) voudrait que ne mette pas tout ce code au niveau de la route mais dans la méthode d’un contrôleur qui serait appelée par la route. Une fois de plus je vais au plus court, mais gardez à l’esprit : toujours un code lisible et maintenable.
1076
1077Il faudra aussi pouvoir se déconnecter. Nous ajoutons donc une route `logoff` qui nous permettra de déconnecter l’utilisateur.
1078
1079Nous avons tout d’abord besoin d’une fonction nous permettant de retrouver un utilisateur par son id de session parmi les utilisateurs connectés :
1080
1081```javascript
1082function findUserBySession(sessionID) {
1083  /*
1084    Permet de retrouver un utilisateur par son id de session
1085  */
1086  return connectedUsers.filter(function(user) {
1087    return user.sessionID == sessionID;
1088  })[0];
1089
1090}
1091```
1092
1093Que nous allons utiliser ensuite dans notre route `logoff` :
1094
1095*Se déconnecter :*
1096
1097```javascript
1098app.get('/logoff', function(req, res) {
1099
1100  //Je recherche l'utilisateur courant parmi les utilisateurs connectés
1101  var alreadyAuthenticatedUser = findUserBySession(req.sessionID);
1102
1103  if (alreadyAuthenticatedUser) {
1104    //Je l'ai trouvé, je le supprime de la liste des utilisateurs connectés
1105    var posInArray = connectedUsers.indexOf(alreadyAuthenticatedUser);
1106    connectedUsers.splice(posInArray, 1);
1107    res.json({
1108      state: "disconnected"
1109    });
1110  } else {
1111    res.json({});
1112  }
1113
1114});
1115```
1116
1117Nous aurons aussi besoin d’un moyen pour savoir (côté client) si un utilisateur est déjà connecté. Donc nous allons créer une route `alreadyauthenticated` que la page web pourra « appeler » pour vérification (par exemple au rechargement de la page) :
1118
1119*Est-ce que je suis déjà authentifié ?*
1120
1121```javascript
1122app.get('/alreadyauthenticated', function(req, res) {
1123
1124  var alreadyAuthenticatedUser = findUserBySession(req.sessionID);
1125
1126  //Si je suis déjà authentifié, renvoyer les informations utilisateur
1127  if (alreadyAuthenticatedUser) {
1128    res.json({
1129      email: alreadyAuthenticatedUser.email,
1130      firstName: alreadyAuthenticatedUser.firstName,
1131      lastName: alreadyAuthenticatedUser.lastName,
1132      isAdmin: alreadyAuthenticatedUser.isAdmin
1133    });
1134  } else {
1135    res.json({});
1136  }
1137
1138});
1139```
1140
1141Nous somme maintenant prêts à utiliser tout cela côté client. Le code définitif de `app.js` devrait ressembler à ceci :
1142
1143```javascript
1144/*--------------------------------------------
1145  Déclaration des librairies
1146--------------------------------------------*/
1147var express = require('express'),
1148  nStore = require('nstore'),
1149  app = module.exports = express.createServer();
1150
1151nStore = nStore.extend(require('nstore/query')());
1152
1153/*--------------------------------------------
1154  Paramétrages de fonctionnement d'Express
1155--------------------------------------------*/
1156app.use(express.bodyParser());
1157app.use(express.methodOverride());
1158app.use(express.static(__dirname + '/public'));
1159app.use(express.cookieParser('ilovebackbone'));
1160app.use(express.session({
1161  secret: "ilovebackbone"
1162}));
1163
1164/*--------------------------------------------
1165  Définition des "bases" posts & users
1166--------------------------------------------*/
1167var posts, users;
1168
1169posts = nStore.new("blog.db", function() {
1170  users = nStore.new("users.db", function() {
1171    Routes();
1172    app.listen(3000);
1173    console.log('Express app started on port 3000');
1174
1175  });
1176});
1177
1178
1179/*======= Authentification =======*/
1180
1181var connectedUsers = [];
1182
1183function addUser(user) {
1184  users.save(null, user, function(err, key) {
1185    if (err) {
1186      console.log("Erreur : ", err);
1187    } else {
1188      user.id = key;
1189      console.log(user);
1190    }
1191  });
1192}
1193
1194function addUsers() {
1195  addUser({
1196    email: "bob@morane.com",
1197    password: "backbone",
1198    isAdmin: true,
1199    firstName: "Bob",
1200    lastName: "Morane"
1201  });
1202  addUser({
1203    email: "sam@lepirate.com",
1204    password: "underscore",
1205    isAdmin: false,
1206    firstName: "Sam",
1207    lastName: "Le Pirate"
1208  });
1209
1210  //etc. ...
1211}
1212
1213function findUserBySession(sessionID) {
1214  /*
1215    Permet de retrouver un utilisateur par son id de session
1216  */
1217  return connectedUsers.filter(function(user) {
1218    return user.sessionID == sessionID;
1219  })[0];
1220
1221}
1222
1223function findUserByMail(email) {
1224  /*
1225    Permet de vérifier si un utilisateur est déjà loggé
1226  */
1227  return connectedUsers.filter(function(user) {
1228    return user.email == email;
1229  })[0];
1230}
1231
1232
1233function Routes() {
1234  /*======= Routes pour authentification =======*/
1235
1236  app.get('/addusers', function(req, res) {
1237    addUsers();
1238    res.json({
1239      MESSAGE: "Users added."
1240    });
1241  });
1242
1243  app.get('/alreadyauthenticated', function(req, res) {
1244
1245    var alreadyAuthenticatedUser = findUserBySession(req.sessionID);
1246
1247    /*  Si je suis déjà authentifié,
1248      renvoyer les informations utilisateur
1249      sans le mot de passe bien sûr
1250    */
1251    if (alreadyAuthenticatedUser) {
1252      res.json({
1253        email: alreadyAuthenticatedUser.email,
1254        firstName: alreadyAuthenticatedUser.firstName,
1255        lastName: alreadyAuthenticatedUser.lastName,
1256        isAdmin: alreadyAuthenticatedUser.isAdmin
1257      });
1258    } else {
1259      res.json({});
1260    }
1261
1262  });
1263
1264  app.post('/authenticate', function(req, res) {
1265    console.log("POST authenticate ", req.body);
1266    //Je récupère les information de connexion de l'utilisateur
1267    var user = req.body;
1268
1269    //est ce que l'email est déjà utilisé ?
1270    if (findUserByMail(user.email)) {
1271      res.json({
1272        infos: "Utilisateur déjà connecté"
1273      })
1274    } else { //si l'email n'est pas utilisé
1275      //Je cherche l'utilisateur dans la base de données
1276      users.find({
1277        email: user.email,
1278        password: user.password
1279      }, function(err, results) {
1280        if (err) {
1281          res.json({
1282            error: "Oups, Houson, on a un problème"
1283          });
1284        } else {
1285          //J'ai trouvé l'utilisateur
1286          var key = Object.keys(results)[0],
1287            authenticatedUser = results[key];
1288
1289          //Je rajoute l'id de session à l'objet utilisateur
1290
1291          authenticatedUser.key = key;
1292          authenticatedUser.sessionID = req.sessionID;
1293
1294          //J'ajoute l'utilisateur authentifié à la liste des utilisateurs connectés
1295          connectedUsers.push(authenticatedUser);
1296
1297          //Je renvoie au navigateur les informations de l'utilisateur
1298          // ... sans le mot de passe bien sûr
1299          res.json({
1300            email: authenticatedUser.email,
1301            firstName: authenticatedUser.firstName,
1302            lastName: authenticatedUser.lastName,
1303            isAdmin: authenticatedUser.isAdmin
1304          });
1305        }
1306      });
1307    }
1308
1309  });
1310
1311  app.get('/logoff', function(req, res) {
1312
1313    //Je recherche l'utilisateur courant parmi les utilisateurs connectés
1314    var alreadyAuthenticatedUser = findUserBySession(req.sessionID);
1315
1316    if (alreadyAuthenticatedUser) {
1317      //Je l'ai trouvé, je le supprime de la liste des utilisateurs connectés
1318      var posInArray = connectedUsers.indexOf(alreadyAuthenticatedUser);
1319      connectedUsers.splice(posInArray, 1);
1320      res.json({
1321        state: "disconnected"
1322      });
1323    } else {
1324      res.json({});
1325    }
1326
1327  });
1328
1329  /*======= Fin des routes "authentification" =======*/
1330
1331  /*
1332    Obtenir la liste de tous les posts lorsque
1333    l'on appelle http://localhost:3000/blogposts
1334    en mode GET
1335  */
1336  app.get('/blogposts', function(req, res) {
1337    console.log("GET (ALL) : /blogposts");
1338    posts.all(function(err, results) {
1339
1340      if (err) {
1341        console.log("Erreur : ", err);
1342        res.json(err);
1343      } else {
1344        var posts = [];
1345        for (var key in results) {
1346          var post = results[key];
1347          post.id = key;
1348          posts.push(post);
1349        }
1350        res.json(posts);
1351      }
1352    });
1353
1354  });
1355
1356  /*
1357    Obtenir la liste de tous les posts correspondant à un critère
1358    lorsque l'on appelle http://localhost:3000/blogposts/ en
1359    mode GET avec une requête en paramètre
1360    ex : query : { "title" : "Mon 1er post"} }
1361  */
1362  app.get('/blogposts/:query', function(req, res) {
1363    console.log("GET (QUERY) : /blogposts/" + req.params.query);
1364
1365    posts.find(JSON.parse(req.params.query), function(err, results) {
1366      if (err) {
1367        console.log("Erreur : ", err);
1368        res.json(err);
1369      } else {
1370        var posts = [];
1371        for (var key in results) {
1372          var post = results[key];
1373          post.id = key;
1374          posts.push(post);
1375        }
1376        res.json(posts);
1377      }
1378    });
1379
1380  });
1381
1382  /*
1383    Retrouver un post par sa clé unique lorsque
1384    l'on appelle http://localhost:3000/blogpost/identifiant_du_post
1385    en mode GET
1386  */
1387
1388  app.get('/blogpost/:id', function(req, res) {
1389    console.log("GET : /blogpost/" + req.params.id);
1390    posts.get(req.params.id, function(err, post, key) {
1391      if (err) {
1392        console.log("Erreur : ", err);
1393        res.json(err);
1394
1395      } else {
1396        post.id = key;
1397        res.json(post);
1398      }
1399    });
1400  });
1401
1402  /*
1403    Créer un nouveau post lorsque
1404    l'on appelle http://localhost:3000/blogpost
1405    avec en paramètre le post au format JSON
1406    en mode POST
1407  */
1408  app.post('/blogpost', function(req, res) {
1409    console.log("POST CREATE ", req.body);
1410
1411    var d = new Date(),
1412      model = req.body;
1413    model.saveDate = (d.valueOf());
1414
1415    posts.save(null, model, function(err, key) {
1416      if (err) {
1417        console.log("Erreur : ", err);
1418        res.json(err);
1419      } else {
1420        model.id = key;
1421        res.json(model);
1422      }
1423    });
1424  });
1425
1426
1427  /*
1428    Mettre à jour un post lorsque
1429    l'on appelle http://localhost:3000/blogpost
1430    avec en paramètre le post au format JSON
1431    en mode PUT
1432  */
1433  app.put('/blogpost/:id', function(req, res) {
1434    console.log("PUT UPDATE", req.body, req.params.id);
1435
1436    var d = new Date(),
1437      model = req.body;
1438    model.saveDate = (d.valueOf());
1439
1440    posts.save(req.params.id, model, function(err, key) {
1441      if (err) {
1442        console.log("Erreur : ", err);
1443        res.json(err);
1444      } else {
1445        res.json(model);
1446      }
1447    });
1448  });
1449
1450  /*
1451    supprimer un post par sa clé unique lorsque
1452    l'on appelle http://localhost:3000/blogpost/identifiant_du_post
1453    en mode DELETE
1454  */
1455  app.delete('/blogpost/:id', function(req, res) {
1456    console.log("DELETE : /delete/" + req.params.id);
1457
1458    posts.remove(req.params.id, function(err) {
1459      if (err) {
1460        console.log("Erreur : ", err);
1461        res.json(err);
1462      } else {
1463        //petit correctif de contournement (bug ds nStore) :
1464        //ré-ouvrir la base lorsque la suppression a été faite
1465        posts = nStore.new("blog.db", function() {
1466          res.json(req.params.id);
1467          //Le modèle est vide si on ne trouve rien
1468        });
1469      }
1470    });
1471  });
1472
1473}
1474```
1475
1476##Authentification (côté client)
1477
1478Nous repassons enfin au code client et nous allons pouvoir vérifier comment sont gérés les évènements dans une vue en implémentant l’authentification côté client.
1479
1480###Formulaire d’authentification
1481
1482Nous allons donc commencer par créer le template du formulaire d’authentification dans notre page `index.html` (je choisis de le placer juste après la liste des 3 derniers posts) :
1483
1484```html
1485<div class="sidebar" id="blog_sidebar">
1486  <!-- Last 3 posts -->
1487</div>
1488
1489<!-- /*======= Formulaire d'authentification =======*/ -->
1490<script type="text/template" id="blog_login_form_template">
1491  <h3>Login :</h3>
1492  <input name="email" type="text" placeholder="email"/><br>
1493  <input name="password" type="password" placeholder="password"/><br>
1494  <a href="#" class="btn btn-primary">Login</a>
1495  <a href="#" class="btn btn-inverse">Logoff</a><br>
1496  <b>{{message}} {{firstName}} {{lastName}}</b>
1497
1498</script>
1499<form class="container" id="blog_login_form">
1500
1501</form>
1502```
1503
1504###L’objet Backbone.View : Login.View
1505
1506Notre composant d’authentification aura 2 zones de saisie (email et mot de passe), un bouton de login, un bouton pour se déconnecter, une zone pour afficher un message (bienvenue, erreur, …).
1507Le composant devra aussi pouvoir vérifier si l’utilisateur est toujours connecté en cas de rafraîchissement de la page.
1508
1509```javascript
1510/*======= Authentification =======*/
1511window.LoginView = Backbone.View.extend({
1512  el: $("#blog_login_form"),
1513
1514  initialize: function() {
1515    var that = this;
1516    this.template = $("#blog_login_form_template").html();
1517
1518    //on vérifie si pas déjà authentifié
1519    $.ajax({
1520      type: "GET",
1521      url: "/alreadyauthenticated",
1522      error: function(err) {
1523        console.log(err);
1524      },
1525      success: function(dataFromServer) {
1526
1527        if (dataFromServer.firstName) {
1528          that.render("Bienvenue", dataFromServer);
1529        } else {
1530          that.render("???", {
1531            firstName : "John",
1532            lastName  : "Doe"
1533          });
1534        }
1535      }
1536    })
1537
1538  },
1539
1540  render: function(message, user) {
1541
1542    var renderedContent = Mustache.to_html(this.template, {
1543      message: message,
1544      firstName : user ? user.firstName : "",
1545      lastName  : user ? user.lastName : ""
1546    });
1547    this.$el.html(renderedContent);
1548  }
1549
1550});
1551
1552window.loginView = new LoginView();
1553/*======= Fin authentification =======*/
1554```
1555
1556A l’initialisation (`initialize`) la vue va vérifier si l’utilisateur en cours est déjà authentifié (par exemple vous vous êtes signé, mais vous avez rafraîchi la page), en appelant la route `/alreadyauthenticated`, si l’utilisateur est déjà authentifié, la méthode `render` de la vue est appelée avec un message de bienvenue et les informations de l’utilisateur ( `that.render("Bienvenue",dataFromServer);` ) dans le cas contraire la méthode `render` est aussi appelée mais avec un message signifiant que l’utilisateur n’est pas connecté ( `that.render("???",{firstName:"John", lastName:"Doe"});` ).
1557
1558###Ajoutons une gestion des évènements
1559
1560Une propriété de l’objet `Backbone.View` permet de gérer les évènements spécifiques à la vue. Si vous vous souvenez, notre template de formulaire ressemble à ceci :
1561
1562```html
1563<!-- /*======= Formulaire d'authentification =======*/ -->
1564<script type="text/template" id="blog_login_form_template">
1565  <h3>Login :</h3>
1566  <input name="email" type="text" placeholder="email"/><br>
1567  <input name="password" type="password" placeholder="password"/><br>
1568  <a href="#" class="btn btn-primary">Login</a>
1569  <a href="#" class="btn btn-inverse">Logoff</a><br>
1570  <b>{{message}} {{firstName}} {{lastName}}</b>
1571</script>
1572```
1573
1574Je souhaite pouvoir déclencher des évènements lorsque je clique sur les boutons du formulaire. Pour cela il suffit d’ajouter à l’objet `Backbone.View` la propriété events :
1575
1576```javascript
1577events: {
1578  "click  .btn-primary": "onClickBtnLogin",
1579  "click  .btn-inverse": "onClickBtnLogoff"
1580},
1581```
1582
1583En fait je demande à mon objet `Backbone.View` d’intercepter tous les évènements de type click sur les éléments html (de la vue considérée) dont la classe `css` est `.btn-primary` ou `.btn-inverse` et de déclencher respectivement les méthodes `onClickBtnLogin` ou `onClickBtnLogoff`.
1584
1585>>**Remarque** : nous aurions très bien pu affecter des id aux boutons :
1586
1587```html
1588<a href="#" id="btnLogIn" class="btn btn-primary">Login</a>
1589<a href="#" id="btnLogOff" class="btn btn-inverse">Logoff</a><br>
1590```
1591
1592et relier les évènements aux ids :
1593
1594```javascript
1595events: {
1596  "click  #btnLogIn"  : "onClickBtnLogin",
1597  "click  #btnLogOff" : "onClickBtnLogoff"
1598},
1599```
1600
1601Il ne nous reste plus qu’à écrire les méthodes `onClickBtnLogin` ou `onClickBtnLogoff` au sein de notre objet de type `Backbone.View` qui vont respectivement appeler les routes que nous avions définies précédement :
1602
1603```javascript
1604onClickBtnLogin: function(domEvent) {
1605
1606  var fields = $("#blog_login_form :input"),
1607    that = this;
1608
1609  $.ajax({
1610    type: "POST",
1611    url: "/authenticate",
1612    data: {
1613      email: fields[0].value,
1614      password: fields[1].value
1615    },
1616    dataType: 'json',
1617    error: function(err) {
1618      console.log(err);
1619    },
1620    success: function(dataFromServer) {
1621
1622      if (dataFromServer.infos) {
1623        that.render(dataFromServer.infos);
1624      } else {
1625        if (dataFromServer.error) {
1626          that.render(dataFromServer.error);
1627        } else {
1628          that.render("Bienvenue", dataFromServer);
1629        }
1630      }
1631
1632    }
1633  });
1634},
1635onClickBtnLogoff: function() {
1636
1637  var that = this;
1638  $.ajax({
1639    type: "GET",
1640    url: "/logoff",
1641    error: function(err) {
1642      console.log(err);
1643    },
1644    success: function(dataFromServer) {
1645      console.log(dataFromServer);
1646      that.render("???", {
1647        firstName: "John",
1648        lastName: "Doe"
1649      });
1650    }
1651  })
1652}
1653```
1654
1655###Vérification
1656
1657Si vous avez bien suivi, nous devons avoir au moins 2 utilisateurs en base de données :
1658
1659```javascript
1660function addUsers() {
1661  addUser({
1662    email     : "bob@morane.com",
1663    password  : "backbone",
1664    isAdmin   : true,
1665    firstName : "Bob",
1666    lastName  : "Morane"
1667  });
1668  addUser({
1669    email     : "sam@lepirate.com",
1670    password  : "underscore",
1671    isAdmin   : false,
1672    firstName : "Sam",
1673    lastName  : "Le Pirate"
1674  });
1675
1676  //etc. ...
1677}
1678```javascript
1679
1680Lançons donc notre page web :
1681
1682![BB](RSRC/07_08_VIEWS.png)\
1683
1684
1685Authentifiez vous en tapant n’importe quoi :
1686
1687![BB](RSRC/07_09_VIEWS.png)\
1688
1689
1690Vous obtenez le message **"Ouups loupé !!!"**
1691
1692Authentifiez vous en utilisant un des utilisateurs existant :
1693
1694![BB](RSRC/07_10_VIEWS.png)\
1695
1696
1697Vous obtenez un message de bienvenue.
1698
1699Vous pouvez essayer de raffraîchir la page, vous restez connecté.
1700
1701Si vous ouvrez un autre navigateur (une autre marque de navigateur pour être sûr de ne pas partager la session), vous vous apercevez qu’il ne considère pas que vous êtes authentifié :
1702
1703![BB](RSRC/07_11_VIEWS.png)\
1704
1705
1706Essayez de vous connecter avec un utilisateur déjà loggé sur une autre session :
1707
1708![BB](RSRC/07_12_VIEWS.png)\
1709
1710
1711Vous obtenez le message **"Utilisateur déjà connecté"**
1712
1713Vous disposez maintenant de suffisament d'élément pour jouer avec les vues. Nous allons pouvoir passer au composant `Backbone.Router`. Nous reviendrons ensuite sur la sécurisation de notre application dans le chapitre sur l'organisation du code.
1714
1715