Commentaires

10

Traduction complétée à

Au programme de ce chapitre :

  • Afficher les commentaires existants
  • Ajouter un formulaire de création de commentaire.
  • Apprendre à charger seulement les commentaires du post courant.
  • Ajouter aux posts le nombre de commentaires.
  • Le but d'un site de nouvelles sociales est de créer une communauté d'utilisateurs, et cela sera difficile à atteindre sans fournir aux gens un moyen de communiquer entre eux. Nous allons donc dans ce chapitre ajouter les commentaires !

    Nous allons commencer par ajouter une nouvelle collection pour y enregistrer les commentaires et quelques données basiques préenregistrées.

    Comments = new Mongo.Collection('comments');
    
    lib/collections/comments.js
    // Données préenregistrées
    if (Posts.find().count() === 0) {
      var now = new Date().getTime();
    
      // crée deux utilisateurs
      var tomId = Meteor.users.insert({
        profile: { name: 'Tom Coleman' }
      });
      var tom = Meteor.users.findOne(tomId);
      var sachaId = Meteor.users.insert({
        profile: { name: 'Sacha Greif' }
      });
      var sacha = Meteor.users.findOne(sachaId);
    
      var telescopeId = Posts.insert({
        title: 'Introducing Telescope',
        userId: sacha._id,
        author: sacha.profile.name,
        url: 'http://sachagreif.com/introducing-telescope/',
        submitted: new Date(now - 7 * 3600 * 1000)
      });
    
      Comments.insert({
        postId: telescopeId,
        userId: tom._id,
        author: tom.profile.name,
        submitted: new Date(now - 5 * 3600 * 1000),
        body: "C'est un projet intéressant Sacha, est-ce-que je peux y participer ?"
      });
    
      Comments.insert({
        postId: telescopeId,
        userId: sacha._id,
        author: sacha.profile.name,
        submitted: new Date(now - 3 * 3600 * 1000),
        body: 'Bien sûr Tom !'
      });
    
      Posts.insert({
        title: 'Meteor',
        userId: tom._id,
        author: tom.profile.name,
        url: 'http://meteor.com',
        submitted: new Date(now - 10 * 3600 * 1000)
      });
    
       Posts.insert({
        title: 'The Meteor Book',
        userId: tom._id,
        author: tom.profile.name,
        url: 'http://themeteorbook.com',
        submitted: new Date(now - 12 * 3600 * 1000)
      });
    }
    
    server/fixtures.js

    N'oublions pas de publier dans notre nouvelle collection et d'y souscrire :

    Meteor.publish('posts', function() {
      return Posts.find();
    });
    
    Meteor.publish('comments', function() {
      return Comments.find();
    });
    
    server/publications.js
    Router.configure({
      layoutTemplate: 'layout',
      loadingTemplate: 'loading',
      notFoundTemplate: 'notFound',
      waitOn: function() {
        return [Meteor.subscribe('posts'), Meteor.subscribe('comments')];
      }
    });
    
    lib/router.js

    Commit 10-1

    Avec la collection de commentaires, la publication, la so…

    Notez que pour déclencher ce code de données préenregistrées, il faudra faire un meteor reset pour remettre à zéro votre base de données. Après avoir réinitialisé, n'oubliez pas de créer un nouveau compte et de vous reconnecter.

    Nous avons tout d'abord créé un couple d'utilisateurs (factices), nous les avons enregistrés dans la base de données, et nous pourrons utiliser leur id plus tard pour les référencer en dehors de la base de données. Ensuite nous avons ajouté un commentaire pour chaque utilisateur sur le premier post, en liant le commentaire avec le post (via postId) et avec l'utilisateur (via userId). Nous avons aussi ajouté une date de publication et un body à chaque commentaire, en plus de author, un champ dénormalisé.

    Nous avons aussi amélioré notre router pour attendre un tableau (array) contenant à la fois les commentaires et les souscriptions aux posts.

    Afficher les commentaires

    C'est très bien d'ajouter des commentaires à la base de données, mais nous voulons aussi les afficher sur la page de discussion. Heureusement, ce processus devrait vous être maintenant familier, et vous devriez avoir une idée des étapes à réaliser :

    <template name="postPage">
      <div class="post-page page">
        {{> postItem}}
    
      <ul class="comments">
        {{#each comments}}
          {{> commentItem}}
        {{/each}}
      </ul>
     </div>
    </template>
    
    client/templates/posts/post_page.html
    Template.postPage.helpers({
      comments: function() {
        return Comments.find({postId: this._id});
      }
    });
    
    client/templates/posts/post_page.js

    Nous insérons le bloc {{#each comments}} à l'intérieur du template de post, donc this est un post dans le helper comments. Pour trouver les commentaires pertinents, nous vérifions ceux qui sont liés à ce post via l'attribut postId.

    Sachant ce que nous avons appris à propos des helpers et de Spacebars, représenter un commentaire est plutôt simple. Nous allons créer un nouveau dossier comments à l'intérieur de templates pour y stocker toute l'information de nos commentaires et un nouveau template commentItem :

    <template name="commentItem">
      <li>
        <h4>
          <span class="author">{{author}}</span>
          <span class="date">on {{submittedText}}</span>
        </h4>
        <p>{{body}}</p>
      </li>
    </template>
    
    client/templates/comments/comment_item.html

    Créons donc un rapide helper de template pour représenter notre date submitted dans un format plus convivial :

    Template.commentItem.helpers({
      submittedText: function() {
        return this.submitted.toString();
      }
    });
    
    client/templates/comments/comment_item.js

    Nous allons ensuite afficher le nombre de commentaires de chaque post :

    <template name="postItem">
      <div class="post">
        <div class="post-content">
          <h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
          <p>
            Rédigé par {{author}},
            <a href="{{pathFor 'postPage'}}">{{commentsCount}} commentaires</a>
            {{#if ownPost}}<a href="{{pathFor 'postEdit'}}">Edit</a>{{/if}}
          </p>
        </div>
        <a href="{{pathFor 'postPage'}}" class="discuss btn btn-default">Commenter</a>
      </div>
    </template>
    
    client/templates/posts/post_item.html

    Et ajouter le helper commentsCount à post_item.js :

    Template.postItem.helpers({
      ownPost: function() {
        return this.userId === Meteor.userId();
      },
      domain: function() {
        var a = document.createElement('a');
        a.href = this.url;
        return a.hostname;
      },
      commentsCount: function() {
        return Comments.find({postId: this._id}).count();
      }
    });
    
    client/templates/posts/post_item.js

    Commit 10-2

    Affichage des commentaires sur `postPage`.

    Vous devriez maintenant être capable d'afficher nos commentaires préenregistrés et voir quelque chose comme ça :

    Affichage des commentaires
    Affichage des commentaires

    Publier des commentaires

    Ajoutons un moyen pour nos utilisateurs de créer de nouveaux commentaires. Le procédé que nous allons suivre est similaire à la façon dont nous avons permis à nos utilisateurs de créer de nouveaux posts.

    Nous allons commencer par ajouter un bouton de publication en bas de chaque post :

    <template name="postPage">
      <div class="post-page page">
        {{> postItem}}
    
        <ul class="comments">
          {{#each comments}}
            {{> commentItem}}
          {{/each}}
        </ul>
    
        {{#if currentUser}}
          {{> commentSubmit}}
        {{else}}
          <p>Please log in to leave a comment.</p>
        {{/if}}
      </div>
    </template>
    
    client/templates/posts/post_page.html

    En ensuite créer le template du formulaire de commentaire :

    <template name="commentSubmit">
      <form name="comment" class="comment-form form">
        <div class="form-group {{errorClass 'body'}}">
          <div class="controls">
            <label for="body">Réagir à ce post</label>
            <textarea name="body" id="body" class="form-control" rows="3"></textarea>
            <span class="help-block">{{errorMessage 'body'}}</span>
          </div>
        </div>
        <button type="submit" class="btn btn-primary">Add Comment</button>
      </form>
    </template>
    
    client/templates/comments/comment_submit.html

    Pour publier nos commentaires, nous appelons une Méthode comment dans comment_submit.js qui opère d'une manière similaire à ce que nous avons fait pour les publications de posts :

    Template.commentSubmit.onCreated(function() {
      Session.set('commentSubmitErrors', {});
    });
    
    Template.commentSubmit.helpers({
      errorMessage: function(field) {
        return Session.get('commentSubmitErrors')[field];
      },
      errorClass: function (field) {
        return !!Session.get('commentSubmitErrors')[field] ? 'has-error' : '';
      }
    });
    
    Template.commentSubmit.events({
      'submit form': function(e, template) {
        e.preventDefault();
    
        var $body = $(e.target).find('[name=body]');
        var comment = {
          body: $body.val(),
          postId: template.data._id
        };
    
        var errors = {};
        if (! comment.body) {
          errors.body = "Please write some content";
          return Session.set('commentSubmitErrors', errors);
        }
    
        Meteor.call('commentInsert', comment, function(error, commentId) {
          if (error){
            throwError(error.reason);
          } else {
            $body.val('');
          }
        });
      }
    });
    
    client/templates/comments/comment_submit.js

    Tout comme nous avons précédemment mis en place une Méthode Meteor post côté serveur, nous allons mettre en place une Méthode Meteor comment pour créer nos commentaires, s'assurer que tout est conforme, et finalement insérer le nouveau commentaire dans la collection ‘comments’.

    Comments = new Mongo.Collection('comments');
    
    Meteor.methods({
      commentInsert: function(commentAttributes) {
        check(this.userId, String);
        check(commentAttributes, {
          postId: String,
          body: String
        });
    
        var user = Meteor.user();
        var post = Posts.findOne(commentAttributes.postId);
    
        if (!post)
          throw new Meteor.Error('invalid-comment', 'Vous devez commenter sur un post');
    
        comment = _.extend(commentAttributes, {
          userId: user._id,
          author: user.username,
          submitted: new Date()
        });
    
        return Comments.insert(comment);
      }
    });
    
    lib/collections/comments.js

    Commit 10-3

    Avec le formulaire de publication de commentaires

    Cela ne fait rien de bien fantaisiste, on s'assure simplement que l'utilisateur est connecté, que le commentaire a un corps (body), et qu'il est lié au post.

    Le formulaire de publication de commentaires
    Le formulaire de publication de commentaires

    Contrôle de la publication de commentaires

    Dans l'état des choses, nous publions tous les commentaires à travers les posts à tous les clients connectés. Cela semble un peu déraisonnable. Après tout, nous n'utilisons réellement qu'une petite partie de ces données à tout moment. Améliorons donc nos publications et souscriptions pour contrôler exactement quels commentaires sont publiés.

    Si nous y pensons, le seul moment où on a besoin de souscrire à la publication de nos commentaires est lorsqu'un utilisateur accède à la page individuelle d'un post, et nous avons besoin de charger uniquement les commentaires reliés à ce post particulier.

    La première étape consistera à changer la manière dont nous souscrivons à nos commentaires. Jusqu'à maintenant, nous avons souscrit au niveau du routeur, ce qui signifie que nous chargeons toutes nos données d'un seul coup lorsque le routeur est initialisé.

    Mais nous voulons maintenant que notre souscription dépende d'un paramètre chemin, et ce paramètre doit bien sûr pouvoir changer à n'importe quel moment. Nous allons donc avoir besoin de déplacer notre code de souscription du niveau du routeur à celui des routes.

    Cela a une autre conséquence : au lieu de charger nos données quand nous initialisons notre appli, nous les chargerons maintenant à chaque fois que notre route sera atteinte. Cela veut dire que vous aurez maintenant des temps de chargement pendant l'utilisation de l'appli, mais c'est un inconvénient inévitable à moins que vous ne prévoyiez de charger définitivement l'ensemble des données au début.

    Premièrement, nous allons arrêter de précharger tous les commentaires dans le bloc configure en supprimant Meteor.subscribe('comments') (autrement dit revenir à ce que nous avions précédemment) :

    Router.configure({
      layoutTemplate: 'layout',
      loadingTemplate: 'loading',
      notFoundTemplate: 'notFound',
      waitOn: function() {
        return Meteor.subscribe('posts');
      }
     });
    
    lib/router.js

    Et nous allons ajouter une nouvelle fonction waitOn de niveau route pour la route postPage :

    //...
    
    Router.route('/posts/:_id', {
      name: 'postPage',
      waitOn: function() {
        return Meteor.subscribe('comments', this.params._id);
      },
      data: function() { return Posts.findOne(this.params._id); }
    });
    
    //...
    
    lib/router.js

    Nous passons this.params._id en tant qu'argument à la souscription. Utilisons donc cette nouvelle information pour s'assurer de restreindre notre lot de données aux commentaires appartenant au post courant :

    Meteor.publish('posts', function() {
      return Posts.find();
    });
    
    Meteor.publish('comments', function(postId) {
      check(postId, String);
      return Comments.find({postId: postId});
    });
    
    server/publications.js

    Commit 10-4

    Avec une simple publication/souscription pour les comment…

    Il n'y a qu'un seul problème, quand nous retournons à la page d'accueil, il prétend que tous nos posts n'ont aucun commentaires :

    Nos commentaires ont disparu !
    Nos commentaires ont disparu !

    Compter les commentaires

    La raison va nous apparaître bientôt claire : nous ne chargeons les commentaires que sur la route postPage, donc lorsque nous appelons Comments.find({postId: this._id}) dans le helper commentsCount, Meteor ne peut pas trouver les données nécessaires côté client pour nous fournir un résultat.

    La meilleure façon de régler cela est de dénormaliser le nombre de commentaires sur le post (si vous n'êtes pas sûr de savoir ce que cela veut dire, ne vous inquiétez pas, le prochain aparté s'occupe de vous !). Bien que comme nous allons le voir, notre code s'en retrouve légèrement complexifié, le bénéfice de performance que nous retirons de ne pas avoir à publier tous les commentaires pour afficher le post en vaut le coup.

    Nous allons terminer ça en ajoutant une propriété commentsCount à la structure de données du post. Nous allons commencer par mettre à jour nos données préenregistrées de posts (et faire meteor reset pour les recharger – n'oubliez pas de recréer votre compte utilisateur après) :

    // Données préenregistrées
    if (Posts.find().count() === 0) {
      var now = new Date().getTime();
    
      // create two users
      var tomId = Meteor.users.insert({
        profile: { name: 'Tom Coleman' }
      });
      var tom = Meteor.users.findOne(tomId);
      var sachaId = Meteor.users.insert({
        profile: { name: 'Sacha Greif' }
      });
      var sacha = Meteor.users.findOne(sachaId);
    
      var telescopeId = Posts.insert({
        title: 'Introducing Telescope',
        userId: sacha._id,
        author: sacha.profile.name,
        url: 'http://sachagreif.com/introducing-telescope/',
        submitted: new Date(now - 7 * 3600 * 1000),
        commentsCount: 2
      });
    
      Comments.insert({
        postId: telescopeId,
        userId: tom._id,
        author: tom.profile.name,
        submitted: new Date(now - 5 * 3600 * 1000),
        body: 'Interesting project Sacha, can I get involved?'
      });
    
      Comments.insert({
        postId: telescopeId,
        userId: sacha._id,
        author: sacha.profile.name,
        submitted: new Date(now - 3 * 3600 * 1000),
        body: 'Bien sûr Tom !'
      });
    
      Posts.insert({
        title: 'Meteor',
        userId: tom._id,
        author: tom.profile.name,
        url: 'http://meteor.com',
        submitted: new Date(now - 10 * 3600 * 1000),
        commentsCount: 0
      });
    
      Posts.insert({
        title: 'The Meteor Book',
        userId: tom._id,
        author: tom.profile.name,
        url: 'http://themeteorbook.com',
        submitted: new Date(now - 12 * 3600 * 1000),
        commentsCount: 0
      });
    }
    
    server/fixtures.js

    Comme d'habitude lorsque nous mettons à jour notre fichier fixtures, vous devez lancer meteor reset sur votre base de donnée pour vous assurer qu'il sera relancé.

    Puis, nous nous assurons que tous les nouveaux posts débutent avec 0 commentaire :

    //...
    
    var post = _.extend(postAttributes, {
        userId: user._id,
        author: user.username,
        submitted: new Date(),
        commentsCount: 0
    });
    
    var postId = Posts.insert(post);
    
    //...
    
    lib/collections/posts.js

    Ensuite nous mettons à jour le commentsCount qui s'y rapporte lorsque nous créons un nouveau commentaire en utilisant l'opérateur Mongo $inc (qui incrémente un champ numérique de un) :

    //...
    
    comment = _.extend(commentAttributes, {
      userId: user._id,
        author: user.username,
        submitted: new Date()
    });
    
    // update the post with the number of comments
    Posts.update(comment.postId, {$inc: {commentsCount: 1}});
    
    return Comments.insert(comment);
    
    //...
    
    lib/collections/comments.js

    Finalement, nous pouvons simplement supprimer le helper commentsCount de client/templates/posts/post_item.js, puisque le champ est maintenant directement disponible sur le post.

    Commit 10-5

    Dénormalisation du nombre de commentaires dans le post.

    Maintenant que les utilisateurs peuvent discuter entre eux, ce serait dommage qu'ils ratent les nouveaux commentaires. Et vous savez quoi, le prochain chapitre vous montrera comment implémenter les notifications pour éviter ça !