Application d'une seule page avec Backbone.js

Backbone.js est un framework JavaScript permettant de créer des applications Web flexibles. Il est livré avec des modèles, des collections, des vues, des événements, un routeur et quelques autres fonctionnalités intéressantes. Dans cet article, nous allons développer une application simple ToDo qui prend en charge l’ajout, la modification et la suppression de tâches. Nous devrions également pouvoir marquer une tâche comme terminé et archivez-le. Afin de garder la longueur de ce message raisonnable, nous n'inclurons aucune communication avec une base de données. Toutes les données seront conservées côté client.

Installer

Voici la structure de fichier que nous allons utiliser:

css └── styles.css js └── collections ToDos.js modèles ToDo.js fournisseur backbone.js jquery-1.10.2.min.js └── underscore.js └── vues └── App.js └── index.html 

Il y a peu de choses évidentes, comme /css/styles.css et /index.html. Ils contiennent les styles CSS et le balisage HTML. Dans le contexte de Backbone.js, le modèle est un endroit où nous conservons nos données. Donc, nos ToDos seront simplement des modèles. Et parce que nous aurons plus d'une tâche, nous les organiserons dans une collection. La logique métier est répartie entre les vues et le fichier de l'application principale., App.js. Backbone.js n'a qu'une seule dépendance dure - Underscore.js. Le framework joue aussi très bien avec jQuery, ils vont donc tous les deux vendeur annuaire. Tout ce dont nous avons besoin maintenant, n’est qu’un petit balisage HTML et nous sommes prêts à partir..

   Mes TODOs    

Comme vous pouvez le constater, nous incluons tous les fichiers JavaScript externes vers le bas, car il est recommandé de le faire à la fin de la balise body. Nous préparons également le démarrage de l'application. Il y a un conteneur pour le contenu, un menu et un titre. La navigation principale est un élément statique et nous ne le modifierons pas. Nous allons remplacer le contenu du titre et le div dessous.

Planification de l'application

C'est toujours bien d'avoir un plan avant de commencer à travailler sur quelque chose. Backbone.js n'a pas une architecture super stricte, que nous devons suivre. C'est l'un des avantages du cadre. Donc, avant de commencer avec la mise en œuvre de la logique métier, parlons de la base.

Noms de noms

Une bonne pratique consiste à placer votre code dans son propre champ d'application. L'enregistrement de variables globales ou de fonctions n'est pas une bonne idée. Nous allons créer un modèle, une collection, un routeur et quelques vues Backbone.js. Tous ces éléments doivent vivre dans un espace privé. App.js contiendra la classe qui contient tout.

// App.js var app = (function () var api = vues: , modèles: , collections: , contenu: null, routeur: null, tâches: null, init: function ()  this.content = $ ("# content");, changeContent: function (el) this.content.empty (). append (el); retourne ceci;, titre: function (str) $ ("h1 ") .text (str); renvoyer ceci;; var ViewsFactory = ; var Router = Backbone.Router.extend (); api.router = nouveau Router (); renvoyer api;) (); 

Ci-dessus, une implémentation typique du motif de module de révélation. le api variable est l'objet qui est renvoyé et représente les méthodes publiques de la classe. le vues, des modèles et des collections properties agira en tant que détenteurs pour les classes retournées par Backbone.js. le contenu est un élément jQuery pointant sur le conteneur d'interface de l'utilisateur principal. Il existe deux méthodes d'assistance ici. Le premier met à jour ce conteneur. Le second définit le titre de la page. Ensuite, nous avons défini un module appelé ViewsFactory. Il livrera nos vues et à la fin, nous avons créé le routeur.

Vous pouvez demander, pourquoi avons-nous besoin d'une usine pour les vues? Eh bien, il existe des modèles courants lorsque vous travaillez avec Backbone.js. L’un d’eux concerne la création et l’utilisation des vues..

var ViewClass = Backbone.View.extend (/ * logic here * /); var view = new ViewClass (); 

Il est bon d'initialiser les vues une seule fois et de les laisser en vie. Une fois les données modifiées, nous appelons normalement les méthodes de la vue et mettons à jour le contenu de ses el objet. L’autre approche très populaire consiste à recréer l’ensemble de la vue ou à remplacer l’ensemble de l’élément DOM. Cependant, ce n'est pas vraiment bon du point de vue de la performance. Donc, on se retrouve normalement avec une classe utilitaire qui crée une instance de la vue et la renvoie quand on en a besoin.

Définition des composants

Nous avons un espace de noms, nous pouvons donc commencer à créer des composants. Voici à quoi ressemble le menu principal:

// views / menu.js app.views.menu = Backbone.View.extend (initialize: function () , render: function () ); 

Nous avons créé une propriété appelée menu qui détient la classe de la navigation. Plus tard, nous pouvons ajouter une méthode dans le module factory qui en crée une instance.

var ViewsFactory = menu: function () if (! this.menuView) this.menuView = new api.views.menu (el: $ ("# menu"));  return this.menuView; ; 

Ci-dessus est comment nous allons gérer toutes les vues, et cela nous permettra d’obtenir une seule et même instance. Cette technique fonctionne bien, dans la plupart des cas.

Couler

Le point d'entrée de l'application est App.js et son init méthode. C’est ce que nous appellerons dans le en charge gestionnaire de la la fenêtre objet.

window.onload = function () app.init ();  

Après cela, le routeur défini prend le contrôle. Basé sur l'URL, il décide quel gestionnaire à exécuter. Dans Backbone.js, nous n’avons pas l’architecture habituelle Model-View-Controller. Le contrôleur manque et la majeure partie de la logique est placée dans les vues. Au lieu de cela, nous connectons les modèles directement aux méthodes, à l'intérieur des vues, et obtenons une mise à jour instantanée de l'interface utilisateur, une fois les données modifiées..

Gérer les données

Les données sont la chose la plus importante dans notre petit projet. Nos tâches sont ce que nous devrions gérer, alors commençons par là. Voici notre définition du modèle.

// models / ToDo.js app.models.ToDo = Backbone.Model.extend (valeurs par défaut: title: "ToDo", archivé: false, false: fait); 

Juste trois champs. Le premier contient le texte de la tâche et les deux autres sont des indicateurs qui définissent le statut de l'enregistrement..

Tout ce qui se trouve dans le cadre est en réalité un répartiteur d’événements. Et comme le modèle est modifié avec les paramètres, le framework sait à quel moment les données sont mises à jour et peut en informer le reste du système. Une fois que vous liez quelque chose à ces notifications, votre application réagira aux modifications apportées au modèle. C'est une fonctionnalité vraiment puissante dans Backbone.js.

Comme je l’ai dit au début, nous aurons beaucoup de disques et nous les organiserons dans une collection appelée ToDos.

// collections / ToDos.js app.collections.ToDos = Backbone.Collection.extend (initialize: function () this.add (title: "Apprendre les bases du JavaScript"); this.add (title: "Go à backbonejs.org "); this.add (title:" Développer une application Backbone ");, modèle: app.models.ToDo up: function (index) if (index> 0) var tmp = this.models [index-1]; this.models [index-1] = this.models [index]; this.models [index] = tmp; this.trigger ("change");, bas: fonction ( index) if (index < this.models.length-1)  var tmp = this.models[index+1]; this.models[index+1] = this.models[index]; this.models[index] = tmp; this.trigger("change");  , archive: function(archived, index)  this.models[index].set("archived", archived); , changeStatus: function(done, index)  this.models[index].set("done", done);  ); 

le initialiser méthode est le point d’entrée de la collection. Dans notre cas, nous avons ajouté quelques tâches par défaut. Bien sûr, dans le monde réel, les informations proviendront d’une base de données ou ailleurs. Mais pour vous garder concentré, nous le ferons manuellement. L’autre chose qui caractérise les collections est la définition du modèle propriété. Il indique à la classe quel type de données est stocké. Le reste des méthodes implémente une logique personnalisée, liée aux fonctionnalités de notre application.. en haut et vers le bas les fonctions changent l’ordre des tâches. Pour simplifier les choses, nous allons identifier chaque tâche avec seulement un index dans le tableau de la collection. Cela signifie que si nous voulons récupérer un enregistrement spécifique, nous devons indiquer son index. Ainsi, l'ordre consiste simplement à basculer les éléments d'un tableau. Comme vous pouvez le deviner d'après le code ci-dessus, this.models est le tableau dont nous parlons. archiver et changeStatus définir les propriétés de l'élément donné. Nous avons mis ces méthodes ici, car les vues auront accès à la ToDos collection et non aux tâches directement.

De plus, nous n’avons besoin de créer aucun modèle à partir du app.models.ToDo classe, mais nous devons créer une instance à partir de la app.collections.ToDos collection.

// App.js init: function () this.content = $ ("# content"); this.todos = new api.collections.ToDos (); retournez ceci;  

Affichage de notre première vue (navigation principale)

La première chose que nous devons montrer, est la navigation de l'application principale.

// views / menu.js app.views.menu = Backbone.View.extend (template: _.template ($ ("# tpl-menu"). html ()), initialize: function () this.render ();, render: function () this. $ el.html (this.template ());); 

Ce n'est que neuf lignes de code, mais beaucoup de choses intéressantes se passent ici. Le premier est la définition d'un modèle. Si vous vous en souvenez, nous avons ajouté Underscore.js à notre application? Nous allons utiliser son moteur de templates, car il fonctionne bien et il est assez simple à utiliser.

_.template (templateString, [data], [settings]) 

Ce que vous avez à la fin est une fonction qui accepte un objet contenant vos informations dans des paires clé-valeur et le templateString est le balisage HTML. Ok, donc il accepte une chaîne HTML, mais quelle est $ ("# tpl-menu"). html () Faire là? Lorsque nous développons une petite application d'une seule page, nous plaçons normalement les modèles directement dans la page, comme suit:

// index.html  

Et parce que c'est une balise de script, elle n'est pas montrée à l'utilisateur. D'un autre point de vue, c'est un nœud DOM valide afin que nous puissions obtenir son contenu avec jQuery. Ainsi, le court extrait ci-dessus prend simplement le contenu de cette balise de script.

le rendre La méthode est vraiment importante dans Backbone.js. C'est la fonction qui affiche les données. Normalement, vous liez les événements déclenchés par les modèles directement à cette méthode. Cependant, pour le menu principal, nous n'avons pas besoin d'un tel comportement.

this. $ el.html (this.template ()); 

ceci. $ el est un objet créé par le framework et chaque vue l’a par défaut (il existe un $ en face de el parce que nous avons jQuery inclus). Et par défaut, c'est un vide

. Bien sûr, vous pouvez changer cela en utilisant le tagName propriété. Mais ce qui est plus important ici, c'est que nous n'attribuons pas directement de valeur à cet objet. Nous ne le changeons pas, nous ne changeons que son contenu. Il y a une grande différence entre la ligne ci-dessus et la suivante:

this. $ el = $ (this.template ()); 

Le fait est que si vous voulez voir les modifications dans le navigateur, vous devez appeler la méthode de rendu avant, pour ajouter la vue au DOM. Sinon, seul le div vide sera attaché. Il existe également un autre scénario dans lequel vous avez des vues imbriquées. Et comme vous modifiez directement la propriété, le composant parent n'est pas mis à jour. Les événements liés peuvent également être rompus et vous devez attacher à nouveau les écouteurs. Donc, vous ne devriez vraiment changer que le contenu de ceci. $ el et non la valeur de la propriété.

La vue est maintenant prête et nous devons l'initialiser. Ajoutons-le à notre module d'usine:

// App.js var ViewsFactory = menu: function () if (! This.menuView) this.menuView = new api.views.menu (el: $ ("# menu"));  return this.menuView; ; 

A la fin, appelez simplement le menu méthode dans la zone d'amorçage:

// App.js init: function () this.content = $ ("# content"); this.todos = new api.collections.ToDos (); ViewsFactory.menu (); retournez ceci;  

Notez que lors de la création d'une nouvelle instance à partir de la classe de navigation, nous transmettons un élément DOM existant $ ("# menu"). Alors le ceci. $ el la propriété à l'intérieur de la vue pointe en fait sur $ ("# menu").

Ajout d'itinéraires

Backbone.js supporte le état poussé opérations. En d'autres termes, vous pouvez manipuler l'URL du navigateur actuel et parcourir les pages. Cependant, nous allons rester avec les bonnes vieilles URL de type hash, par exemple / # edit / 3.

// App.js var Router = Backbone.Router.extend (routes: "archive": "archive", "new": "newToDo", "edit /: index": "editToDo", "delete /: index ":" delteToDo "," ":" liste ", liste: fonction (archive) , archive: fonction () , nouveauToDo: fonction () , editToDo: fonction (index) , delteToDo: fonction (index) ); 

Ci-dessus, notre routeur. Il y a cinq routes définies dans un objet de hachage. La clé est ce que vous allez taper dans la barre d'adresse du navigateur et la valeur est la fonction qui sera appelée. Notez qu'il y a :indice sur deux des itinéraires. C'est la syntaxe que vous devez utiliser si vous souhaitez prendre en charge les URL dynamiques. Dans notre cas, si vous tapez # edit / 3 la editToDo sera exécuté avec le paramètre indice = 3. La dernière ligne contient une chaîne vide, ce qui signifie qu’elle gère la page d’accueil de notre application..

Afficher une liste de toutes les tâches

Jusqu'à présent, ce que nous avons construit est la vue principale de notre projet. Il récupérera les données de la collection et les imprimera à l'écran. Nous pourrions utiliser la même vue pour deux choses: afficher tous les ToDos actifs et montrer ceux qui sont archivés..

Avant de continuer avec l'implémentation de la vue liste, voyons comment elle est réellement initialisée.

// dans la liste des fabriques de vues App.js: function () if (! this.listView) this.listView = new api.views.list (model: api.todos);  return this.listView;  

Notez que nous passons dans la collection. C'est important parce que nous utiliserons plus tard ce modèle accéder aux données stockées. L'usine renvoie notre vue liste, mais le routeur est le gars qui doit l'ajouter à la page.

// dans la liste de routeurs d'App.js: function (archive) var view = ViewsFactory.list (); api .title (archive? "Archive:": "Votre ToDos:") .changeContent (view. $ el); view.setMode (archive? "archive": null) .render ();  

Pour l'instant, la méthode liste dans le routeur est appelé sans aucun paramètre. Donc, la vue n'est pas dans archiver mode, il affichera uniquement les ToDos actifs.

// views / list.js app.views.list = Backbone.View.extend (mode: null, événements: , initialize: function () var handler = _.bind (this.render, this); this .model.bind ('change', gestionnaire); this.model.bind ('add', gestionnaire); this.model.bind ('remove', gestionnaire);, render: function () , priorityUp: fonction (e) , priorityDown: fonction (e) , archive: fonction (e) , changeStatus: fonction (e) , setMode: fonction (mode) this.mode = mode; renvoie this; ); 

le mode la propriété sera utilisée lors du rendu. Si sa valeur est mode = "archive" alors seuls les ToDos archivés seront affichés. le événements est un objet que nous allons remplir tout de suite. C'est l'endroit où nous plaçons le mappage des événements DOM. Les autres méthodes sont des réponses de l’interaction de l’utilisateur et sont directement liées aux fonctionnalités requises. Par exemple, prioritéUp et priorityDown modifie l'ordre des tâches. archiver déplace l'élément dans la zone d'archives. changeStatus marque simplement la tâche comme étant terminée.

C'est intéressant ce qui se passe à l'intérieur du initialiser méthode. Nous avons dit plus tôt que vous lieriez normalement les modifications du modèle (la collection dans notre cas) à la rendre méthode de la vue. Vous pouvez taper this.model.bind ('change', this.render). Mais très vite, vous remarquerez que le ce mot-clé, dans le rendre méthode ne pointera pas sur la vue elle-même. C'est parce que la portée est modifiée. Pour contourner le problème, nous créons un gestionnaire avec une portée déjà définie. C'est ce que Underscore lier la fonction est utilisée pour.

Et voici la mise en œuvre de la rendre méthode.

// views / list.js render: function () ) var html = '
    ', soi = ceci; this.model.each (fonction (todo, index) if (self.mode === "archive"? todo.get ("archivé") === true: todo.get ("archivé") === faux ) var template = _.template ($ ("# élément-liste-tpl"). html ()); html + = modèle (title: todo.get ("title")), index: index, archiveLink: self .mode === "archive"? "unarchive": "archive", done: todo.get ("done")? "yes": "non", doneChecked: todo.get ("done")? 'vérifié = = "vérifié" ': "");); html + = '
'; this. $ el.html (html); this.delegateEvents (); retournez ceci;

Nous parcourons tous les modèles de la collection et générons une chaîne HTML, qui est ensuite insérée dans l'élément DOM de la vue. Il y a peu de contrôles qui distinguent les ToDos de archivés à actifs. La tâche est marquée comme terminé à l'aide d'une case à cocher. Donc, pour indiquer cela, nous devons passer un vérifié == "vérifié" attribuer à cet élément. Vous remarquerez peut-être que nous utilisons this.delegateEvents (). Dans notre cas, cela est nécessaire, car nous séparons et attachons la vue du DOM. Oui, nous ne remplaçons pas l'élément principal, mais les gestionnaires d'événements sont supprimés. C'est pourquoi nous devons dire à Backbone.js de les attacher à nouveau. Le modèle utilisé dans le code ci-dessus est:

// index.html  

Notez qu’il existe une classe CSS définie appelée fait-oui, qui peint le ToDo avec un fond vert. En plus de cela, il y a un tas de liens que nous allons utiliser pour implémenter les fonctionnalités nécessaires. Ils ont tous des attributs de données. Le noeud principal de l'élément, li, a index de données. La valeur de cet attribut montre l'index de la tâche dans la collection. Notez que les expressions spéciales enveloppées dans <%=… %> sont envoyés au modèle une fonction. Ce sont les données qui sont injectées dans le modèle.

Il est temps d'ajouter quelques événements à la vue.

// views / list.js events: 'cliquez sur un [data-up]': 'priorityUp', 'cliquez sur un [data-down]': 'priorityDown', 'cliquez sur un [archive de données]': 'archive ',' cliquez sur l'entrée [état des données] ':' changeStatus ' 

Dans Backbone.js, la définition de l'événement est un hachage. Vous tapez d’abord le nom de l’événement, puis un sélecteur. Les valeurs des propriétés sont en réalité des méthodes de la vue.

// views / list.js priorityUp: function (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("data-index")); this.model.up (index); , priorityDown: function (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("data-index")); this.model.down (index); , archive: function (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("data-index")); this.model.archive (this.mode! == "archive", index); , changeStatus: function (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("data-index")); this.model.changeStatus (e.target.checked, index);  

Ici nous utilisons e.target venir chez le gestionnaire. Il pointe sur l'élément DOM qui a déclenché l'événement. Nous obtenons l'index de la tâche cliquée et mettons à jour le modèle dans la collection. Avec ces quatre fonctions nous avons terminé notre cours et maintenant les données sont montrées à la page.

Comme nous l’avons mentionné ci-dessus, nous utiliserons la même vue pour la Archiver page.

liste: fonction (archive) var view = ViewsFactory.list (); api .title (archive? "Archive:": "Votre ToDos:") .changeContent (view. $ el); view.setMode (archive? "archive": null) .render (); , archive: function () this.list (true);  

Ci-dessus, le même gestionnaire d’itinéraire que précédemment, mais cette fois avec vrai en paramètre.

Ajouter et éditer des ToDos

En suivant l'amorce de la vue liste, nous pourrions en créer une autre qui montre un formulaire pour l'ajout et la modification de tâches. Voici comment cette nouvelle classe est créée:

// Formulaire de fabrique App.js / views: function () if (! This.formView) this.formView = new api.views.form (modèle: api.todos). On ("saved", function ( ) api.router.navigate ("", trigger: true);) return this.formView;  

Quasiment la même chose. Cependant, cette fois, nous devons faire quelque chose une fois le formulaire soumis. Et cela transmet l'utilisateur à la page d'accueil. Comme je l'ai dit, chaque objet qui étend les classes Backbone.js est en réalité un répartiteur d'événements. Il y a des méthodes comme sur et déclencheur que vous pouvez utiliser.

Avant de continuer avec le code d'affichage, examinons le modèle HTML:

 

Nous avons un zone de texte et un bouton. Le modèle attend un Titre paramètre qui devrait être une chaîne vide, si nous ajoutons une nouvelle tâche.

// views / form.js app.views.form = Backbone.View.extend (index: false, événements: 'clic sur le bouton': 'enregistrer', initialiser: fonction () this.render (); , render: function (index) modèle var, html = $ ("# tpl-form"). html (); if (typeof index == 'undefined') this.index = false; template = _.template ( html, title: ""); else this.index = parseInt (index); this.todoForEditing = this.model.at (this.index); template = _.template ($ ("# tpl-form ") .html (), title: this.todoForEditing.get (" title ")); this. $ el.html (modèle); this. $ el.find (" textarea "). focus (); this.delegateEvents (); renvoie this;, enregistrez: function (e) e.preventDefault (); var title = this. $ el.find ("textarea"). val (); if (title == "" ) alert ("Textarea vide!"); return; if (this.index! == false) this.todoForEditing.set ("title", titre); else this.model.add (title: titre); this.trigger ("saved");); 

La vue ne contient que 40 lignes de code, mais elle fait bien son travail. Un seul événement est associé et il s’agit du clic sur le bouton de sauvegarde. La méthode de rendu agit différemment en fonction du passé indice paramètre. Par exemple, si nous modifions une tâche, nous passons l'index et récupérons le modèle exact. Sinon, le formulaire est vide et une nouvelle tâche sera créée. Il y a plusieurs points intéressants dans le code ci-dessus. Premièrement, dans le rendu, nous avons utilisé le .concentrer() méthode pour amener le focus sur le formulaire une fois la vue rendue. Encore la delegateEvents La fonction doit être appelée, car le formulaire peut être détaché et attaché à nouveau. le enregistrer méthode commence par e.preventDefault (). Cela supprime le comportement par défaut du bouton, qui dans certains cas peut soumettre le formulaire. Et à la fin, une fois que tout est terminé, nous avons déclenché la enregistré événement notifiant au monde extérieur que la tâche est enregistrée dans la collection.

Il y a deux méthodes pour le routeur que nous devons renseigner.

// App.js newToDo: function () var view = ViewsFactory.form (); api.title ("Créer une nouvelle tâche:"). changeContent (view. $ el); view.render (), editToDo: function (index) var view = ViewsFactory.form (); api.title ("Edit:"). changeContent (view. $ el); view.render (index);  

La différence entre eux est que nous passons dans un index, si le éditer /: index route est appariée. Et bien sûr, le titre de la page est modifié en conséquence.

Suppression d'un enregistrement de la collection

Pour cette fonctionnalité, nous n'avons pas besoin d'une vue. L'ensemble du travail peut être effectué directement dans le gestionnaire du routeur..

delteToDo: fonction (index) api.todos.remove (api.todos.at (parseInt (index))); api.router.navigate ("", trigger: true);  

Nous connaissons l'index du ToDo que nous voulons supprimer. Il y a un retirer méthode dans la classe collection qui accepte un objet de modèle. À la fin, il suffit de renvoyer l'utilisateur à la page d'accueil, qui affiche la liste mise à jour.

Conclusion

Backbone.js a tout ce dont vous avez besoin pour créer une application d'une page entièrement fonctionnelle. Nous pourrions même le lier à un service d'arrière-plan REST et la structure synchronisera les données entre votre application et la base de données. L'approche événementielle encourage la programmation modulaire, ainsi qu'une bonne architecture. J'utilise personnellement Backbone.js pour plusieurs projets et cela fonctionne très bien.