PageRenderTime 66ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/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. >*Sommaire*
  3. >>- *1ère vue*
  4. >>- *Mise à jour automatique de laffichage*
  5. >>- *Sous-vues*
  6. >>- *Templating*
  7. >>- *évènements*
  8. >*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.*
  9. Le 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, aujourdhui 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 lon est dans un contexte client (navigateur) et que le concept MVC « classique » nest pas forcément « portable » en létat. Lessentiel est que cela fonctionne, et si les contrôleurs vous manquent à ce point, nous verrons comment en créer quelques chapitres plus loin.
  10. ##Préparons le terrain
  11. Pour 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.
  12. Ensuite, 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();`)
  13. *Instancier une collection :*
  14. ```javascript
  15. $(function() {
  16. window.Post = Backbone.Model.extend({
  17. urlRoot: "/blogposts"
  18. });
  19. window.Posts = Backbone.Collection.extend({
  20. model: Post,
  21. all: function() {
  22. this.url = "/blogposts";
  23. return this;
  24. },
  25. query: function(query) {
  26. this.url = "/blogposts/query/" + query;
  27. return this;
  28. }
  29. });
  30. window.blogPosts = new Posts();
  31. });
  32. ```
  33. Sauvegardez, 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.
  34. ###Création et sauvegarde des modèles
  35. Commencez par saisir ceci dans la console du navigateur :
  36. *Ajouter des modèles à la collection :*
  37. ```javascript
  38. var messages = [
  39. "Maecenas sed diam eget risus varius blandit sit amet non magna.",
  40. "Integer posuere erat a ante venenatis dapibus posuere velit aliquet.",
  41. "Donec id elit non mi porta gravida at eget metus.",
  42. "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
  43. "Cras mattis consectetur purus sit amet fermentum.",
  44. "Nulla vitae elit libero, a pharetra augue."];
  45. blogPosts.add([
  46. new Post({
  47. title: "Premier Message",
  48. message: messages[0],
  49. date: new Date(2012, 10, 23, 7, 4, 0, 0),
  50. author: "bob"
  51. }),
  52. new Post({
  53. title: "Backbone ???",
  54. message: messages[1],
  55. date: new Date(2012, 10, 23, 7, 5, 0, 0),
  56. author: "bob"
  57. }),
  58. new Post({
  59. title: "Les Modèles",
  60. message: messages[2],
  61. date: new Date(2012, 10, 23, 7, 6, 0, 0),
  62. author: "sam"
  63. }),
  64. new Post({
  65. title: "Les Vues",
  66. message: messages[3],
  67. date: new Date(2012, 10, 23, 7, 7, 0, 0),
  68. author: "sam"
  69. }),
  70. new Post({
  71. title: "Les Routes",
  72. message: messages[4],
  73. date: new Date(2012, 10, 23, 7, 8, 0, 0),
  74. author: "bob"
  75. }),
  76. new Post({
  77. title: "Mais où sont les contrôleurs ?",
  78. message: messages[5],
  79. date: new Date(2012, 10, 23, 7, 9, 0, 0),
  80. author: "bob"
  81. })
  82. ])
  83. ```
  84. >>**Remarque** : en javascript, pour les dates, le chiffre 10 correspond à Novembre (faire +1)
  85. Nous 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) :
  86. *Sauvegarder les modèles en base :*
  87. ```javascript
  88. blogPosts.each(function(post) {
  89. post.save({}, {
  90. success: function(post) {
  91. console.log(post.get("title"), " sauvegardé");
  92. },
  93. error: function() {
  94. console.log("Oupss");
  95. }
  96. });
  97. })
  98. ```
  99. Vous devriez au final obtenir ceci :
  100. ![BB](RSRC/07_01_VIEWS.png)\
  101. Pour vérifier que la sauvegarde a bien fonctionné, raffraichissez votre page et lancez ce code dans la console du navigateur :
  102. *Charger la collection avec les modèles sauvegardés en base :*
  103. ```javascript
  104. blogPosts.all()
  105. .fetch({
  106. success: function(result) {
  107. console.log(result);
  108. }
  109. })
  110. ```
  111. Si tout va bien (il ny a pas de raison), vous devriez obtenir ceci :
  112. ![BB](RSRC/07_02_VIEWS.png)\
  113. Ainsi, quoiquil 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.
  114. ##1ère vue
  115. Un objet Vue dans Backbone (`Backbone.View`) et généralement composé (au minimum) par convention de :
  116. - une propriété `el` : c'est l'élément du DOM (la partie de votre page html à laquelle on rattache l'objet `View`)
  117. - une méthode `initialize` (déclenchée à linstanciation de la vue)
  118. - une méthode `render` (chargée dafficher les données liées à la vue)
  119. >>**Remarque** : Libre à vous de vous faire vos propres bonnes pratiques concernant les responsabilités de lobjet View afin de rendre votre code lisible et maintenable Vous trouverez toujours quelquun pour les discuter mais cest comme cela que lon apprend et saméliore Et vous pouvez aussi avoir raison :-).
  120. Dans notre page `index.html` nous allons ajouter un tag `<div id="posts_list"></div>` comme ceci :
  121. ```html
  122. <div class="container">
  123. <div class="hero-unit">
  124. <h1>Backbone rocks !!!</h1>
  125. </div>
  126. <div id="posts_list"></div>
  127. </div>
  128. ```
  129. Et 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 :
  130. *1ère vue pour afficher le contenu de la collection :*
  131. ```javascript
  132. window.PostsListView = Backbone.View.extend({
  133. el: $("#posts_list"),
  134. initialize: function(data) {
  135. this.collection = data;
  136. },
  137. render: function() {
  138. var html = "";
  139. $(this.el).html(""); //on vide le div
  140. this.collection.each(function(model) {
  141. html += [
  142. '<h1>' + model.get("title") + '</h1><hr>',
  143. '<b>par : ' + model.get("author") + '</b> le : ' + model.get("date") + '<br>',
  144. '<p>' + model.get("message") + '</p>'].join("");
  145. });
  146. $(this.el).append(html);
  147. }
  148. });
  149. ```
  150. ###Explications & utilisation
  151. Notre vue `PostsListView` est reliée au tag `<div id="posts_list"></div>` par la propriété `el` qui nest ni plus ni moins un objet **jQuery**. La méthode `initialize` (qui sera appelée à linstanciation 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 ».
  152. Sauvegardez, rafraichissez la page et en mode console, passez les étapes qui suivent :
  153. *1] Chargez les données de la collection :*
  154. ```javascript
  155. blogPosts.all()
  156. .fetch({
  157. success: function(result) {
  158. console.log(result);
  159. }
  160. })
  161. ```
  162. *2] Instanciez la vue en lui passant la collection en paramètre :*
  163. ```javascript
  164. postsListView = new PostsListView(blogPosts)
  165. ```
  166. *3] Appelez la méthode render de la vue :*
  167. ```javascript
  168. postsListView.render()
  169. ```
  170. Et vous obtenez la liste de vos messages :
  171. ![BB](RSRC/07_03_VIEWS.png)\
  172. Souvenez 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 :
  173. *Je ne veux que les posts de lauteur "Sam" :*
  174. ```javascript
  175. blogPosts.query('{"author" : "sam"}')
  176. .fetch({
  177. success: function(result) {
  178. console.log(result);
  179. }
  180. })
  181. ```
  182. Puis faite à nouveau un :
  183. ```javascript
  184. postsListView.render()
  185. ```
  186. Et , laffichage sactualise automatiquement :
  187. ![BB](RSRC/07_04_VIEWS.png)\
  188. ##Maintenant, un peu de magie ...
  189. ###S'abonner aux événements
  190. Modifions une nouvelle fois notre vue en ajoutans le code suivant à la méthode `initialize` :
  191. ```javascript
  192. _.bindAll(this, 'render');
  193. this.collection.bind('reset', this.render);
  194. ```
  195. Nous venons dexpliquer 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.
  196. >>**Remarque** : Une collection Backbone déclenche un `reset` lors de lappel dun `fetch`. La méthode `reset` vide la collection.
  197. //TODO: faire un chapitre à part sur `_.bindAll`
  198. Le code de notre vue doit donc ressembler à ceci :
  199. *PostListView :*
  200. ```javascript
  201. window.PostsListView = Backbone.View.extend({
  202. el: $("#posts_list"),
  203. initialize: function(data) {
  204. this.collection = data;
  205. _.bindAll(this, 'render');
  206. this.collection.bind('reset', this.render);
  207. },
  208. render: function() {
  209. var html = "";
  210. $(this.el).html(""); //on vide le div
  211. this.collection.each(function(model) {
  212. html += [
  213. '<h1>' + model.get("title") + '</h1><hr>',
  214. '<b>par : ' + model.get("author") + '</b> le : ' + model.get("date") + '<br>',
  215. '<p>' + model.get("message") + '</p>'].join("");
  216. });
  217. $(this.el).append(html);
  218. }
  219. });
  220. ```
  221. Sauvegardez ensuite la page, puis retournez dans le navigateur, rafraichissez la page et retournez dans la console du navigateur pour instancier une nouvelle vue :
  222. ```javascript
  223. postsListView = new PostsListView(blogPosts)
  224. ```
  225. Puis essayez ceci :
  226. ```javascript
  227. blogPosts.all()
  228. .fetch({
  229. success: function(result) {
  230. console.log(result);
  231. }
  232. })
  233. ```
  234. et cela :
  235. ```javascript
  236. blogPosts.query('{"author" : "sam"}')
  237. .fetch({
  238. success: function(result) {
  239. console.log(result);
  240. }
  241. })
  242. ```
  243. Vous remarquez que votre vue se met à jour automatiquement à chaque changement, sans avoir à rappeler la méthode render de la vue.
  244. Mais il est possible de faire ceci aussi avec les changements sur les modèles.
  245. ###Sabonner à dautres évènements (modèles)
  246. Toujours dans la méthode `initialize` de la vue, ajoutez le code suivant :
  247. ```javascript
  248. this.collection.bind('change', this.render);
  249. this.collection.bind('add', this.render);
  250. this.collection.bind('remove', this.render);
  251. ```
  252. Maintenant, 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.
  253. Sauvegardez 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 :
  254. ```javascript
  255. postsListView = new PostsListView(blogPosts)
  256. blogPosts.all()
  257. .fetch({
  258. success: function(result) {
  259. console.log(result);
  260. }
  261. })
  262. ```
  263. Puis changez le titre du 1er post :
  264. ```javascript
  265. blogPosts.at(0).set("title","BACKBONE ???!!!")
  266. ```
  267. ou ajoutez un post :
  268. ```javascript
  269. blogPosts.add(new Post({title:"HELLO",message : "salut", author : "k33g"}))
  270. ```
  271. ou encore supprimez un post :
  272. ```javascript
  273. blogPosts.remove(blogPosts.at(0));
  274. ```
  275. encore, votre page sactualise instantanément.
  276. ###Amélioration & Finalisation du code
  277. Avant de passer à lutilisation des templates dans les vue, nous allons apporter quelques modifications et améliorer un peu notre code pour nous préparer à la suite.
  278. Dans ses dernières versions, Backbone a hérité dun raccourcis concernant la propriété `el` de la vue qui consiste à remplacer (avec pour objectif loptimisation dexécution de code) le sélecteur `$(this.el)` par `this.$el`.
  279. De plus, vous devez savoir quil nest pas obligatoire de déclarer laffectation de la collection dans la méthode initialize de la vue, mais que lon peut faire ceci directement en paramètre du constructeur à linstanciation de la vue. Comme ceci :
  280. ```javascript
  281. new PostsListView({collection : blogPosts})
  282. ```
  283. On pourrait faire de même avec `el`, et utiliser ceci :
  284. ```javascript
  285. new PostsListView({el : $("#posts_list"), collection : blogPosts})
  286. ```
  287. En fait tout dépend de vos besoins (et de vos habitudes).
  288. En ce qui nous concerne, modifions le code de notre vue de la manière suivante :
  289. *PostsListView :*
  290. ```javascript
  291. window.PostsListView = Backbone.View.extend({
  292. el: $("#posts_list"),
  293. initialize: function() {
  294. _.bindAll(this, 'render');
  295. this.collection.bind('reset', this.render);
  296. this.collection.bind('change', this.render);
  297. this.collection.bind('add', this.render);
  298. this.collection.bind('remove', this.render);
  299. },
  300. render: function() {
  301. var html = "";
  302. this.$el.html(""); //on vide le div
  303. this.collection.each(function(model) {
  304. html += [
  305. '<h1>' + model.get("title") + '</h1><hr>',
  306. '<b>par : ' + model.get("author") + '</b> le : ' + model.get("date") + '<br>',
  307. '<p>' + model.get("message") + '</p>'].join("");
  308. });
  309. this.$el.append(html);
  310. }
  311. });
  312. ```
  313. Puis à 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é.) :
  314. ```javascript
  315. window.postsListView = new PostsListView({
  316. collection: blogPosts
  317. })
  318. blogPosts.all().fetch({
  319. success: function(result) {
  320. console.log(result);
  321. }
  322. });
  323. ```
  324. Le code final du script dans la page devrait ressembler à ceci :
  325. *Code final :*
  326. ```html
  327. <!-- === code applicatif === -->
  328. <script>
  329. $(function() {
  330. window.Post = Backbone.Model.extend({
  331. urlRoot: "/blogposts"
  332. });
  333. window.Posts = Backbone.Collection.extend({
  334. model: Post,
  335. all: function() {
  336. this.url = "/blogposts";
  337. return this;
  338. },
  339. query: function(query) {
  340. this.url = "/blogposts/query/" + query;
  341. return this;
  342. }
  343. });
  344. window.PostsListView = Backbone.View.extend({
  345. el: $("#posts_list"),
  346. initialize: function() {
  347. _.bindAll(this, 'render');
  348. this.collection.bind('reset', this.render);
  349. this.collection.bind('change', this.render);
  350. this.collection.bind('add', this.render);
  351. this.collection.bind('remove', this.render);
  352. },
  353. render: function() {
  354. var html = "";
  355. this.$el.html(""); //on vide le div
  356. this.collection.each(function(model) {
  357. html += [
  358. '<h1>' + model.get("title") + '</h1><hr>',
  359. '<b>par : ' + model.get("author") + '</b> le : ' + model.get("date") + '<br>',
  360. '<p>' + model.get("message") + '</p>'].join("");
  361. });
  362. this.$el.append(html);
  363. }
  364. });
  365. window.blogPosts = new Posts();
  366. window.postsListView = new PostsListView({
  367. collection: blogPosts
  368. })
  369. blogPosts.all().fetch({
  370. success: function(result) {
  371. //ça marche !!!
  372. }
  373. });
  374. });
  375. </script>
  376. ```
  377. Nous avons fait un peu de magie, passons donc à la sorcellerie ;) ...
  378. ##Utilisation du templating ... 1ère fois
  379. Vous vous souvenez ? Je vous avez parlé d'underscore avec les templates ? Eh bien il est temps de les mettre en œuvre.
  380. ###Définition de notre 1er template
  381. Dans la partie HTML de notre page, juste avant `<div id="posts_list"></div>`, ajoutez le code ci-dessous (ce sera notre template) :
  382. ```html
  383. <!-- template pour les posts -->
  384. <script type="text/template" id="posts_list_template">
  385. <% _.each(posts ,function(post){ %>
  386. <h1><%= post.get("title") %></h1><hr>
  387. <b>par : <%= post.get("author") %></b> le : <%= post.get("date") %><br>
  388. <p><%= post.get("message") %></p>
  389. <% }); %>
  390. </script>
  391. ```
  392. >>**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.
  393. En 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.
  394. Modifions donc notre vue de la façon suivante :
  395. ```javascript
  396. window.PostsListView = Backbone.View.extend({
  397. el: $("#posts_list"),
  398. initialize: function() {
  399. this.template = _.template($("#posts_list_template").html());
  400. _.bindAll(this, 'render');
  401. this.collection.bind('reset', this.render);
  402. this.collection.bind('change', this.render);
  403. this.collection.bind('add', this.render);
  404. this.collection.bind('remove', this.render);
  405. },
  406. render: function() {
  407. var renderedContent = this.template({
  408. posts: this.collection.models
  409. });
  410. this.$el.html(renderedContent);
  411. }
  412. });
  413. ```
  414. Nous avons inséré dans la méthode `initialize` : `this.template = _.template($("#posts_list_template").html());`, 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, nous faisons générer le contenu HTML à partir du template et des données.
  415. >>**Remarque** : notez bien que `this.collection.models` est un tableau de modèles.
  416. Vous 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.
  417. ![BB](RSRC/07_05_VIEWS.png)\
  418. ##Sous-vue(s)
  419. Il est possible de faire des sous vues pour gérer différentes parties de votre page web. En fait il sagit de vues encapsulées dans une autre vue, ce qui peut être pratique en termes dorganisation, mais aussi dans les cas les comportements de chacunes des vues dépendent les uns des autres.
  420. Nous 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.
  421. ###Réorganisation du code html
  422. Modifions notre code html de la manière suivante :
  423. ```html
  424. <div class="navbar navbar-fixed-top">
  425. <div class="navbar-inner">
  426. <div class="container">
  427. <a class="brand">Mon Blog</a>
  428. </div>
  429. </div>
  430. </div>
  431. <div class="container-fluid">
  432. <div class="row-fluid">
  433. <div class="span3">
  434. <script type="text/template" id="blog_sidebar_template">
  435. <h2>Les 3 derniers :</h2>
  436. <ul>
  437. <% _.each(posts ,function(post){ %>
  438. <li><%= post.get("title") %></li>
  439. <% }); %>
  440. </ul>
  441. </script>
  442. <div class="sidebar" id="blog_sidebar">
  443. <!-- Last 3 posts -->
  444. </div>
  445. </div>
  446. <div class="span9">
  447. <div class="hero-unit">
  448. <h1>Backbone rocks !!!</h1>
  449. </div>
  450. <!-- template pour les posts -->
  451. <script type="text/template" id="posts_list_template">
  452. <% _.each(posts ,function(post){ %>
  453. <h1><%= post.get("title") %></h1><hr>
  454. <b>par : <%= post.get("author") %></b> le : <%= post.get("date") %><br>
  455. <p><%= post.get("message") %></p>
  456. <% }); %>
  457. </script>
  458. <div class="row-fluid" id="posts_list"></div>
  459. </div>
  460. </div>
  461. </div>
  462. ```
  463. >>**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"`).
  464. ###Création & Modification des vues
  465. Nous allons créer une vue principale (`MainView`) qui se chargera de "piloter" 2 sous-vues (`SidebarView` et `PostsListView`) :
  466. *1] Simplifions PostsListView :*
  467. ```javascript
  468. window.PostsListView = Backbone.View.extend({
  469. el: $("#posts_list"),
  470. initialize: function() {
  471. this.template = _.template($("#posts_list_template").html());
  472. },
  473. render: function() {
  474. var renderedContent = this.template({
  475. posts: this.collection.models
  476. });
  477. this.$el.html(renderedContent);
  478. }
  479. });
  480. ```
  481. *2] Création de SidebarView :*
  482. ```javascript
  483. window.SidebarView = Backbone.View.extend({
  484. el: $("#blog_sidebar"),
  485. initialize: function() {
  486. this.template = _.template($("#blog_sidebar_template").html());
  487. },
  488. render: function() {
  489. var renderedContent = this.template({
  490. posts: this.collection.models
  491. });
  492. this.$el.html(renderedContent);
  493. }
  494. });
  495. ```
  496. *3] Création de la vue principale :*
  497. ```javascript
  498. window.MainView = Backbone.View.extend({
  499. initialize: function() {
  500. _.bindAll(this, 'render');
  501. this.collection.bind('reset', this.render);
  502. this.collection.bind('change', this.render);
  503. this.collection.bind('add', this.render);
  504. this.collection.bind('remove', this.render);
  505. this.sidebarView = new SidebarView();
  506. this.postsListView = new PostsListView({
  507. collection: blogPosts
  508. });
  509. },
  510. render: function() {
  511. this.sidebarView.collection = new Posts(this.collection.first(3));
  512. this.sidebarView.render();
  513. this.postsListView.render();
  514. }
  515. });
  516. ```
  517. C'est donc maintenant la vue `MainView` qui s'abonne aux changements de la collection et déclenche le rendu des 2 autres vues.
  518. Et enfin instancions la collection ainsi que la vue principale (qui se chargera dinstancier les deux sous-vues). Donc à la place de :
  519. ```javascript
  520. window.blogPosts = new Posts();
  521. window.postsListView = new PostsListView({
  522. collection: blogPosts
  523. })
  524. ```
  525. **Nous aurons ceci :**
  526. ```javascript
  527. window.blogPosts = new Posts();
  528. window.mainView = new MainView({
  529. collection: blogPosts
  530. });
  531. ```
  532. Vous pouvez sauvegarder votre code et raffraichir votre page :
  533. ![BB](RSRC/07_06_VIEWS.png)\
  534. Et si vous faites ceci en mode console :
  535. ```javascript
  536. blogPosts.at(0).set("title","Bonjour à tous !")
  537. ```
  538. Vous verrez que les modifications sont bien propagées dans les 2 vues simultanément.
  539. ###Un dernier petit réglage : tri des collections
  540. Nous souhaitons avant daller plus loin trier la collection de posts pour avoir les message en ordre décroissant. Pour cela nous allons créer ce que lon appelle un **« comparator »** dans la méthode `initialize` de la vue principale `MainView` :
  541. *Trier la collection par ordre décroissant de date :*
  542. ```javascript
  543. this.collection.comparator = function(model) {
  544. return -(new Date(model.get("date")).getTime());
  545. }
  546. ```
  547. Donc le code final de MainView sera celui-ci :
  548. *Vue principale :*
  549. ```javascript
  550. window.MainView = Backbone.View.extend({
  551. initialize: function() {
  552. this.collection.comparator = function(model) {
  553. return -(new Date(model.get("date")).getTime());
  554. }
  555. _.bindAll(this, 'render');
  556. this.collection.bind('reset', this.render);
  557. this.collection.bind('change', this.render);
  558. this.collection.bind('add', this.render);
  559. this.collection.bind('remove', this.render);
  560. this.sidebarView = new SidebarView();
  561. this.postsListView = new PostsListView({
  562. collection: this.collection
  563. });
  564. },
  565. render: function() {
  566. this.sidebarView.collection = new Posts(this.collection.first(3));
  567. this.sidebarView.render();
  568. this.postsListView.render();
  569. }
  570. });
  571. ```
  572. Et le rendu dans le navigateur devrait vous donner ceci :
  573. ![BB](RSRC/07_07_VIEWS.png)\
  574. //TODO : faire un paragraphe sur le comparator dans le chapitre sur les collections
  575. ##Utilisation dautre(s) moteur(s) de template
  576. Vous 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**.
  577. Un 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` :
  578. ```javascript
  579. <script src="libs/vendors/mustache.js"></script>
  580. ```
  581. Et nous allons une fois de plus "casser" notre code html.
  582. ###Redéfinissons donc nos templates
  583. *1] Avant pour la partie concernant la vue `SidebarView` nous avions ceci :*
  584. ```html
  585. <script type="text/template" id="blog_sidebar_template">
  586. <h2>Les 3 derniers :</h2>
  587. <ul>
  588. <% _.each(posts ,function(post){ %>
  589. <li><%= post.get("title") %></li>
  590. <% }); %>
  591. </ul>
  592. </script>
  593. ```
  594. *2] Que vous allez remplacer par ceci :*
  595. ```html
  596. <script type="text/template" id="blog_sidebar_template">
  597. <h2>Les 3 derniers :</h2>
  598. <ul>{{#posts}}
  599. <li>{{title}}</li>
  600. {{/posts}}</ul>
  601. </script>
  602. ```
  603. *3] En ce qui concerne le template lié à la vue PostsListView, remplacez :*
  604. ```html
  605. <script type="text/template" id="posts_list_template">
  606. <% _.each(posts ,function(post){ %>
  607. <h1><%= post.get("title") %></h1><hr>
  608. <b>par : <%= post.get("author") %></b> le : <%= post.get("date") %><br>
  609. <p><%= post.get("message") %></p>
  610. <% }); %>
  611. </script>
  612. ```
  613. *4] Par :*
  614. ```html
  615. <script type="text/template" id="posts_list_template">
  616. {{#posts}}
  617. <h1>{{title}}</h1>
  618. <b>par : {{author}}</b> le : {{date}}<br>
  619. <p>{{message}}</p>
  620. {{/posts}}
  621. </script>
  622. ```
  623. Nous obtenons donc des templates html plus lisibles, utilisable moyennant une petite modification de nos vues :
  624. *5] Avant (avec le moteur de template dunderscore) :*
  625. ```javascript
  626. window.PostsListView = Backbone.View.extend({
  627. el: $("#posts_list"),
  628. initialize: function() {
  629. this.template = _.template($("#posts_list_template").html());
  630. },
  631. render: function() {
  632. var renderedContent = this.template({
  633. posts: this.collection.models
  634. });
  635. this.$el.html(renderedContent);
  636. }
  637. });
  638. window.SidebarView = Backbone.View.extend({
  639. el: $("#blog_sidebar"),
  640. initialize: function() {
  641. this.template = _.template($("#blog_sidebar_template").html());
  642. },
  643. render: function() {
  644. var renderedContent = this.template({
  645. posts: this.collection.models
  646. });
  647. this.$el.html(renderedContent);
  648. }
  649. });
  650. ```
  651. *6] Après (en utilisant Mustache) nous aurons ceci :*
  652. ```javascript
  653. window.PostsListView = Backbone.View.extend({
  654. el: $("#posts_list"),
  655. initialize: function() {
  656. this.template = $("#posts_list_template").html();
  657. },
  658. render: function() {
  659. var renderedContent = Mustache.to_html(
  660. this.template, {
  661. posts: this.collection.toJSON()
  662. });
  663. this.$el.html(renderedContent);
  664. }
  665. });
  666. window.SidebarView = Backbone.View.extend({
  667. el: $("#blog_sidebar"),
  668. initialize: function() {
  669. this.template = $("#blog_sidebar_template").html();
  670. },
  671. render: function() {
  672. var renderedContent = Mustache.to_html(
  673. this.template, {
  674. posts: this.collection.toJSON()
  675. });
  676. this.$el.html(renderedContent);
  677. }
  678. });
  679. ```
  680. Vous 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.
  681. Sauvegardez, lancez, il ny a pas de changement, l'affichages est identique (heureusement), vous avez juste utilisé une autre façon de travailler.
  682. ##Gestion des événements dans les vues
  683. Les 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 dauthentification dans notre application, qui utilisera donc cette possibilité. Il est temps de retourner travailler côté serveur quelques instants.
  684. Si 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).
  685. Donc ...
  686. ##Authentification (côté serveur) : les utilisateurs
  687. >>*Ce paragraphe ne parle pas des vues, mais est nécessaire pour la mise en place des paragraphes suivants.*
  688. Nous aurons besoin dune 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 dun tableau de variables (ou dobjets) :
  689. ```javascript
  690. var connectedUsers = [];
  691. ```
  692. Nous aurons besoin dajouter 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 :
  693. *Ajouter un utilisateur en base :*
  694. ```javascript
  695. function addUser(user) {
  696. users.save(null, user, function(err, key) {
  697. if (err) {
  698. console.log("Erreur : ", err);
  699. } else {
  700. user.id = key;
  701. console.log(user);
  702. }
  703. });
  704. }
  705. ```
  706. Nous appellerons n fois cette fonctions pour ajouter des utilisateurs :
  707. *Ajouter des utilisateurs :*
  708. ```javascript
  709. function addUsers() {
  710. addUser({
  711. email : "bob@morane.com",
  712. password : "backbone",
  713. isAdmin : true,
  714. firstName : "Bob",
  715. lastName : "Morane"
  716. });
  717. addUser({
  718. email : "sam@lepirate.com",
  719. password : "underscore",
  720. isAdmin : false,
  721. firstName : "Sam",
  722. lastName : "Le Pirate"
  723. });
  724. //etc. ...
  725. }
  726. ```
  727. Et pour déclencher lajout des utilisateurs, nous créeons une « route » `addusers` :
  728. ```javascript
  729. app.get('/addusers', function(req, res) {
  730. addUsers();
  731. res.json({
  732. MESSAGE: "Users added."
  733. });
  734. });
  735. ```
  736. Quil suffira dappeler comme ceci dans le navigateur : [http://localhost:3000/addusers/](http://localhost:3000/addusers/)
  737. >>**Remarque** : notez bien que mon système dauthentification est très « léger ». En production, il vous faudrait quelque chose de plus abouti, mais ce nest pas le propos de cet ouvrage. Nous avions besoin de quelque chose de simple.
  738. ###Sauthentifier Se déconnecter
  739. Nous aurons besoin de nous authentifier. Il nous faut donc dabord une fonction « utilitaire » qui nous permette de vérifier si lemail de lutilisateur nest pas déjà pris (utilisateur déjà connecté sous une autre session) :
  740. *Vérifier si un utilisateur est déjà connecté :*
  741. ```javascript
  742. function findUserByMail(email) {
  743. /*
  744. Permet de vérifier si un utilisateur est déjà loggé
  745. */
  746. return connectedUsers.filter(function(user) {
  747. return user.email == email;
  748. })[0];
  749. }
  750. ```
  751. Nous allons donc créer une route `authenticate` avec le code suivant :
  752. *Code pour sauthentifier :*
  753. ```javascript
  754. app.post('/authenticate', function(req, res) {
  755. console.log("POST authenticate ", req.body);
  756. //Je récupère les information de connexion de l'utilisateur
  757. var user = req.body;
  758. //est ce que l'email est déjà utilisé ?
  759. if (findUserByMail(user.email)) {
  760. res.json({
  761. infos: "Utilisateur déjà connecté"
  762. })
  763. } else { //si l'email n'est pas utilisé
  764. //Je cherche l'utilisateur dans la base de données
  765. users.find({
  766. email: user.email,
  767. password: user.password
  768. },
  769. function(err, results) {
  770. if (err) {
  771. res.json({
  772. error: "Oups, Houson, on a un problème"
  773. });
  774. } else {
  775. //J'ai trouvé l'utilisateur
  776. var key = Object.keys(results)[0],
  777. authenticatedUser = results[key];
  778. //Je rajoute l'id de session à l'objet utilisateur
  779. authenticatedUser.key = key;
  780. authenticatedUser.sessionID = req.sessionID;
  781. //Ajouter l'utilisateur authentifié à la liste des utilisateurs connectés
  782. connectedUsers.push(authenticatedUser);
  783. //Je renvoie au navigateur les informations de l'utilisateur
  784. // ... sans le mot de passe bien sûr
  785. res.json({
  786. email: authenticatedUser.email,
  787. firstName: authenticatedUser.firstName,
  788. lastName: authenticatedUser.lastName,
  789. isAdmin: authenticatedUser.isAdmin
  790. });
  791. }
  792. });
  793. }
  794. });
  795. ```
  796. >>**Remarque** : La « bienséance » (dun point de vue architecture) voudrait que ne mette pas tout ce code au niveau de la route mais dans la méthode dun contrôleur qui serait appelée par la route. Une fois de plus je vais au plus court, mais gardez à lesprit : toujours un code lisible et maintenable.
  797. Il faudra aussi pouvoir se déconnecter. Nous ajoutons donc une route `logoff` qui nous permettra de déconnecter lutilisateur.
  798. Nous avons tout dabord besoin dune fonction nous permettant de retrouver un utilisateur par son id de session parmi les utilisateurs connectés :
  799. ```javascript
  800. function findUserBySession(sessionID) {
  801. /*
  802. Permet de retrouver un utilisateur par son id de session
  803. */
  804. return connectedUsers.filter(function(user) {
  805. return user.sessionID == sessionID;
  806. })[0];
  807. }
  808. ```
  809. Que nous allons utiliser ensuite dans notre route `logoff` :
  810. *Se déconnecter :*
  811. ```javascript
  812. app.get('/logoff', function(req, res) {
  813. //Je recherche l'utilisateur courant parmi les utilisateurs connectés
  814. var alreadyAuthenticatedUser = findUserBySession(req.sessionID);
  815. if (alreadyAuthenticatedUser) {
  816. //Je l'ai trouvé, je le supprime de la liste des utilisateurs connectés
  817. var posInArray = connectedUsers.indexOf(alreadyAuthenticatedUser);
  818. connectedUsers.splice(posInArray, 1);
  819. res.json({
  820. state: "disconnected"
  821. });
  822. } else {
  823. res.json({});
  824. }
  825. });
  826. ```
  827. Nous aurons aussi besoin dun 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) :
  828. *Est-ce que je suis déjà authentifié ?*
  829. ```javascript
  830. app.get('/alreadyauthenticated', function(req, res) {
  831. var alreadyAuthenticatedUser = findUserBySession(req.sessionID);
  832. //Si je suis déjà authentifié, renvoyer les informations utilisateur
  833. if (alreadyAuthenticatedUser) {
  834. res.json({
  835. email: alreadyAuthenticatedUser.email,
  836. firstName: alreadyAuthenticatedUser.firstName,
  837. lastName: alreadyAuthenticatedUser.lastName,
  838. isAdmin: alreadyAuthenticatedUser.isAdmin
  839. });
  840. } else {
  841. res.json({});
  842. }
  843. });
  844. ```
  845. Nous somme maintenant prêts à utiliser tout cela côté client. Le code définitif de `app.js` devrait ressembler à ceci :
  846. ```javascript
  847. /*--------------------------------------------
  848. Déclaration des librairies
  849. --------------------------------------------*/
  850. var express = require('express'),
  851. nStore = require('nstore'),
  852. app = module.exports = express.createServer();
  853. nStore = nStore.extend(require('nstore/query')());
  854. /*--------------------------------------------
  855. Paramétrages de fonctionnement d'Express
  856. --------------------------------------------*/
  857. app.use(express.bodyParser());
  858. app.use(express.methodOverride());
  859. app.use(express.static(__dirname + '/public'));
  860. app.use(express.cookieParser('ilovebackbone'));
  861. app.use(express.session({
  862. secret: "ilovebackbone"
  863. }));
  864. /*--------------------------------------------
  865. Définition des "bases" posts & users
  866. --------------------------------------------*/
  867. var posts, users;
  868. posts = nStore.new("blog.db", function() {
  869. users = nStore.new("users.db", function() {
  870. Routes();
  871. app.listen(3000);
  872. console.log('Express app started on port 3000');
  873. });
  874. });
  875. /*======= Authentification =======*/
  876. var connectedUsers = [];
  877. function addUser(user) {
  878. users.save(null, user, function(err, key) {
  879. if (err) {
  880. console.log("Erreur : ", err);
  881. } else {
  882. user.id = key;
  883. console.log(user);
  884. }
  885. });
  886. }
  887. function addUsers() {
  888. addUser({
  889. email: "bob@morane.com",
  890. password: "backbone",
  891. isAdmin: true,
  892. firstName: "Bob",
  893. lastName: "Morane"
  894. });
  895. addUser({
  896. email: "sam@lepirate.com",
  897. password: "underscore",
  898. isAdmin: false,
  899. firstName: "Sam",
  900. lastName: "Le Pirate"
  901. });
  902. //etc. ...
  903. }
  904. function findUserBySession(sessionID) {
  905. /*
  906. Permet de retrouver un utilisateur par son id de session
  907. */
  908. return connectedUsers.filter(function(user) {
  909. return user.sessionID == sessionID;
  910. })[0];
  911. }
  912. function findUserByMail(email) {
  913. /*
  914. Permet de vérifier si un utilisateur est déjà loggé
  915. */
  916. return connectedUsers.filter(function(user) {
  917. return user.email == email;
  918. })[0];
  919. }
  920. function Routes() {
  921. /*======= Routes pour authentification =======*/
  922. app.get('/addusers', function(req, res) {
  923. addUsers();
  924. res.json({
  925. MESSAGE: "Users added."
  926. });
  927. });
  928. app.get('/alreadyauthenticated', function(req, res) {
  929. var alreadyAuthenticatedUser = findUserBySession(req.sessionID);
  930. /* Si je suis déjà authentifié,
  931. renvoyer les informations utilisateur
  932. sans le mot de passe bien sûr
  933. */
  934. if (alreadyAuthenticatedUser) {
  935. res.json({
  936. email: alreadyAuthenticatedUser.email,
  937. firstName: alreadyAuthenticatedUser.firstName,
  938. lastName: alreadyAuthenticatedUser.lastName,
  939. isAdmin: alreadyAuthenticatedUser.isAdmin
  940. });
  941. } else {
  942. res.json({});
  943. }
  944. });
  945. app.post('/authenticate', function(req, res) {
  946. console.log("POST authenticate ", req.body);
  947. //Je récupère les information de connexion de l'utilisateur
  948. var user = req.body;
  949. //est ce que l'email est déjà utilisé ?
  950. if (findUserByMail(user.email)) {
  951. res.json({
  952. infos: "Utilisateur déjà connecté"
  953. })
  954. } else { //si l'email n'est pas utilisé
  955. //Je cherche l'utilisateur dans la base de données
  956. users.find({
  957. email: user.email,
  958. password: user.password
  959. }, function(err, results) {
  960. if (err) {
  961. res.json({
  962. error: "Oups, Houson, on a un problème"
  963. });
  964. } else {
  965. //J'ai trouvé l'utilisateur
  966. var key = Object.keys(results)[0],
  967. authenticatedUser = results[key];
  968. //Je rajoute l'id de session à l'objet utilisateur
  969. authenticatedUser.key = key;
  970. authenticatedUser.sessionID = req.sessionID;
  971. //J'ajoute l'utilisateur authentifié à la liste des utilisateurs connectés
  972. connectedUsers.push(authenticatedUser);
  973. //Je renvoie au navigateur les informations de l'utilisateur
  974. // ... sans le mot de passe bien sûr
  975. res.json({
  976. email: authenticatedUser.email,
  977. firstName: authenticatedUser.firstName,
  978. lastName: authenticatedUser.lastName,
  979. isAdmin: authenticatedUser.isAdmin
  980. });
  981. }
  982. });
  983. }
  984. });
  985. app.get('/logoff', function(req, res) {
  986. //Je recherche l'utilisateur courant parmi les utilisateurs connectés
  987. var alreadyAuthenticatedUser = findUserBySession(req.sessionID);
  988. if (alreadyAuthenticatedUser) {
  989. //Je l'ai trouvé, je le supprime de la liste des utilisateurs connectés
  990. var posInArray = connectedUsers.indexOf(alreadyAuthenticatedUser);
  991. connectedUsers.splice(posInArray, 1);
  992. res.json({
  993. state: "disconnected"
  994. });
  995. } else {
  996. res.json({});
  997. }
  998. });
  999. /*======= Fin des routes "authentification" =======*/
  1000. /*
  1001. Obtenir la liste de tous les posts lorsque
  1002. l'on appelle http://localhost:3000/blogposts
  1003. en mode GET
  1004. */
  1005. app.get('/blogposts', function(req, res) {
  1006. console.log("GET (ALL) : /blogposts");
  1007. posts.all(function(err, results) {
  1008. if (err) {
  1009. console.log("Erreur : ", err);
  1010. res.json(err);
  1011. } else {
  1012. var posts = [];
  1013. for (var key in results) {
  1014. var post = results[key];
  1015. post.id = key;
  1016. posts.push(post);
  1017. }
  1018. res.json(posts);
  1019. }
  1020. });
  1021. });
  1022. /*
  1023. Obtenir la liste de tous les posts correspondant à un critère
  1024. lorsque l'on appelle http://localhost:3000/blogposts/ en
  1025. mode GET avec une requête en paramètre
  1026. ex : query : { "title" : "Mon 1er post"} }
  1027. */
  1028. app.get('/blogposts/:query', function(req, res) {
  1029. console.log("GET (QUERY) : /blogposts/" + req.params.query);
  1030. posts.find(JSON.parse(req.params.query), function(err, results) {
  1031. if (err) {
  1032. console.log("Erreur : ", err);
  1033. res.json(err);
  1034. } else {
  1035. var posts = [];
  1036. for (var key in results) {
  1037. var post = results[key];
  1038. post.id = key;
  1039. posts.push(post);
  1040. }
  1041. res.json(posts);
  1042. }
  1043. });
  1044. });
  1045. /*
  1046. Retrouver un post par sa clé unique lorsque
  1047. l'on appelle http://localhost:3000/blogpost/identifiant_du_post
  1048. en mode GET
  1049. */
  1050. app.get('/blogpost/:id', function(req, res) {
  1051. console.log("GET : /blogpost/" + req.params.id);
  1052. posts.get(req.params.id, function(err, post, key) {
  1053. if (err) {
  1054. console.log("Erreur : ", err);
  1055. res.json(err);
  1056. } else {
  1057. post.id = key;
  1058. res.json(post);
  1059. }
  1060. });
  1061. });
  1062. /*
  1063. Créer un nouveau post lorsque
  1064. l'on appelle http://localhost:3000/blogpost
  1065. avec en paramètre le post au format JSON
  1066. en mode POST
  1067. */
  1068. app.post('/blogpost', function(req, res) {
  1069. console.log("POST CREATE ", req.body);
  1070. var d = new Date(),
  1071. model = req.body;
  1072. model.saveDate = (d.valueOf());
  1073. posts.save(null, model, function(err, key) {
  1074. if (err) {
  1075. console.log("Erreur : ", err);
  1076. res.json(err);
  1077. } else {
  1078. model.id = key;
  1079. res.json(model);
  1080. }
  1081. });
  1082. });
  1083. /*
  1084. Mettre à jour un post lorsque
  1085. l'on appelle http://localhost:3000/blogpost
  1086. avec en paramètre le post au format JSON
  1087. en mode PUT
  1088. */
  1089. app.put('/blogpost/:id', function(req, res) {
  1090. console.log("PUT UPDATE", req.body, req.params.id);
  1091. var d = new Date(),
  1092. model = req.body;
  1093. model.saveDate = (d.valueOf());
  1094. posts.save(req.params.id, model, function(err, key) {
  1095. if (err) {
  1096. console.log("Erreur : ", err);
  1097. res.json(err);
  1098. } else {
  1099. res.json(model);
  1100. }
  1101. });
  1102. });
  1103. /*
  1104. supprimer un post par sa clé unique lorsque
  1105. l'on appelle http://localhost:3000/blogpost/identifiant_du_post
  1106. en mode DELETE
  1107. */
  1108. app.delete('/blogpost/:id', function(req, res) {
  1109. console.log("DELETE : /delete/" + req.params.id);
  1110. posts.remove(req.params.id, function(err) {
  1111. if (err) {
  1112. console.log("Erreur : ", err);
  1113. res.json(err);
  1114. } else {
  1115. //petit correctif de contournement (bug ds nStore) :
  1116. //ré-ouvrir la base lorsque la suppression a été faite
  1117. posts = nStore.new("blog.db", function() {
  1118. res.json(req.params.id);
  1119. //Le modèle est vide si on ne trouve rien
  1120. });
  1121. }
  1122. });
  1123. });
  1124. }
  1125. ```
  1126. ##Authentification (côté client)
  1127. Nous 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 lauthentification côté client.
  1128. ###Formulaire dauthentification
  1129. Nous allons donc commencer par créer le template du formulaire dauthentification dans notre page `index.html` (je choisis de le placer juste après la liste des 3 derniers posts) :
  1130. ```html
  1131. <div class="sidebar" id="blog_sidebar">
  1132. <!-- Last 3 posts -->
  1133. </div>
  1134. <!-- /*======= Formulaire d'authentification =======*/ -->
  1135. <script type="text/template" id="blog_login_form_template">
  1136. <h3>Login :</h3>
  1137. <input name="email" type="text" placeholder="email"/><br>
  1138. <input name="password" type="password" placeholder="password"/><br>
  1139. <a href="#" class="btn btn-primary">Login</a>
  1140. <a href="#" class="btn btn-inverse">Logoff</a><br>
  1141. <b>{{message}} {{firstName}} {{lastName}}</b>
  1142. </script>
  1143. <form class="container" id="blog_login_form">
  1144. </form>
  1145. ```
  1146. ###Lobjet Backbone.View : Login.View
  1147. Notre composant dauthentification 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, ).
  1148. Le composant devra aussi pouvoir vérifier si lutilisateur est toujours connecté en cas de rafraîchissement de la page.
  1149. ```javascript
  1150. /*======= Authentification =======*/
  1151. window.LoginView = Backbone.View.extend({
  1152. el: $("#blog_login_form"),
  1153. initialize: function() {
  1154. var that = this;
  1155. this.template = $("#blog_login_form_template").html();
  1156. //on vérifie si pas déjà authentifié
  1157. $.ajax({
  1158. type: "GET",
  1159. url: "/alreadyauthenticated",
  1160. error: function(err) {
  1161. console.log(err);
  1162. },
  1163. success: function(dataFromServer) {
  1164. if (dataFromServer.firstName) {
  1165. that.render("Bienvenue", dataFromServer);
  1166. } else {
  1167. that.render("???", {
  1168. firstName : "John",
  1169. lastName : "Doe"
  1170. });
  1171. }
  1172. }
  1173. })
  1174. },
  1175. render: function(message, user) {
  1176. var renderedContent = Mustache.to_html(this.template, {
  1177. message: message,
  1178. firstName : user ? user.firstName : "",
  1179. lastName : user ? user.lastName : ""
  1180. });
  1181. this.$el.html(renderedContent);
  1182. }
  1183. });
  1184. window.loginView = new LoginView();
  1185. /*======= Fin authentification =======*/
  1186. ```
  1187. A linitialisation (`initialize`) la vue va vérifier si lutilisateur 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 lutilisateur est déjà authentifié, la méthode `render` de la vue est appelée avec un message de bienvenue et les informations de lutilisateur ( `that.render("Bienvenue",dataFromServer);` ) dans le cas contraire la méthode `render` est aussi appelée mais avec un message signifiant que lutilisateur nest pas connecté ( `that.render("???",{firstName:"John", lastName:"Doe"});` ).
  1188. ###Ajoutons une gestion des évènements
  1189. Une propriété de lobjet `Backbone.View` permet de gérer les évènements spécifiques à la vue. Si vous vous souvenez, notre template de formulaire ressemble à ceci :
  1190. ```html
  1191. <!-- /*======= Formulaire d'authentification =======*/ -->
  1192. <script type="text/template" id="blog_login_form_template">
  1193. <h3>Login :</h3>
  1194. <input name="email" type="text" placeholder="email"/><br>
  1195. <input name="password" type="password" placeholder="password"/><br>
  1196. <a href="#" class="btn btn-primary">Login</a>
  1197. <a href="#" class="btn btn-inverse">Logoff</a><br>
  1198. <b>{{message}} {{firstName}} {{lastName}}</b>
  1199. </script>
  1200. ```
  1201. Je souhaite pouvoir déclencher des évènements lorsque je clique sur les boutons du formulaire. Pour cela il suffit dajouter à lobjet `Backbone.View` la propriété events :
  1202. ```javascript
  1203. events: {
  1204. "click .btn-primary": "onClickBtnLogin",
  1205. "click .btn-inverse": "onClickBtnLogoff"
  1206. },
  1207. ```
  1208. En fait je demande à mon objet `Backbone.View` dintercepter 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`.
  1209. >>**Remarque** : nous aurions très bien pu affecter des id aux boutons :
  1210. ```html
  1211. <a href="#" id="btnLogIn" class="btn btn-primary">Login</a>
  1212. <a href="#" id="btnLogOff" class="btn btn-inverse">Logoff</a><br>
  1213. ```
  1214. et relier les évènements aux ids :
  1215. ```javascript
  1216. events: {
  1217. "click #btnLogIn" : "onClickBtnLogin",
  1218. "click #btnLogOff" : "onClickBtnLogoff"
  1219. },
  1220. ```
  1221. Il 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 :
  1222. ```javascript
  1223. onClickBtnLogin: function(domEvent) {
  1224. var fields = $("#blog_login_form :input"),
  1225. that = this;
  1226. $.ajax({
  1227. type: "POST",
  1228. url: "/authenticate",
  1229. data: {
  1230. email: fields[0].value,
  1231. password: fields[1].value
  1232. },
  1233. dataType: 'json',
  1234. error: function(err) {
  1235. console.log(err);
  1236. },
  1237. success: function(dataFromServer) {
  1238. if (dataFromServer.infos) {
  1239. that.render(dataFromServer.infos);
  1240. } else {
  1241. if (dataFromServer.error) {
  1242. that.render(dataFromServer.error);
  1243. } else {
  1244. that.render("Bienvenue", dataFromServer);
  1245. }
  1246. }
  1247. }
  1248. });
  1249. },
  1250. onClickBtnLogoff: function() {
  1251. var that = this;
  1252. $.ajax({
  1253. type: "GET",
  1254. url: "/logoff",
  1255. error: function(err) {
  1256. console.log(err);
  1257. },
  1258. success: function(dataFromServer) {
  1259. console.log(dataFromServer);
  1260. that.render("???", {
  1261. firstName: "John",
  1262. lastName: "Doe"
  1263. });
  1264. }
  1265. })
  1266. }
  1267. ```
  1268. ###Vérification
  1269. Si vous avez bien suivi, nous devons avoir au moins 2 utilisateurs en base de données :
  1270. ```javascript
  1271. function addUsers() {
  1272. addUser({
  1273. email : "bob@morane.com",
  1274. password : "backbone",
  1275. isAdmin : true,
  1276. firstName : "Bob",
  1277. lastName : "Morane"
  1278. });
  1279. addUser({
  1280. email : "sam@lepirate.com",
  1281. password : "underscore",
  1282. isAdmin : false,
  1283. firstName : "Sam",
  1284. lastName : "Le Pirate"
  1285. });
  1286. //etc. ...
  1287. }
  1288. ```javascript
  1289. Lançons donc notre page web :
  1290. ![BB](RSRC/07_08_VIEWS.png)\
  1291. Authentifiez vous en tapant nimporte quoi :
  1292. ![BB](RSRC/07_09_VIEWS.png)\
  1293. Vous obtenez le message **"Ouups loupé !!!"**
  1294. Authentifiez vous en utilisant un des utilisateurs existant :
  1295. ![BB](RSRC/07_10_VIEWS.png)\
  1296. Vous obtenez un message de bienvenue.
  1297. Vous pouvez essayer de raffraîchir la page, vous restez connecté.
  1298. Si vous ouvrez un autre navigateur (une autre marque de navigateur pour être sûr de ne pas partager la session), vous vous apercevez quil ne considère pas que vous êtes authentifié :
  1299. ![BB](RSRC/07_11_VIEWS.png)\
  1300. Essayez de vous connecter avec un utilisateur déjà loggé sur une autre session :
  1301. ![BB](RSRC/07_12_VIEWS.png)\
  1302. Vous obtenez le message **"Utilisateur déjà connecté"**
  1303. Vous 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.