├── .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 | ![Figure 1-1. MVC Vision "Back-End"](RSRC/01_01_MVC.png) 80 | 81 | ***Figure 1-1. MVC Vision "Back-End"*** 82 | 83 | 84 | ![Figure 1-2. MVC Vision "Front-End"](RSRC/01_02_MVC.png) 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 | ![Arborecsence](RSRC/02_01_ARBO.png) 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 | 155 | 156 |
157 | 158 |
159 |

Backbone rocks !!!

160 |

161 | "Ma vie mon oeuvre" 162 |

163 |
164 | 165 |
166 | 167 |

les articles du blogs

168 | 169 | 174 | 175 |

les articles à venir

176 | 177 | 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 | ![Page](RSRC/02_02_PAGE.png) 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 | ![Console](RSRC/02_03_CONSOLE.png) 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 | ![jQuery](RSRC/02_04_JQUERY.png) 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 | ![jQuery](RSRC/02_05_JQUERY.png) 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 | ![jQuery](RSRC/02_06_JQUERY.png) 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 | ![jQuery](RSRC/02_07_JQUERY.png) 245 | 246 | 247 | ![jQuery](RSRC/02_08_JQUERY.png) 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 | ![jQuery](RSRC/02_09_JQUERY.png) 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 | ![jQuery](RSRC/02_11_JQUERY.png) 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 | ![Underscore](RSRC/02_12_UNDERSCORE.png) 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 | ![Underscore](RSRC/02_13_UNDERSCORE.png) 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 | ![Underscore](RSRC/02_14_UNDERSCORE.png) 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 | ""; 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 | ![Underscore](RSRC/02_15_UNDERSCORE.png) 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 | 50 | 51 |
    52 | 53 |
    54 |

    Backbone rocks !!!

    55 |
    56 | 57 | 58 |
    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 | ![BB](RSRC/03_01_BB.png) 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 | ![BB](RSRC/03_02_BB.png) 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 |

    <%= article.title %>

    206 |
    <%= article.publicationDate %>
    207 |

    <%= article.content %>

    208 | <% }); %> 209 | ``` 210 | 211 | donc : 212 | 213 | ```html 214 | 215 | 216 | 223 | 224 |
    225 | 226 |
    227 |

    Backbone rocks !!!

    228 |
    229 | 230 | 231 | 240 | 241 |
    242 | 243 |
    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 | ![BB](RSRC/03_03_BB.png) 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 | ![BB](RSRC/03_04_BB.png) 344 | 345 | ![BB](RSRC/03_05_BB.png) 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 | 385 | 386 |
    387 | 388 |
    389 |

    Backbone rocks !!!

    390 |
    391 | 392 | 393 | 402 | 403 |
    404 | 405 |
    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 | 116 | 117 |
    118 |
    119 |

    Backbone rocks !!!

    120 |
    121 | 122 |
    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 | ![BB](RSRC/05_01_SERV.png) 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 | ![BB](RSRC/05_02_SERV.png) 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 | ![BB](RSRC/05_03_SERV.png) 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 | ![BB](RSRC/05_04_SERV.png) 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 | ![BB](RSRC/05_05_SERV.png) 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 | ![BB](RSRC/05_06_SERV.png) 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 | ![BB](RSRC/05_07_SERV.png) 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 | ![BB](RSRC/05_08_SERV.png) 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 | ![BB](RSRC/08_01_routeur.png) 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 | ![BB](RSRC/08_02_routeur.png) 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 | ![BB](RSRC/08_03_routeur.png) 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 | ![BB](RSRC/09_01_ORGA.png)\ 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 | ![BB](RSRC/10_01_ORGA.png)\ 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 |
    244 | 245 |
    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 | ![BB](RSRC/10_02_ORGA.png)\ 437 | 438 | 439 | Saissez quelques modifications, cliquez sur le bouton “Sauvegarder” . Vous obtiendrez un message d’erreur dans la console : 440 | 441 | ![BB](RSRC/10_03_ORGA.png)\ 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 | ![BB](RSRC/10_04_ORGA.png)\ 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 | ![BB](RSRC/10_05_ORGA.png)\ 453 | 454 | 455 | Faites quelques modifications, et sauvegardez les modifications : 456 | 457 | ![BB](RSRC/10_06_ORGA.png)\ 458 | 459 | 460 | Vous pouvez ensuite vérifiez qu’elles ont bien été prises en compte : 461 | 462 | ![BB](RSRC/10_07_ORGA.png)\ 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 |
        305 | Your Message 306 | 307 | 308 | 309 |
        310 | 311 | 312 |
        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 | ![BB](RSRC/16_01_RBS.png)\ 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 | Creative Commons License
        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 | } --------------------------------------------------------------------------------