I. Préparation

Avant de commencer ce tutoriel, vous aurez besoin de ce qui suit :

  • alexyoung/dailyjs-backbone-tutorial en version 8d88095 ;
  • la clé de l'API de la partie 2 ;
  • l'ID client de la partie 2 ;
  • la mise à jour de app/js/config.js avec vos clés.

Pour consulter la source, exécutez les commandes suivantes (ou utilisez un outil approprié pour Git GUI) :

 
Sélectionnez
git clone git@github.com:alexyoung/dailyjs-backbone-tutorial.git
cd dailyjs-backbone-tutorial
git reset --hard 8d88095

II. Tâches CRUD

Les choses ont été calmes dans le projet Tasks Bootstrap au cours de ces dernières semaines. Les listes sont apparues, mais il manque un air de Hamburglar ou de Burger King en vue. Comment attirer toutes ces mascottes importantes de fast food à notre projet ? En ajoutant la prise en charge des tâches bien sûr ! Sinon, comment peuvent-ils écrire leurs listes exhaustives des inspections de la franchise ainsi que parler de l'édition spéciale McRibs ?

Ce tutoriel couvre les éléments suivants :

  • création d'une vue pour une tâche ;
  • création d'une vue pour une liste de tâches ;
  • ajout de tâches à la collection ;
  • extraction des tâches de l'API de Google.

La partie vraiment intéressante dont vous voudrez vous souvenir traite de la relation entre une vue parent et les vues enfants. Backbone ne dresse pas spécifiquement des relations entre modèles ou entre vues. Dans cet exemple, nous voulons idéalement dire aux tâches qu'elles appartiennent à des listes ou que les affichages des tâches appartiennent à des affichages de listes. Cependant, il n'existe pas une manière de facto d'exprimer de telles relations. Il existe des bibliothèques pour le faire, mais je vais vous montrer comment penser les choses en Backbone/Underscore.

III. Mise en place

Avant de commencer, créez quelques nouveaux répertoires :

 
Sélectionnez
$ mkdir app/js/views/tasks
$ mkdir app/js/templates/tasks

Et ajoutez une nouvelle collection au fichier app/js/collections/tasks.js :

 
Sélectionnez
define(['models/task'], function(Task) {
  var Tasks = Backbone.Collection.extend({
    model: Task,
    url: 'tasks'
  });

  return Tasks;
});

La collection Tasks ne fait rien que vous n'ayez vu avant. Chercher des tâches à l'aide de l'API de Google nécessite tasklist, vous devez donc appeler fetch avec un paramètre supplémentaire :

 
Sélectionnez
collection.fetch({ data: { tasklist: this.model.get('id') }, // ...

C'est cool, parce que nous avons traité l'extraction de TaskLists comme ça quand nous avons passé { userId: '@me' } donc ça reste cohérent dans le cadre de ce projet.

Le template qui contient l'affichage des tâches comprend un formulaire permettant de créer de nouvelles tâches, un conteneur pour la liste des tâches et un autre conteneur pour la tâche actuellement sélectionnée (donc elle peut être modifiée). Ce fichier doit être enregistré sous app/js/templates/index.js :

 
Sélectionnez
<div class="span6">
  <div id="add-task">
    <form class="well row form-inline add-task">
      <input type="text" class="pull-left" placeholder="Encodez un nouveau nom de tâche et appuyez sur Enter" name="title">
      <button type="submit" class="pull-right btn"><i class="icon-plus"></i></button>
    </form>
  </div>
  <ul id="task-list"></ul>
</div>
<div class="span6">
  <div id="selected-task"></div>
  <div class="alert" id="warning-no-task-selected">
    <strong>Note :</strong> sélectionnez une tâche pour la modifier ou la supprimer.
  </div>
</div>

Celui-ci utilise des classes BootstrapBootstrap pour créer des colonnes. Le TaskView, dans app/js/templates/tasks/task.html, a quelques éléments pour contenir le titre, des notes et une case à cocher pour la sélection/désélection de la tâche :

 
Sélectionnez
<input type="checkbox" data-task-id="" name="task_check_" class="check-task" value="t">
<span class="title "></span>
<span class="notes"></span>

IV. Les vues

La TasksIndexView principal charge les tâches à l'aide de la collection Tasks et les restitue ensuite à l'aide de TaskView. Il s'agit de la source de TasksIndexView dans le fichier app/js/views/tasks/index.js :

 
Sélectionnez
define(['text!templates/tasks/index.html', 'views/tasks/task', 'collections/tasks'], function(template, TaskView, Tasks) {
  var TasksIndexView = Backbone.View.extend({
    tagName: 'div',
    className: 'row-fluid',

    template: _.template(template),

    events: {
      'submit .add-task': 'addTask'
    },

    initialize: function() {
      this.children = [];
    },

    addTask: function() {
    },

    render: function() {
      this.$el.html(this.template());

      var $el = this.$el.find('#task-list')
        , self = this;

      this.collection = new Tasks();
      this.collection.fetch({ data: { tasklist: this.model.get('id') }, success: function() {
        self.collection.each(function(task) {
          var item = new TaskView({ model: task, parentView: self });
          $el.append(item.render().el);
          self.children.push(item);
        });
      }});

      return this;
    }
  });

  return TasksIndexView;
});

Ceci charge les tâches à l'aide de collection.fetch et ajoute ensuite un TaskView pour chaque tâche. Voici TaskView :

 
Sélectionnez
define(['text!templates/tasks/task.html'], function(template) {
  var TaskView = Backbone.View.extend({
    tagName: 'li',
    className: 'controls well task row',

    template: _.template(template),

    events: {
      'click': 'open'
    },

    initialize: function(options) {
      this.parentView = options.parentView;
    },

    render: function(e) {
      var $el = $(this.el);
      $el.data('taskId', this.model.get('id'));
      $el.html(this.template(this.model.toJSON()));
      $el.find('.check-task').attr('checked', this.model.get('status') === 'completed');

      return this;
    },

    open: function(e) {
      if (this.parentView.activeTaskView) {
        this.parentView.activeTaskView.close();
      }
      this.$el.addClass('active');
      this.parentView.activeTaskView = this;
    },

    close: function(e) {
      this.$el.removeClass('active');
    }
  });

  return TaskView;
});

La vue parente est surveillée donc open peut déterminer si une autre tâche a été sélectionnée et, le cas échéant, « désactiver » la tâche précédente (supprimer la classe active). Il existe plusieurs façons de le faire. J'ai vu des gens itérer sur des vues pour les fermer toutes à l'aide de $('selector').removeClass('active') pour supprimer tous les objets disposant d'une classe active, ou bien déclencher des événements sur les modèles. J'estime que le code lié à la vue doit être géré dans la vue. Les modèles et les collections devraient faire leurs propres jobs.

Ensuite, vous devrez ajouter TasksIndexView dans le define du fichier app/js/views/lists/menuitem.js et modifier la méthode open pour instancier une TasksIndexView :

 
Sélectionnez
open: function() {
  if (bTask.views.activeListMenuItem) {
    bTask.views.activeListMenuItem.$el.removeClass('active');
  }

  bTask.views.activeListMenuItem = this;
  this.$el.addClass('active');

  // Afficher les tâches
  if (bTask.views.tasksIndexView) {
    bTask.views.tasksIndexView.remove();
  }

  bTask.views.tasksIndexView = new TasksIndexView({ collection: bTask.collections.tasks, model: this.model });
  bTask.views.app.$el.find('#tasks-container').html(bTask.views.tasksIndexView.render().el);

  return false;
}

Cela permet de suivre la dernière instance de TasksIndexView donc il peut la supprimer manuellement. C'est généralement une bonne idée d'appeler remove car les événements peuvent donc être indépendants avant que les vues ne soient hors de portée. Je vais écrire un tutoriel sur Backbone et la collection garbage par la suite.

J'ai aussi ajouté quelques valeurs par défaut pour le modèle Task (dans le fichier app/js/models/task.js) :

 
Sélectionnez
define(function() {
  var Task = Backbone.Model.extend({
    url: 'tasks',
    defaults: { title: '', notes: '' }
  });

  return Task;
});

La raison pour laquelle je fais cela est que TaskView déclenche des erreurs lorsque l'interpolation utilise un modèle qui n'a pas un titre ou des notes, ce qui est assez fréquent pour les tâches de Google Tasks.

Avec ces templates, ces vues et ces modifications, vous devriez être en mesure de sélectionner des listes et d'afficher leurs tâches mais également de sélectionner les tâches.

V. Styles

Image non disponible

En l'état, l'application n'a aucun attrait visuel. J'ai ajouté Bootstrap, il faut simplement télécharger les fichiers CSS et les images et les placer respectivement dans app/css et dans app/img. En outre, app/index.html charge css/bootstrap.min.css.

J'ai ajouté quelques styles personnalisés pour créer une disposition en layout qui affiche les tâches de façon similaire aux ThingsThings.

VI. Backbone 0.9.10

J'ai mis à jour Backbone pour avoir sa version 0.9.10 et je l'ai ajouté au répertoire. J'ai dû changer la méthode Backbone.sync pour utiliser une signature différente lorsque vous appelez options.success dans le fichier app/js/gapi.js :

 
Sélectionnez
options.success(model, result, request);

VII. Résumé

Le code source de ce tutoriel se trouve ici : alexyoung/dailyjs-backbone-tutorial en version 0491ad.

VIII. Remerciements

Cet article a été publié avec l'aimable autorisation de Alex Young. L'article original peut être lu sur le site DailyJSDailyJS : Backbone.js Tutorial: TasksBackbone.js Tutorial: Tasks.
Je remercie également _Max_ pour sa relecture attentive et assidue.