Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Meteor comments


May 10, 2021 Meteor


Table of contents


Comments

The goal of social news sites is to create a community of users, which would be difficult without providing a way for people to communicate with each other. So in this chapter, let's add comments!

Let's start by creating a new set to store comments and adding some initial data to that set.

Comments = new Mongo.Collection('comments');
// Fixture data
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)
  });

  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: 'You sure can 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)
  });
}

Don't forget to publish and subscribe to our new collection:

Meteor.publish('posts', function() {
  return Posts.find();
});

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

Note that in order to use the new data, you need to command Meteor reset clear the database. Don't forget to create a new user and sign in again!

First, we created several (fake) users in the database and selected them id T hen add a comment to the first post, linking to postId and the userId We've also added a submission date, comments, and a denormalized author item.

In addition, we add an array in the router that waits for a subscription with comments and posts.

Show comments

Save comments to the database and display them on the comments page.

<template name="postPage">
  {{> postItem}}
  <ul class="comments">
    {{#each comments}}
      {{> commentItem}}
    {{/each}}
  </ul>
</template>
Template.postPage.helpers({
  comments: function() {
    return Comments.find({postId: this._id});
  }
});

We put {{#each comments}} post template, so comments helper, this to the current post. To find relevant comments, we can find postId to the post via the postId property.

After we've learned about helper and Spacebars, it's fairly simple to show a comment. Under templates create a comments and commentItem template to store all comment-related information:

<template name="commentItem">
  <li>
    <h4>
      <span class="author">{{author}}</span>
      <span class="date">on {{submittedText}}</span>
    </h4>
    <p>{{body}}</p>
  </li>
</template>

Let's write a template helper to help us submitted submission date format with the date format:

Template.commentItem.helpers({
  submittedText: function() {
    return this.submitted.toString();
  }
});

We'll then show the number of comments in each post:

<template name="postItem">
  <div class="post">
    <div class="post-content">
      <h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
      <p>
        submitted by {{author}},
        <a href="{{pathFor 'postPage'}}">{{commentsCount}} comments</a>
        {{#if ownPost}}<a href="{{pathFor 'postEdit'}}">Edit</a>{{/if}}
      </p>
    </div>
    <a href="{{pathFor 'postPage'}}" class="discuss btn btn-default">Discuss</a>
  </div>
</template>

Add commentsCount helper 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();
  }
});

You should now be able to display the initial comments and see the following:

Meteor comments

Submit a new review

Let users create new comments, a process that will be very similar to in the past when we allowed users to create new posts.

First we add a submission box at the bottom of each post:

<template name="postPage">
  {{> postItem}}

  <ul class="comments">
    {{#each comments}}
      {{> commentItem}}
    {{/each}}
  </ul>

  {{#if currentUser}}
    {{> commentSubmit}}
  {{else}}
    <p>Please log in to leave a comment.</p>
  {{/if}}
</template>

Then create a comment form template:

<template name="commentSubmit">
  <form name="comment" class="comment-form form">
    <div class="form-group {{errorClass 'body'}}">
        <div class="controls">
            <label for="body">Comment on this 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>

Call comment_submit.js the message to comment a new comment, which is similar to the way you submitted a post in the past:

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('');
      }
    });
  }
});

Similar to post server-side Meteor method, we'll establish a comment Meteor method to create a comment, check for correctness, and insert it into the comment collection.

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', 'You must comment on a post');

    comment = _.extend(commentAttributes, {
      userId: user._id,
      author: user.username,
      submitted: new Date()
    });

    return Comments.insert(comment);
  }
});

What's not too fancy here is just check to see if the user is signed in, the comment has a content, and links to a post.

Meteor comments

Control subscription reviews

As always, we'll post all comments to each connected client that belongs to all posts. I t seems a bit wasteful. A fter all, in any given period of time, only a small portion of that data is actually used. So let's improve the accuracy of publishing and subscribing to reviews.

If you think about it, we need comments time, when the user visits the page of a post, and at this point only needs to load a subset of the comments for that post.

The first step will change the way we subscribe to reviews. Currently, it is a router-level subscription, which means that when the router is initialized, all data is loaded.

But now hopefully our subscription depends on the path parameters, and the parameters can change at any time. Therefore, we need to change our subscription code from router level to path level.

Another consequence of this is that the data is loaded whenever the path is opened, rather than when the app is initialized. T his means that you wait for the load time while you browse within the program. Unless you intend to load all the contents to the client, this is an inevitable disadvantage.

First, stop configure all comments (in other words, go back to the previous method) by removing Meteor.subscribe('comments') in the configuration block:

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

We'll add a new path-level waitOn function for postPage waitOn

//...

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

//...

We this.params._id an argument to the subscription. So let's use this new information to make sure that restricting comments belongs to the current post:

Meteor.publish('posts', function() {
  return Posts.find();
});

Meteor.publish('comments', function(postId) {
  check(postId, String);
  return Comments.find({postId: postId});
});

Here's a question: When we go back to the page, all posts are shown with 0 comments:

Meteor comments

Comment count

The reason for this problem is that we only load comments on postPage path, so when we call Comments.find({postId: this._id}) Meteor commentsCount client data to provide us with a count value.

The best way to deal with this problem is to add a denormalized comment count to the post (if you don't understand what that means, don't worry, the next appendix will explain!). ) As we'll see, the complexity in the code increases slightly, but in the All comments that never post the post list, the resulting performance improvement is worth it.

We'll post by adding a commentsCount to the post data structure. First, update the post's test data meteor reset -- don't forget to re-create your account later):

// 测试数据
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: 'You sure can 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
  });
}

As always, when you update the test data file, you must reset the database meteor reset to make sure it runs again.

Then we want to make sure that the comment count for all new posts starts at 0:

//...

var post = _.extend(postAttributes, {
  userId: user._id,
  author: user.username,
  submitted: new Date(),
  commentsCount: 0
});

var postId = Posts.insert(post);

//...

When we create a new comment, update $inc operation (add one commentsCount

//...

comment = _.extend(commentAttributes, {
  userId: user._id,
  author: user.username,
  submitted: new Date()
});

// 更新帖子的评论数
Posts.update(comment.postId, {$inc: {commentsCount: 1}});

return Comments.insert(comment);

//...

Finally, simply client/templates/posts/post_item.js because the value is available from the post. commentsCount

Users can now talk to each other, and if they miss out on new comments, it will be unforgivable. The next sections will show you how to implement notifications to prevent this from happening!