├── .02-MAINS-DANS-LE-CAMBOUIS.md.swp
├── .gitignore
├── 00-PREAMBULE.md
├── 01-PRESENTATION.md
├── 02-MAINS-DANS-LE-CAMBOUIS.md
├── 03-1ERS-CONTACTS-AVEC-BACKBONE.md
├── 04-LE-MODELE-OBJET-DE-BACKBONE.md
├── 05-IL-NOUS-FAUT-UN-SERVEUR.md
├── 06-MODELES-ET-COLLECTIONS.md
├── 07-VUES-ET-TEMPLATING.md
├── 08-ROUTEUR.md
├── 09-ORGANISATION-CODE.md
├── 10-SECURISATION.md
├── 11-BACKBONE-SYNC.md
├── 12-BB-COFFEESCRIPT.md
├── 13-AUTRES-FWKS-MVC.md
├── 14-BACKBONE-ET-TYPESCRIPT.md
├── 15-RESSOURCES.md
├── 16-HS-RESTHUB-BB-STACK.md
├── README.md
├── RSRC
├── 01_01_MVC.png
├── 01_02_MVC.png
├── 02_01_ARBO.png
├── 02_02_PAGE.png
├── 02_03_CONSOLE.png
├── 02_04_JQUERY.png
├── 02_05_JQUERY.png
├── 02_06_JQUERY.png
├── 02_07_JQUERY.png
├── 02_08_JQUERY.png
├── 02_09_JQUERY.png
├── 02_10_JQUERY.png
├── 02_11_JQUERY.png
├── 02_12_UNDERSCORE.png
├── 02_13_UNDERSCORE.png
├── 02_14_UNDERSCORE.png
├── 02_15_UNDERSCORE.png
├── 03_01_BB.png
├── 03_02_BB.png
├── 03_03_BB.png
├── 03_04_BB.png
├── 03_05_BB.png
├── 05_01_SERV.png
├── 05_02_SERV.png
├── 05_03_SERV.png
├── 05_04_SERV.png
├── 05_05_SERV.png
├── 05_06_SERV.png
├── 05_07_SERV.png
├── 05_08_SERV.png
├── 06_01_MODS.png
├── 06_02_MODS.png
├── 06_03_MODS.png
├── 06_04_MODS.png
├── 06_05_MODS.png
├── 06_06_MODS.png
├── 06_07_MODS.png
├── 06_08_MODS.png
├── 06_09_MODS.png
├── 06_10_MODS.png
├── 06_11_MODS.png
├── 06_12_MODS.png
├── 06_13_MODS.png
├── 06_14_MODS.png
├── 06_15_MODS.png
├── 06_16_MODS.png
├── 06_17_MODS.png
├── 06_18_MODS.png
├── 06_19_MODS.png
├── 06_20_MODS.png
├── 06_21_MODS.png
├── 06_22_MODS.png
├── 06_23_MODS.png
├── 06_24_MODS.png
├── 06_25_MODS.png
├── 07_01_VIEWS.png
├── 07_02_VIEWS.png
├── 07_03_VIEWS.png
├── 07_04_VIEWS.png
├── 07_05_VIEWS.png
├── 07_06_VIEWS.png
├── 07_07_VIEWS.png
├── 07_08_VIEWS.png
├── 07_09_VIEWS.png
├── 07_10_VIEWS.png
├── 07_11_VIEWS.png
├── 07_12_VIEWS.png
├── 08_01_routeur.png
├── 08_02_routeur.png
├── 08_03_routeur.png
├── 09_01_ORGA.png
├── 10_01_ORGA.png
├── 10_02_ORGA.png
├── 10_03_ORGA.png
├── 10_04_ORGA.png
├── 10_05_ORGA.png
├── 10_06_ORGA.png
├── 10_07_ORGA.png
├── 13_01_MVC.png
├── 13_02_MVC.png
├── 13_03_MVC.png
├── 13_04_MVC.png
├── 13_05_MVC.png
├── 13_06_MVC.png
├── 13_07_MVC.png
└── 16_01_RBS.png
├── backbone.en.douceur.epub
├── backbone.en.douceur.html
├── backbone.en.douceur.pdf
├── epub-metadata.xml
├── epub-title.txt
├── epub.css
├── latex.template.tex
├── makebook.sh
└── style.css
/.02-MAINS-DANS-LE-CAMBOUIS.md.swp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/.02-MAINS-DANS-LE-CAMBOUIS.md.swp
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
--------------------------------------------------------------------------------
/00-PREAMBULE.md:
--------------------------------------------------------------------------------
1 | #Préambule & Remerciements
2 |
3 | J'ai eu la prétention d'écrire un livre, et sur Backbone en plus ! En fait il existe peu de littérature française spécialisée sur des frameworks, qui plus est des frameworks javascript, alors que nos amis Anglo-Saxons écrivent sur Dart, Coffeescript, Backbone, etc.
4 | Au départ ce "bouquin" est un projet un peu fou, puisque j'ai même contacté les éditions Eyrolles (quand je vous disais que j'étais prétentieux ;) ). Et ils ont été d'accord ! Alors vous vous demandez pourquoi, finalement je publie ça de manière open source ?
5 |
6 | Eh bien, écrire un livre est un travail de longue haleine, qui doit se faire dans la durée. D'un autre côté, les technologies, tout particulièrement ce qui gravite autour de javascript, progressent et changent à une allure vertigineuse. Mon constat est que, si je veux écrire tout ce que j'ai en tête, cela ne finira jamais, ou bien le contenu n'aura plus d'intérêt (obsolète) et qu'il me semble plus approprié de livrer déjà ce que j'ai "gratté" et de transformer ce livre en projet open source.
7 |
8 | Ainsi, ceux qui souhaitent se mettre à Backbone peuvent commencer dès maintenant (même si on ne m'a pas attendu, j'imagine qu'un peu de documentation en français devrait faire des heureux). Pour les autres, s'ils souhaitent compléter, corriger, modifier, discuter, je me tiens à leur disposition. C'est pour cela que j'ai publié le contenu sur Github : ainsi vous avez l'opportunité de faire des pull-requests (proposer des modifications) sur le contenu, ou créer des issues pour donner votre avis.
9 |
10 | Je vous attends, j'espère que cela vous sera utile. Je m'adresse à tous les publics (les plus forts n'apprendront rien, mais peuvent contribuer). Ceux qui connaissent déjà Backbone peuvent directement passer au chapitre 04.
11 |
12 | Je tiens à remercier très fortement et tout particulièrement pour leur écoute, leurs conseils et leur relecture :
13 |
14 | - Muriel SHANSEIFAN (Éditions Eyrolles - Responsable éditoriales du secteur informatique)
15 | - Laurène GIBAUD (Éditions Eyrolles - Secteur Informatique)
16 |
17 |
18 | Remerciements aussi pour :
19 |
20 | - [ehsavoie](https://github.com/ehsavoie) : 1ère pull request ;)
21 | - [lodennan](https://github.com/lodennan) : correctifs
22 | - [cbonnissent](https://github.com/cbonnissent) : format epub
23 | - [loicdescotte](https://github.com/loicdescotte) : relecture & corrections
24 | - [hasalex](https://github.com/hasalex) : relecture & corrections (nombreuses)
25 | - [iammichiel](https://github.com/iammichiel) : typo
26 | - [sdeleuze](https://github.com/sdeleuze) : optimisation de code et typo
27 | - [Prestaspirit](https://github.com/Prestaspirit) : corrections
28 | - [lionelB](https://github.com/lionelB) : amélioration de l'epub
29 | - [FlorianBruniaux ](https://github.com/FlorianBruniaux) : update versions, Twitter Bootstrap etc.
30 | - [htulipe](https://github.com/htulipe) : correctifs
31 | - [cedricmessiant](https://github.com/cedricmessiant) : routeur et fixes
32 | - [AlainGourves](https://github.com/AlainGourves) : correctifs
33 | - [jcsuzanne](https://github.com/jcsuzanne) : correctifs
34 | - [brexis](https://github.com/brexis) : correctifs
35 |
36 |
37 | ##Avertissement
38 |
39 | Cet ouvrage est destiné à être purement éducatif. Donc le code n'est pas toujours fait dans les "règles de l'art", mais plutôt avec une "vision" pédagogique. Désolé donc pour les puristes, mais _a priori_ vous n'êtes pas la cible ;). Cependant, je serais ravi de pouvoir inclure vos remarques et bonnes pratiques sous forme de notes dans chacun des chapitres. Donc à vos pull-requests !
40 |
--------------------------------------------------------------------------------
/01-PRESENTATION.md:
--------------------------------------------------------------------------------
1 | #Présentation de Backbone & rappels MVC
2 |
3 | >*Sommaire*
4 |
5 | >>- *A quoi sert Backbone.js ?*
6 | >>- *Qu’est-ce qu’une « Webapp » ?*
7 | >>- *Petit rappel à propos de MVC*
8 |
9 | >*Où nous allons voir pourquoi Backbone existe et quels sont les grands principes qu’il met en œuvre.*
10 |
11 | Ce chapitre est très court, il présente les origines de Backbone.js, le pourquoi de son utilisation, et enfin un rappel sur le patron de conception Modèle-Vue-Contrôleur, essentiel pour la bonne compréhension du framework.
12 |
13 | ##Backbone ? Webapps ? MVC ?
14 |
15 | Backbone est un framework javascript dédié à la création de **Webapps** en mode **"Single Page Application"**. Il implémente le pattern MVC (l'acronyme signifie : Model View Controller / Modèle Vue Contrôleur) mais, et c'est là qu'est la nouveauté, côté client, plus précisément au sein de votre navigateur. Il reproduit les mécanismes des frameworks MVC côté serveur tels Ruby on Rails, CakePHP, Play!>Framework, ASP.Net MVC (avec Razor)…
16 | Backbone a été écrit par Jeremy Ashkenas (le papa de Coffeescript et de Underscore) à l’origine pour ses propres besoins lors du développement du site [DocumentCloud](http://www.documentcloud.org/home)). Son idée était de créer un framework qui permette de structurer ses développements en s’appuyant justement sur MVC.
17 | Mais avant toute chose, faisons quelques petits rappels (ou découvertes ?).
18 |
19 | ###Qu’est-ce qu’une “Webapp” ?
20 |
21 | Une “Webapp” n’a pas la même vocation qu’un site Web même si les technologies mises en œuvre sont les mêmes. Elle a une réelle valeur applicative (gestion de catalogue produits, utilitaires, client mails, agenda, etc.) contrairement au site Web qui le plus souvent est un médium de communication (journaux, blogs, etc.). Assez rapidement, les technologies Web ont été détournées pour tenter de remplacer les applications de gestion “client-riche” classique. Imaginez, le rêve des DSI : plus de déploiement, tout se passe dans le navigateur. Cependant, c’était compter sans les utilisateurs. Je me souviens avoir vu, il y a plus d'une dizaine d’années, une gestion de catalogue, en ASP 3 où, à chaque création d’article, il fallait attendre que toute la liste des articles se recharge. Ce qui en mode texte (sous Dos) prenait 1 minute avant, venait de prendre 3 à 5 minutes dans la vue : 3 à 5 fois plus de temps ! Bravo la productivité ! Ensuite les technologies se sont cherchées longtemps pour tenter de remédier à ce problème : utilisation des applets java (qui existaient depuis un moment), des ActiveX (MS qui veut faire comme Sun) et là on commençait à retomber dans les travers de déploiements compliqués. Puis il y eu Flash, Flex et Silverlight, pas mal du tout il faut l’avouer, mais parallèlement le moteur javascript avait évolué, les navigateurs aussi et avec l’avènement du triptyque HTML5 – CSS3 – Javascript, un nouveau concept est apparu, la “Single Page Application” (probablement boosté par les mobiles, les tablettes… et Steve Jobs refusant que Flash/Air/Flex & Java s’installent sur l’iPhone & l’iPad). Mais qu’est donc une “Single Page Application” ? Il existe moult définitions, je vais donc vous donner la mienne, ensuite ce sera à vous de vous construire votre vision de “l’application Web monopage”.
22 |
23 | Une "Single Page Application" est une application Web qui embarque tous les éléments nécessaires à son fonctionnement dans une seule page HTML. Les scripts javascript liés seront chargés en même temps. Ensuite l’application Web chargera les ressources nécessaires (généralement des données, des images…) à la demande en utilisant Ajax, évitant ainsi tout rechargement de page et procurant une expérience utilisateur proche de celle que nous connaissions en mode “client-serveur”, voire meilleure dans certains cas. Ces webapps nouvelle génération peuvent aussi fonctionner offline en profitant des possibilités des derniers navigateurs (localStorage).
24 |
25 | >>**Remarque** *: ce qui est amusant, c’est que dès 1999 ou 2000, Microsoft avait déjà introduit cette possibilité avec Internet Explorer 4 qui intégrait une applet Java (si si !) qui permettait de faire du Remote Protocol Call d’une page html vers le serveur sans recharger la page et en s’abonnant en javascript à l’évènement de retour (à vérifier, c’est loin, tout ça). Mais ce fut éclipsé par l’apparente simplicité de mise en œuvre des ActiveX (Flash était alors utilisé principalement pour de l’animation, mais permettait aussi ce genre d’artifice).*
26 |
27 | Tout ça c’est bien beau, mais vous savez comme moi qu’un code HTML+JS (+CSS) peut rapidement devenir un plat de spaghettis impossible à maintenir, pour les autres comme pour vous (retournez dans votre code 6 mois plus tard ;)). Il faut donc s’astreindre à des règles et s’équiper des bons outils afin de se faciliter la tâche, ne pas avoir à réinventer la poudre à chaque fois et pouvoir coder des applications robustes facilement modifiables (faciles à corriger, faciles à faire évoluer). Et si en plus vous pouvez vous faire plaisir…
28 |
29 | C’est de ce constat qu’est parti Jeremy Ashkenas, et c’est en mettant en pratique les préceptes depuis longtemps éprouvés de MVC qu’il a conçu Backbone, pour répondre à une problématique existante, ce qui le rend d’autant plus légitime.
30 | Rafraîchissons donc un peu notre mémoire à propos de MVC.
31 |
32 | >>**Remarque** *: pour les lecteurs qui ne connaîtraient pas ce concept, ne refermez pas le livre tout de suite, vous verrez avec les exemples pratiques que le concept est simple et facilement assimilable. Donc si les quelques paragraphes théoriques qui suivent vous semblent obscurs, je vous promets que dans quelques chapitres vous aurez tout compris.*
33 |
34 | ###Petit rappel : MVC ?
35 |
36 | MVC est un pattern (modèle) de programmation utilisé pour développer des applications de manière structurée et organisée. Le pattern MVC, comme tous les patterns, cadre votre façon de développer. Son objectif particulier est de séparer les responsabilités de vos "bouts" de code en les regroupant selon 3 rôles (responsabilités) : le modèle donc, la vue et le contrôleur. Détaillons-les :
37 |
38 | - **la vue** : c'est l'IHM (Interface Homme-Machine), ce qui va apparaître à l'écran de l'utilisateur. Elle va recevoir des infos du contrôleur ("affiche moi ça !"), elle va envoyer des infos au contrôleur ("on m'a cliquée dessus, il me faut la liste des clients")
39 | - **le contrôleur** : c'est lui donc qui reçoit des infos de la vue, qui va aller récupérer des données métiers chez le modèle ("j'ai besoin pour la vue de la liste des clients"), et va les renvoyer à la vue et éventuellement appliquer des traitements à ces données avant de les renvoyer.
40 | - **le modèle** : c'est votre objet client, fournisseur, utilisateur... avec toute la mécanique qui sert à les sauvegarder, les retrouver, modifier, supprimer...
41 |
42 | >>**AVERTISSEMENT 1** *: C’est la lisibilité qui importe. Il peut y avoir des interprétations différentes du modèle MVC quant aux responsabilités de ses composants. Par exemple, est-ce le modèle qui se sauvegarde lui-même ou est-ce un contrôleur qui se chargera de la persistance ? Peu importe (ce sont des querelles de chapelles), gardons juste à l'esprit qu'il y a trois grands modes de classement de nos objets et que l'important est d'avoir un code propre, structuré et maintenable (dans 6 mois, vous devez être capables de relire votre code).*
43 |
44 | >>**AVERTISSEMENT 2** *: La difficulté n’est plus de mise. À l'attention des réfugiés de STRUTS. mon "1er contact" avec MVC a été avec STRUTS. J'ai trouvé l'expérience peu concluante (expérience développeur désastreuse) et je suis retourné faire de l'ASP.Net "à la souris" (c'était avant 2005). Si certains d'entre vous se sont éloignés de la technique et ont des velléités de s'y remettre mais sont effrayés par MVC, je les rassure tout de suite, les développeurs nous ont enfin "concocté" des frameworks simples et faciles à mettre en œuvre tels :*
45 |
46 | >>- *ASP.Net MVC avec le moteur de template Razor*
47 | >>- *Play!>Framework*
48 | >>- *Express.js (avec nodeJS)*
49 | >>- *Et beaucoup d'autres*
50 |
51 | *Backbone.js respecte ce principe. Donc n'ayez pas peur, cela va être facile ;)*
52 |
53 | L'interprétation de MVC par Backbone est un peu particulière et les développeurs Java, .Net, PHP, Ruby, Python, etc. pourraient être surpris. Mais passons donc à quelques explications.
54 |
55 | ##Backbone & MVC
56 |
57 | Backbone "embarque" plusieurs composants qui vont nous permettre d'organiser notre webapp selon les "préceptes" MVC et simplifier les communications avec le serveur (avec le code applicatif côté serveur).
58 |
59 | Voyons l'interprétation que fait Backbone du modèle MVC :
60 |
61 | - Le composant **Modèle** (selon Backbone : `Backbone.Model`) représente les données qui vont interagir avec votre code "backend" (côté serveur) via des requêtes Ajax. Ce sont eux qui auront la responsabilité de la logique métier et de la validation des données.
62 | - Le composant **Vue** (selon Backbone : `Backbone.View`) n'est pas complètement une vue au sens où on l'entend habituellement (couche présentation). Dans le cas qui nous intéresse, la "vraie" vue est un fragment de code "natif" HTML dans la page Web qui s'affiche dans le navigateur (il y a donc plusieurs vues dans une même page). Et `Backbone.View` est en fait un **Contrôleur de vues** (1) (vous verrez, ce sera plus facile à appréhender en le codant) qui va ordonnancer les événements et interactions au sein de la page Web.
63 |
64 | >>*(1) C'est une interprétation très personnelle, c'est discutable, je reste à votre disposition*
65 |
66 | Pour résumer, avec un parallèle avec du MVC dit "classique", nous avons :
67 |
68 | - Modèle : `Backbone.Model`
69 | - Vue : le code HTML
70 | - Contrôleur (de vues) : `Backbone.View`
71 |
72 | **Mais ce n'est pas fini !** Backbone apporte 3 composants supplémentaires :
73 |
74 | - Le composant **Routeur** : `Backbone.Router`, qui écoute/surveille les changements d'URL (dans la barre d'url du navigateur, lors d'un clic sur un lien…) et qui fait le lien avec les `Backbone.Model(s)` et les `Backbone.View(s)`.
75 | - Le composant **Collection** : `Backbone.Collection`, des collections de modèles avec des méthodes pour "travailler" avec ceux-ci (each, filter, map…)
76 | - et enfin le petit dernier, mais non des moindres, Le composant de **Synchronisation** `Backbone.sync`, que l'on pourrait comparer à une couche middleware, qui va permettre à nos modèles de communiquer avec le serveur. C'est `Backbone.sync` qui va faire les requêtes Ajax au serveur et "remonter" les résultats aux modèles.
77 |
78 |
79 | 
80 |
81 | ***Figure 1-1. MVC Vision "Back-End"***
82 |
83 |
84 | 
85 |
86 | ***Figure 1-2. MVC Vision "Front-End"***
87 |
88 |
89 | ##Pourquoi j’ai choisi Backbone ?
90 |
91 | //TODO:
92 |
93 |
94 | Tout ceci vous paraît bien théorique ? Alors passons tout de suite à la pratique.
95 |
96 |
--------------------------------------------------------------------------------
/02-MAINS-DANS-LE-CAMBOUIS.md:
--------------------------------------------------------------------------------
1 | #Tout de suite “les mains dans le cambouis”
2 |
3 | >*Sommaire*
4 |
5 | >>- *Les prérequis & IDE pour faire fonctionner Backbone*
6 | >>- *jQuery en 15 minutes*
7 | >>- *Underscore.js en 10 minutes*
8 |
9 |
10 | >*Où nous allons lister les éléments nécessaires pour installer Backbone et commencer à développer avec.*
11 |
12 | Le plus frustrant lorsque l'on débute la lecture d'un ouvrage informatique dans l'optique de s'auto-former c'est que l'on est obligé de lire de nombreux chapitres avant de pouvoir commencer à s'y mettre. Je vais donc tenter de vous faire faire un 1er tour de Backbone.js en 20 minutes pour que vous en saisissiez rapidement la "substantifique moelle". Mais avant d’utiliser Backbone, quelques prérequis sont nécessaires.
13 |
14 |
15 | ##Prérequis : les dépendances de Backbone
16 |
17 | Backbone a besoin au minimum de deux autres frameworks javascript pour fonctionner :
18 |
19 | - **Underscore.js** par le créateur de Backbone. Underscore est un ensemble d'outils qui permettent d'étendre javascript et qui vont vous faciliter la vie dans la gestion des Collections, Arrays, Objects... mais aussi vous permettre de faire du templating (nous verrons plus loin ce que c'est). Le gros avantage d'Underscore, c'est qu'il fonctionne quel que soit votre navigateur (comme Backbone). Underscore est une dépendance de Backbone, il est donc indispensable.
20 | - **jQuery**, qui est un framework dédié à la manipulation des éléments de votre page HTML (on parlera du DOM, Document Object Model) mais aussi aux appels de type Ajax (nécessaires pour "discuter" avec le serveur). On peut dire que jQuery est un DSL (Domain Specific Language) pour le DOM. jQuery n'est pas indispensable pour faire fonctionner Backbone, mais il va grandement nous faciliter la vie dans la création de nos Webapps et va nous garantir le fonctionnement de notre code quel que soit le navigateur.
21 |
22 | Nous verrons dans quelques chapitres qu'il est tout à fait possible de "marier" d'autres frameworks javascript à Backbone pour :
23 |
24 | - faire du templating (certains peuvent trouver la fonctionnalité de template d'Underscore limitée)
25 | - gérer la persistance locale (localStorage du navigateur)
26 | - ...
27 |
28 | >>**Remarque :** *Il est possible d'utiliser Zepto.js à la place de jQuery, Zepto fonctionne à l'identique de jQuery mais il est dédié principalement aux navigateurs mobiles et est beaucoup plus léger que jQuery (avantageux sur un mobile), cependant vous n'avez plus la garantie que votre code fonctionne dans d'autres navigateurs (Zepto "marchera" très bien sous Chrome, Safari et Firefox).*
29 |
30 | ##Outils de développement
31 |
32 | ###IDE (Éditeur)
33 |
34 | Pour coder choisissez l'éditeur de code avec lequel vous vous sentez le plus à l'aise. Ils ont tous leurs spécificités, ils sont gratuits, open-source ou payants. Certains "puristes" utilisent même Vim ou Emacs. Je vous en livre ici quelques-uns que j'ai trouvés agréables à utiliser si vous n'avez pas déjà fait votre choix :
35 |
36 | - Mon préféré mais payant : WebStorm de chez JetBrains, il possède des fonctionnalités de refactoring très utiles (existe sous Windows, Linux et OSX)
37 | - Dans le même esprit et gratuit : Netbeans, il propose un éditeur HTML/Javascript très pertinent quant à la qualité de votre code (existe sous Windows, Linux et OSX)
38 | - Textmate (payant) un éditeur de texte avec colorisation syntaxique, un classique sous OSX
39 | - SublimeText (payant) un peu l'équivalent de Textmate mais toutes plateformes
40 | - Un bon compromis est KomodoEdit dans sa version communauté (donc non payant) et qui lui aussi fonctionne sur toutes les plateformes.
41 | - Aptana fourni aussi un bon IDE dédié Javascript sur une base Eclipse, mais je trouve qu'il propose finalement trop de fonctionnalités (comme Eclipse), et personnellement je m’y perds.
42 |
43 | Vous voyez, il y en a pour tous les goûts. En ce qui me concerne j'utilise essentiellement WebStorm ou SublimeText.
44 |
45 | ###Navigateur
46 |
47 | Le navigateur le plus agréable à utiliser pour faire du développement Web est, selon moi, certainement Chrome (c'est un avis très personnel, donc amis utilisateurs de Firefox ne m'en veuillez pas). En effet, Chrome propose une console d'administration particulièrement puissante. C'est ce que je vais utiliser, rien ne vous empêche d'utiliser votre navigateur préféré. Par contre, que cela ne vous dispense pas d'aller tester régulièrement votre code sous d'autres navigateurs.
48 |
49 | ##Initialisation de notre projet de travail
50 |
51 | Maintenant que nous sommes “outillés” (un éditeur de code et un navigateur) nous allons pouvoir initialiser notre environnement de développement.
52 |
53 | ###Installation
54 |
55 | - Créer un répertoire de travail `backbone001`
56 | - Créer ensuite un sous-répertoire `libs` avec un sous-répertoire `vendors`
57 |
58 | Nous copierons les frameworks javascript dans `vendors`.
59 |
60 | - Téléchargez **Backbone** : [http://documentcloud.github.com/backbone/](http://documentcloud.github.com/backbone/)
61 | - Téléchargez **Underscore** : [http://documentcloud.github.com/underscore/](http://documentcloud.github.com/underscore/)
62 |
63 |
64 | >>**CONSEIL** *: Utilisez les versions non minifiées des fichiers. Il est toujours intéressant de pouvoir lire le code source des frameworks lorsqu'ils sont bien documentés, ce qui est le cas de Backbone et Underscore. N'hésitez pas à aller mettre le nez dedans, c'est instructif et ces 2 frameworks sont très lisibles, même pour des débutants.*
65 |
66 | - Téléchargez **jQuery** : [http://jquery.com/](http://jquery.com/)
67 |
68 | Nous allons aussi récupérer le framework CSS **Twitter Bootstrap** qui nous permettra de faire de "jolies" pages sans effort. Ce n'est pas du tout obligatoire, mais c'est toujours plus satisfaisant d'avoir une belle page d'exemple. Téléchargez `bootstrap.zip` : [http://twitter.github.com/bootstrap/](http://twitter.github.com/bootstrap/), "dé-zippez" le fichier et copiez le répertoire `bootstrap` dans votre répertoire `vendors`.
69 |
70 | ###Préparons notre page HTML
71 |
72 | À la racine de votre répertoire de travail, créez une page index.html avec le code suivant :
73 |
74 | ```html
75 |
76 |
77 |
78 |
79 | Backbone
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
102 |
103 | ```
104 |
105 | À ce niveau, vous devriez avoir un squelette de projet fonctionnel avec l'arborescence suivante :
106 |
107 | 
108 |
109 |
110 | Les deux paragraphes qui suivent ne sont là que pour ceux d'entre vous qui ne connaissent ni **jQuery** ni **Underscore**. Ces paragraphes n'ont pas la prétention de vous apprendre ces outils, mais vous donneront les bases nécessaires pour vous en servir, pour comprendre leur utilité et pour vous donner envie d'aller plus loin. Les autres (ceux qui connaissent déjà), passez directement au **§ "1er contact… avec Backbone"**.
111 |
112 | ##Jouons avec jQuery
113 |
114 | JQuery est un framework javascript initialement créé par John Resig qui vous permet de prendre le contrôle de votre page HTML. Voyons tout de suite comment nous en servir.
115 | Dans notre toute nouvelle page `index.html`, préparons un peu notre bac à sable et saisissons le code suivant :
116 |
117 | ```html
118 |
119 |
120 |
121 |
122 | Backbone
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
138 |
139 |
140 |
141 |
142 |
143 |
148 |
Backbone : mais y a-t-il vraiment un contrôleur dans l'avion ?
173 |
174 |
175 |
les articles à venir
176 |
177 |
178 |
Backbone et le localstorage
179 |
Backbone.sync : comment ça marche
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
195 |
196 | ```
197 |
198 | Une fois votre page terminée, sauvegardez-la et ouvrez-la dans votre navigateur préféré (qui je le rappelle, pour des raisons purement pédagogique est Chrome) :
199 |
200 |
201 | 
202 |
203 | Notez au passage la qualité graphique de votre page ;), tout ça sans trop d'efforts, grâce à Twitter Bootstrap.
204 |
205 | ###"Jouons" avec notre page en mode commande
206 |
207 | Dans un premier temps, ouvrez la console de Chrome (ou Safari) : faite un clic droit sur la page et sélectionner "Inspect Element" (ou "Inspecter l'élément). Pour les aficionados de Firefox : utilisez les menus : Tools/Web Developer/Web Console. Vous devriez obtenir ceci (cliquez sur le bouton "Console" si nécessaire :
208 |
209 | 
210 |
211 |
212 | ####Saisissons nos 1ères commandes :
213 |
214 |
215 | Je voudrais la liste de mes titres `
` : dans la console, saisir : `$('h1')`, validez, et vous obtenez un tableau (Array au sens javascript) des nodes html de type `
` présents dans votre page html :
216 |
217 | 
218 |
219 |
220 | Je voudrais le texte de l'élément dont l'id est `"current_articles_title"` : dans la console, saisir : `$('#current_articles_title').text()`. L'identifiant étant unique, en fait le type du node est peu important :
221 |
222 | 
223 |
224 |
225 | Mais comment dois-je faire pour avoir le texte du premier `
` de ma page, il n'a pas d'id ?!?. Tout simplement, en utilisant la commande suivante : `$('h1').first().text()` :
226 |
227 | 
228 |
229 |
230 | ####Modifions l’aspect de notre page dynamiquement :
231 |
232 |
233 | Les commandes sont toujours à saisir dans la console du navigateur.
234 | Je voudrais :
235 |
236 | - changer le titre de mon blog : `$('h1').first().text("Backbone c'est top !")`, attention pensez bien au `first()` sinon vous allez changer tous les textes de tous les `H1` de la page.
237 | - récupérer le code HTML de la "boîte de titre" (le div avec la classe css : `class="jumbotron"`) : `$('[class="jumbotron"]').html()`, notez bien que `$('[class="jumbotron"]').text()` ne retourne pas le même résultat. On peut aussi écrire ceci plus simplement : `$('.jumbotron').html()` : le `"."` correspond à une classe CSS, comme le `"#"` permet de rechercher un élément par son id.
238 | - changer les couleurs de police et de fond de tous les tags H1 :
239 |
240 | `$('h1').css("color","white").css("background-color","black")`, vous voyez que vous pouvez faire des appels chaînés, mais une autre possibilité serait la suivante :
241 |
242 | $('h1').css({color:"yellow", backgroundColor:"green"})
243 |
244 | 
245 |
246 |
247 | 
248 |
249 |
250 | ####Allons plus loin…
251 |
252 |
253 | Je voudrais :
254 |
255 | - la valeur de l'id de la deuxième liste (`UL`) : `$('ul').eq(1).attr("id")`, je cherche la liste d'index 1 (le 1er élément possède l'index 0).
256 | - parcourir les lignes (`LI`) de la liste dont l'id est `"next_articles_list"` et obtenir leur texte : `$('#next_articles_list').find('li').each(function (index) { console.log( $(this).text() ); })`
257 | - ajouter une nouvelle ligne à la 2ème liste :
258 |
259 | $('
Templating et Backbone
').appendTo('#next_articles_list')
260 |
261 | - cacher la 1ère liste : `$('#current_articles_list').hide()`
262 | - l'afficher à nouveau : `$('#current_articles_list').show()`
263 | - la cacher à nouveau, mais "doucement" : `$('#current_articles_list').hide('slow')`
264 | - l'afficher à nouveau, mais "rapidement" : `$('#current_articles_list').show('fast')`
265 |
266 | 
267 |
268 |
269 | ###Les événements
270 |
271 | //À traiter ...
272 |
273 | ###Quelques bonnes pratiques
274 |
275 | ####Pensez performances :
276 |
277 |
278 | Si vous devez utiliser plusieurs fois le même élément de votre page : par exemple `$('#current_articles_list')`, sachez qu'à chaque fois jQuery "interroge" le DOM. Pour des raisons de performances, il est conseillé d'affecter le résultat de la sélection à une variable que vous réutiliserez ensuite. De cette manière, le DOM n'est interrogé qu'une seule fois. Vous pouvez tester ceci dans la console :
279 |
280 | ```javascript
281 | var currArtList = $('#current_articles_list');
282 | currArtList.hide('slow');
283 | currArtList.show('fast');
284 | ```
285 |
286 | ####Soyez sûr que les éléments de votre page sont tous chargés :
287 |
288 |
289 | Il est intéressant (indispensable) d'avoir la garantie que son code javascript n'est exécuté qu'une fois la page HTML entièrement chargée, surtout si ce code accède à des éléments du DOM. jQuery a une fonction pour ça : `$(document).ready(handler)` ou encore plus court : `$(handler)` où `handler` est une fonction.
290 | Mettez ce code dans la balise `` et le code source que nous avons écrit au niveau du header (balise ``) de la page, ce qui est plus "classique" et rechargez la page :
305 |
306 | 
307 |
308 |
309 | Et là on voit bien qu'au 1er appel `$('#current_articles_list')` jQuery ne trouve rien, puis une fois le DOM chargé, jQuery trouve la liste. J'ai mis mes codes en bas de page, pour des raisons de performances et c'est pour ça que cela "semblait" fonctionner même à l'extérieur de `$(document).ready(handler)`, les éléments se chargeant plus rapidement, mais ça ne garantit rien, tout particulièrement lorsque votre page n’est plus en local. Donc n'oubliez jamais d'exécuter votre code au bon moment grâce à `$(document).ready(handler)`... Et remettez quand même votre code en bas de page ;).
310 |
311 | Vous venez de voir une infime partie des possibilités de jQuery, mais cela vous donne déjà un aperçu et vous permet de commencer à jouer avec et aller plus loin. jQuery permet aussi de faire des requêtes AJAX (http) vers des serveurs web, mais nous verrons cela un peu plus tard.
312 |
313 | //TODO: traiter la notion d’id versus la notion de name
314 |
315 |
316 | ##Jouons avec Underscore
317 |
318 | Underscore est un framework javascript (par le créateur de Backbone) qui apporte de nombreuses fonctionnalités pour faire des traitements sur des tableaux de valeurs (Array), des collections (tableaux d'objet). Certaines de ces fonctionnalités existent en javascript, mais uniquement dans sa dernière version, alors qu'avec Underscore vous aurez la garantie qu'elles s'exécutent sur tous les navigateurs. Mais Underscore, ce sont aussi des fonctionnalités autour des fonctions et des objets (là aussi, le framework vous procure les possibilités de la dernière version de javascript quel que soit votre navigateur... ou presque, je n'ai pas testé sous IE6) et autres utilitaires, tels le templating. Je vous engage à aller sur le site, la documentation est particulièrement bien faite.
319 |
320 | ###Quelques exemples d'utilisations
321 |
322 | Backbone utilise et encapsule de nombreuses fonctionnalités d'Underscore (Collection, modèle objet…) donc vous n'aurez pas forcément l'obligation d'utiliser Underscore directement. Je vous livre cependant quelques exemples, car cette puissante librairie peut vous aider sur d'autres projets pas forcément dédiés Backbone.
323 | Pour les tester, nous continuons avec la console de notre navigateur (toujours avec notre page index.html).
324 |
325 | ####Tableaux et Collections :
326 |
327 |
328 | Commencez par saisir ceci :
329 |
330 | ```javascript
331 | var buddies = [],
332 | bob = { name : "Bob Morane", age : 32 },
333 | sam = { name : "Sammy", age : 43 },
334 | tom = { name : "Tommy", age : 25 };
335 | buddies.push(bob, sam, tom);
336 | ```
337 |
338 | Nous avons donc un tableau de 3 objets :
339 |
340 | 
341 |
342 |
343 | Je souhaite maintenant parcourir le tableau d’objets et afficher les informations de chacun d’eux. Pour cela utilisez la commande `each()` de la manière suivante :
344 |
345 | ```javascript
346 | _.each(buddies, function (buddy) {
347 | console.log (buddy.name, buddy.age);
348 | });
349 | ```
350 |
351 | Et vous obtiendrez ceci :
352 |
353 | 
354 |
355 |
356 | Je voudrais maintenant les “buddies” dont l’âge est inférieur à 43 ans. Nous allons utiliser la commande `filter()` :
357 |
358 | ```javascript
359 | _.filter(buddies, function (filteredBuddies) {
360 | return filteredBuddies.age < 43;
361 | });
362 | ```
363 |
364 | Et nous obtenons bien :
365 |
366 | 
367 |
368 |
369 | ####Templating :
370 |
371 |
372 | Je vous en parle maintenant, car ce "bijou" va nous servir très rapidement. Je voudrais générer une liste au sens HTML (`
`) à partir de mon tableau d'objets buddies. Nous allons donc créer une variable “template” (un peu comme une page JSP ou ASP) :
373 |
374 | ```html
375 | var templateList =
376 | "
<% _.each(buddies, function (buddy) { %>\
377 |
<%= buddy.name %> : <%= buddy.age %>
\
378 | <% }); %>\
379 |
";
380 | ```
381 |
382 | Que nous utiliserons de cette façon (nous passons à la méthode le template et les données) :
383 |
384 | ```javascript
385 | _.template(templateList, buddies);
386 | ```
387 |
388 | Pour le résultat suivant :
389 |
390 | 
391 |
392 |
393 | Voilà, nous avons fait un rapide tour d’horizon des éléments qui nous seront nécessaires par la suite. Nous pouvons enfin commencer.
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
--------------------------------------------------------------------------------
/03-1ERS-CONTACTS-AVEC-BACKBONE.md:
--------------------------------------------------------------------------------
1 | #1er contact … avec Backbone
2 |
3 | >*Sommaire*
4 |
5 | >>- *Premier modèle*
6 | >>- *Première collection*
7 | >>- *Première vue & premier template*
8 |
9 |
10 | >*Nous allons faire un premier exemple Backbone pas à pas, même sans connaître le framework. Cela va permettre de « désacraliser » la bête et de mettre un peu de liant avec tout ce que nous avons vu précédemment. Puis nous passerons dans le détail tous les composants de Backbone dans les chapitres qui suivront.*
11 |
12 | Voilà, il est temps de s'y mettre. L'application que nous allons réaliser avec Backbone tout au long de cet ouvrage va être un blog, auquel nous ajouterons au fur et à mesure des fonctionnalités pour finalement le transformer en CMS (Content Management System). Je vous l'accorde ce n'est pas très original, mais cela répond à des problématiques classiques (récurrentes ?) dans notre vie "d'informaticien" et cela a le mérite d'avoir un aspect pratique et utile. Notre point de départ va être un blog que nous agrémenterons de fonctionnalités au fil des chapitres.
13 |
14 | ##1ère application Backbone
15 |
16 | Nous allons faire ici un exemple très rapide, sans forcément entrer dans le détail ni mettre en œuvre les bonnes pratiques d'organisation de code. Cet exercice est là pour démontrer la simplicité d'utilisation, et le code devrait être suffisamment simple pour se passer d'explications. Donc, "pas de panique !", laissez-vous guider, dans **15 minutes** vous aurez une 1ère ébauche.
17 |
18 | ###Préparons notre page
19 |
20 | Nous allons utiliser notre même page `index.html`, mais faisons un peu de ménage à l'intérieur avant de commencer :
21 |
22 | ```html
23 |
24 |
25 |
26 |
27 | Backbone
28 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
71 |
72 | ```
73 |
74 | L'essentiel de notre travail va se passer dans la balise `` en bas de page. De quoi avons-nous besoin dans un blog ?
75 |
76 | - Des articles : un ensemble d'articles (ou "posts"), généralement écrits par une seule personne (le blog est personnel, c'est en lui donnant des fonctionnalités multi-utilisateurs que nous nous dirigerons doucement vers un CMS).
77 | - Des commentaires : Il est de bon ton de permettre aux lecteurs du blog de pouvoir commenter les articles.
78 |
79 | Pour le moment nous allons nous concentrer uniquement sur les articles, notre objectif sera le suivant : "Afficher une liste d'articles sur la page principale".
80 |
81 |
82 | ##Le Modèle “Article”
83 |
84 | Dans la balise `` saisissez le code suivant :
85 |
86 | *Définition d’un modèle Article :*
87 |
88 | ```html
89 |
114 | ```
115 |
116 | Sauvegardez, relancez dans le navigateur et allez dans la console :
117 |
118 | - Pour créer un nouvel article : tapez la commande `myFirstArticle = new blog.Article()`
119 | - Pour "voir" le titre de l'article : tapez la commande `myFirstArticle.get("title")`
120 | - Pour "voir" le contenu de l'article : tapez la commande `myFirstArticle.get("content")`
121 | - Pour changer le titre de l'article : tapez la commande `myFirstArticle.set("title","MON TITRE")` ou `myFirstArticle.set({title : "MON TITRE"})`
122 | - Pour changer simultanément le titre et le contenu : tapez la commande `myFirstArticle.set({title : "MON TITRE...", content : "blablabla"})`
123 | - Pour créer un article directement avec un titre et du contenu : tapez la commande `mySecondArticle = new blog.Article({title : "MON AUTRE ARTICLE", content : "lorem ipsum..."})`
124 |
125 |
126 | 
127 |
128 |
129 | Vous venez donc de voir que nous avons défini le modèle article “un peu” comme une classe qui hériterait (`extend`) de la classe `Backbone.Model`, que nous lui avons défini des valeurs par défaut (`defaults`), et affecté une méthode d’initialisation (`initialize`). Et qu’il existe un système de getter et de setter un peu particulier (`model.get(property_name)`, `model.set(property_name, value)`), mais nous verrons ultérieurement dans le détail comment fonctionnent les modèles.
130 |
131 | >>**Remarque** *: le modèle de programmation de Javascript est bien orienté objet, mais n’est pas orienté “classe” comme peut l’être par exemple Java. Cela peut déstabiliser au départ, mais je vous engage à lire [REF VERS ARTICLE] à ce propos.*
132 |
133 | ##La Collection d’Articles
134 |
135 | Nous allons maintenant définir une collection qui nous aidera à gérer nos articles. Donc, à la suite du modèle Article saisissez le code suivant :
136 |
137 | *Définition d’une collection d’articles :*
138 |
139 | ```javascript
140 | /*--- Collection d'articles ---*/
141 |
142 | blog.ArticlesCollection = Backbone.Collection.extend({
143 | model: blog.Article,
144 | initialize: function() {
145 | console.log("Création d'une collection d'articles")
146 | }
147 | });
148 | ```
149 |
150 | >>**Notez** *qu'il faut bien préciser le type de modèle associé à la collection (on pourrait dire que la collection est typée).*
151 |
152 | Sauvegarder, relancer dans le navigateur, retournez à nouveau dans la console et saisissez les commandes suivantes :
153 |
154 | - Création de la collection :
155 |
156 | ```javascript
157 | listeArticles = new blog.ArticlesCollection()
158 | ```
159 |
160 | - Ajout d’articles à la collection :
161 |
162 | ```javascript
163 | listeArticles.add(new blog.Article({ title : "titre1", content : "contenu1" }))
164 | listeArticles.add(new blog.Article({ title : "titre2", content : "contenu2" }))
165 | listeArticles.add(new blog.Article({ title : "titre3", content : "contenu3" }))
166 | ```
167 | Nous venons donc d'ajouter 3 articles à notre collection,
168 |
169 | - Si vous tapez la commande `listeArticles.models` vous obtiendrez un tableau de modèles
170 | - Si vous souhaitez obtenir le titre du 2ème article de la collection, tapez :
171 |
172 | `listeArticles.models[1].get("title")`
173 |
174 | - vous souhaitez parcourir les articles de la collection et afficher leur titre :
175 |
176 | `listeArticles.each(function(article){ console.log (article.get("title")); });`
177 |
178 | >>Cela vous rappelle quelque chose ? Le `each` de Backbone est implémenté grâce à Underscore.
179 |
180 |
181 | 
182 |
183 |
184 | **Maintenant que nous avons de quoi gérer nos données, il est temps de les afficher dans notre page HTML.**
185 |
186 | ##Vue et Template
187 |
188 | Avant toute chose, allons ajouter dans notre code javascript (en bas de la page HTML) le bout de code qui va créer les articles et la collection d'articles pour nous éviter de tout re-saisir à chaque fois. Donc après le code de la collection, ajoutez ceci :
189 |
190 | ```javascript
191 | /*--- bootstrap ---*/
192 | blog.listeArticles = new blog.ArticlesCollection();
193 |
194 | blog.listeArticles.add(new blog.Article({ title : "titre1", content : "contenu1" }));
195 | blog.listeArticles.add(new blog.Article({ title : "titre2", content : "contenu2" }));
196 | blog.listeArticles.add(new blog.Article({ title : "titre3", content : "contenu3" }));
197 | blog.listeArticles.add(new blog.Article({ title : "titre4", content : "contenu4" }));
198 | blog.listeArticles.add(new blog.Article({ title : "titre5", content : "contenu5" }));
199 | ```
200 |
201 | Ensuite dans le code html, ajoutons le template de notre vue et le div dans lequel les données seront affichées :
202 |
203 | ```html
204 | <% _.each(articles, function(article) { %>
205 |
244 |
245 |
246 | ```
247 |
248 | Puis dans le code javascript, à la suite du code de la collection et avant le code de chargement des données (bootstrap), ajoutez le code de la vue Backbone :
249 |
250 | ```javascript
251 | /*--- Vues ---*/
252 | blog.ArticlesView = Backbone.View.extend({
253 | el: $("#articles-collection-container"),
254 |
255 | initialize: function() {
256 | this.template = _.template($("#articles-collection-template").html());
257 | },
258 |
259 | render: function() {
260 | var renderedContent = this.template({
261 | articles: this.collection.toJSON()
262 | });
263 | $(this.el).html(renderedContent);
264 | return this;
265 | }
266 | });
267 | ```
268 |
269 | ###Qu’avons-nous fait ?
270 |
271 | Eh bien, nous avons défini une vue avec :
272 |
273 | - Une propriété `el` (pour élément) à laquelle on “attache” le `
` dont l’id est : `“articles-collection-container”`. C’est dans ce `
` que seront affichés les articles
274 |
275 | - Une méthode `initialize`, qui affecte une méthode `template()` à l’instance de la vue en lui précisant que nous utiliserons le modèle de code html définit dans le `
` dont l’id est `“articles-collection-template”`
276 | - Une méthode `render`, qui va passer les données en paramètre à la méthode `template()` puis les afficher dans la page
277 |
278 | Sauvegarder, relancer dans le navigateur et retournez encore dans la console pour saisir les commandes suivantes :
279 |
280 | - Pour instancier une vue : `articlesView = new blog.ArticlesView({ collection : blog.listeArticles })` à laquelle nous passons la collection d’articles en paramètre
281 | - Pour afficher les données : `articlesView.render()`
282 |
283 | **Et là, la "magie" de Backbone opère, vos articles s'affichent instantanément dans votre page :** :)
284 |
285 |
286 | 
287 |
288 |
289 | >>**Remarque** : Notez bien que la collection doit être transformée en chaîne JSON pour être interprétée dans le template ( `this.template({ articles : this.collection.toJSON() })` ) et que nous avons nommé le paramètre `articles` pour faire le lien avec le template ( `_.each(articles, function(article) {}` ).
290 |
291 | ##Un dernier tour de magie pour clôturer le chapitre d’initiation : “binding”
292 |
293 | A la fin de la méthode `initialize` de la vue, ajoutez le code suivant :
294 |
295 | ```javascript
296 | /*--- binding ---*/
297 | _.bindAll(this, 'render');
298 |
299 | this.collection.bind('change', this.render);
300 | this.collection.bind('add', this.render);
301 | this.collection.bind('remove', this.render);
302 | /*---------------*/
303 | ```
304 |
305 | ###Que venons-nous de faire ?
306 |
307 | Nous venons "d'expliquer" à Backbone, qu'à chaque changement dans la collection, la vue doit rafraîchir son contenu. `_.bindAll` est une méthode d'Underscore ([http://documentcloud.github.com/underscore/#bindAll](http://documentcloud.github.com/underscore/#bindAll)) qui permet de conserver le contexte initial, c'est à dire : quel que soit "l'endroit" d'où l'on appelle la méthode `render`, ce sera bien l'instance de la vue (attachée à `this`) qui sera utilisée.
308 |
309 | //TODO: à expliquer plus simplement
310 |
311 | Une dernière fois, sauvegarder, relancer le navigateur et retournez encore dans la console pour saisir les commandes suivantes :
312 |
313 | - Création de la vue : `articlesView = new blog.ArticlesView({ collection : blog.listeArticles })`
314 | - Afficher les données : `articlesView.render()`
315 | - Ajouter un nouvel article à la collection : `blog.listeArticles.add(new blog.Article({title:"Hello", content:"Hello World"}))`
316 |
317 | **Et là, magique ! : L’affichage s'est actualisé tout seul :**
318 |
319 |
320 | ###Oh la vilaine erreur !!!
321 |
322 | Si vous avez bien suivi, j'ai fait une grossière erreur (je l’ai laissée volontairement, car c'est une erreur que j'ai déjà faite, et il n'est donc pas impossible que d'autres la fassent), la date de publication ne change pas ! En effet, je l'affecte dans les valeurs par défaut qui ne sont "settées" qu'une seule et unique fois lors de la définition de la "pseudo" classe `Backbone.Model`. Il faut donc initialiser la date de publication lors de l'instanciation du modèle, et ce dans la méthode `initialize()`. Modifiez donc le code du modèle de la manière suivante :
323 |
324 | ```javascript
325 | /*--- Modèle article ---*/
326 |
327 | blog.Article = Backbone.Model.extend({ // une "sorte" de classe Article
328 | defaults: { //les valeurs par défaut d'un article
329 | title: "titre de l'article",
330 | content: "contenu de l'article",
331 | //publicationDate : null
332 | },
333 | initialize: function() { // s'exécute à la création d'un article
334 | console.log("Création d'un nouvel article");
335 | this.set("publicationDate", new Date());
336 | }
337 | });
338 | ```
339 |
340 | Refaites les manipulations précédentes, et là (si vous avez laissez suffisamment de temps entre la création des articles), vous pourrez noter que la date est bien mise à jour :
341 |
342 |
343 | 
344 |
345 | 
346 |
347 |
348 | >>**Remarque** : la propriété date n’existe plus dans les valeurs par défaut, elle est créée à l’instanciation du modèle lors de l’appel de `this.set("publicationDate",new Date())` dans la méthode `initialize`. De la même manière, vous pouvez créer à la volée des propriétés _a posteriori_ pour les instances des modèles.
349 |
350 | **Et voilà, l’initiation est terminée. Nous allons pouvoir passer “aux choses sérieuses” et découvrir jusqu’où nous pouvons “pousser” Backbone.**
351 |
352 | ##Code final de l'exemple
353 |
354 | Le code final de votre page devrait ressembler à ceci :
355 |
356 | ```html
357 |
358 |
359 |
360 |
361 | Backbone
362 |
363 |
364 |
365 |
366 |
367 |
373 |
374 |
375 |
376 |
377 |
378 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
492 |
493 | ```
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
--------------------------------------------------------------------------------
/04-LE-MODELE-OBJET-DE-BACKBONE.md:
--------------------------------------------------------------------------------
1 | #Le modèle objet de Backbone
2 |
3 | >*Sommaire*
4 |
5 | >>- *Un petit tour dans le code*
6 | >>- *Une classe de base*
7 | >>- *Héritage*
8 |
9 |
10 | >*Ce qui est souvent déstabilisant pour le développeur Java (PHP, .Net, etc.) c’est le modèle objet de javascript qui diffère du classique modèle orienté « classes » que nous connaissons tous (normalement). De nombreux ouvrages, articles… se sont attaqués au sujet, mais ce n’est pas l’objet de ce chapitre.*
11 |
12 | Je vais vous présenter de quelle façon Backbone gère son « Orientation objet » et comment réutiliser cette fonctionnalité. L’objectif est double : mieux comprendre le fonctionnement de Backbone et vous donner un moyen de faire de l’objet en javascript sans être dépaysé (quelque chose qui ressemble dans sa logique, à ce que vous connaissez déjà).
13 |
14 | ##Un petit tour dans le code
15 |
16 | Si vous avez la curiosité d’aller lire le code de Backbone (je vous engage à le faire, le code est clair et simple et avec le temps très instructif), vous « tomberez » sur une ligne particulièrement intéressante (vers la fin du code source dans `backbone.js` pour ceux qui iront réellement lire le code) :
17 |
18 | ```javascript
19 | // Set up inheritance for the model, collection, and view.
20 | Model.extend = Collection.extend = Router.extend = View.extend = extend;
21 | ```
22 |
23 | Il existe une méthode (privée) `extend` dans Backbone qui permet à un objet d’hériter des membres d’un autre objet, par exemple, si j’écris:
24 |
25 | ```javascript
26 | /*--- Modèle article ---*/
27 |
28 | // une "sorte" de classe Article
29 | var Article = Backbone.Model.extend({
30 |
31 | });
32 | ```
33 |
34 | Je signifie que je crée une “sorte” de classe `Article` qui hérite des fonctionnalités de `Model`. De la même façon je pourrais ensuite définir une autre classe `ArticleSpecial` qui héritera de `Article` (et qui conservera les spécificités (membres de classe) de `Model`):
35 |
36 | ```javascript
37 | var ArticleSpecial = Article.extend({
38 |
39 | });
40 | ```
41 |
42 | Je vous expliquais que la méthode `extend` était privée, Backbone ne l’expose pas directement, mais il est tout à fait possible d’y accéder par un des composants de Backbone, de la façon suivante :
43 |
44 | ```javascript
45 | var Kind = function() {};
46 | Kind.extend = Backbone.Model.extend;
47 | ```
48 |
49 | >>**Remarque 1** : J’ai utilisé « Kind » pour ne pas utiliser « Class » ou « class » qui est un terme réservé pour les futures versions de javascript.
50 |
51 | >>**Remarque 2** : Je vais utiliser du français dans mon code. Je sais que c’est moche, promis j’essaye de ne plus le faire (à part dans les commentaires).
52 |
53 | Nous pouvons donc maintenant écrire :
54 |
55 | ```javascript
56 | var Personne = Kind.extend({});
57 | ```
58 |
59 | ##1ère "classe"
60 |
61 | Voyons donc ce que nous apporte le modèle objet de Backbone.
62 |
63 | ###Un constructeur
64 |
65 | La déclaration d’un constructeur se fait avec le mot clé `constructor` :
66 |
67 | Utilisation de `Kind.extend()` et définition de `constructor()`
68 |
69 | ```javascript
70 | var Personne = Kind.extend({
71 | constructor: function() {
72 | console.log("Bonjour, je suis le constructeur de Personne");
73 | }
74 | });
75 |
76 | var bob = new Personne();
77 | ```
78 |
79 | Nous obtiendrons à l’exécution :
80 |
81 | Bonjour, je suis le constructeur de Personne
82 |
83 | ###Des propriétés
84 |
85 | Les propriétés se déclarent dans le constructeur (elles sont générées à l’exécution)
86 |
87 | Ajout de propriétés
88 |
89 | ```javascript
90 | var Personne = Kind.extend({
91 |
92 | constructor: function(prenom, nom) {
93 | this.prenom = "John";
94 | this.nom = "Doe";
95 | if (prenom) {
96 | this.prenom = prenom;
97 | }
98 | if (nom) {
99 | this.nom = nom;
100 | }
101 |
102 | console.log("Bonjour, je suis ", this.prenom, this.nom);
103 | }
104 | });
105 |
106 | var john = new Personne();
107 | var bob = new Personne("Bob", "Morane");
108 | ```
109 |
110 | **Warning:** ne jamais définir les propriétés en dehors du `constructor`
111 |
112 |
113 | Nous obtiendrons à l’éxécution :
114 |
115 | Bonjour, je suis John Doe
116 | Bonjour, je suis Bob Morane
117 |
118 |
119 | ###Des méthodes
120 |
121 | Les méthodes se déclarent de la même façon que le constructeur, ajoutons une méthode `bonjour()` :
122 |
123 | Ajout d’une méthode
124 |
125 | ```javascript
126 | var Personne = Kind.extend({
127 |
128 | constructor: function(prenom, nom) {
129 | this.prenom = "John";
130 | this.nom = "Doe";
131 | if (prenom) {
132 | this.prenom = prenom;
133 | }
134 | if (nom) {
135 | this.nom = nom;
136 | }
137 | },
138 | bonjour: function() {
139 | console.log("Bonjour, je suis ", this.prenom, this.nom);
140 | }
141 |
142 | });
143 |
144 | var john = new Personne();
145 | var bob = new Personne("Bob", "Morane");
146 |
147 | john.bonjour();
148 | bob.bonjour();
149 | ```
150 |
151 | Nous obtiendrons à l’exécution :
152 |
153 | Bonjour, je suis John Doe
154 | Bonjour, je suis Bob Morane
155 |
156 | ###Des membres statiques
157 |
158 | La méthode `extend` accepte un deuxième paramètre qui permet de déclarer des membres statiques :
159 |
160 | Ajout & utilisation de membres statiques
161 |
162 | ```javascript
163 | var Personne = Kind.extend({
164 |
165 | constructor: function(prenom, nom) {
166 | this.prenom = "John";
167 | this.nom = "Doe";
168 | if (prenom) {
169 | this.prenom = prenom;
170 | }
171 | if (nom) {
172 | this.nom = nom;
173 | }
174 |
175 | //Utilisation de la propriété statique
176 | Personne.compteur += 1;
177 | },
178 | bonjour: function() {
179 | console.log("Bonjour, je suis ", this.prenom, this.nom);
180 | }
181 |
182 | }, { //ici les membres statiques
183 | compteur: 0,
184 | combien: function() {
185 | return Personne.compteur;
186 | }
187 | });
188 |
189 | var john = new Personne();
190 | var bob = new Personne("Bob", "Morane");
191 |
192 | console.log("Il y a ", Personne.combien(), " personnes");
193 | ```
194 |
195 | Nous avons donc une propriété statique `compteur` et une méthode statique `combien()`. Nous obtiendrons ceci à l’exécution :
196 |
197 | Il y a 2 personnes
198 |
199 | ##Sans héritage point de salut ! … ?
200 |
201 | Même s’il ne faut pas abuser de l’héritage en programmation objet (mais c’est un autre débat), il faut avouer que cela peut être pratique pour la structuration de notre code. Dès le départ, dans ce chapitre nous avons fait de l’héritage :
202 |
203 | var Personne = Kind.extend({ });
204 |
205 | Donc `Personne` hérite de `Kind`. Mais essayons un exemple plus complet pour bien appréhender les possibilités de Backbone. `Personne` héritant de `Kind` possède donc aussi une méthode `extend`, nous allons donc pouvoir créer une `Femme` et un `Homme` :
206 |
207 | ```javascript
208 | var Homme = Personne.extend({
209 | getSexe: function() { return "male"; }
210 | });
211 |
212 | var Femme = Personne.extend({
213 | getSexe: function() { return "femelle"; }
214 | });
215 |
216 | var angelina = new Femme("Angelina", "Jolie");
217 | var bob = new Homme("Bob", "Morane");
218 |
219 | angelina.bonjour();
220 | bob.bonjour();
221 |
222 | console.log("Il y a ", Personne.combien(), " personnes");
223 | ```
224 |
225 | A l’exécution nous obtiendrons ceci :
226 |
227 | Bonjour, je suis Angelina Jolie
228 | Bonjour, je suis Bob Morane
229 | Il y a 2 personnes
230 |
231 | Nous pouvons donc vérifier que l’on a bien hérité de la méthode `bonjour()`, du constructeur `constructor()` et de `nom` et `prenom` (ainsi que de leurs valeurs par défaut). Nous remarquons aussi que l’incrémentation des “personnes” continue puisque `Homme` et `Femme` héritent de `Personne`.
232 | Voyons maintenant, comment surcharger les méthodes du parent et continuer à appeler les méthodes du parent.
233 |
234 |
235 | ##Surcharge & super
236 |
237 | Modifions le code des “pseudo-classes” de la façon suivante :
238 |
239 | Surcharge et utilisation de `super()`
240 |
241 | ```javascript
242 | var Homme = Personne.extend({
243 | getSexe: function() { return "male"; },
244 | //surcharge du constructeur
245 | constructor: function(prenom, nom) {
246 | //appeler le constructeur de Personne
247 | Homme.__super__.constructor.call(this, prenom, nom);
248 | console.log("Hello, je suis un ", this.getSexe());
249 | },
250 | bonjour: function() {
251 | //appeler la methode bonjour() du parent
252 | Homme.__super__.bonjour.call(this);
253 | console.log("Bonjour, je suis un garçon");
254 | }
255 | });
256 |
257 | var Femme = Personne.extend({
258 | getSexe: function() { return "femelle"; },
259 | //surcharge du constructeur
260 | constructor: function(prenom, nom) {
261 | //appeler le constructeur de Personne
262 | Femme.__super__.constructor.call(this, prenom, nom);
263 | console.log("Hello, je suis une ", this.getSexe());
264 | },
265 | bonjour: function() {
266 | //appeler la methode bonjour() du parent
267 | Femme.__super__.bonjour.call(this);
268 | console.log("Bonjour, je suis une fille");
269 | }
270 | });
271 |
272 | var angelina = new Femme("Angelina", "Jolie");
273 | var bob = new Homme("Bob", "Morane");
274 |
275 | angelina.bonjour();
276 | bob.bonjour();
277 | ```
278 |
279 | Nous avons surchargé les constructeurs pour pouvoir afficher un message au moment de l’instanciation et nous avons appelé le constructeur du parent pour continuer à permettre l’affectation de `nom` et `prenom`. Nous avons appliqué le même principe pour la méthode `bonjour()`. Donc l’appel d’une méthode du parent se fait de la manière suivante : `Nom_de_la_classe_courante.__super__.methode.call(this, paramètres)`.
280 |
281 | A l’exécution nous obtiendrons donc :
282 |
283 | Hello, je suis une femelle
284 | Hello, je suis un male
285 | Bonjour, je suis Angelina Jolie
286 | Bonjour, je suis une fille
287 | Bonjour, je suis Bob Morane
288 | Bonjour, je suis un garçon
289 |
290 | ##Conclusion
291 |
292 | Nous venons de voir comment continuer à programmer objet sans trop bouleverser vos habitudes (cela ne doit pas vous empêcher d’étudier le modèle objet de javascript plus en profondeur). Vous pourrez mieux structurer votre code (et en javascript, c’est important) mais aussi vos idées, mieux comprendre le fonctionnement de Backbone, et aussi écrire des extensions à Backbone plus facilement.
293 |
294 |
295 |
--------------------------------------------------------------------------------
/05-IL-NOUS-FAUT-UN-SERVEUR.md:
--------------------------------------------------------------------------------
1 | #Il nous faut un serveur !
2 |
3 | >*Sommaire*
4 |
5 | >>- *Petit rappel sur les requêtes http*
6 | >>- *Installations des composants nécessaires*
7 | >>- *Construction et test de notre serveur d’application*
8 |
9 |
10 | >*Faisons un dernier détour avant de revenir à Backbone. Pour bien en comprendre le fonctionnement, nous allons nous mettre en situation réelle. Ne laissons pas notre future webapp toute seule. Généralement une application web comporte une partie serveur qui sert à distribuer des données vers l’IHM client (dans le navigateur). Pour que le tour de Backbone.js soit complet, il serait impensable de ne pas étudier les interaction avec un serveur (interrogation de données, Ajax…)*
11 |
12 | Pour ce chapitre, je me suis longuement posé la question : « quelle technologie serveur utiliser ? ». PHP, Ruby, Java, .Net… ? C’était aussi prendre le risque de vous désintéresser complètement. D’un autre côté, je souhaitais que vous puissiez rapidement entrer dans le vif du sujet. J’ai donc finalement choisi de vous faire utiliser Node.js puisque c’est aussi du Javascript et que sa mise en œuvre est rapide sans pour autant être obligé de connaître Node.js dans son ensemble. **Objectif : disposer d’un serveur d’application fournissant des services JSON en moins d’une demi-heure !**
13 |
14 | Nous aurons besoin de :
15 |
16 | - **Node.js** pour le serveur d’application
17 | - **Express.js** qui va nous permettre de construire notre application (gestion des routes, sessions, etc.)
18 | - **nStore** : une petite base de données noSQL simulée avec un fichier texte (nous n’allons pas nous embêter à faire des requêtes SQL, et le noSQL est à la mode ;) )
19 |
20 | ##Principes http : GET, POST, PUT, DELETE
21 |
22 | Mon but est de faire une application web avec Node & Express sur les principes REST (Representational State Transfer) qui permettra de faire des opérations de type **CRUD** avec les modèles Backbone en utilisant des services basés sur le protocole http avec les verbes suivants :
23 |
24 | - **C**reate : POST
25 | - **R**ead : GET
26 | - **U**pdate : PUT
27 | - **D**elete : DELETE
28 |
29 | Si cela vous paraît obscur, pas d'inquiétude, la partie pratique qui suit devrait vous éclairer. Mais je vous engage fortement à lire [http://naholyr.fr/2011/08/ecrire-service-rest-nodejs-express-partie-1/](http://naholyr.fr/2011/08/ecrire-service-rest-nodejs-express-partie-1/) de [@naholyr](https://twitter.com/naholyr).
30 |
31 | //TODO: vois si ça nécessite d'être développé
32 |
33 | ##Installation(s)
34 |
35 | ###Installer Node.js
36 |
37 | Tout d’abord, allez sur le site [http://nodejs.org/](http://nodejs.org/), si vous êtes sous OSX ou Windows, vous avez de la chance, il existe des installeurs tout prêts, si vous êtes sous Linux, les manipulations ne sont pas compliquées, je vous engage à lire ceci : [https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager).
38 | Une fois les installeurs utilisés (ou les manipulations Linux) vous disposerez de Node.js ainsi que du gestionnaire de paquets NPM qui va nous permettre d’installer de nombreux modules pour Node.js.
39 |
40 | ###Installer Express.js
41 |
42 | Express.js est un framework qui se greffe sur Node.js et vous permet de réaliser rapidement des applications web, en vous apportant quelques facilités comme la gestion des routes, des sessions, etc.
43 |
44 | Commençons à créer notre application serveur. Créez un répertoire `blog` sur votre disque dur.
45 | Quel que soit votre système d’exploitation, ouvrez une console ou un terminal et tapez les commandes suivantes (et validez):
46 |
47 | ```
48 | cd blog
49 | npm install express
50 | ```
51 |
52 | >>**Remarque 1** : sous OSX ou linux vous devrez probablement passer en mode super utilisateur, faites donc précéder la commande par `sudo` : `sudo npm install express`
53 |
54 | Nous venons donc d'installer le module **express** dans notre répertoire `blog`.
55 |
56 | >>**Remarque 2** : il y a d'autres méthodes pour créer une application avec express, mais dans notre cas j'ai besoin du strict minimum.
57 |
58 |
59 | ###Installer nStore
60 |
61 | Nous aurons besoin d’un moyen de sauvegarder de nos données. Pour cela nous allons utiliser **nStore** qui est une sorte de base de données NoSQL clé/valeur pour node.js (il existe de nombreuses autres solutions telles MongoDB, CouchDB, des bases de donnée relationnelles… mais ce n’est pas l’objet de cet ouvrage).
62 | Pour installer nStore (toujours dans le répertoire `blog`) tapez la commande suivante :
63 |
64 | ```
65 | npm install nstore
66 | ```
67 |
68 | >>**Remarque** : vous pouvez noter maintenant la présence d’un répertoire `node_modules` dans `blog`, contenant lui-même deux sous-répertoires `express` et `nstore`.
69 |
70 | Voilà, nous avons tout ce qu'il faut pour commencer à créer notre application.
71 |
72 | ##Codons notre application serveur
73 |
74 | Notre application se découpe en 2 parties :
75 |
76 | - une partie statique, c'est là que viendra tout ce "qui touche" à Backbone
77 | - une partie dynamique, notre serveur, ce sera notre "fournisseur" de données
78 |
79 | ###Ressources statiques
80 |
81 | Tout d’abord vous devez créer un sous-répertoire `public`, où nous allons copier les ressources statiques de notre application. Pour cela, utilisez les fichiers dont nous nous sommes servis pour notre exemple de découverte, et copiez les fichiers ci-dessous dans le répertoire `public` :
82 |
83 | - `libs/vendors/backbone.js`
84 | - `libs/vendors/underscore.js`
85 | - `libs/vendors/jquery-1.7.2.js` *(ou une version plus récente)*
86 |
87 | Et copiez aussi le répertoire `bootstrap` de notre exemple. Ensuite, préparez une page `index.html` dans le répertoire `public`, avec le code suivant :
88 |
89 | ```html
90 |
91 |
92 |
93 |
94 | Backbone
95 |
96 |
97 |
98 |
99 |
105 |
106 |
107 |
108 |
109 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
133 |
134 | ```
135 |
136 | ###Ressources dynamiques
137 |
138 | Toujours dans le répertoire `public`, créez un fichier `app.js` qui sera le programme principal de notre application et qui contiendra le code suivant :
139 |
140 | >>**Remarque** : ce n’est pas grave si vous ne comprenez pas le code à ce stade, l’important c’est que cela fonctionne. Mais vous allez voir, à l’utilisation « tout s’éclaire ».
141 |
142 | Rapidement, le code serveur comporte :
143 |
144 | - l'initialisation de la base de données des posts et celle des users
145 | - 6 routes :
146 |
147 | - `'/blogposts'` (`GET`) : pour récupérer tous les posts du blog
148 | - `'/blogposts/query/:query'` (`GET`) : pour récupérer certains posts du blog
149 | - `'/blogposts/:id'` (`GET`) : pour récupérer un post
150 | - `'/blogposts` (`POST`) : pour créer un nouveau post
151 | - `'/blogposts/:id'` (`PUT`) : pour modifier un post existant
152 | - `'/blogposts/:id'` (`DELETE`) : pour supprimer un post
153 |
154 | ```javascript
155 | /*--------------------------------------------
156 | Déclaration des librairies
157 | --------------------------------------------*/
158 | var express = require('express'),
159 | nStore = require('nstore'),
160 | app = module.exports = express();
161 |
162 | nStore = nStore.extend(require('nstore/query')());
163 |
164 | /*--------------------------------------------
165 | Paramétrages de fonctionnement d'Express
166 | --------------------------------------------*/
167 | app.use(express.json());
168 | app.use(express.urlencoded());
169 | app.use(express.methodOverride());
170 | app.use(express.static(__dirname + '/public'));
171 | app.use(express.cookieParser('ilovebackbone'));
172 | app.use(express.session({
173 | secret: "ilovebackbone"
174 | }));
175 |
176 | /*--------------------------------------------
177 | Définition des "bases" posts & users
178 | --------------------------------------------*/
179 | var posts, users;
180 |
181 | posts = nStore.new("blog.db", function() {
182 | users = nStore.new("users.db", function() {
183 | /*
184 | une fois les bases ouvertes, on passe
185 | en attente de requête http (cf. code de
186 | la fonction Routes())
187 | Si les bases n'existent pas,
188 | elles sont crées automatiquement
189 | */
190 | Routes();
191 | app.listen(3000);
192 | console.log('Express app started on port 3000');
193 |
194 | });
195 | });
196 |
197 | function Routes() {
198 | /*
199 | Obtenir la liste de tous les posts lorsque
200 | l'on appelle http://localhost:3000/blogposts
201 | en mode GET
202 | */
203 | app.get('/blogposts', function(req, res) {
204 | console.log("GET (ALL) : /blogposts");
205 | posts.all(function(err, results) {
206 | if (err) {
207 | console.log("Erreur : ", err);
208 | res.json(err);
209 | } else {
210 | var posts = [];
211 | for (var key in results) {
212 | var post = results[key];
213 | post.id = key;
214 | posts.push(post);
215 | }
216 | res.json(posts);
217 | }
218 | });
219 | });
220 |
221 | /*
222 | Obtenir la liste de tous les posts correspondant à un critère
223 | lorsque l'on appelle http://localhost:3000/blogposts/query/ en
224 | mode GET avec une requête en paramètre
225 | ex : query : { "title" : "Mon 1er post"} }
226 | */
227 | app.get('/blogposts/query/:query', function(req, res) {
228 | console.log("GET (QUERY) : /blogposts/query/" + req.params.query);
229 |
230 | posts.find(JSON.parse(req.params.query), function(err, results) {
231 | if (err) {
232 | console.log("Erreur : ", err);
233 | res.json(err);
234 | } else {
235 | var posts = [];
236 | for (var key in results) {
237 | var post = results[key];
238 | post.id = key;
239 | posts.push(post);
240 | }
241 | res.json(posts);
242 | }
243 | });
244 |
245 | });
246 |
247 | /*
248 | Retrouver un post par sa clé unique lorsque
249 | l'on appelle http://localhost:3000/blogposts/identifiant_du_post
250 | en mode GET
251 | */
252 |
253 | app.get('/blogposts/:id', function(req, res) {
254 | console.log("GET : /blogposts/" + req.params.id);
255 | posts.get(req.params.id, function(err, post, key) {
256 | if (err) {
257 | console.log("Erreur : ", err);
258 | res.json(err);
259 |
260 | } else {
261 | post.id = key;
262 | res.json(post);
263 | }
264 | });
265 | });
266 |
267 | /*
268 | Créer un nouveau post lorsque
269 | l'on appelle http://localhost:3000/blogpost
270 | avec en paramètre le post au format JSON
271 | en mode POST
272 | */
273 | app.post('/blogposts', function(req, res) {
274 | console.log("POST CREATE ", req.body);
275 |
276 | var d = new Date(),
277 | model = req.body;
278 | model.saveDate = (d.valueOf());
279 |
280 | posts.save(null, model, function(err, key) {
281 | if (err) {
282 | console.log("Erreur : ", err);
283 | res.json(err);
284 | } else {
285 | model.id = key;
286 | res.json(model);
287 | }
288 | });
289 | });
290 |
291 |
292 | /*
293 | Mettre à jour un post lorsque
294 | l'on appelle http://localhost:3000/blogpost
295 | avec en paramètre le post au format JSON
296 | en mode PUT
297 | */
298 | app.put('/blogposts/:id', function(req, res) {
299 | console.log("PUT UPDATE", req.body, req.params.id);
300 |
301 | var d = new Date(),
302 | model = req.body;
303 | model.saveDate = (d.valueOf());
304 |
305 | posts.save(req.params.id, model, function(err, key) {
306 | if (err) {
307 | console.log("Erreur : ", err);
308 | res.json(err);
309 | } else {
310 | res.json(model);
311 | }
312 | });
313 | });
314 |
315 | /*
316 | supprimer un post par sa clé unique lorsque
317 | l'on appelle http://localhost:3000/blogpost/identifiant_du_post
318 | en mode DELETE
319 | */
320 | app.delete('/blogposts/:id', function(req, res) {
321 | console.log("DELETE : /delete/" + req.params.id);
322 |
323 | posts.remove(req.params.id, function(err) {
324 | if (err) {
325 | console.log("Erreur : ", err);
326 | res.json(err);
327 | } else {
328 | //petit correctif de contournement (bug ds nStore) :
329 | //ré-ouvrir la base lorsque la suppression a été faite
330 | posts = nStore.new("blog.db", function() {
331 | res.json(req.params.id);
332 | //Le modèle est vide si on ne trouve rien
333 | });
334 | }
335 | });
336 | });
337 | }
338 | ```
339 |
340 | Maintenant, nous allons faire un dernier travail avant de revenir à Backbone : Nous allons vérifier que notre serveur d’application fonctionne. Pour le lancer : en mode console, allez dans le répertoire `blog` et lancez la commande `node app.js `.
341 |
342 | >>**Astuce :** plutôt que de devoir arrêter et relancer votre application à chaque modification, installez **nodemon** : `npm install -g nodemon`, dorénavant pour lancer votre application web, tapez `nodemon app.js` au lieu de `node app.js`, et elle se relancera toute seule à chaque fois que nodemon détectera un changement dans vos fichiers (au moment de la sauvegarde).
343 |
344 | ##Testons notre application serveur
345 |
346 | Une fois notre application lancée, ouvrez un navigateur, appelez l’url [http://localhost:3000](http://localhost:3000) et ouvrez la console de debug du navigateur. Et c'est parti pour les tests, où nous allons utiliser intensivement les fonctionnalité Ajax de jQuery. Rappelez vous, nous l'avons inclus(e) dans notre projet, via notre page `index.html`, et au début de notre code dans `app.js`, nous avons la ligne suivante : `app.use(express.static(__dirname + '/public'));`, donc si à l'appel de [http://localhost:3000](http://localhost:3000), le serveur ne trouve pas de route `"/"`, il nous dirigera directement vers `index.html`.
347 |
348 | ###Ajoutons un enregistrement
349 |
350 | Dans la console tapez ceci (et validez) :
351 |
352 | *Requête http de type POST :*
353 |
354 | ```javascript
355 | $.ajax({
356 | type: "POST",
357 | url: "/blogposts",
358 | data: {
359 | title: "My 1st post",
360 | message: "Once upon a time..."
361 | },
362 | dataType: 'json',
363 | error: function(err) {
364 | console.log(err);
365 | },
366 | success: function(dataFromServer) {
367 | console.log(dataFromServer);
368 | }
369 | })
370 | ```
371 |
372 | Vous venez donc de créer votre tout 1er post (vous avez appelé la route `'/blogposts'` de type `POST`), et vous devriez obtenir ceci dans la console du navigateur :
373 |
374 | 
375 |
376 |
377 | Vous noterez que vous obtenez en retour le numéro de clé unique de votre enregistrement (généré par la base nStore) et aussi une date de sauvegarde. **Renouvelez l’opération plusieurs fois pour ajouter plusieurs enregistrements** *(si, si, faites-le)*.
378 | De même dans le terminal, vous noterez l’apparition du message `POST CREATE`, nous avons donc bien appelé la “route” `/blogposts` de type `POST` :
379 |
380 | 
381 |
382 |
383 | ###Obtenir tous les enregistrements
384 |
385 | Dans la console, tapez ceci :
386 |
387 | *Requête http de type GET :*
388 |
389 | ```javascript
390 | $.ajax({
391 | type: "GET",
392 | url: "/blogposts",
393 | error: function(err) {
394 | console.log(err);
395 | },
396 | success: function(dataFromServer) {
397 | console.log(dataFromServer);
398 | }
399 | })
400 | ```
401 |
402 | Vous obtenez un tableau d’objets correspondant à nos enregistrements :
403 |
404 | 
405 |
406 |
407 | De même dans le terminal, vous noterez l’apparition du message `GET (ALL)`, nous avons donc bien appelé la “route” `/blogposts` de type `GET` :
408 |
409 | 
410 |
411 |
412 | ###Retrouver un enregistrement particulier (par sa clé)
413 |
414 | Dans la console, tapez ceci (vous remarquerez que j’utilise une des clés d’enregistrement):
415 |
416 | *Requête http de type GET :*
417 |
418 | ```javascript
419 | $.ajax({
420 | type: "GET",
421 | url: "/blogposts/2o03macl",
422 | error: function(err) {
423 | console.log(err);
424 | },
425 | success: function(dataFromServer) {
426 | console.log(dataFromServer);
427 | }
428 | })
429 | ```
430 |
431 | Vous obtenez :
432 |
433 | 
434 |
435 |
436 | De même dans le terminal, vous obtiendrez le message `GET : /blogposts/2o03macl`, nous avons donc bien appelé la “route” `/blogposts` de type `GET` avec la clé du modèle en paramètre.
437 |
438 | ###Mettre à jour un enregistrement
439 |
440 | Dans la console, tapez ceci :
441 |
442 | *Requête http de type PUT :*
443 |
444 | ```javascript
445 | $.ajax({
446 | type: "PUT",
447 | url: "/blogposts/2o03macl",
448 | data: {
449 | title: "My 3rd post",
450 | message: "Red is really cute"
451 | },
452 | dataType: 'json',
453 | error: function(err) {
454 | console.log(err);
455 | },
456 | success: function(dataFromServer) {
457 | console.log(dataFromServer);
458 | }
459 | })
460 | ```
461 |
462 | Puis appelez à nouveau pour vérifier :
463 |
464 | *Requête http de type GET :*
465 |
466 | ```javascript
467 | $.ajax({
468 | type: "GET",
469 | url: "/blogposts/2o03macl",
470 | error: function(err) {
471 | console.log(err);
472 | },
473 | success: function(dataFromServer) {
474 | console.log(dataFromServer);
475 | }
476 | })
477 | ```
478 |
479 | La mise à jour a bien été prise en compte :
480 |
481 | 
482 |
483 |
484 | De même, vous pouvez vérifier dans le terminal, l’apparition des messages correspondant à chacune de nos requêtes.
485 |
486 | ###Faire une requête
487 |
488 | Dans la console, tapez la commande Ajax ci-dessous *(je veux les posts dont le titre est égal à “My 3rd post”)* :
489 |
490 | *Requête http de type GET :*
491 |
492 | ```javascript
493 | $.ajax({
494 | type: "GET",
495 | url: '/blogposts/query/{ "title" : "My 3rd post"}',
496 | error: function(err) {
497 | console.log(err);
498 | },
499 | success: function(dataFromServer) {
500 | console.log(dataFromServer);
501 | }
502 | })
503 | ```
504 |
505 | Vous obtenez :
506 |
507 | 
508 |
509 |
510 | Une fois de plus, vous pouvez vérifier dans le terminal, l’apparition du message `GET (QUERY)`, nous avons donc bien appelé la “route” `/blogposts` de type `GET` (avec les paramètres de requête en paramètres).
511 |
512 | ###Supprimer un enregistrement
513 |
514 | Supprimons l'enregistrement qui a la clé d'id égale à `2o03macl` *(chez vous c'est sans doute autre chose)*. Dans la console, tapez ceci :
515 |
516 | *Requête http de type DELETE :*
517 |
518 | ```javascript
519 | $.ajax({
520 | type: "DELETE",
521 | url: "/blogposts/2o03macl",
522 | error: function(err) {
523 | console.log(err);
524 | },
525 | success: function(dataFromServer) {
526 | console.log(dataFromServer);
527 | }
528 | })
529 | ```
530 |
531 | Puis recherchez à nouveau l’enregistrement :
532 |
533 | *Requête http de type GET :*
534 |
535 | ```javascript
536 | $.ajax({
537 | type: "GET",
538 | url: "/blogposts/2o03macl",
539 | error: function(err) {
540 | console.log(err);
541 | },
542 | success: function(dataFromServer) {
543 | console.log(dataFromServer);
544 | }
545 | })
546 | ```
547 |
548 | Vous obtenez un objet vide :
549 |
550 | 
551 |
552 | De même dans le terminal, vous noterez l’apparition du message `DELETE` puis `GET`, nous avons donc bien appelé la “route” `/blogpost` de type `DELETE` (puis `GET`) avec la clé du modèle en paramètre. Puis un message d’erreur apparaît car l'enregistrement (post) n’existe plus.
553 |
554 | La base de notre application côté serveur est donc posée. Nous la ferons évoluer en fonction de nos besoins. **Maintenant, passons aux choses sérieuses** :-).
555 |
556 | >>**Remarque** *: Vous avez remarqué que l’url dans le cas de la création, la sauvegarde, et la suppression d’un modèle, ne changeait pas. Ce qui fait la distinction c’est le verbe http (GET, POST, PUT, DELETE).*
557 |
558 |
559 |
560 |
561 |
562 |
--------------------------------------------------------------------------------
/08-ROUTEUR.md:
--------------------------------------------------------------------------------
1 | #Le Routeur
2 |
3 | >*Sommaire*
4 |
5 | >>- *Modifier (un peu) la vue principale*
6 | >>- *Ajouter un template*
7 | >>- *"Maîtriser" les urls*
8 |
9 |
10 | >*Parmi les composants principaux de Backbone, il y a le Routeur (`Backbone.Router`), qui n’est pas obligatoire pour construire une application Backbone, mais qui est néanmoins très pratique. Son rôle principal est de déclencher des actions en fonction de l’url saisie dans votre navigateur, ou des liens cliqués.*
11 |
12 | Dans une SPA (Single Page Application), la barre d’url est un composant à part entière de votre application. C’est une partie à laquelle l’utilisateur accède facilement, vous ne pouvez donc pas l’ignorer, et votre application devra pouvoir réagir en fonction des actions de l’utilisateur. Nous allons donc mettre en œuvre `Backbone.Router` pour pouvoir « réagir » aux changements d’url.
13 | Nous allons transformer notre vue d’affichages de posts, pour qu’elle n’affiche plus le détail des messages, mais à la place un lien qui lorsqu’il sera cliqué affichera le détail du message. Nous verrons qu’ensuite cette méthode nous permettra d’accéder directement à partir de la barre de l’url à des fonctionnalités de notre application.
14 |
15 | ##Modifions notre vue
16 |
17 | Dans la page index.html, modifions le template « posts_list_template » afin qu’il n’affiche plus le contenu du post (`{{message}}`). À la place nous ajoutons un lien dont l’url sera post-fixée de l’id du post (`#post/{{id}}`) :
18 |
19 | ```html
20 |
30 | ```
31 |
32 | Ensuite nous ajoutons un nouveau template (pour voir le détail du post) qui sera utilisé lorsque nous cliquerons sur le lien « Lire… », avec un lien (`#/`) qui déclenchera l’affichage de l’ensemble de la liste des posts :
33 |
34 | ```html
35 |
42 | ```
43 |
44 | Nous créons ensuite un nouvel objet de type `Backbone.View` qui sera « chargé » d’afficher le détail du message du post, à partir du nouveau template (`#post_details_template`), en lieu et place de la liste des posts (`#posts_list`) :
45 |
46 | ```javascript
47 | window.PostView = Backbone.View.extend({
48 | el: $("#posts_list"),
49 | initialize: function() {
50 | this.template = $("#post_details_template").html();
51 | },
52 | render: function(post) {
53 | var renderedContent = Mustache.to_html(this.template, {
54 | post: post.toJSON()
55 | });
56 | this.$el.html(renderedContent);
57 | }
58 |
59 | });
60 |
61 | window.postView = new PostView();
62 | ```
63 |
64 | ##Création du routeur
65 |
66 | Nous pouvons maintenant créer notre routeur. La propriété importante du routeur est `routes`. Dans notre exemple (juste en dessous), je lui ai affecté trois routes :
67 |
68 | - `post/:id_post ` : lorsque le clic sur un lien de type `Lire...` la méthode `displayPost` du routeur sera appelée avec l’id du post en paramètre
69 | - `hello`, qui appellera la méthode `hello` si par exemple on saisit `http://localhost:3000/#hello` dans la barre d’url du navigateur (notez le “#”, nous y reviendrons plus tard)
70 | - et enfin `*path` qui appellera la méthode `root` pour toute autre url comme `#/`, `/`…
71 |
72 | Le code qui sert à récupérer la liste des posts en provenance du serveur est déplacé dans la méthode `root` du routeur :
73 |
74 | *Le routeur de notre application de blog :*
75 |
76 | ```javascript
77 | window.RoutesManager = Backbone.Router.extend({
78 | routes: {
79 | "post/:id_post": "displayPost",
80 | "hello": "hello",
81 | "*path": "root"
82 | },
83 | root: function() {
84 | blogPosts.all().fetch({
85 | success: function(result) {
86 | //ça marche !!!
87 | }
88 | });
89 | },
90 |
91 | hello: function() {
92 | $(".jumbotron > h1").html("Hello World !!!");
93 | },
94 |
95 | displayPost: function(id_post) {
96 |
97 | var tmp = new Post({
98 | id: id_post
99 | });
100 |
101 | tmp.fetch({
102 | success: function(result) {
103 | postView.render(result);
104 | }
105 | });
106 | }
107 | });
108 |
109 | window.router = new RoutesManager();
110 |
111 | Backbone.history.start();
112 | ```
113 |
114 | //TODO: faire un § sur Backbone.history.start({pushState: true});
115 |
116 | Sauvegardez le tout, rafraîchissez la page, et testez :
117 |
118 | 
119 |
120 |
121 | Si vous cliquez sur un des liens « Lire… », la liste des posts disparaît au profit du message relatif au post sélectionné :
122 |
123 | 
124 |
125 |
126 | >>**Remarque IMPORTANTE** : Il est maintenant possible d'utiliser des bookmarks pour pointer directement sur les urls des posts du blog.
127 |
128 | Maintenant, essayez aussi de taper directement l'url suivante dans la barre d’url : `http://localhost:3000/#hello`. Et là, le titre de notre blog change. Donc l’url peut bien déclencher directement des actions javascript. Vous pouvez donc réagir, prévenir… toute modification de l’url (comme le retour à la page précédente) pour déclencher l’action nécessaire (par exemple la sauvegarde des données en cours).
129 |
130 | 
131 |
132 |
133 | Voilà. C'est un peu court pour le Routeur, mais cela devrait suffire pour le moment. Maintenant il est temps d'organiser notre code "comme les vrais" avant que notre projet devienne un sac de nouilles.
134 |
135 |
--------------------------------------------------------------------------------
/09-ORGANISATION-CODE.md:
--------------------------------------------------------------------------------
1 | #Organiser son code
2 |
3 | >*Sommaire*
4 |
5 | >>- *Namespaces & Modules, à l'ancienne*
6 | >>- *"Loader" javascript, comme les vrais*
7 |
8 |
9 | >*Notre application (côté client) tient dans une seule page. Elle mélange en un seul fichier du code HTML et du code Javascript, et cela commence à devenir difficilement lisible, difficilement modifiable et donc difficilement maintenable. J’aimerais ajouter la possibilité d’ajouter ou de modifier des posts, mais… Faisons d’abord le ménage et rangeons notre code.*
10 |
11 | Il y a de nombreuses querelles de chapelles autour du sujet de l’organisation du code (des façons de le faire), et je vous avoue que je n’ai pas encore fait complètement mon choix, mais l’essentiel est de produire quelque chose qui fonctionne (correctement) et que vous pourrez facilement faire évoluer. Je vais donc vous présenter 2 méthodes, la méthode « à l’ancienne » qui a le mérite de fonctionner, d’être rapide, et la méthode « hype » pour les champions qui est intéressante à connaître tout particulièrement dans le cadre de gros projets.
12 |
13 | >>**Remarque** : pour la méthode "hype", j'utilise le(s) outil(s) (yepnope) que je préfère, il est tout à fait possible de suivre le principe décrit avec d'autres comme require.js et d'autres... Je n'ai pas la science infuse, je me sens plus à l'aise avec YepNope... c'est tout, et tant que ça marche ;) ...
14 |
15 | ##"À l'ancienne"
16 |
17 | ###Namespace
18 |
19 | Créer une application Backbone, c’est écrire des modèles, des vues, des templates, etc. Et une des bonnes pratiques pour parvenir à garder ceci bien organisé est d’utiliser le **namespacing** (comme en .Net, Java…) de la façon suivante :
20 |
21 | *Namespace Blog :*
22 |
23 | ```javascript
24 | var Blog = {
25 | Models: {},
26 | Collections: {},
27 | Views: {},
28 | Router: {}
29 | }
30 | ```
31 |
32 | Vous enregistrez ceci dans un fichier `Blog.js`, que vous pensez à référencer dans votre page html :
33 |
34 | ```html
35 |
36 | ```
37 |
38 | Ainsi par la suite vous pourrez déclarer et faire référence à vos composants de la manière suivante :
39 |
40 | ```javascript
41 | Blog.Models.Post = Backbone.Model.extend({
42 | //...
43 | });
44 |
45 | Blog.Collections.Posts = Backbone.Collection.extend({
46 | //...
47 | });
48 | Blog.Views.PostForm = Backbone.View.extend({
49 | //...
50 | });
51 |
52 | //etc…
53 | ```
54 |
55 |
56 | Puis les utiliser comme ceci :
57 |
58 | ```javascript
59 | var myPost = new Blog.Models.Post();
60 | var posts = new Blog.Collections.Posts();
61 | var postForm = new Blog.Views.PostForm();
62 | ```
63 |
64 | Du coup, à la lecture du code, on voit tout de suite que `myPost` est un modèle, `posts` une collection et `postForm` une vue.
65 |
66 | Cela va donc permettre de créer d’autres fichiers javascript spécifiques à chacun de vos composants, par exemple un fichier `post.js` avec le code de votre modèle `post` *(en général j’y ajoute aussi la collection correspondante)*, puis autant de fichiers javascript que de vues. Cela va augmenter le nombre de vos fichiers, mais à chaque modification vous n’aurez à vous concentrer que sur une seule portion de code.
67 |
68 | Puis vous pouvez aussi organiser vos fichiers javascript dans des répertoires. Voici comment je procède :
69 |
70 | \
71 |
72 | Je crée un répertoire `libs/vendors` pour tous les scripts « qui ne sont pas de moi » (jQuery, Backbone, etc.), puis je range mes modèles dans un répertoire `models`, mes vues dans un répertoire `views`. Au même endroit que ma page `index.html`, je positionne le fichier `routes.js `(mon routeur) ainsi que le fichier `Blog.js` qui contient mes **« namespaces »**, et enfin je déclare tout ceci dans ma page `index.html `:
73 |
74 |
75 | ```html
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | ```
96 |
97 | Pour ensuite écrire mon code javascript de « lancement », clairement simplifié par rapport à ce que nous avons fait jusqu’ici :
98 |
99 | ```html
100 |
122 | ```
123 |
124 | >>**Remarque** : Vous n’êtes pas obligés de faire comme moi, adaptez selon vos goûts ou les normes imposées sur les projets. Et n’hésitez pas à me contacter pour me donner des astuces pour améliorer mon organisation de code.
125 |
126 |
127 | ###Modules
128 |
129 | J’aime bien combiner la notion de module à la notion de namespace, ce qui permet d’associer à notre namespace une notion de variable ou de fonctions privées, mais aussi de créer un système de plugin. Je vous montre avec le code, ce sera plus « parlant » :
130 |
131 | Avant nous avions donc ceci :
132 |
133 | *Namespace Blog :*
134 |
135 | ```javascript
136 | var Blog = {
137 | Models: {},
138 | Collections: {},
139 | Views: {},
140 | Router: {}
141 | }
142 | ```
143 |
144 | Que nous allons changer par ceci :
145 |
146 | *Module+Namespace :*
147 |
148 | ```javascript
149 | var Blog = (function() {
150 | var blog = {};
151 |
152 | blog.Models = {};
153 | blog.Collections = {};
154 | blog.Views = {};
155 | blog.Router = {};
156 |
157 | return blog;
158 | }());
159 | ```
160 |
161 | Cela signifie que seul la variable `blog` sera exposée par l’entremise de la variable `Blog`. Tout ce qui est entre `(function () {` et `}());` sera exécuté. A l’intérieur de cette closure vous pouvez coder des variables et des méthodes privées.
162 |
163 | Ensuite vous allez pouvoir déclarer des « plug-ins » à votre module de la manière suivante (par exemple):
164 |
165 | ```javascript
166 | var Blog = (function(blog) {
167 |
168 | blog.Models.Post = Backbone.Model.extend({
169 | urlRoot: "/blogposts"
170 |
171 | });
172 |
173 | blog.Collections.Posts = Backbone.Collection.extend({
174 | model: blog.Models.Post,
175 | all: function() {
176 | this.url = "/blogposts";
177 | return this;
178 | },
179 | query: function(query) {
180 | this.url = "/blogposts/query/" + query;
181 | return this;
182 | }
183 |
184 | });
185 |
186 | return blog;
187 | }(Blog));
188 | ```
189 |
190 | ###Au final, nous aurons...
191 |
192 | Avec les principes décrits plus haut, nous allons donc pouvoir "découper" notre code afin de bien tout ordonner, et nous obtiendrons les fichiers suivants :
193 |
194 | *Blog.js :*
195 |
196 | ```javascript
197 | var Blog = (function() {
198 | var blog = {};
199 |
200 | blog.Models = {};
201 | blog.Collections = {};
202 | blog.Views = {};
203 | blog.Router = {};
204 |
205 | return blog;
206 | }());
207 | ```
208 |
209 | *routes.js :*
210 |
211 | ```javascript
212 | var Blog = (function(blog) {
213 |
214 | blog.Router.RoutesManager = Backbone.Router.extend({
215 | initialize: function(args) {
216 | this.collection = args.collection;
217 | },
218 | routes: {
219 | "post/:id_post": "displayPost",
220 | "hello": "hello",
221 | "*path": "root"
222 | },
223 | root: function() {
224 | this.collection.all().fetch({
225 | success: function(result) {
226 | //ça marche !!!
227 | }
228 | });
229 | },
230 |
231 | hello: function() {
232 | $(".jumbotron > h1").html("Hello World !!!");
233 | },
234 |
235 | displayPost: function(id_post) {
236 |
237 | var tmp = new blog.Models.Post({
238 | id: id_post
239 | });
240 |
241 | tmp.fetch({
242 | success: function(result) {
243 | postView.render(result);
244 | }
245 | });
246 | }
247 | });
248 |
249 | return blog;
250 | }(Blog));
251 | ```
252 |
253 | *models/post.js :*
254 |
255 | ```javascript
256 | var Blog = (function(blog) {
257 |
258 | blog.Models.Post = Backbone.Model.extend({
259 | urlRoot: "/blogposts"
260 | });
261 |
262 | blog.Collections.Posts = Backbone.Collection.extend({
263 | model: blog.Models.Post,
264 | all: function() {
265 | this.url = "/blogposts";
266 | return this;
267 | },
268 | query: function(query) {
269 | this.url = "/blogposts/query/" + query;
270 | return this;
271 | }
272 | });
273 |
274 | return blog;
275 | }(Blog));
276 | ```
277 |
278 | *views/SidebarView.js :*
279 |
280 | ```javascript
281 | var Blog = (function(blog) {
282 |
283 | blog.Views.SidebarView = Backbone.View.extend({
284 | el: $("#blog_sidebar"),
285 | initialize: function() {
286 | this.template = $("#blog_sidebar_template").html();
287 | },
288 | render: function() {
289 | var renderedContent = Mustache.to_html(this.template, {
290 | posts: this.collection.toJSON()
291 | });
292 |
293 | this.$el.html(renderedContent);
294 | }
295 | });
296 |
297 | return blog;
298 | }(Blog));
299 | ```
300 |
301 | *views/PostsListViews.js :*
302 |
303 | ```javascript
304 | var Blog = (function(blog) {
305 |
306 | blog.Views.PostsListView = Backbone.View.extend({
307 | el: $("#posts_list"),
308 | initialize: function() {
309 | this.template = $("#posts_list_template").html();
310 | },
311 | render: function() {
312 | var renderedContent = Mustache.to_html(this.template, {
313 | posts: this.collection.toJSON()
314 | });
315 |
316 | this.$el.html(renderedContent);
317 | }
318 | });
319 |
320 | return blog;
321 | }(Blog));
322 | ```
323 |
324 | *views/MainView.js :*
325 |
326 | ```javascript
327 | var Blog = (function(blog) {
328 |
329 | blog.Views.MainView = Backbone.View.extend({
330 | initialize: function() {
331 |
332 | this.collection.comparator = function(model) {
333 | return -(new Date(model.get("date")).getTime());
334 | }
335 |
336 | _.bindAll(this, 'render');
337 | this.collection.bind('reset', this.render);
338 | this.collection.bind('change', this.render);
339 | this.collection.bind('add', this.render);
340 | this.collection.bind('remove', this.render);
341 |
342 | this.sidebarView = new blog.Views.SidebarView();
343 | this.postsListView = new blog.Views.PostsListView({
344 | collection: this.collection
345 | });
346 |
347 | },
348 | render: function() {
349 |
350 | //this.collection.models = this.collection.models.reverse();
351 | this.sidebarView.collection = new blog.Collections.Posts(this.collection.first(3));
352 | this.sidebarView.render();
353 | this.postsListView.render();
354 | }
355 | });
356 |
357 | return blog;
358 | }(Blog));
359 | ```
360 |
361 | *views/LoginView.js :*
362 |
363 | ```javascript
364 | var Blog = (function(blog) {
365 |
366 | blog.Views.LoginView = Backbone.View.extend({
367 | el: $("#blog_login_form"),
368 |
369 | initialize: function() {
370 | var that = this;
371 | this.template = $("#blog_login_form_template").html();
372 |
373 | //on vérifie si pas déjà authentifié
374 | $.ajax({
375 | type: "GET",
376 | url: "/alreadyauthenticated",
377 | error: function(err) {
378 | console.log(err);
379 | },
380 | success: function(dataFromServer) {
381 |
382 | if (dataFromServer.firstName) {
383 | that.render("Bienvenue", dataFromServer);
384 | } else {
385 | that.render("???", {
386 | firstName: "John",
387 | lastName: "Doe"
388 | });
389 | }
390 | }
391 | })
392 |
393 | },
394 |
395 | render: function(message, user) {
396 |
397 | var renderedContent = Mustache.to_html(this.template, {
398 | message: message,
399 | firstName: user ? user.firstName : "",
400 | lastName: user ? user.lastName : ""
401 | });
402 | this.$el.html(renderedContent);
403 | },
404 | events: {
405 | "click .btn-primary": "onClickBtnLogin",
406 | "click .btn-default": "onClickBtnLogoff"
407 | },
408 | onClickBtnLogin: function(domEvent) {
409 |
410 | var fields = $("#blog_login_form :input"),
411 | that = this;
412 |
413 | $.ajax({
414 | type: "POST",
415 | url: "/authenticate",
416 | data: {
417 | email: fields[0].value,
418 | password: fields[1].value
419 | },
420 | dataType: 'json',
421 | error: function(err) {
422 | console.log(err);
423 | },
424 | success: function(dataFromServer) {
425 |
426 | if (dataFromServer.infos) {
427 | that.render(dataFromServer.infos);
428 | } else {
429 | if (dataFromServer.error) {
430 | that.render(dataFromServer.error);
431 | } else {
432 | that.render("Bienvenue", dataFromServer);
433 | }
434 | }
435 |
436 | }
437 | });
438 | },
439 | onClickBtnLogoff: function() {
440 |
441 | var that = this;
442 | $.ajax({
443 | type: "GET",
444 | url: "/logoff",
445 | error: function(err) {
446 | console.log(err);
447 | },
448 | success: function(dataFromServer) {
449 | console.log(dataFromServer);
450 | that.render("???", {
451 | firstName: "John",
452 | lastName: "Doe"
453 | });
454 | }
455 | })
456 | }
457 |
458 | });
459 |
460 | return blog;
461 | }(Blog));
462 | ```
463 |
464 | *views/PostView.js :*
465 |
466 | ```javascript
467 | var Blog = (function(blog) {
468 |
469 | blog.Views.PostView = Backbone.View.extend({
470 | el: $("#posts_list"),
471 | initialize: function() {
472 | this.template = $("#post_details_template").html();
473 | },
474 | render: function(post) {
475 | var renderedContent = Mustache.to_html(this.template, {
476 | post: post.toJSON()
477 | });
478 |
479 | this.$el.html(renderedContent);
480 | }
481 | });
482 |
483 | return blog;
484 | }(Blog));
485 | ```
486 |
487 | ... Sauvegardez tout ça et essayez, normalement cela devrait fonctionner et vous verrez qu'à l'usage, le code en devient plus lisible. Mais passons donc à la 2ème méthode.
488 |
489 | ##Méthode “hype”, comme les vrais
490 |
491 | Plus les fichiers javascript se multiplient, plus la gestion des `
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 | ```
537 |
538 | Et nous écrivons ceci à la place :
539 |
540 | ```html
541 |
542 |
543 | ```
544 |
545 | Et c’est donc dans le script `main.js` que nous allons procéder au chargement de nos différents scripts de la manière suivante :
546 |
547 | *1ère utilisation de yepnope :*
548 |
549 | ```javascript
550 | yepnope({
551 | load: {
552 | jquery: 'libs/vendors/jquery-1.7.2.js',
553 | underscore: 'libs/vendors/underscore.js',
554 | backbone: 'libs/vendors/backbone.js',
555 | mustache: 'libs/vendors/mustache.js',
556 |
557 | //NameSpace
558 | blog: 'Blog.js',
559 |
560 | //Models
561 | posts: 'models/post.js',
562 |
563 | //Controllers
564 | sidebarview: 'views/SidebarView.js',
565 | postslistviews: 'views/PostsListView.js',
566 | mainview: 'views/MainView.js',
567 | loginview: 'views/LoginView.js',
568 | postview: 'views/PostView.js',
569 |
570 | //Routes
571 | routes: 'routes.js'
572 |
573 | },
574 |
575 | callback: {
576 | "routes": function() {
577 | console.log("routes loaded ...");
578 | }
579 | },
580 | complete: function() {
581 | //...
582 | }
583 | });
584 | ```
585 |
586 | Vous l’aurez compris, le paramètre `load` sert à définir les scripts à charger. Vous notez aussi que l’on peut donner un alias à chacun des scripts (sinon YepNope le fera automatiquement à partir du nom du fichier javascript), alias que l’on peut ensuite utiliser dans le paramètre `callback` pour déclencher un traitement une fois que le script est inclus dans la page (dans notre exemple c’est au moment de l’inclusion du fichier `routes.js`).
587 |
588 | Et enfin, il y a aussi le paramètre `complete` qui permet de lancer un traitement une fois tous les scripts inclus.
589 |
590 | Nous allons donc déplacer le javascript restant dans notre page à l’intérieur de complete, pour finalement obtenir ceci :
591 |
592 | *Code définitif :*
593 |
594 | ```javascript
595 | yepnope({
596 | load: {
597 | jquery: 'libs/vendors/jquery-1.7.2.js',
598 | underscore: 'libs/vendors/underscore.js',
599 | backbone: 'libs/vendors/backbone.js',
600 | mustache: 'libs/vendors/mustache.js',
601 |
602 | //NameSpace
603 | blog: 'Blog.js',
604 |
605 | //Models
606 | posts: 'models/post.js',
607 |
608 | //Controllers
609 | sidebarview: 'views/SidebarView.js',
610 | postslistviews: 'views/PostsListView.js',
611 | mainview: 'views/MainView.js',
612 | loginview: 'views/LoginView.js',
613 | postview: 'views/PostView.js',
614 |
615 | //Routes
616 | routes: 'routes.js'
617 |
618 | },
619 |
620 | callback: {
621 | "routes": function() {
622 | console.log("routes loaded ...");
623 | }
624 | },
625 | complete: function() {
626 | $(function() {
627 |
628 | console.log("Lauching application ...");
629 |
630 | window.blogPosts = new Blog.Collections.Posts();
631 |
632 | window.mainView = new Blog.Views.MainView({
633 | collection: blogPosts
634 | });
635 |
636 | /*======= Authentification =======*/
637 | window.loginView = new Blog.Views.LoginView();
638 | /*======= Fin authentification =======*/
639 |
640 | window.postView = new Blog.Views.PostView();
641 |
642 | window.router = new Blog.Router.RoutesManager({
643 | collection: blogPosts
644 | });
645 |
646 | Backbone.history.start();
647 |
648 | });
649 | }
650 | });
651 | ```
652 |
653 | Et voilà ! Vous disposez maintenant d’un code structuré, d’un outil de chargement de script facile à utiliser et modifier : désactivation ou changement provisoire de librairie pour tests par exemple mais aussi chargement conditionnel de script en fonction du contexte… Je ne vous ai dévoilé qu’une infime partie de YepNope qui, en dépit de sa taille, est très puissant. Lisez la documentation, vous verrez…
654 |
655 | Maintenant que notre projet est "propre", nous allons dans le chapitre suivant en profiter pour sécuriser un peu plus notre application.
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
--------------------------------------------------------------------------------
/10-SECURISATION.md:
--------------------------------------------------------------------------------
1 | #Securisation
2 |
3 | >*Sommaire*
4 |
5 | >>- *Un (tout) petit écran d'administration*
6 | >>- *Une "vraie" sécurisation des routes côté serveur*
7 |
8 |
9 | >*Profitons-en pour terminer (presque) notre application et la sécuriser. Je dis “presque”, car il y aura toujours des fonctionnalités à ajouter, des contrôles de saisie à faire, des codes à optimiser (vous verrez dans ce qui va suivre que ma façon d’accéder au DOM n’est pas forcément la plus élégante à défaut d’être la plus lisible), la gestion des commentaires, refaire l’authentification...*
10 |
11 | Je souhaite cependant pouvoir modifier et ajouter des messages (post). C’est un exercice pour démontrer l’utilité de l’organisation du code en modules pour mieux s’y retrouver.
12 |
13 |
14 | ##Il nous faut un "écran d’administration"
15 |
16 | Je retourne donc dans le code html de ma page `index.html`. Ajoutons ceci juste après le “grand titre” de notre blog :
17 |
18 | *Template de l’écran d’administration :*
19 |
20 | ```html
21 |
22 |
23 |
Backbone rocks !!!
24 |
25 |
26 |
27 |
44 |
45 |
46 |
47 |
48 | ```
49 |
50 |
51 | J’aurais donc un écran qui va ressembler à ceci :
52 |
53 | \
54 |
55 |
56 | Pour gérer cet écran je vais avoir besoin d’un objet de type `Backbone.View`, que nous allons créer dans le répertoire `/views` sous le nom d’`AdminView.js`.
57 | Avant de “coder” `AdminView`, nous allons le déclarer et l’instancier dans `main.js` :
58 |
59 | *main.js : :*
60 |
61 | ```javascript
62 | yepnope({
63 | load: {
64 | jquery: 'libs/vendors/jquery-1.7.2.js',
65 | underscore: 'libs/vendors/underscore.js',
66 | backbone: 'libs/vendors/backbone.js',
67 | mustache: 'libs/vendors/mustache.js',
68 |
69 | //NameSpace
70 | blog: 'Blog.js',
71 |
72 | //Models
73 | posts: 'models/post.js',
74 |
75 | //Controllers
76 | sidebarview: 'views/SidebarView.js',
77 | postslistviews: 'views/PostsListView.js',
78 | mainview: 'views/MainView.js',
79 | loginview: 'views/LoginView.js',
80 | postview: 'views/PostView.js',
81 |
82 | //--- ADMINVIEW---
83 | adminview: 'views/AdminView.js',
84 |
85 | //Routes
86 | routes: 'routes.js'
87 | },
88 |
89 | callback: {
90 | "routes": function() {
91 | console.log("routes loaded ...");
92 | }
93 | },
94 | complete: function() {
95 | $(function() {
96 | console.log("Lauching application ...");
97 |
98 | window.blogPosts = new Blog.Collections.Posts();
99 |
100 | window.mainView = new Blog.Views.MainView({
101 | collection: blogPosts
102 | });
103 |
104 | /*======= Admin =======*/
105 | window.adminView = new Blog.Views.AdminView({
106 | collection: blogPosts
107 | });
108 | /*======= Fin Admin =======*/
109 |
110 | /*======= Authentification =======*/
111 | window.loginView = new Blog.Views.LoginView({
112 | adminView: adminView
113 | });
114 | /*======= Fin authentification =======*/
115 |
116 | window.postView = new Blog.Views.PostView();
117 |
118 | window.router = new Blog.Router.RoutesManager({
119 | collection: blogPosts
120 | });
121 | //Backbone.history.start({pushState: true});
122 | Backbone.history.start();
123 |
124 | });
125 | }
126 | });
127 |
128 | ```
129 |
130 | >>**Remarque** : Je passe la vue adminView à la vue loginView pour que cette dernière puisse déclencher le rendue de la première si nécessaire.
131 |
132 |
133 | ###Création d'AdminView
134 |
135 | Alors il n'y a pas grand chose à expliquer (c'est dans le code)
136 |
137 | *AdminView.js :*
138 |
139 | ```javascript
140 | var Blog = (function(blog) {
141 |
142 | blog.Views.AdminView = Backbone.View.extend({
143 | el: $("#admin"),
144 | initialize: function() {
145 | this.template = $("#admin_template").html();
146 | //je prévois de trier ma collection
147 | this.collection.comparator = function(model) {
148 | return -(new Date(model.get("date")).getTime());
149 | }
150 | },
151 | render: function() {
152 | var renderedContent = Mustache.to_html(this.template, {
153 | posts: this.collection.toJSON()
154 | });
155 | this.$el.html(renderedContent);
156 | },
157 | events: {
158 | "click #btn_update": "onClickBtnUpdate",
159 | "click #btn_create": "onClickBtnCreate",
160 | "click #btn_send": "sendPost"
161 | },
162 |
163 | onClickBtnUpdate: function() {
164 | var selectedId = $("#post_choice").val(),
165 | post = this.collection.get(selectedId);
166 |
167 | //Je récupère les informations du post et les affiche
168 | $("#admin > [name='id']").html(post.get("id"));
169 | $("#admin > [name='author']").val(post.get("author"));
170 | $("#admin > [name='title']").val(post.get("title"));
171 | $("#admin > [name='message']").val(post.get("message"));
172 |
173 | },
174 | onClickBtnCreate: function() {
175 | //je ré-initialise les zones de saisie
176 | $("#admin > [name='id']").html("");
177 | $("#admin > [name='author']").val("");
178 | $("#admin > [name='title']").val("");
179 | $("#admin > [name='message']").val("");
180 | },
181 | sendPost: function() { //Sauvegarde
182 | var that = this //pour conserver le contexte
183 | ,
184 | id = $("#admin > [name='id']").html(),
185 | post;
186 |
187 | if (id === "") { //si l'id est vide c'est une création
188 | post = new Blog.Models.Post();
189 | } else { //l'id n'est pas vide c'est une mise à jour
190 | post = new Blog.Models.Post({
191 | id: $("#admin > [name='id']").html()
192 | });
193 | }
194 |
195 | post.save({
196 | author: $("#admin > [name='author']").val(),
197 | title: $("#admin > [name='title']").val(),
198 | message: $("#admin > [name='message']").val(),
199 | date: new Date()
200 | }, {
201 | success: function() {
202 | //Si la transaction côté serveur a fonctionné
203 |
204 | //je recharge ma collection
205 | that.collection.fetch({
206 | success: function() {
207 | //mise à jour de la vue admin
208 | that.render();
209 | //La vue principale se re-mettra à jour
210 | //automatiquement, car elle est "abonnée"
211 | //aux changement de la collection
212 | }
213 | });
214 | },
215 | error: function() {}
216 | });
217 | }
218 | });
219 | return blog;
220 | }(Blog));
221 |
222 | ```
223 |
224 | >>**Remarque** : je n’abonne pas la vue aux changement de la collection pour pouvoir maîtriser le moment où je déclenche la méthode render() de celle-ci.
225 |
226 | ###Modification de LoginView
227 |
228 | Nous allons modifier `LoginView.js` pour prendre en compte l’ajout de notre fonctionnalité d’administration. Tout d’abord modifions le template associé :
229 |
230 | *Template du formulaire de login :*
231 |
232 | ```html
233 |
234 |
243 |
246 |
247 | ```
248 |
249 | Puis modifions le code de LoginView.js :
250 |
251 | *LoginView.js :*
252 |
253 | ```javascript
254 | var Blog = (function(blog) {
255 |
256 | blog.Views.LoginView = Backbone.View.extend({
257 | el: $("#blog_login_form"),
258 |
259 | initialize: function(args) {
260 | var that = this;
261 |
262 | this.adminView = args.adminView;
263 |
264 | this.template = $("#blog_login_form_template").html();
265 |
266 | //on vérifie si pas déjà authentifié
267 | $.ajax({
268 | type: "GET",
269 | url: "/alreadyauthenticated",
270 | error: function(err) {
271 | console.log(err);
272 | },
273 | success: function(dataFromServer) {
274 |
275 | if (dataFromServer.firstName) {
276 |
277 | that.render("Bienvenue", dataFromServer);
278 |
279 | } else {
280 | that.render("???", {
281 | firstName: "John",
282 | lastName: "Doe"
283 | });
284 | }
285 | }
286 | })
287 | },
288 | render: function(message, user) {
289 |
290 | var renderedContent = Mustache.to_html(this.template, {
291 | message: message,
292 | firstName: user ? user.firstName : "",
293 | lastName: user ? user.lastName : "",
294 | adminLinkLabel: user ? user.isAdmin ? "Administration" : "" : ""
295 | });
296 | this.$el.html(renderedContent);
297 | },
298 | events: {
299 | "click .btn-primary": "onClickBtnLogin",
300 | "click .btn-default": "onClickBtnLogoff",
301 | "click #adminbtn": "displayAdminPanel"
302 | },
303 |
304 | displayAdminPanel: function() {
305 | //this.adminView.render();
306 | },
307 |
308 | onClickBtnLogin: function(domEvent) {
309 | //code non affiché pour des raisons de mise en page
310 | //ne pas modifier
311 | },
312 | onClickBtnLogoff: function() {
313 | //code non affiché pour des raisons de mise en page
314 | //ne pas modifier
315 | }
316 |
317 | });
318 | return blog;
319 | }(Blog));
320 | ```
321 |
322 | Maintenant que nous avons fait nos modifications côté client, allons sécuriser les actions côté serveur.
323 |
324 | ##Sécurisation côté serveur
325 |
326 | Vous pouvez tester les modifications dès maintenant, cela va fonctionner, mais il est de bon ton de sécuriser côté serveur les actions de création, modification, suppression, etc. …
327 |
328 | >>**Remarque** : La gestion des session avec express.js ainsi que la sécurisation des routes peut être prise en compte de manière plus « professionnel ». Gardez à l’esprit que les exemples donnés le sont à titre « didactique » et que certains points sont perfectibles.
329 |
330 | Nous allons retourner dans le code de `app.js` (vous vous souvenez : notre application express.js) et créer une fonction qui permet de vérifier si l’utilisateur est connecté et est administrateur et cette fonction servira à sécuriser les routes :
331 |
332 | ###Sécurisation des routes
333 |
334 | Notre fonction vérifie si l'utilisateur connecté est un administrateur, si c'est le cas, elle passe la main à la fonction "callback" que j'ai appelée `next` sinon elle génère une erreur.
335 |
336 | *Fonction haveToBeAdmin :*
337 |
338 | ```javascript
339 | haveToBeAdmin = function(req, res, next) {
340 | console.log("NEEDS ADMIN RIGHTS");
341 | if (findUserBySession(req.sessionID)) {
342 | if (findUserBySession(req.sessionID).isAdmin == true) {
343 | next();
344 | } else {
345 | throw "You have to be administrator";
346 | }
347 | } else {
348 | throw "You have to be connected";
349 | }
350 | }
351 | ```
352 |
353 | >>**Remarque** : Si la condition est vérifiée, la fonction `next()` est appelée, elle correspond au traitement de la route sécurisée.
354 |
355 | Et sécurisons ensuite nos routes de la manière suivante :
356 |
357 | *Ajout :*
358 |
359 | ```javascript
360 | app.post('/blogposts', [haveToBeAdmin], function(req, res, next) {
361 | console.log("POST CREATE ", req.body);
362 |
363 | var d = new Date(),
364 | model = req.body;
365 | model.saveDate = (d.valueOf());
366 |
367 | posts.save(null, model, function(err, key) {
368 | if (err) {
369 | console.log("Erreur : ", err);
370 | res.json(err);
371 | } else {
372 | model.id = key;
373 | res.json(model);
374 | }
375 | });
376 | });
377 | ```
378 |
379 | >>**Remarque "au passage"** : vous remarquez le passage du paramètre `[haveToBeAdmin]`, le contenu de `app.post` ne sera exécuté que si `haveToBeAdmin` "déclenche" `next`.
380 |
381 | De la même façon pour la mise à jour et la suppression :
382 |
383 | *Mise à jour :*
384 |
385 | ```javascript
386 | app.put('/blogposts/:id', [haveToBeAdmin], function(req, res, next) {
387 | console.log("PUT UPDATE", req.body, req.params.id);
388 |
389 | var d = new Date(),
390 | model = req.body;
391 | model.saveDate = (d.valueOf());
392 |
393 | posts.save(req.params.id, model, function(err, key) {
394 | if (err) {
395 | console.log("Erreur : ", err);
396 | res.json(err);
397 | } else {
398 | res.json(model);
399 | }
400 | });
401 | });
402 | ```
403 |
404 | *Suppression :*
405 |
406 | ```javascript
407 | app.delete('/blogposts/:id', [haveToBeAdmin], function(req, res, next) {
408 | console.log("DELETE : /delete/" + req.params.id);
409 |
410 | posts.remove(req.params.id, function(err) {
411 | if (err) {
412 | console.log("Erreur : ", err);
413 | res.json(err);
414 | } else {
415 | //petit correctif de contournement (bug ds nStore) :
416 | //ré-ouvrir la base lorsque la suppression a été faite
417 | posts = nStore.new("blog.db", function() {
418 | res.json(req.params.id);
419 | //Le modèle est vide si on ne trouve rien
420 | });
421 | }
422 | });
423 | });
424 | ```
425 |
426 | ###Eh bien testons, maintenant
427 |
428 | - Relancez l’application côté serveur.
429 | - Ouvrez le navigateur et connectez vous au blog sans vous authentifier.
430 | - Ouvrez la console du navigateur et saisissez ceci : `adminView.render()`.
431 |
432 | Cela va faire apparaître l’écran d’administration alors que vous n’êtes pas administrateur (vous faites une tentativve de hacking sur vous même) !
433 |
434 | Sélectionnez un Post dans la liste, et cliquez sur “modifer Post” pour charger le formulaire :
435 |
436 | \
437 |
438 |
439 | Saissez quelques modifications, cliquez sur le bouton “Sauvegarder” . Vous obtiendrez un message d’erreur dans la console :
440 |
441 | \
442 |
443 |
444 | A vous de gérer les message côté client (avec le callback “error” lors du `save()`)
445 | Et côté serveur cela vous affiche que vous devez être connecté :
446 |
447 | \
448 |
449 |
450 | Donc notre blog est bien "sécurisé". Maintenant rafraichissez votre page et connectez vous en tant qu’administrateur. Vous obtiendrez un lien “Administration” au niveau du message de bienvenue qui vous permettra de déclencher l’affichage de l’écran d’administration :
451 |
452 | \
453 |
454 |
455 | Faites quelques modifications, et sauvegardez les modifications :
456 |
457 | \
458 |
459 |
460 | Vous pouvez ensuite vérifiez qu’elles ont bien été prises en compte :
461 |
462 | \
463 |
464 |
465 | Voilà. Notre application est "terminée". Je sais il faut le dire vite ;) mais vous avez une base, que vous pouvez faire évoluer (il faudrait ajouter les commentaires par exemple) ou améliorer, ou mieux, cassez tout et faites votre propre application avec votre "propre style".
466 |
467 | Par contre, l'ouvrage n'est pas fini, j'ai encore 2,3 petites choses dont j'aimerais vous parlez.
468 |
469 |
470 |
471 |
472 |
473 |
--------------------------------------------------------------------------------
/11-BACKBONE-SYNC.md:
--------------------------------------------------------------------------------
1 | #Backbone.sync()
2 |
3 | //TODO: je n'ai encore rien "gratté" à ce sujet (viendra probablement en dernier)
4 |
5 |
--------------------------------------------------------------------------------
/12-BB-COFFEESCRIPT.md:
--------------------------------------------------------------------------------
1 | #Backbone <3 Coffeescript
2 |
3 | >*Sommaire*
4 |
5 | >>- *Coffeescript*
6 | >>- *On s'outille*
7 | >>- *Traduction*
8 |
9 |
10 | >*Depuis longtemps, les développeurs “serveur” ont de nombreux a priori vis-à-vis de Javascript : un modèle objet “particulier” difficile à comprendre après des années de programmation orientée classes, un système d’héritage par prototype générant beaucoup d’effets de bord s’il n’est pas maîtrisé (sans compter la maintenabilité du code) et justement pas de classes en javascript, ce qui rend difficile l’organisation du code (toujours d’un point de vue approche “classique”).*
11 |
12 | L’arrivée de CoffeeScript tend aujourd’hui à gommer ces problématiques et tout particulièrement par l’introduction d’un système de classes qui prend en charge (pour/à la place du développeur) toutes les problématiques liées au modèle objet Javascript, garantissant ainsi la réduction de l’apparition de bugs dus à la méconnaissance de javascript. Gardez cependant une chose à l’esprit : Coffeescript, cela reste du Javascript, mais avec une manière différente de l’écrire, plus simple, plus efficace et (à mon avis) avec moins d’erreurs. Coffeescript, vous aidera aussi à comprendre et mieux écrire le Javascript.
13 |
14 | ##Coffeescript qu’est-ce que c’est ?
15 |
16 | Coffeescript est un langage de script qui ressemble beaucoup au Python. Il a été créé par Jeremy Ashkenas, vous savez, le brillant développeur aussi à l’origine de frameworks connus tels Backbone.js et Underscore.js. Coffeescript, c’est aussi un “Transpiler” Javascript. C’est-à-dire, qu’au lieu de compiler pour obtenir un binaire, on “compile” le code Coffeescript en Javascript directement exécutable dans un navigateur (ou côté serveur avec Node.js). Classiquement, le transpiler Coffeescript s’exécute sous Node.js, mais vous pouvez très bien l’utiliser en mode “run-time” et insérer du code Coffeescript directement (inline) dans vos pages HTML. C’est moins performant, mais cela peut être utile pour debugger.
17 | L’objet de ce chapitre n’est pas de vous apprendre Coffeescript, mais de vous le présenter rapidement et vous montrer à quoi pourrait ressembler le code de la partie cliente de notre blog traduite en Coffeescript. Vous trouverez les informations complémentaire ici : [http://coffeescript.org/](http://coffeescript.org/).
18 |
19 | ##Un code plus lisible ?
20 |
21 | Donc, selon moi (et d’autres), Coffeescript permet de simplifier le javascript, générer du javascript « propre » et apporte des évolutions de langage qui vont simplifier et rendre le code plus lisible. Nous allons tenter de voir ça par le biais de quelques exemples.
22 |
23 | ###Les fonctions
24 |
25 | Le terme `function` disparaît au profit d’une « flèche » ! `return` disparaît complètement !!! (par défaut c'est la dernière ligne qui fait office de return) et les parenthèses ne sont pas partout obligatoires (ce qui facilite l’écriture de DSL) ... Et plus de point-virgule.
26 |
27 | *Fonction d’addition en javascript :*
28 |
29 | ```javascript
30 | function addition(a, b) {
31 | return a + b
32 | }
33 |
34 | //Utilisation :
35 |
36 | var c = addition(45, 12)
37 | ```
38 |
39 | Deviendra :
40 |
41 | *Fonction d’addition en Coffeescript :*
42 |
43 | ```python
44 | addition = (a,b)->
45 | a+b
46 |
47 | #Utilisation :
48 |
49 | c = addition 45, 12
50 | ```
51 |
52 | ###Interopérabilité
53 |
54 | Coffeescript reste compatible avec les librairies existantes (pas besoin de ré-écrire jQuery ... ouf !)
55 |
56 | *Attendre le chargement de la page pour lancer les commandes :*
57 |
58 | ```javascript
59 | $(function() {
60 | some();
61 | init();
62 | calls();
63 | });
64 | ```
65 |
66 | *La même chose en Coffeescript :*
67 |
68 | ```python
69 | $ ->
70 | some()
71 | init()
72 | calls()
73 | ```
74 |
75 | ###Interpolation et Chaînes de caractères
76 |
77 | Imaginons un objet bob :
78 |
79 | *En Javascript :*
80 |
81 | ```javascript
82 | var bob = {
83 | firstName : "Bob",
84 | lastName : "Morane",
85 | sayHello : function() {
86 | return "Hello !"
87 | }
88 | }
89 | ```
90 |
91 | Nous voulons afficher un message à partir des propriétés de Bob, aujourd’hui nous faisons comme ceci :
92 |
93 | *En Javascript :*
94 |
95 | ```javascript
96 | console.log(
97 | "Firstname : " + bob.firstName +
98 | " Lastname : " + bob.lastName +
99 | "Hello : " +
100 | bob.sayHello()
101 | ) ;
102 | ```
103 |
104 | Coffeescript introduit des nouveaux concepts à propos des chaînes de caractères et particulièrement `#{variable}` qui permet d’insérer la valeur de variable dans une chaîne. Nous aurons donc ceci :
105 |
106 | *Version Coffeescript :*
107 |
108 | ```python
109 | bob =
110 | firstName : "Bob"
111 | lastName : "Morane"
112 | sayHello : ->
113 | "Hello !"
114 |
115 | console.log "
116 | Firstname : #{bob.firstName}
117 | Lastname : #{bob.lastName}
118 | Hello : #{bob.sayHello()}
119 | "
120 | ```
121 |
122 | >>**Remarquez au passage**, la possibilité d’écrire vos chaînes de caractères sur plusieurs lignes (dans ce cas les saut de lignes ne sont pas pris en comptes, pour les prendre en comptes utilisez des triples « doubles-quotes » : """)
123 |
124 | ###Jouez un peu avec les tableaux
125 |
126 | Nous avons le tableau suivant (en Coffeescript) :
127 |
128 | *Copains :*
129 |
130 | ```python
131 | copains = [
132 | { nom : "Bob", age : 30 }
133 | { nom : "Sam", age : 50 }
134 | { nom : "John", age : 20 }
135 | ]
136 | ```
137 |
138 | Nous voudrions obtenir tous les copains de moins de 50 ans :
139 |
140 | *Jeunes copains :*
141 |
142 | ```python
143 | Result = (copain for copain in copains when copain.age < 50)
144 | ```
145 |
146 | ##Et enfin (et surtout ?) les classes
147 |
148 | Maintenant, grâce à Coffeescript, nous pouvons écrire des classes. Il faut aussi savoir ceci : le mot clé `this` devient `@`, les « champs » (propriétés) de la classes sont déclarés dans le constructeur. Mais un exemple est souvent plus parlant que trop d’explications :
149 |
150 | ###Notre 1ère classe
151 |
152 | *Classe “Human” :*
153 |
154 | ```python
155 | class Human
156 | constructor : (firstName, lastName) ->
157 | #fields : @ = this
158 | @firstName = firstName
159 | @lastName = lastName
160 |
161 | #method
162 | hello : ->
163 | console.log "Hello #{@firstName} #{@lastName}"
164 |
165 | #Utilisation
166 | Bob = new Human "Bob", "Morane"
167 |
168 | Bob.hello()
169 | ```
170 |
171 | ###Propriétés & valeurs par défaut
172 |
173 | Vous pouvez aller encore plus vite et déclarer vos propriétés et leur valeur par défaut directement en paramètres du constructeur.
174 |
175 | *Classe “Human”, autre version, même résultats :*
176 |
177 | ```python
178 | class Human
179 | constructor : (@firstName = "John", @lastName = "Doe") ->
180 | hello : ->
181 | console.log "Hello #{@firstName} #{@lastName}"
182 | ```
183 |
184 | ###Comme en Java : Composition, Association, Encapsulation
185 |
186 | La notion de classe apportée par Coffeescript (même s’il existe en javascript, comme nous avons pu le voir par exemple avec le modèle objet de Backbone) permet facilement de mettre en œuvre les différents concepts habituels en programmation « orientée classes » et plus facilement encore décliner les « design patterns ».
187 |
188 | *Composition de classes :*
189 |
190 | ```python
191 | class Dog
192 | constructor:(@name)->
193 |
194 | class Human
195 | constructor:(@first, @last, @dog)->
196 |
197 | Bob = new Human "Bob", "Morane", new Dog "Wolf"
198 | console.log "Le chien de Bob s'appelle #{Bob.dog.name}"
199 | ```
200 |
201 | *Encapsulation de classes :*
202 |
203 | ```python
204 | class Human
205 | class Hand
206 | constructor:(@whichOne)->
207 |
208 | constructor:(@first, @last)->
209 | @leftHand = new Hand "left"
210 | @rightHand = new Hand "right"
211 |
212 | console.log JSON.stringify new Human "Bob", "Morane"
213 | ```
214 |
215 | ###Comme en Java : les membres statiques
216 |
217 | La définition/déclaration de membres statiques se fait pour les propriétés en dehors du constructeur cette fois-ci et pré-fixées par `@`, les méthodes statiques « commencent » elles aussi par `@` :
218 |
219 | *Toujours et encore notre classe « Human » :*
220 |
221 | ```python
222 | class Human
223 | @counter : 0 #static property
224 |
225 | constructor : (@firstName, @lastName) ->
226 | Human.counter += 1
227 |
228 | #static method
229 | @howMany : ->
230 | Human.counter
231 |
232 | Bob = new Human "Bob", "Morane"
233 | console.log "Human.counter #{Human.howMany()}"
234 | ```
235 |
236 | ###Mais aussi (comme en Java) : l’héritage !
237 |
238 | Coffeescript introduit le mot clé `extends` pour permettre à une classe d’hériter d’une autre classe, et cela s’utilise de façon très intuitive :
239 |
240 | *Un humain, et un super héros qui hérite d’humain … :*
241 |
242 | ```python
243 | class Human
244 | constructor : (@firstName, @lastName) ->
245 |
246 | hello : ->
247 | "Hello #{@firstName} #{@lastName}"
248 |
249 | class Superhero extends Human
250 | constructor : (@firstName, @lastName, @name) ->
251 |
252 | hello : ->
253 | super + " known as #{@name}"
254 |
255 | SuperMan = new Superhero "Clark", "Kent", "SuperMan"
256 | console.log SuperMan.hello()
257 | ```
258 |
259 | >>**Notez au passage** l’apparition du mot-clé super qui permet « d’appeler » la méthode du « parent ».
260 |
261 | Une première conclusion s’impose : Maintenant vous pouvez faire du javascript « orienté classes » tout en conservant les possibilités actuelles de javascript.
262 |
263 |
264 | ##J’aime / Je n’aime pas
265 |
266 | Coffeescript fait beaucoup de bruit dans la communauté javascript, mais aussi du côtés des développeurs java, .Net, etc. ... Il a été assez bien adopté par les développeurs Ruby car il a une syntaxe assez proche.
267 |
268 | Ce qui plaît beaucoup, plus particulièrement aux développeurs « non javascript », c’est le concept de classes, mais les développeurs javascript « puristes » ne voient pas l’intérêt d’utiliser des classes, les concepts objet de javascript apparaîssant largement suffisants.
269 |
270 |
271 | >>Personnellement, si l’aspect classe doit contribuer à l’adoption de javascript, laissons les classes ! Il y a encore trop de développeurs rebutés par javascript pour de fausses raisons. Ce n’est probablement pas pour rien si le concept de classe apparaît dans la future version de javascript ES6.
272 |
273 | Ce qui plaît beaucoup moins, et là les développeurs javascripts et java se rejoignent, c’est l’abscence de points virgule, d’accolades, et la notion d’indentation « significative » comme en python, rendant le code difficilement lisible s’il est trop long.
274 |
275 | >>D’autres vous diront que la simplification du code grâce à Coffeescript contribue à sa lisibilité. Comme quoi, les goûts et les couleurs ...
276 |
277 |
278 | Le plus gros défaut de Coffeescript, reste la difficulté à debbuger du code coffeescript, l’impossibilité de l’utiliser directement dans la console du navigateur. Cependant le projet **Source Map** devrait régler ce type de problème à l’avenir (et pour d’autres transpilers aussi) : [http://www.coffeescriptlove.com/2012/04/source-maps-for-coffeescript.html](http://www.coffeescriptlove.com/2012/04/source-maps-for-coffeescript.html).
279 |
280 | On pourrait ensuite se demander, mais que va devenir Coffeescript dans le futur avec l’apparition de la nouvelle version de javascript ? Je vous répondrais, qu’avant que cette version soit déployée sur tous les postes de travail (donc que tout le monde dispose d’un navigateur de dernière génération) il va se passer plusieurs années et que Coffeescript génère du code javascript qui fonctionne sur la majorité des navigateurs existants.
281 |
282 | Brendan Heich (le papa de javascript) a officiellement donné sa bénédiction « publique » à Coffeescript, nul doute que Jeremy Ashkenas ne fasse évoluer Coffeescript en fonction des spécifications javascript pour garder une compatibilté ascendante et descendante. Pour rappel, en début de page du site de Coffeescript, il est écrit : **« The golden rule of CoffeeScript is: "It's just JavaScript" »**.
283 |
284 | >>Pour ma part, je pense que c’est un excellent outil d’apprentissage (il génère du code javascript « propre ») mais sur un projet de réalisation professionnel, il est à réserver aux « gurus » qui maîtrise déjà javascript.
285 |
286 | Attention, un challenger de poids vient de naître dans le monde des transpilers javascript : **TypeScript**, porté par Microsoft, développé entre autre par le papa de l’ancêtre du Turbo Pascal mais aussi du C# et qui tend à gommer une grande partie des défauts reprochés à Coffeescript. Cela fera l’objet d’un prochain chapitre, mais avant cela, passons à la ré-écriture de notre Blog en Coffeescript.
287 |
288 | ##Ré-écriture de notre blog ?! S'outiller
289 |
290 | Avant toute chose, sauvegardez votre arborescence applicative à un autre emplacement pour pouvoir la réutiliser.
291 |
292 | ###Installation de Coffeescript
293 |
294 | L’installation de Coffeescript est très simple, elle s’effectue en mode commande avec npm (installé en même temps que Node.js) :
295 |
296 | npm install –g coffee-script
297 |
298 | >>**Remarque :** pour les utilisateurs sous OSX, vous devez normalement utiliser la commande `sudo npm install –g coffee-script`
299 |
300 | A partir de maintenant, vous pouvez compiler/transpiler du code Coffeescript en javascript avec la commande suivante :
301 |
302 | coffee –compile Human.coffee
303 |
304 | qui produira un fichier javascript `Human.js`.
305 |
306 | Pour plus d’informations : [http://jashkenas.github.com/coffee-script/#usage](http://jashkenas.github.com/coffee-script/#usage).
307 |
308 | ###“Industrialisation” de la transpilation
309 |
310 | Nous avons donc décidé de re-écrire la partie javascript cliente de notre blog. Pour cela dans le répertoire de notre application, créez un répertoire `public.coffee` avec un sous-répertoire `models` et un sous répertoire `views`. Ce répertoire et ces deux sous-répertoires contiendrons les fichiers source coffeescript (que vous pouvez d’ores et déjà créer “vides” dans les répertoires correspondants) :
311 |
312 | public.coffee\
313 | |-models\
314 | | |-post.coffee
315 | |-views\
316 | | |-AdminView.coffee
317 | | |-LoginView.coffee
318 | | |-MainView.coffee
319 | | |-PostsListView.coffee
320 | | |-PostView.coffee
321 | | |-SidebarView.coffee
322 | |
323 | |-Blog.coffee
324 | |-main.coffee
325 | |-routes.coffee
326 |
327 | Le “but du jeu” étant de transpiler tous les fichiers `.coffee` en fichier javascript `.js` dans le répertoire `public` (et ses sous-répertoire).
328 | Faire ceci “à la main” en prenant les fichiers un par un peut devenir très rapidement fastidieux. Mais heureusement, l’installation de Coffeescript inclut un système simple de “build” qui s’appelle **Cake**, qui est capable d’effectuer des tâches simples décrites dans un fichier `Cakefile`. Plus d’information ici : [http://jashkenas.github.com/coffee-script/#cake](http://jashkenas.github.com/coffee-script/#cake).
329 |
330 | Donc à la racine de votre application, créez un fichier `Cakefile` avec le contenu suivant :
331 |
332 | *Cakefile :*
333 |
334 | ```python
335 | fs = require 'fs'
336 | {print} = require 'util'
337 | {spawn} = require 'child_process'
338 |
339 | build = (callback) ->
340 | coffee = spawn 'coffee', ['-c', '-o', 'public', 'public.coffee']
341 | coffee.stderr.on 'data', (data) ->
342 | process.stderr.write data.toString()
343 | coffee.stdout.on 'data', (data) ->
344 | print data.toString()
345 | coffee.on 'exit', (code) ->
346 | callback?() if code is 0
347 |
348 | task 'build', 'Build public/ from public.coffee/', ->
349 | build()
350 | ```
351 |
352 | En fait nous expliquons à Cake que nous souhaitons transpiler en javascript tous les fichiers du répertoire source `public.coffee` vers le répertoire cible `public`. Et pour cela, il suffira, dans un terminal (ou une console) de lancer la commande : `cake build` (à la racine de notre application, là où est situé le fichier `Cakefile`).
353 |
354 | ##Ré-écriture / Traductions
355 |
356 | Alors, l’objectif n’est pas d’apprendre Coffeescript (cela pourrait donner lieu à un ouvrage entier), mais de montrer comment il est possible d’écrire notre blog “autrement”, donc je vous livre ici la traduction en Coffeescript (pas forcément dans les règles de l’art) qui fonctionnera et assortie de quelques remarques.
357 |
358 | ###Blog.coffee
359 |
360 | ```python
361 | window.Blog =
362 | Models : {}
363 | Collections : {}
364 | Views : {}
365 | Router : {}
366 | ```
367 |
368 | ###main.coffee
369 |
370 | ```python
371 | yepnope
372 | load:
373 | jquery: "libs/vendors/jquery-1.7.2.js"
374 | underscore: "libs/vendors/underscore.js"
375 | backbone: "libs/vendors/backbone.js"
376 | mustache: "libs/vendors/mustache.js"
377 |
378 | #NameSpace
379 | blog: "Blog.js"
380 |
381 | #Models
382 | posts: "models/post.js"
383 |
384 | #Controllers
385 | sidebarview: "views/SidebarView.js"
386 | postslistviews: "views/PostsListView.js"
387 | mainview: "views/MainView.js"
388 | loginview: "views/LoginView.js"
389 | postview: "views/PostView.js"
390 | adminview: "views/AdminView.js"
391 |
392 | #Routes
393 | routes: "routes.js"
394 |
395 | callback:
396 | routes: ->
397 | console.log "routes loaded ..."
398 |
399 | complete: ->
400 | $ ->
401 | console.log "Lauching application ..."
402 | window.blogPosts = new Blog.Collections.Posts()
403 | window.mainView = new Blog.Views.MainView(collection: blogPosts)
404 |
405 | #======= Admin =======
406 | window.adminView = new Blog.Views.AdminView(collection: blogPosts)
407 |
408 |
409 | #======= Authentification =======
410 | window.loginView = new Blog.Views.LoginView(adminView: adminView)
411 |
412 | window.postView = new Blog.Views.PostView()
413 | window.router = new Blog.Router.RoutesManager(collection: blogPosts)
414 |
415 | Backbone.history.start()
416 | ```
417 |
418 | ###post.coffee
419 |
420 | ```python
421 | class Blog.Models.Post extends Backbone.Model
422 | urlRoot:"/blogposts"
423 |
424 | class Blog.Collections.Posts extends Backbone.Collection
425 |
426 | model: Blog.Models.Post
427 |
428 | all: ->
429 | @url = "/blogposts"
430 | @
431 |
432 | query: (query) ->
433 | @url = "/blogposts/query/" + query
434 | @
435 | ```
436 |
437 | ###AdminView.coffee
438 |
439 | ```python
440 | class Blog.Views.AdminView extends Backbone.View
441 | el: $ "#admin"
442 | initialize: ->
443 | @template = $("#admin_template").html()
444 |
445 | #je prévois de trier ma collection
446 | @collection.comparator = (model) ->
447 | -(new Date(model.get("date")).getTime())
448 |
449 | render: ->
450 | renderedContent = Mustache.to_html(@template,
451 | posts: @collection.toJSON()
452 | )
453 | @$el.html renderedContent
454 |
455 | events:
456 | "click #btn_update": "onClickBtnUpdate"
457 | "click #btn_create": "onClickBtnCreate"
458 | "click #btn_send": "sendPost"
459 |
460 | onClickBtnUpdate: ->
461 | selectedId = $("#post_choice").val()
462 | post = @collection.get(selectedId)
463 |
464 | #Je récupère les informations du post et les affiche
465 | $("#admin > [name='id']").html post.get("id")
466 | $("#admin > [name='author']").val post.get("author")
467 | $("#admin > [name='title']").val post.get("title")
468 | $("#admin > [name='message']").val post.get("message")
469 |
470 | onClickBtnCreate: ->
471 |
472 | #je ré-initialise les zones de saisie
473 | $("#admin > [name='id']").html ""
474 | $("#admin > [name='author']").val ""
475 | $("#admin > [name='title']").val ""
476 | $("#admin > [name='message']").val ""
477 |
478 | sendPost: -> #Sauvegarde
479 | #that = this #pour conserver le contexte
480 | id = $("#admin > [name='id']").html()
481 | post = undefined
482 | if id is "" #si l'id est vide c'est une création
483 | post = new Blog.Models.Post()
484 | else #l'id n'est pas vide c'est une mise à jour
485 | post = new Blog.Models.Post(id: $("#admin > [name='id']").html())
486 | post.save
487 | author: $("#admin > [name='author']").val()
488 | title: $("#admin > [name='title']").val()
489 | message: $("#admin > [name='message']").val()
490 | date: new Date()
491 | ,
492 | success: ->
493 |
494 | #Si la transaction côté serveur a fonctionné
495 |
496 | #je recharge ma collection
497 | @collection.fetch success: =>
498 |
499 | #mise à jour de la vue admin
500 | @render()
501 |
502 |
503 |
504 | #La vue principale se re-mettra à jour
505 | #automatiquement, car elle est "abonnée"
506 | #aux changement de la collection
507 | error: ->
508 | ```
509 |
510 | //TODO : parler de fat arrow =>
511 |
512 | ###LoginView.coffee
513 |
514 | ```python
515 | class Blog.Views.LoginView extends Backbone.View
516 | el: $ "#blog_login_form"
517 | initialize: (args) ->
518 | that = this
519 | @adminView = args.adminView
520 | @template = $("#blog_login_form_template").html()
521 |
522 | #on vérifie si pas déjà authentifié
523 | $.ajax
524 | type: "GET"
525 | url: "/alreadyauthenticated"
526 | error: (err) ->
527 | console.log err
528 |
529 | success: (dataFromServer) ->
530 | if dataFromServer.firstName
531 | that.render "Bienvenue", dataFromServer
532 | else
533 | that.render "???",
534 | firstName: "John"
535 | lastName: "Doe"
536 |
537 |
538 |
539 | render: (message, user) ->
540 | renderedContent = Mustache.to_html(@template,
541 | message: message
542 | firstName: (if user then user.firstName else "")
543 | lastName: (if user then user.lastName else "")
544 |
545 | #adminLink : user.isAdmin ? '#/admin' : "",
546 | adminLinkLabel:
547 | (if user then (if user.isAdmin then "Administration" else "") else "")
548 | )
549 | @$el.html renderedContent
550 |
551 | events:
552 | "click .btn-primary": "onClickBtnLogin"
553 | "click .btn-default": "onClickBtnLogoff"
554 | "click #adminbtn": "displayAdminPanel"
555 |
556 | displayAdminPanel: ->
557 | @adminView.render()
558 |
559 | onClickBtnLogin: (domEvent) ->
560 | fields = $("#blog_login_form :input")
561 | #that = this
562 | $.ajax
563 | type: "POST"
564 | url: "/authenticate"
565 | data:
566 | email: fields[0].value
567 | password: fields[1].value
568 |
569 | dataType: "json"
570 | error: (err) ->
571 | console.log err
572 |
573 | success: (dataFromServer) =>
574 | if dataFromServer.infos
575 | @render dataFromServer.infos
576 | else
577 | if dataFromServer.error
578 | @render dataFromServer.error
579 | else
580 | @render "Bienvenue", dataFromServer
581 |
582 |
583 | onClickBtnLogoff: =>
584 | #that = this
585 | $.ajax
586 | type: "GET"
587 | url: "/logoff"
588 | error: (err) ->
589 | console.log err
590 |
591 | success: (dataFromServer) ->
592 | console.log dataFromServer
593 | @render "???",
594 | firstName: "John"
595 | lastName: "Doe"
596 | ```
597 |
598 | ###PostsListView.coffee
599 |
600 | ```python
601 | class Blog.Views.PostsListView extends Backbone.View
602 | el: $ "#posts_list"
603 | initialize: ->
604 | @template = $("#posts_list_template").html()
605 |
606 | render: ->
607 | renderedContent = Mustache.to_html(@template,
608 | posts: @collection.toJSON()
609 | )
610 | @$el.html renderedContent
611 | ```
612 |
613 | ###PostView.coffee
614 |
615 | ```python
616 | class Blog.Views.PostView extends Backbone.View
617 | el: $ "#posts_list"
618 | initialize: ->
619 | @template = $("#post_details_template").html()
620 |
621 | render: (post) ->
622 | renderedContent = Mustache.to_html(@template,
623 | post: post.toJSON()
624 | )
625 | @$el.html renderedContent
626 | ```
627 |
628 | ###SidebarView.coffee
629 |
630 | ```python
631 | class Blog.Views.SidebarView extends Backbone.View
632 | el: $ "#blog_sidebar"
633 | initialize: ->
634 | @template = $("#blog_sidebar_template").html()
635 |
636 | render: ->
637 | renderedContent = Mustache.to_html(@template,
638 | posts: @collection.toJSON()
639 | )
640 | @$el.html renderedContent
641 | ```
642 |
643 | ###MainView.coffee
644 |
645 | ```python
646 | class Blog.Views.MainView extends Backbone.View
647 | initialize: ->
648 | @collection.comparator = (model) ->
649 | -(new Date(model.get("date")).getTime())
650 |
651 | _.bindAll this, "render"
652 | @collection.bind "reset", @render
653 | @collection.bind "change", @render
654 | @collection.bind "add", @render
655 | @collection.bind "remove", @render
656 | @sidebarView = new Blog.Views.SidebarView()
657 | @postsListView = new Blog.Views.PostsListView(collection: @collection)
658 |
659 | render: ->
660 |
661 | @sidebarView.collection = new Blog.Collections.Posts(@collection.first(3))
662 | @sidebarView.render()
663 | @postsListView.render()
664 | ```
665 |
666 | ###routes.coffee
667 |
668 | ```python
669 | class Blog.Router.RoutesManager extends Backbone.Router
670 | initialize: (args) ->
671 | @collection = args.collection
672 |
673 | routes:
674 | "post/:id_post": "displayPost"
675 | hello: "hello"
676 | "*path": "root"
677 |
678 | root: ->
679 | @collection.all().fetch success: (result) ->
680 |
681 | #ça marche !!!
682 | console.log "fetching collection", result
683 |
684 |
685 | hello: ->
686 | $(".jumbotron > h1").html "Hello World !!!"
687 |
688 | displayPost: (id_post) ->
689 | tmp = new Blog.Models.Post(id: id_post)
690 | tmp.fetch success: (result) ->
691 | postView.render result
692 | ```
693 |
694 | Ouf, c'est fini.
695 |
696 | ###Transpilons
697 |
698 | Si vous lancez la commande : `cake build` vous obtiendrez de nouveau fichiers javascript dans le répertoire public. Relancez votre application, vous noterez qu’elle fonctionne comme avant.
699 |
700 | ###Conclusion(s)
701 |
702 | Les conclusions sont très personnelles. J'aime beaucoup le principe de "classe" et je le trouve très utile pour organiser son code, tout particulièrement en ce qui concerne les modèles et les collections. Au passage, vous avez du remarquer que le modèle objet de Backbone se marie parfaitement avec celui de Coffeescript (on peut hériter directement d'un Backbone.Model par exemple). C'est normal, les 2 outils sont codés par le même auteur.
703 |
704 | Ce que j'aime aussi, c'est les possibilités des chaînes de caractères.
705 |
706 | Par contre, je trouve que pour des portions de code très longue, on perd en visibilité (cf. `AdminView` ou `LoginView`) (mais ça n'engage que moi).
707 |
708 | Cependant, je ne peux m'empêcher d'aimer ce "petit" langage et les concepts qu'il apporte.
709 |
710 | ... A vous de voir ;)
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
--------------------------------------------------------------------------------
/14-BACKBONE-ET-TYPESCRIPT.md:
--------------------------------------------------------------------------------
1 | #Backbone et Typescript
2 |
3 | >*Sommaire*
4 |
5 | >>- *Installation de Typescript ...*
6 | >>- *... des Classes, des interfaces, des modules et du typage !!!*
7 | >>- *La cohabitation avec Backbone*
8 |
9 | >*A l'heure où j'écris, Microsoft vient, il y a peu, de présenter au monde son nouveau bébé : TypeScript, un transpiler javascript (un autre !) qui préfigure ce que sera la future version de javascript. Sur le même principe que Coffescript, Typescript apporte les concepts qui manquent tant à de nombreux développeurs "allergiques" à javascript. C'est à dire les, les Classes mais aussi les modules (pensez namespaces ou packages), les interfaces, les propriétés… Mais il y a un petit plus : on conserve les {}, les (), les ; ... tout ce qui contribue finalement à rendre le code lisible.*
10 |
11 | L'objectif n'est pas de vous expliquer Typescript de A à Z (ça pourrait faire un autre bouquin, si ça vous intéresse faites moi signe), mais de vous montrer rapidement ce que ça pourrait vous apporter utilisé conjointement avec Backbone.
12 |
13 |
14 | ##TypeScript, on installe
15 |
16 | Sachez le, Typescript fonctionne aussi bien sous Windows, Linux qu'OSX. Et oui Microsoft n'a pas fait du "fermé", et en plus c'est **opensource** et ça respecte au plus proche les spécifications d'Ecmascript 6 en transpilant aussi bien de l'Ecmascript 5 ou 3, donc compatible avec nos navigateurs actuels.
17 |
18 | ###Pré-requis
19 |
20 | Avant toute chose, il faut installer **Node.js**, vous trouverez tout ce qu'il faut sur le site :
21 |
22 | [http://nodejs.org/download/](http://nodejs.org/download/).
23 |
24 | Pensez aussi à installer **Npm** (node package manager) dans le cas de linux.
25 |
26 | ###Installation de Typescript
27 |
28 | C'est extrêment simple :
29 |
30 | - sous Windows : `npm install -g typescript`
31 | - sous Linux : `sudo npm install -g typescript`
32 | - sous OSX : `sudo npm install -g typescript`
33 |
34 | ###Comment transpiler ?
35 |
36 | Sous Windows vous pouvez utiliser le plugin Typescript pour Visual Studio 2012 (existe aussi en version express) mais nous, on va le faire à l'ancienne, à la mimine :
37 |
38 | tsc nom_de_mon_script.ts
39 |
40 | Et vous obtiendrez un fichier `nom_de_mon_script.js`.
41 |
42 | >>Vous l'aurez donc compris, l'extension d'un fichier typescript est `.ts`
43 |
44 | ###IDE ?
45 |
46 | En plus de Visual Studio vous pouvez aussi utiliser **Vim**, **Emacs** ou **SublimeText2** pour lesquels vous trouverez "la colorisation syntaxique" ici [http://blogs.msdn.com/b/interoperability/archive/2012/10/01/sublime-text-vi-emacs-typescript-enabled.aspx](http://blogs.msdn.com/b/interoperability/archive/2012/10/01/sublime-text-vi-emacs-typescript-enabled.aspx).
47 |
48 | ###TypeScript, quoi de plus que Javascript ou Coffeescript ?
49 |
50 | Sachez le, Typescript, c'est du javascript. Vous pouvez mettre du "pur" javascript dans un code Typescript sans problème, cela fonctionnera. Typescript apporte juste quelques notions supplémentaires, comme les types (qui sont juste vérifiés à la transpilation), les classes, les interfaces, mais aussi les modules qui sont très pratiques pour organiser votre code.
51 |
52 | >>le typage n'est pas obligatoire, c'est juste finalement très utile pour vérifier que l'on n'a pas codé n'importe quoi (je reste poli ;) ). Je n'en voyais pas l'utilité au début (je faisais juste une fixette sur les classes), mais finalement cela m'a évité pas mal d'erreur.
53 |
54 | ##Le (petit) tour de TypeScript en 1 exemple
55 |
56 | Commencez par créer (dans un répertoire de travail) l'arborecence suivante (avec les fichiers correspondants) :
57 |
58 | mon_repertoire_de_travail---|
59 | |-core\
60 | | |-core.ts
61 | |
62 | |-models\ <-- je mettrais mes fichiers modèles ici
63 | | |-Animal.ts
64 | | |-Human.ts
65 | |-index.html
66 | |-app.ts
67 |
68 | ###core.ts
69 |
70 | Nous allons définir notre 1er module avec le mot clé `module`, notre 1ère classe `Model` qui implémente les interfaces `Persistable` et `Entity`. Notre classe `Model` dispose d'une variable `public` & `static` appelée `store` de type `Entity[]` initialisé à vide ` = [];`. Le modèle aura une méthode lui permettant de "s'ajouter" au store. La classe `Model` aura une méthode statique `getAll(kind:string)` qui permettra de retourner un tableau de modèles (d'un certain type).
71 |
72 | >>**Pourquoi Entity ?** En fait je souhaite ajouter dans le même tableau `store` différents type de modèles, donc je leur ferais implémenter la même interface pour pouvoir tous les "pousser" à mon tableau.
73 |
74 | >>Notez au passage la notion de "fat arrow" ou `=>` appelée aussi "arrow function expression" (fonctionnalité prévue pour Ecmascript 6). Ca vous rappelle quelque chose ;) ?
75 |
76 | >>Notez aussi au passage, par contre que mon code ne fonctionnera que pour des navigateur Ecmascript 5 "compliant", puisque j'utilise le `filter` des `Array`, par contre j'ai un code plus "sympa" avec `=>`.
77 |
78 | >>Remarquez le mot clé `export` qui permet de rendre la classe accessible à partir du module : `Core.Model`
79 |
80 | ```javascript
81 | module Core {
82 |
83 | interface Persistable {
84 | saved : bool;
85 | addToStore() : void;
86 | }
87 |
88 | export interface Entity {
89 | kind : string;
90 | }
91 |
92 | export class Model implements Persistable, Entity {
93 | saved : bool = false;
94 | kind : string = "kindOfModel";
95 | public static store : Entity[] = [];
96 |
97 | public addToStore() : void {
98 | if(!this.saved) {
99 | this.saved = true
100 | Model.store.push(this);
101 | }
102 | }
103 |
104 | public static getAll(kind:string) : Entity[] {
105 | return Model.store.filter((model:Entity) => {
106 | return model.kind == kind;
107 | });
108 | }
109 | }
110 |
111 | }
112 | ```
113 |
114 | ###Allons créer nos modèles
115 |
116 | Nous allons créer 2 modèles : Animal and Human (je sais ce n'est pas très original, un orignal, à Montréal ...).
117 |
118 | - Pour hériter, nous utilisons `extends`
119 | - Pour référencer notre module `Core`, nous utilisons ceci en en-tête de fichier : `///`
120 |
121 | *Animal.ts :*
122 |
123 | ```javascript
124 | ///
125 | module Models {
126 | export class Animal extends Core.Model implements Core.Entity {
127 | kind = "Animal";
128 | constructor(public name?="Wolf"){
129 | super();
130 | }
131 | }
132 | }
133 | ```
134 |
135 | >> l'utilisation de `public name?="Wolf"` dans le constructeur est un raccourci pour déclarer la propriété `name` tout en l'initialisant, la notation `?=` permet aussi de donner une valeur par défaut.
136 |
137 | De la même manière, codons notre modèle Human :
138 |
139 | *Human.ts :*
140 |
141 | ```javascript
142 | ///
143 | ///
144 | module Models {
145 | export class Human extends Core.Model implements Core.Entity {
146 | kind = "Human";
147 | animal : Models.Animal;
148 | constructor(public name?="John Doe"){
149 | super();
150 | }
151 | }
152 | }
153 | ```
154 |
155 | >>J'explique que le modèle Human a une propriété `animal` de type `Models.Animal`, si j'avais voulu être propre, j'aurais du faire la même chose pour `kind` avec `kind:string="Human"`, mais bon ... Notez aussi l'utilisation de `super` pour appeler le constructeur de la "maman".
156 |
157 | >>**Vous avez du vous apercevoir que nous pouvions utiliser le même nom de module dans 2 fichiers distincs, ce qui est extrêment pratique !**
158 |
159 | ###Utilisons tout cela
160 |
161 | Créez un fichier `index.html` avec le code ci-dessous :
162 |
163 | ```html
164 |
165 |
166 |
167 |
168 | TypeScript
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | ```
180 |
181 | >>**Attention** vous devez référencer les fichiers `.js` et non pas `.ts`
182 |
183 | Et enfin codons notre fichier principal `app.ts` :
184 |
185 | ```javascript
186 | ///
187 | ///
188 |
189 | var bob = new Models.Human("Bob Morane");
190 | var wolf = new Models.Animal("Wolf");
191 | var john = new Models.Human("John Doe");
192 |
193 | bob.animal = wolf;
194 |
195 | bob.addToStore();
196 | john.addToStore();
197 | wolf.addToStore();
198 |
199 | var ul = document.querySelector('ul');
200 |
201 | Core.Model.getAll("Human").forEach((model:Models.Human)=>{
202 | var li = document.createElement('li');
203 | li.innerHTML = model.name
204 | if(model.animal) li.innerHTML+= " / " + model.animal.name;
205 | ul.appendChild(li);
206 | });
207 | ```
208 |
209 | Nous avons donc :
210 |
211 | - Référencé nos 2 modèles en en-tête de fichier
212 | - instancié 2 humains et un animal
213 | - affecté l'animal `wolf` à `bob`
214 | - "enregistré" tous ces modèles dans le `Core.Model.store`
215 | - puis demandé d'afficher la liste des humains (`Core.Model.getAll("Human")`) dans une liste (`
`).
216 |
217 | et pour afficher tout ça, il va falloir transpiler :
218 |
219 | tsc --target ES5 app.ts
220 |
221 | Et maintenant, vous pouvez ouvrir votre page dans le navigateur, vous aurez la liste de vos Humains.
222 |
223 | ##Et Backbone dans tout ça ?
224 |
225 | Après ce bref aperçu, voyons comment nous pouvons profiter des possibilités de Typescript avec Backbone. Comme je le disais au début du chapitre, Typescript reste du javascript et est donc compatible avec les librairies existantes telles **jQuery** ou **Underscore**. pour les utiliser dans tu Typescript il suffit de les déclarer comme ceci :
226 |
227 | declare var $: any;
228 | declare var _: any;
229 |
230 | >>le type `any` est un peu à javascript ce que le Variant est à Visual Basic.
231 |
232 | Côté Backbone, nous pourrions aussi écrire `declare var Backbone`, mais ça serait dommage de ne pas pouvoir utiliser la notation de classe de TypeScript et continuer "à l'ancienne" avec le modèle objet de Backbone.
233 |
234 | Il est en fait possible de décrire des déclarations plus complexes pour que TypeScript "comprennent" Backbone, comme ceci par exemple :
235 |
236 | ```javascript
237 | declare module Backbone {
238 |
239 | export class Model {
240 | constructor (attr? , opts? );
241 | intialize (attr? , opts? );
242 | get(name: string): any;
243 | set(name: string, val: any): void;
244 | set(obj: any): void;
245 | save(attr? , opts? ): void;
246 | destroy(): void;
247 | bind(ev: string, f: Function, ctx?: any): void;
248 | toJSON(): any;
249 | }
250 | }
251 | ```
252 |
253 | Et ensuite vous pourrez écrire votre code Backbone en TypeScript de la façon suivante :
254 |
255 | ```javascript
256 | class Human extends Backbone.Model {
257 | intialize () {
258 | console.log("Hello from Human constructor.");
259 | }
260 | }
261 |
262 | var Bob = new Human({firstName:"Bob", lastName:"Morane"});
263 | console.log(Bob.get("firstName"), Bob.get("lastName"));
264 | ```
265 |
266 | >>*J'utilise `initialize` car en fait dans Backbone c'est une méthode appelée par le constructeur et je préfère ne pas modifier le fonctionnement intrinsèque de Backbone.*
267 |
268 | Dans la “vraie vie”, il faudra déclarer toutes les méthodes des autres composants Backbone. Mais pas de panique, il existe un repository GitHub avec les déclarations des librairies javascript les plus courantes : [https://github.com/borisyankov/DefinitelyTyped](https://github.com/borisyankov/DefinitelyTyped). Il suffit ensuite d'y faire référence dans le code de cette manière `/// `.
269 |
270 | De mon point de vue, TypeScript a un potentiel énorme, si ce n'est pour son côté typé, cela peut être pour sa contribution à l'adoption de javascript par les réfractaires. En tous les cas, j'aime beaucoup, et j'avais très envie de vous en parler.
271 |
272 | Si vous voulez aller plus loin, d'un point de vue outillage par exemple, vous pouvez lire un de mes article ici qui explique comment préparer sont environnement de "transpilation continue en TypeScript" avec Grunt : [http://k33g.github.com/2012/11/12/TSTRANSPIL.html](http://k33g.github.com/2012/11/12/TSTRANSPIL.html).
273 |
274 |
--------------------------------------------------------------------------------
/15-RESSOURCES.md:
--------------------------------------------------------------------------------
1 | #Ressources Backbone
2 |
3 | //TODO : une liste à faire vivre des ressources intéressantes concernant Backbone
--------------------------------------------------------------------------------
/16-HS-RESTHUB-BB-STACK.md:
--------------------------------------------------------------------------------
1 | #RESThub Backbone Stack
2 |
3 | >>*Ou comment organiser son code de manière professionnelle*
4 |
5 | >*Sommaire*
6 |
7 | >>- *Cht'ite intro*
8 | >>- *Cht'ite stack server avant de commencer*
9 | >>- *Cht'ite application*
10 |
11 | ##RESThub Backbone Stack : mais qu'est-ce donc ?
12 |
13 | Pour faire court, RestHub est une stack technique Java orientée MVC et services REST, c'est un framework opensource (Lyonnais :) ) [http://resthub.org/](http://resthub.org/). Il se trouve que l'équipe projet est aussi friande de javascript et a livré il y a peu une stack javascript pour RESThub à base de Backbone pour la partie cliente [http://resthub.org/backbone-stack.html](http://resthub.org/backbone-stack.html). Comme l'équipe est sympa, ils ont fait une stack js indépendante de la stack serveur, ce qui permet de l'utiliser avec n'importe quelle autre stack serveur quelle que soit la technologie utilisée.
14 |
15 | Alors, RBS (je lui donne ce petit nom pour aller plus vite) ne vient pas seule, mais est bien accompagnée, puisqu'elle embarque :
16 |
17 | - twitter bootstrap
18 | - handelbars
19 | - underscore (forcément)
20 | - jquery (forcément)
21 | - async.js
22 | - require.js
23 | - etc. ...
24 |
25 | Et notamment des composants maison tels **backbone-datagrid** :
26 |
27 | [https://github.com/loicfrering/backbone.datagrid](https://github.com/loicfrering/backbone.datagrid)
28 |
29 | et plein d'autres bonnes choses.
30 |
31 | L'objet de ce chapitre n'étant pas de faire le tour complet de RBS, mais de vous mettre le pied à l'étrier, de vous donner des idées, de trouver des exemples de bonnes pratiques (l'utilisation de require.js est un modèle du genre), et surtout de vous faire plaisir.
32 |
33 | ##Avant de s'en servir, nous avons besoin d'une "stack serveur"
34 |
35 | Normalement, vous avez Node et npm installés, donc créez un répertoire `rbs` (par exemple), puis :
36 |
37 | cd rbs
38 | npm install express
39 | npm install nstore
40 |
41 | dans `rbs`, créez un répertoire `public`, puis toujours dans `rbs`, créez un fichier `app.js`, avec le code suivant :
42 |
43 | ```javascript
44 | /*--------------------------------------------
45 | Déclaration des librairies
46 | --------------------------------------------*/
47 | var express = require('express'),
48 | nStore = require('nstore'),
49 | app = express();
50 |
51 | nStore = nStore.extend(require('nstore/query')());
52 |
53 | /*--------------------------------------------
54 | Paramétrages de fonctionnement d'Express
55 | --------------------------------------------*/
56 | app.use(express.json());
57 | app.use(express.urlencoded());
58 | app.use(express.methodOverride());
59 | app.use(express.static(__dirname + '/public'));
60 | app.use(express.cookieParser('ilovebackbone'));
61 | app.use(express.session({
62 | secret: "ilovebackbone"
63 | }));
64 |
65 | /*--------------------------------------------
66 | Définition de la base
67 | --------------------------------------------*/
68 | var models;
69 |
70 | models = nStore.new("models.db", function() {
71 | Routes();
72 | app.listen(3000);
73 | console.log('Express app started on port 3000');
74 | });
75 |
76 |
77 | function Routes() {
78 | /*
79 | Obtenir la liste de tous les models lorsque
80 | l'on appelle http://localhost:3000/models
81 | en mode GET
82 | */
83 | app.get('/models', function(req, res) {
84 | models.all(function(err, results) {
85 | if (err) {
86 | console.log("Erreur : ", err);
87 | res.json(err);
88 | } else {
89 | var models = [];
90 | for (var key in results) {
91 | var model = results[key];
92 | model.id = key;
93 | models.push(model);
94 | }
95 | res.json(models);
96 | }
97 | });
98 | });
99 |
100 | /*
101 | Obtenir la liste de tous les models correspondant à un critère
102 | lorsque l'on appelle http://localhost:3000/models/ en
103 | mode GET avec une requête en paramètre
104 | ex : query : { "kind" : "message"} }
105 | */
106 | app.get('/models/:query', function(req, res) {
107 | models.find(JSON.parse(req.params.query), function(err, results) {
108 | if (err) {
109 | console.log("Erreur : ", err);
110 | res.json(err);
111 | } else {
112 | var models = [];
113 | for (var key in results) {
114 | var model = results[key];
115 | model.id = key;
116 | models.push(model);
117 | }
118 | res.json(models);
119 | }
120 | });
121 |
122 | });
123 |
124 | /*
125 | Retrouver un model par sa clé unique lorsque
126 | l'on appelle http://localhost:3000/models/identifiant_du_model
127 | en mode GET
128 | */
129 |
130 | app.get('/models/:id', function(req, res) {
131 | models.get(req.params.id, function(err, post, key) {
132 | if (err) {
133 | console.log("Erreur : ", err);
134 | res.json(err);
135 | } else {
136 | model.id = key;
137 | res.json(model);
138 | }
139 | });
140 | });
141 |
142 | /*
143 | Créer un nouveau model lorsque
144 | l'on appelle http://localhost:3000/models
145 | avec en paramètre le post au format JSON
146 | en mode POST
147 | */
148 | app.post('/models', function(req, res) {
149 | var d = new Date(),
150 | model = req.body;
151 | model.saveDate = (d.valueOf());
152 |
153 | models.save(null, model, function(err, key) {
154 | if (err) {
155 | console.log("Erreur : ", err);
156 | res.json(err);
157 | } else {
158 | model.id = key;
159 | res.json(model);
160 | }
161 | });
162 | });
163 |
164 |
165 | /*
166 | Mettre à jour un model lorsque
167 | l'on appelle http://localhost:3000/blogpost
168 | avec en paramètre le post au format JSON
169 | en mode PUT
170 | */
171 | app.put('/models/:id', function(req, res) {
172 | var d = new Date(),
173 | model = req.body;
174 | model.saveDate = (d.valueOf());
175 |
176 | models.save(req.params.id, model, function(err, key) {
177 | if (err) {
178 | console.log("Erreur : ", err);
179 | res.json(err);
180 | } else {
181 | res.json(model);
182 | }
183 | });
184 | });
185 |
186 | /*
187 | supprimer un model par sa clé unique lorsque
188 | l'on appelle http://localhost:3000/blogpost/identifiant_du_post
189 | en mode DELETE
190 | */
191 | app.delete('/models/:id', function(req, res) {
192 | models.remove(req.params.id, function(err) {
193 | if (err) {
194 | console.log("Erreur : ", err);
195 | res.json(err);
196 | } else {
197 | //petit correctif de contournement (bug ds nStore) :
198 | //ré-ouvrir la base lorsque la suppression a été faite
199 | models = nStore.new("models.db", function() {
200 | res.json(req.params.id);
201 | //Le modèle est vide si on ne trouve rien
202 | });
203 | }
204 | });
205 | });
206 |
207 | }
208 |
209 | ```
210 |
211 | ##Installation de RBS
212 |
213 | Télécharger la stack complète ici : [https://github.com/resthub/resthub-backbone-stack/downloads](https://github.com/resthub/resthub-backbone-stack/downloads).
214 |
215 | Dézippez et coller le contenu de la racine dans le répertoire `rbs/public`.
216 |
217 | Vous devriez avoir une arborescence de ce type :
218 |
219 | rbs-|
220 | |-node_modules\
221 | |-public\
222 | | |-css\
223 | | |-img\
224 | | |-js\
225 | | |-build\
226 | | |-lib\
227 | | |-router\
228 | | |-app.js
229 | | |-main.js
230 | |-index.html
231 |
232 | Le script `public/js/app.js` contiendra votre code applicatif (ou partie de code), le script `public/js/main.js` permet de "charger" l'ensemble des scripts nécessaires (par exemple, ceux qui sont dans `public/js/lib` comme Backbone, Underscore, jQuery, etc. ...), puis de lancer le code contenu dans `app.js`.
233 |
234 | La déclaration dans la page `index.html` se fait de la manière suivante ``.
235 |
236 | Ensuite dans `js`, créez les répertoires suivants `models`, `views`, `collections` et dans `public` un répertoire `templates`. Notre arborescence devrez ressemblez à ceci :
237 |
238 | rbs-|
239 | |-node_modules\
240 | |-public\
241 | | |-css\
242 | | |-img\
243 | | |-templates\
244 | | |-js\
245 | | |-models\
246 | | |-collections\
247 | | |-views\
248 | | |-build\
249 | | |-lib\
250 | | |-router\
251 | | |-app.js
252 | | |-main.js
253 | |-index.html
254 |
255 | Il nous reste une petite modification du fichier `main.js` à faire, vers la fin du fichier, vous devez avoir une ligne `template: '../template'` que vous remplacez par `templates: '../templates',`. Oui, je sais, j'aurais pu nommer mon réperoire `templates` en `template`, mais que voulez vous, chacun ses petites habitudes.
256 |
257 | Maintenant nous pouvons coder rapidement une petite application : une liste de messages, avec un formulaire qui vous permet d'ajouter des messages, tout en les sauvegardant sur le serveur. Je sais, ce n'est pas mieux qu'une todo list, mais à vous d'être créatifs (moi, je fais du déblayage ;))
258 |
259 | ##Création d'un modèle et d'une collection
260 |
261 | Dans `js/models`, créez le fichier `message.js` avec le contenu suivant :
262 |
263 | ```javascript
264 | define(['backbone'], function(Backbone) {
265 |
266 | var Message = Backbone.Model.extend({
267 | urlRoot: "/models"
268 | });
269 |
270 | return Message;
271 |
272 | });
273 | ```
274 |
275 | Dans `js/collections`, créez le fichier `messages.js` avec le contenu suivant :
276 |
277 | ```javascript
278 | define([
279 | 'backbone',
280 | 'models/message'], function(Backbone, Message) {
281 |
282 | var Messages = Backbone.Collection.extend({
283 | url: "/models",
284 | model: Message
285 | });
286 |
287 | return Messages;
288 |
289 | });
290 | ```
291 |
292 | >>**Premières remarques :** vous pouvez voir que dans chacun des fichiers, on déclare les dépendances nécessaires (un peu comme le `import` en java) à Backbone et aux modèles.
293 |
294 |
295 | ##Templates
296 |
297 | Nous allons avoir besoin d'un formulaire de saisie de message, et d'une liste de messages.
298 |
299 | ###Template formulaire
300 |
301 | Dans `public/templates` créez un fichier `message-forms.hbs` avec le code suivant :
302 |
303 | ```html
304 |
313 | ```
314 |
315 | >>*les attributs `data-field` ne sont pas une spécificité RestHub, c'est pour me faciliter la vie plus tard quand je vais devoir récupérer les valeurs de mon formulaire.*
316 |
317 | ###Template de liste
318 |
319 | Dans `public/templates` créez un fichier `message.hbs` avec le code suivant :
320 |
321 | ```html
322 |
323 | {{#each collection}}
324 |
{{from}} : {{subject}} / {{body}}
325 | {{/each}}
326 |
327 | ```
328 |
329 | ##Vues associées aux templates
330 |
331 | Nous allons créer dans `public/js/views` 2 vues backbone associées à chacun des templates.
332 |
333 | ###messages-view.js
334 |
335 | ```javascript
336 | define([
337 | 'resthub',
338 | 'hbs!templates/messages'],
339 |
340 | function(Resthub, messagesTemplate) {
341 |
342 | var MessagesView = Resthub.View.extend({
343 | el: $('#messages'),
344 | template: messagesTemplate,
345 |
346 | initialize: function() {
347 | this.collection.on('reset', this.render, this);
348 | this.collection.on('add', this.render, this);
349 | }
350 | });
351 |
352 | return MessagesView;
353 | });
354 | ```
355 |
356 | >>*Notez l'utilisation de Resthub.View qui est une version améliorée de Backbone.View fournissant notamment une implémentation par défaut du render() ainsi que la gestion de l'élément $root sur lequel est attaché la vue ($el représentant l'élément DOM de la vue elle même) ainsi que tout un tas de fonctionnalités très pratiques*
357 |
358 | >>*Remarquez ceci : `'hbs!templates/messages'` pour référencer les template*
359 |
360 | >>*avec `this.collection.on('reset', this.render, this);`, j'explique qu'à chaque `reset` de la collection, je déclenche un rendu de ma vue, et il se trouve que la méthode `reset()` est déclenchée à chaque fois que l'on fait un `fetch` de la collection*
361 |
362 | ###message-form-view.js
363 |
364 | ```javascript
365 | define([
366 | 'backbone',
367 | 'hbs!templates/message-form',
368 | 'models/message'],
369 |
370 | function(Resthub, messageFormTemplate, Message, MessagesView) {
371 |
372 | var MessageFormView = Resthub.View.extend({
373 | el: $('#message_form'),
374 | template: messageFormTemplate,
375 |
376 | events: {
377 | "click button[data-action='add']": 'add',
378 | "click button[data-action='cancel']": 'cancel',
379 | },
380 |
381 | initialize: function() {
382 | this.render();
383 | },
384 |
385 | add: function() {
386 | var model = new Message();
387 | var view = this;
388 |
389 | this.$el.find("input[data-field]").each(function() {
390 | model.set(this.getAttribute("data-field"), this.value);
391 | })
392 |
393 | model.save({}, {
394 | success: function() {
395 | view.collection.fetch({
396 | success: function() {
397 | view.$el[0].reset();
398 | },
399 | error: function(err) {
400 | throw err;
401 | }
402 | });
403 | },
404 | error: function(err) {
405 | throw err;
406 | }
407 | });
408 |
409 | },
410 | cancel: function() {
411 | console.log("CANCEL");
412 | this.$el[0].reset();
413 | }
414 | });
415 | return MessageFormView;
416 | });
417 | ```
418 |
419 | >>*Notez, qu'à chaque fois que je sauvegarde un modèle, je "fetch" ma collection qui va donc déclencher un `render` de l'instance de `MessagesView`*;
420 |
421 | ##app.js
422 |
423 | Il est temps d'apposer la touche finale, et de saisir le code suivant dans `app.js` :
424 |
425 | ```javascript
426 | define([
427 | 'collections/messages',
428 | 'views/messages-view',
429 | 'views/message-form-view'],
430 |
431 | function(Messages, MessagesView, MessageFormView) {
432 |
433 | window.messages = new Messages();
434 | window.messagesView = new MessagesView({
435 | collection: messages
436 | });
437 | window.messageForm = new MessageFormView({
438 | collection: messages
439 | });
440 |
441 | messages.fetch();
442 | });
443 | ```
444 |
445 | Vous pouvez maintenant lancer votre application : `node app.js` et vérifier que tout fonctionne.
446 |
447 | \
448 |
449 |
450 | ##Conclusion
451 |
452 | Voilà, il existe dans la stack RESThub Backbone, de nombreux autres composants que je n'ai pas encore eu le temps d'étudier et qui certainement permettent de faire du code plus simple que celui que je vous montre, mais je tenais à vous parler de cette stack car l'organisation des différents fichiers de scripts, templates… ainsi que la gestion des dépendance me plaît beaucoup, et est sans nul doute un critère de réussite sur un projet en équipe. Ce côté structurant peut paraître fastidieux au départ, mais au bout de quelques heures de code intensif, vous vous apercevrez que vous retrouvez beaucoup plus facilement vos petits.
453 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ###- [Télécharger le "LIVRE" en pdf (v° alpha du 20/12/2012)](backbone.en.douceur.20121220.pdf)
4 |
5 | ###- [Format epub disponible aussi ...](backbone.en.douceur.20121220.epub)
6 |
7 | ###- [Voir le code source ...](https://github.com/k33g/backbone.en.douceur)
8 |
9 |
10 |
11 |
12 | Cet ouvrage va traiter au moins les points suivants :
13 |
14 | - 01-Préambule & Remerciements *(publié)*
15 | - 02-Présentation *(publié)*
16 | - 03-Prérequis : jQuery & Underscore *(publié)*
17 | - 04-Premiers contacts avec Backbone *(publié)*
18 | - 05-Le modèle objet de Backbone *(publié)*
19 | - 06-Il nout faut un serveur & Principes REST *(publié)*
20 | - 07-Les Modèles & Collections en détail *(publié)*
21 | - 08-Les Vues & Templating *(publié)*
22 | - 09-Le Routeur *(publié)*
23 | - 10-Organiser son code *(publié)*
24 | - 11-"Securisation" de notre application *(publié)*
25 | - 12-Backbone.Sync *(à écrire)*
26 | - 13-Backbone & Coffeescript *(publié)*
27 | - 14-Autres frameworks MVC *(publié)*
28 | - 15-Backbone & Typescript *(publié)*
29 | - 16-Ressources *(à écrire)*
30 | - 17-Hors Serie : RestHub Backbone Stack : "quick start" *(publié)*
31 |
32 | Soyez patient, l'ensemble des chapitres sera publié avant fin décembre. J'ai juste besoin d'un peu de temps entre ma vie de famille, ma vie pro et ma vie de geek.
33 |
34 | Un pdf sera généré à chaque mise à jour (j'utilise Pandoc).
35 |
36 | Toute aide est la bienvenue, tant pour les corrections, les conseils, les ajouts que pour la mise en forme (je vous avoue que le paramétrage d'un template latex pour générer un pdf me rend chèvre, mais le résultat est déjà correct).
37 |
38 | Backbone.en.douceur by Philippe CHARRIERE is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. Based on a work at https://github.com/k33g/backbone.en.douceur.
39 |
--------------------------------------------------------------------------------
/RSRC/01_01_MVC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/01_01_MVC.png
--------------------------------------------------------------------------------
/RSRC/01_02_MVC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/01_02_MVC.png
--------------------------------------------------------------------------------
/RSRC/02_01_ARBO.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_01_ARBO.png
--------------------------------------------------------------------------------
/RSRC/02_02_PAGE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_02_PAGE.png
--------------------------------------------------------------------------------
/RSRC/02_03_CONSOLE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_03_CONSOLE.png
--------------------------------------------------------------------------------
/RSRC/02_04_JQUERY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_04_JQUERY.png
--------------------------------------------------------------------------------
/RSRC/02_05_JQUERY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_05_JQUERY.png
--------------------------------------------------------------------------------
/RSRC/02_06_JQUERY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_06_JQUERY.png
--------------------------------------------------------------------------------
/RSRC/02_07_JQUERY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_07_JQUERY.png
--------------------------------------------------------------------------------
/RSRC/02_08_JQUERY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_08_JQUERY.png
--------------------------------------------------------------------------------
/RSRC/02_09_JQUERY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_09_JQUERY.png
--------------------------------------------------------------------------------
/RSRC/02_10_JQUERY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_10_JQUERY.png
--------------------------------------------------------------------------------
/RSRC/02_11_JQUERY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_11_JQUERY.png
--------------------------------------------------------------------------------
/RSRC/02_12_UNDERSCORE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_12_UNDERSCORE.png
--------------------------------------------------------------------------------
/RSRC/02_13_UNDERSCORE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_13_UNDERSCORE.png
--------------------------------------------------------------------------------
/RSRC/02_14_UNDERSCORE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_14_UNDERSCORE.png
--------------------------------------------------------------------------------
/RSRC/02_15_UNDERSCORE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/02_15_UNDERSCORE.png
--------------------------------------------------------------------------------
/RSRC/03_01_BB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/03_01_BB.png
--------------------------------------------------------------------------------
/RSRC/03_02_BB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/03_02_BB.png
--------------------------------------------------------------------------------
/RSRC/03_03_BB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/03_03_BB.png
--------------------------------------------------------------------------------
/RSRC/03_04_BB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/03_04_BB.png
--------------------------------------------------------------------------------
/RSRC/03_05_BB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/03_05_BB.png
--------------------------------------------------------------------------------
/RSRC/05_01_SERV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/05_01_SERV.png
--------------------------------------------------------------------------------
/RSRC/05_02_SERV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/05_02_SERV.png
--------------------------------------------------------------------------------
/RSRC/05_03_SERV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/05_03_SERV.png
--------------------------------------------------------------------------------
/RSRC/05_04_SERV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/05_04_SERV.png
--------------------------------------------------------------------------------
/RSRC/05_05_SERV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/05_05_SERV.png
--------------------------------------------------------------------------------
/RSRC/05_06_SERV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/05_06_SERV.png
--------------------------------------------------------------------------------
/RSRC/05_07_SERV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/05_07_SERV.png
--------------------------------------------------------------------------------
/RSRC/05_08_SERV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/05_08_SERV.png
--------------------------------------------------------------------------------
/RSRC/06_01_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_01_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_02_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_02_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_03_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_03_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_04_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_04_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_05_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_05_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_06_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_06_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_07_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_07_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_08_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_08_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_09_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_09_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_10_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_10_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_11_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_11_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_12_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_12_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_13_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_13_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_14_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_14_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_15_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_15_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_16_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_16_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_17_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_17_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_18_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_18_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_19_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_19_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_20_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_20_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_21_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_21_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_22_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_22_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_23_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_23_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_24_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_24_MODS.png
--------------------------------------------------------------------------------
/RSRC/06_25_MODS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/06_25_MODS.png
--------------------------------------------------------------------------------
/RSRC/07_01_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_01_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_02_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_02_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_03_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_03_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_04_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_04_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_05_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_05_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_06_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_06_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_07_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_07_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_08_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_08_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_09_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_09_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_10_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_10_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_11_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_11_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/07_12_VIEWS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/07_12_VIEWS.png
--------------------------------------------------------------------------------
/RSRC/08_01_routeur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/08_01_routeur.png
--------------------------------------------------------------------------------
/RSRC/08_02_routeur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/08_02_routeur.png
--------------------------------------------------------------------------------
/RSRC/08_03_routeur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/08_03_routeur.png
--------------------------------------------------------------------------------
/RSRC/09_01_ORGA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/09_01_ORGA.png
--------------------------------------------------------------------------------
/RSRC/10_01_ORGA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/10_01_ORGA.png
--------------------------------------------------------------------------------
/RSRC/10_02_ORGA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/10_02_ORGA.png
--------------------------------------------------------------------------------
/RSRC/10_03_ORGA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/10_03_ORGA.png
--------------------------------------------------------------------------------
/RSRC/10_04_ORGA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/10_04_ORGA.png
--------------------------------------------------------------------------------
/RSRC/10_05_ORGA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/10_05_ORGA.png
--------------------------------------------------------------------------------
/RSRC/10_06_ORGA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/10_06_ORGA.png
--------------------------------------------------------------------------------
/RSRC/10_07_ORGA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/10_07_ORGA.png
--------------------------------------------------------------------------------
/RSRC/13_01_MVC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/13_01_MVC.png
--------------------------------------------------------------------------------
/RSRC/13_02_MVC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/13_02_MVC.png
--------------------------------------------------------------------------------
/RSRC/13_03_MVC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/13_03_MVC.png
--------------------------------------------------------------------------------
/RSRC/13_04_MVC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/13_04_MVC.png
--------------------------------------------------------------------------------
/RSRC/13_05_MVC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/13_05_MVC.png
--------------------------------------------------------------------------------
/RSRC/13_06_MVC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/13_06_MVC.png
--------------------------------------------------------------------------------
/RSRC/13_07_MVC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/13_07_MVC.png
--------------------------------------------------------------------------------
/RSRC/16_01_RBS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/RSRC/16_01_RBS.png
--------------------------------------------------------------------------------
/backbone.en.douceur.epub:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/backbone.en.douceur.epub
--------------------------------------------------------------------------------
/backbone.en.douceur.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/e-books/backbone.en.douceur/cdadc3314124b3c07e43106ef4fb535b9bbb7426/backbone.en.douceur.pdf
--------------------------------------------------------------------------------
/epub-metadata.xml:
--------------------------------------------------------------------------------
1 | Philippe Charrière
2 | fr-FR
--------------------------------------------------------------------------------
/epub-title.txt:
--------------------------------------------------------------------------------
1 | % Backbone.en.douceur
2 | % Philippe Charrière
--------------------------------------------------------------------------------
/epub.css:
--------------------------------------------------------------------------------
1 | body { margin: 5%; text-align: justify; font-size: medium; }
2 |
3 | pre,
4 | code { font-family: monospace; margin: 10px 0;}
5 |
6 | ul,
7 | ol {margin: 20px; }
8 |
9 | p code,
10 | pre.sourceCode {
11 | border:1px solid #eaeaea;
12 | background: #f8f8f8;
13 | border-radius: 4px;
14 | }
15 |
16 | p code {
17 | margin: 0 2px;
18 | padding: 0 5px;
19 | }
20 |
21 | pre.sourceCode {
22 | padding: 10px;
23 | }
24 |
25 | /* For source-code highlighting */
26 | .kw { color: #007020; font-weight: bold; }
27 | .dt { color: #902000; }
28 | .dv { color: #40a070; }
29 | .bn { color: #40a070; }
30 | .fl { color: #40a070; }
31 | .ch { color: #4070a0; }
32 | .st { color: #4070a0; }
33 | .co { color: #60a0b0; font-style: italic; }
34 | .ot { color: #007020; }
35 | .al { color: red; font-weight: bold; }
36 | .fu { color: #06287e; }
37 | .er { color: red; font-weight: bold; }
--------------------------------------------------------------------------------
/latex.template.tex:
--------------------------------------------------------------------------------
1 | \documentclass[$if(fontsize)$$fontsize$,$endif$$if(lang)$$lang$,$endif$]{$documentclass$}
2 | \usepackage[T1]{fontenc}
3 | \usepackage{lmodern}
4 | \usepackage{amssymb,amsmath}
5 | \usepackage{ifxetex,ifluatex}
6 | \usepackage[$geometry$,paper=$paper$,hmargin=$hmargin$,vmargin=$vmargin$]{geometry}
7 | \usepackage{fixltx2e} % provides \textsubscript
8 | % use microtype if available
9 | \IfFileExists{microtype.sty}{\usepackage{microtype}}{}
10 | \ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
11 | \usepackage[utf8]{inputenc}
12 |
13 | $if(euro)$
14 | \usepackage{eurosym}
15 | $endif$
16 | \else % if luatex or xelatex
17 | \usepackage{fontspec}
18 | \ifxetex
19 | \usepackage{xltxtra,xunicode}
20 | \fi
21 | \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
22 | \newcommand{\euro}{€}
23 | $if(mainfont)$
24 | \setmainfont{$mainfont$}
25 | $endif$
26 | $if(sansfont)$
27 | \setsansfont{$sansfont$}
28 | $endif$
29 | $if(monofont)$
30 | \setmonofont{$monofont$}
31 | $endif$
32 | $if(mathfont)$
33 | \setmathfont{$mathfont$}
34 | $endif$
35 | \fi
36 |
37 | \usepackage{fancyhdr}
38 | \pagestyle{fancy}
39 | \pagenumbering{arabic}
40 | \lhead{\itshape $title$}
41 | \chead{}
42 | \rhead{\itshape{\nouppercase{\leftmark}}}
43 | \lfoot{\itshape version $version$}
44 | \cfoot{}
45 | \rfoot{\thepage}
46 |
47 | $if(geometry)$
48 | \usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry}
49 | $endif$
50 | $if(natbib)$
51 | \usepackage{natbib}
52 | \bibliographystyle{plainnat}
53 | $endif$
54 | $if(biblatex)$
55 | \usepackage{biblatex}
56 | $if(biblio-files)$
57 | \bibliography{$biblio-files$}
58 | $endif$
59 | $endif$
60 | $if(listings)$
61 | \usepackage{listings}
62 | $endif$
63 | $if(lhs)$
64 | \lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{}
65 | $endif$
66 | $if(highlighting-macros)$
67 | $highlighting-macros$
68 | $endif$
69 | $if(verbatim-in-note)$
70 | \usepackage{fancyvrb}
71 | $endif$
72 | $if(fancy-enums)$
73 | % Redefine labelwidth for lists; otherwise, the enumerate package will cause
74 | % markers to extend beyond the left margin.
75 | \makeatletter\AtBeginDocument{%
76 | \renewcommand{\@listi}
77 | {\setlength{\labelwidth}{4em}}
78 | }\makeatother
79 | \usepackage{enumerate}
80 | $endif$
81 | $if(tables)$
82 | \usepackage{ctable}
83 | \usepackage{float} % provides the H option for float placement
84 | $endif$
85 | $if(graphics)$
86 | \usepackage{graphicx}
87 | % We will generate all images so they have a width \maxwidth. This means
88 | % that they will get their normal width if they fit onto the page, but
89 | % are scaled down if they would overflow the margins.
90 | \makeatletter
91 | \def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth
92 | \else\Gin@nat@width\fi}
93 | \makeatother
94 | \let\Oldincludegraphics\includegraphics
95 | \renewcommand{\includegraphics}[1]{\Oldincludegraphics[width=\maxwidth]{#1}}
96 | $endif$
97 | \ifxetex
98 | \usepackage[setpagesize=false, % page size defined by xetex
99 | unicode=false, % unicode breaks when used with xetex
100 | xetex]{hyperref}
101 | \else
102 | \usepackage[unicode=true]{hyperref}
103 | \fi
104 | \hypersetup{breaklinks=true,
105 | bookmarks=true,
106 | pdfauthor={$author-meta$},
107 | pdftitle={$title-meta$},
108 | colorlinks=true,
109 | urlcolor=$if(urlcolor)$$urlcolor$$else$blue$endif$,
110 | linkcolor=$if(linkcolor)$$linkcolor$$else$magenta$endif$,
111 | pdfborder={0 0 0}}
112 | $if(links-as-notes)$
113 | % Make links footnotes instead of hotlinks:
114 | \renewcommand{\href}[2]{#2\footnote{\url{#1}}}
115 | $endif$
116 | $if(strikeout)$
117 | \usepackage[normalem]{ulem}
118 | % avoid problems with \sout in headers with hyperref:
119 | \pdfstringdefDisableCommands{\renewcommand{\sout}{}}
120 | $endif$
121 | \setlength{\parindent}{0pt}
122 | \setlength{\parskip}{6pt plus 2pt minus 1pt}
123 | \setlength{\emergencystretch}{3em} % prevent overfull lines
124 | $if(numbersections)$
125 | $else$
126 | \setcounter{secnumdepth}{0}
127 | $endif$
128 | $if(verbatim-in-note)$
129 | \VerbatimFootnotes % allows verbatim text in footnotes
130 | $endif$
131 | $if(lang)$
132 | \ifxetex
133 | \usepackage{polyglossia}
134 | \setmainlanguage{$mainlang$}
135 | \else
136 | \usepackage[$lang$]{babel}
137 | \fi
138 | $endif$
139 | $for(header-includes)$
140 | $header-includes$
141 | $endfor$
142 |
143 | $if(title)$
144 | \title{$title$}
145 | $endif$
146 | \author{$for(author)$$author$$sep$ \and $endfor$}
147 | \date{$date$}
148 |
149 | \begin{document}
150 | $if(title)$
151 | \maketitle
152 | $endif$
153 |
154 | $for(include-before)$
155 | $include-before$
156 |
157 | $endfor$
158 | $if(toc)$
159 | {
160 | \hypersetup{linkcolor=black}
161 | \tableofcontents
162 | }
163 | $endif$
164 | $body$
165 |
166 | $if(natbib)$
167 | $if(biblio-files)$
168 | $if(biblio-title)$
169 | $if(book-class)$
170 | \renewcommand\bibname{$biblio-title$}
171 | $else$
172 | \renewcommand\refname{$biblio-title$}
173 | $endif$
174 | $endif$
175 | \bibliography{$biblio-files$}
176 |
177 | $endif$
178 | $endif$
179 | $if(biblatex)$
180 | \printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$
181 |
182 | $endif$
183 | $for(include-after)$
184 | $include-after$
185 |
186 | $endfor$
187 | \end{document}
188 |
--------------------------------------------------------------------------------
/makebook.sh:
--------------------------------------------------------------------------------
1 | #HTML version
2 | pandoc -s --toc --highlight-style pygments -c style.css -N -o backbone.en.douceur.html \
3 | 00-PREAMBULE.md \
4 | 01-PRESENTATION.md \
5 | 02-MAINS-DANS-LE-CAMBOUIS.md \
6 | 03-1ERS-CONTACTS-AVEC-BACKBONE.md \
7 | 04-LE-MODELE-OBJET-DE-BACKBONE.md \
8 | 05-IL-NOUS-FAUT-UN-SERVEUR.md \
9 | 06-MODELES-ET-COLLECTIONS.md \
10 | 07-VUES-ET-TEMPLATING.md \
11 | 08-ROUTEUR.md \
12 | 09-ORGANISATION-CODE.md \
13 | 10-SECURISATION.md \
14 | 11-BACKBONE-SYNC.md \
15 | 12-BB-COFFEESCRIPT.md \
16 | 13-AUTRES-FWKS-MVC.md \
17 | 14-BACKBONE-ET-TYPESCRIPT.md \
18 | 15-RESSOURCES.md \
19 | 16-HS-RESTHUB-BB-STACK.md
20 |
21 | #PDF version
22 | pandoc -s --toc --latex-engine=xelatex --template=latex.template.tex -N \
23 | --variable=version:"alpha du 20.12.2012 | Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License." \
24 | --variable=monofont:Consolas \
25 | --variable=mainfont:Georgia \
26 | --variable fontsize=12pt \
27 | --variable=sansfont:Georgia \
28 | --variable=paper:a4paper \
29 | --variable=hmargin:2cm \
30 | --variable=vmargin:2cm \
31 | --variable=geometry:portrait \
32 | --variable=nohyphenation:true \
33 | --variable=author-meta:"Philippe Charrière" \
34 | --variable=title-meta$:"Backbone.en.douceur" \
35 | -o backbone.en.douceur.pdf \
36 | 00-PREAMBULE.md \
37 | 01-PRESENTATION.md \
38 | 02-MAINS-DANS-LE-CAMBOUIS.md \
39 | 03-1ERS-CONTACTS-AVEC-BACKBONE.md \
40 | 04-LE-MODELE-OBJET-DE-BACKBONE.md \
41 | 05-IL-NOUS-FAUT-UN-SERVEUR.md \
42 | 06-MODELES-ET-COLLECTIONS.md \
43 | 07-VUES-ET-TEMPLATING.md \
44 | 08-ROUTEUR.md \
45 | 09-ORGANISATION-CODE.md \
46 | 10-SECURISATION.md \
47 | 11-BACKBONE-SYNC.md \
48 | 12-BB-COFFEESCRIPT.md \
49 | 13-AUTRES-FWKS-MVC.md \
50 | 14-BACKBONE-ET-TYPESCRIPT.md \
51 | 15-RESSOURCES.md \
52 | 16-HS-RESTHUB-BB-STACK.md
53 |
54 |
55 | #Epub version
56 | pandoc
57 | --epub-stylesheet=epub.css \
58 | --highlight-style=kate \
59 | --epub-metadata=epub-metadata.xml \
60 | -o backbone.en.douceur.epub \
61 | epub-title.txt \
62 | 00-PREAMBULE.md \
63 | 01-PRESENTATION.md \
64 | 02-MAINS-DANS-LE-CAMBOUIS.md \
65 | 03-1ERS-CONTACTS-AVEC-BACKBONE.md \
66 | 04-LE-MODELE-OBJET-DE-BACKBONE.md \
67 | 05-IL-NOUS-FAUT-UN-SERVEUR.md \
68 | 06-MODELES-ET-COLLECTIONS.md \
69 | 07-VUES-ET-TEMPLATING.md \
70 | 08-ROUTEUR.md \
71 | 09-ORGANISATION-CODE.md \
72 | 10-SECURISATION.md \
73 | 11-BACKBONE-SYNC.md \
74 | 12-BB-COFFEESCRIPT.md \
75 | 13-AUTRES-FWKS-MVC.md \
76 | 14-BACKBONE-ET-TYPESCRIPT.md \
77 | 15-RESSOURCES.md \
78 | 16-HS-RESTHUB-BB-STACK.md
79 |
80 |
81 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 | html {
4 | font-size: 100%;
5 | overflow-y: scroll;
6 | -webkit-text-size-adjust: 100%;
7 | -ms-text-size-adjust: 100%;
8 | }
9 |
10 | body{
11 |
12 | color: #333;
13 | background-color: #fff;
14 | border-color: #999999;
15 | border-width: 2px;
16 | line-height: 1.5;
17 | margin: 2em 3em;
18 | text-align:left;
19 | padding: 0 100px 0 100px;
20 | }
21 |
22 |
23 | /* ~~ Element/tag selectors ~~ */
24 | ul, ol, dl { /* Due to variations between browsers, it's best practices to zero padding and margin on lists. For consistency, you can either specify the amounts you want here, or on the list items (LI, DT, DD) they contain. Remember that what you do here will cascade to the .nav list unless you write a more specific selector. */
25 | padding: 0;
26 | margin: 0 0 0 30px;
27 | }
28 | h1, h2, h3, h4, h5, h6, p {
29 | margin-top: 0; /* removing the top margin gets around an issue where margins can escape from their containing div. The remaining bottom margin will hold it away from any elements that follow. */
30 | padding-right: 15px;
31 | padding-left: 15px; /* adding the padding to the sides of the elements within the divs, instead of the divs themselves, gets rid of any box model math. A nested div with side padding can also be used as an alternate method. */
32 | text-align: left;
33 | font-family:Georgia;
34 | }
35 |
36 | h1{
37 | font-size: 32px;
38 | line-height: 43px;
39 | text-align: left;
40 | border-bottom:3px solid #CCCCCC;
41 | padding-bottom:13px;
42 | }
43 |
44 | h2.booktitle{
45 | font-size:1.5em;
46 | color:#666;
47 | }
48 |
49 | h2 {
50 | font-size:28px;
51 | }
52 |
53 | a img { /* this selector removes the default blue border displayed in some browsers around an image when it is surrounded by a link */
54 | border: none;
55 | }
56 | /* ~~ Styling for your site's links must remain in this order - including the group of selectors that create the hover effect. ~~ */
57 | a:link {
58 | color: #42413C;
59 | text-decoration: underline; /* unless you style your links to look extremely unique, it's best to provide underlines for quick visual identification */
60 | }
61 | a:visited {
62 | color: #6E6C64;
63 | text-decoration: underline;
64 | }
65 | a:hover, a:active, a:focus { /* this group of selectors will give a keyboard navigator the same hover experience as the person using a mouse. */
66 | text-decoration: none;
67 | }
68 |
69 | /* ~~ this fixed width container surrounds the other divs ~~ */
70 | .container {
71 | width: 740px;
72 | background: #FFF;
73 | margin: 0 auto; /* the auto value on the sides, coupled with the width, centers the layout */
74 | }
75 |
76 | /* ~~ the header is not given a width. It will extend the full width of your layout. It contains an image placeholder that should be replaced with your own linked logo ~~ */
77 | header {
78 | background: #fff;
79 | }
80 |
81 |
82 | /* ~~ This is the layout information. ~~
83 |
84 | 1) Padding is only placed on the top and/or bottom of the div. The elements within this div have padding on their sides. This saves you from any "box model math". Keep in mind, if you add any side padding or border to the div itself, it will be added to the width you define to create the *total* width. You may also choose to remove the padding on the element in the div and place a second div within it with no width and the padding necessary for your design.
85 |
86 | */
87 |
88 | .content {
89 | padding: 10px 0;
90 | text-align:left;
91 | }
92 |
93 | /* ~~ The footer ~~ */
94 | .footer {
95 | padding: 10px 0;
96 | background: #fff;
97 | }
98 |
99 | /* ~~ miscellaneous float/clear classes ~~ */
100 | .fltrt { /* this class can be used to float an element right in your page. The floated element must precede the element it should be next to on the page. */
101 | float: right;
102 | margin-left: 8px;
103 | }
104 | .fltlft { /* this class can be used to float an element left in your page. The floated element must precede the element it should be next to on the page. */
105 | float: left;
106 | margin-right: 8px;
107 | }
108 | .clearfloat { /* this class can be placed on a or empty div as the final element following the last floated div (within the #container) if the #footer is removed or taken out of the #container */
109 | clear:both;
110 | height:0;
111 | font-size: 1px;
112 | line-height: 0px;
113 | }
114 |
115 |
116 | .copyright {
117 | text-align: left;
118 | }
119 |
120 | .booktitle{
121 | text-align: center;
122 | line-height: 41px;
123 | border-bottom: 1px solid white;
124 | padding: 0px;
125 | font-size: 2.2em;
126 | }
127 |
128 | .booktitle.author{
129 | font-size:24px;
130 | }
131 |
132 |
133 | #contents-list {
134 | background:none repeat scroll 0 0 #EEEEEE;
135 | border:3px solid #DDDDDD;
136 | padding:1em 1em 1em 3em;
137 | }
138 |
139 | .subitem{
140 | margin-left:25px;
141 | }
142 |
143 | #references-list {
144 | word-wrap: break-word;
145 | }
146 |
147 |
148 | code {
149 | font-family: "Consolas", "Bitstream Vera Sans Mono", monospace !important;
150 | font-weight: normal !important;
151 | font-style: normal !important;
152 | font-size: 15px !important;
153 | }
154 |
155 | pre {
156 |
157 | font-family: "Consolas", "Bitstream Vera Sans Mono", monospace !important;
158 | font-weight: normal !important;
159 | font-style: normal !important;
160 | font-size: 15px !important;
161 |
162 | display: block;
163 | padding: 8.5px;
164 | margin: 0 0 9px;
165 | font-size: 12px;
166 | line-height: 18px;
167 | background-color: #f5f5f5;
168 | border: 1px solid #ccc;
169 | border: 1px solid rgba(0, 0, 0, 0.15);
170 | -webkit-border-radius: 4px;
171 | -moz-border-radius: 4px;
172 | border-radius: 4px;
173 | white-space: pre;
174 | white-space: pre-wrap;
175 | word-break: break-all;
176 | word-wrap: break-word;
177 | }
--------------------------------------------------------------------------------