课程表

Meteor课程

工具箱
速查手册

Meteor 评论

当前位置:免费教程 » JS/JS库/框架 » Meteor

社交新闻网站的目标是创建一个用户社区,如果没有提供一种方式让人们互相交流,这将是很难做到的。因此在本章中,我们添加评论!

我们首先创建一个新的集来存储评论,并在该集中添加一些初始数据。

  1. Comments = new Mongo.Collection('comments');
  1. // Fixture data
  2. if (Posts.find().count() === 0) {
  3. var now = new Date().getTime();
  4. // create two users
  5. var tomId = Meteor.users.insert({
  6. profile: { name: 'Tom Coleman' }
  7. });
  8. var tom = Meteor.users.findOne(tomId);
  9. var sachaId = Meteor.users.insert({
  10. profile: { name: 'Sacha Greif' }
  11. });
  12. var sacha = Meteor.users.findOne(sachaId);
  13. var telescopeId = Posts.insert({
  14. title: 'Introducing Telescope',
  15. userId: sacha._id,
  16. author: sacha.profile.name,
  17. url: 'http://sachagreif.com/introducing-telescope/',
  18. submitted: new Date(now - 7 * 3600 * 1000)
  19. });
  20. Comments.insert({
  21. postId: telescopeId,
  22. userId: tom._id,
  23. author: tom.profile.name,
  24. submitted: new Date(now - 5 * 3600 * 1000),
  25. body: 'Interesting project Sacha, can I get involved?'
  26. });
  27. Comments.insert({
  28. postId: telescopeId,
  29. userId: sacha._id,
  30. author: sacha.profile.name,
  31. submitted: new Date(now - 3 * 3600 * 1000),
  32. body: 'You sure can Tom!'
  33. });
  34. Posts.insert({
  35. title: 'Meteor',
  36. userId: tom._id,
  37. author: tom.profile.name,
  38. url: 'http://meteor.com',
  39. submitted: new Date(now - 10 * 3600 * 1000)
  40. });
  41. Posts.insert({
  42. title: 'The Meteor Book',
  43. userId: tom._id,
  44. author: tom.profile.name,
  45. url: 'http://themeteorbook.com',
  46. submitted: new Date(now - 12 * 3600 * 1000)
  47. });
  48. }

不要忘记来发布和订阅我们这个新的集合:

  1. Meteor.publish('posts', function() {
  2. return Posts.find();
  3. });
  4. Meteor.publish('comments', function() {
  5. return Comments.find();
  6. });
  1. Router.configure({
  2. layoutTemplate: 'layout',
  3. loadingTemplate: 'loading',
  4. notFoundTemplate: 'notFound',
  5. waitOn: function() {
  6. return [Meteor.subscribe('posts'), Meteor.subscribe('comments')];
  7. }
  8. });

请注意,为了使用新的数据,你需要命令 Meteor reset 清除数据库。不要忘了创建一个新的用户,并重新登录!

首先,我们在数据库中创建了几个(假的)用户,并从数据库中用他们的 id 选择出来。然后给第一篇添加注释,链接注释到帖子(postId)和用户(userId)。同时我们还添加了提交日期,评论内容和一个非规范化的作者 author 项。

此外我们在路由器中增加等待一个含有评论和帖子订阅的数组

显示评论

把评论存到数据库,同时还需要在评论页上显示。

  1. <template name="postPage">
  2. {{> postItem}}
  3. <ul class="comments">
  4. {{#each comments}}
  5. {{> commentItem}}
  6. {{/each}}
  7. </ul>
  8. </template>
  1. Template.postPage.helpers({
  2. comments: function() {
  3. return Comments.find({postId: this._id});
  4. }
  5. });

我们把 {{#each comments}} 块放在帖子模板里面,所以在 comments helper 里,this 指向的是当前帖子。要找到相关的评论,我们可通过 postId 属性找到链接到该帖子的评论。

我们已经了解 helper 和 Spacebars 后,显示一个评论是相当简单的。我们将在 templates 下,创建一个新的 comments 目录和一个新的 commentItem 模板,来存储所有的评论相关的信息:

  1. <template name="commentItem">
  2. <li>
  3. <h4>
  4. <span class="author">{{author}}</span>
  5. <span class="date">on {{submittedText}}</span>
  6. </h4>
  7. <p>{{body}}</p>
  8. </li>
  9. </template>

让我们编写一个模板 helper 来帮助我们把 submitted 提交日期格式人性化的日期格式:

  1. Template.commentItem.helpers({
  2. submittedText: function() {
  3. return this.submitted.toString();
  4. }
  5. });

然后,我们将在每个帖子中显示评论数:

  1. <template name="postItem">
  2. <div class="post">
  3. <div class="post-content">
  4. <h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
  5. <p>
  6. submitted by {{author}},
  7. <a href="{{pathFor 'postPage'}}">{{commentsCount}} comments</a>
  8. {{#if ownPost}}<a href="{{pathFor 'postEdit'}}">Edit</a>{{/if}}
  9. </p>
  10. </div>
  11. <a href="{{pathFor 'postPage'}}" class="discuss btn btn-default">Discuss</a>
  12. </div>
  13. </template>

添加 commentsCount helper 到 post_item.js 中:

  1. Template.postItem.helpers({
  2. ownPost: function() {
  3. return this.userId === Meteor.userId();
  4. },
  5. domain: function() {
  6. var a = document.createElement('a');
  7. a.href = this.url;
  8. return a.hostname;
  9. },
  10. commentsCount: function() {
  11. return Comments.find({postId: this._id}).count();
  12. }
  13. });

现在您应该能够显示初始的评论并看到如下的内容:

提交新评论

让用户创建新的评论,这个过程将是非常类似过去我们允许用户创建新的帖子。

首先我们通过在每个帖子底部增加一个提交框:

  1. <template name="postPage">
  2. {{> postItem}}
  3. <ul class="comments">
  4. {{#each comments}}
  5. {{> commentItem}}
  6. {{/each}}
  7. </ul>
  8. {{#if currentUser}}
  9. {{> commentSubmit}}
  10. {{else}}
  11. <p>Please log in to leave a comment.</p>
  12. {{/if}}
  13. </template>

然后创建评论表单模板:

  1. <template name="commentSubmit">
  2. <form name="comment" class="comment-form form">
  3. <div class="form-group {{errorClass 'body'}}">
  4. <div class="controls">
  5. <label for="body">Comment on this post</label>
  6. <textarea name="body" id="body" class="form-control" rows="3"></textarea>
  7. <span class="help-block">{{errorMessage 'body'}}</span>
  8. </div>
  9. </div>
  10. <button type="submit" class="btn btn-primary">Add Comment</button>
  11. </form>
  12. </template>

comment_submit.js 中调用 comment 方法,提交新的评论,这是类似于过去提交帖子的方法:

  1. Template.commentSubmit.onCreated(function() {
  2. Session.set('commentSubmitErrors', {});
  3. });
  4. Template.commentSubmit.helpers({
  5. errorMessage: function(field) {
  6. return Session.get('commentSubmitErrors')[field];
  7. },
  8. errorClass: function (field) {
  9. return !!Session.get('commentSubmitErrors')[field] ? 'has-error' : '';
  10. }
  11. });
  12. Template.commentSubmit.events({
  13. 'submit form': function(e, template) {
  14. e.preventDefault();
  15. var $body = $(e.target).find('[name=body]');
  16. var comment = {
  17. body: $body.val(),
  18. postId: template.data._id
  19. };
  20. var errors = {};
  21. if (! comment.body) {
  22. errors.body = "Please write some content";
  23. return Session.set('commentSubmitErrors', errors);
  24. }
  25. Meteor.call('commentInsert', comment, function(error, commentId) {
  26. if (error){
  27. throwError(error.reason);
  28. } else {
  29. $body.val('');
  30. }
  31. });
  32. }
  33. });

类似以前 post 服务器端的 Meteor 方法,我们将建立一个 comment 的 Meteor 方法来创建评论,检查正确性后,插入到评论集合。

  1. Comments = new Mongo.Collection('comments');
  2. Meteor.methods({
  3. commentInsert: function(commentAttributes) {
  4. check(this.userId, String);
  5. check(commentAttributes, {
  6. postId: String,
  7. body: String
  8. });
  9. var user = Meteor.user();
  10. var post = Posts.findOne(commentAttributes.postId);
  11. if (!post)
  12. throw new Meteor.Error('invalid-comment', 'You must comment on a post');
  13. comment = _.extend(commentAttributes, {
  14. userId: user._id,
  15. author: user.username,
  16. submitted: new Date()
  17. });
  18. return Comments.insert(comment);
  19. }
  20. });

这里没做什么太花哨的,只是检查该用户是否已经登录,该评论有一个内容,并且链接到一个帖子。

控制订阅评论

如同以往一样,我们将发布属于所有帖子的全部评论到每个连接的客户端。这似乎有点浪费。毕竟在任何给定的时间段,实际上只使用该数据的一小部分。因此让我们提高发布和订阅评论的精度。

如果仔细想想,我们需要订阅 comments 评论发布的时间,是当用户访问一个帖子的页面,而此刻只需要加载这个帖子的评论子集。

第一步将改变我们订阅评论的方式。目前是路由器级订阅,这意味着当路由器初始化时,加载所有数据。

但是现在希望我们的订阅依赖于路径参数,并且参数可以在任何时候改变。因此需要将我们的订阅代码从路由器级改到路径级。

这样做的另一个后果:每当打开路径时加载数据,而不是初始化应用时加载它。这意味着你在程序内浏览时,会等待加载时间。除非你打算加载全部内容到客户端,这是一个不可避免的缺点。

首先,在 configure 块中通过删除 Meteor.subscribe('comments'),停止预装全部评论(换句话说,要回以前的方法):

  1. Router.configure({
  2. layoutTemplate: 'layout',
  3. loadingTemplate: 'loading',
  4. notFoundTemplate: 'notFound',
  5. waitOn: function() {
  6. return Meteor.subscribe('posts');
  7. }
  8. });

我们将为 postPage 添加一个新的路径级的 waitOn 函数:

  1. //...
  2. Router.route('/posts/:_id', {
  3. name: 'postPage',
  4. waitOn: function() {
  5. return Meteor.subscribe('comments', this.params._id);
  6. },
  7. data: function() { return Posts.findOne(this.params._id); }
  8. });
  9. //...

我们将 this.params._id 作为参数传递给订阅。所以让我们用这个新信息来确保限制评论属于当前帖子:

  1. Meteor.publish('posts', function() {
  2. return Posts.find();
  3. });
  4. Meteor.publish('comments', function(postId) {
  5. check(postId, String);
  6. return Comments.find({postId: postId});
  7. });

这里有一个问题:当我们返回主页,显示所有的帖子有0条评论:

评论计数

这个问题的原因是:我们只装载 postPage 路径上的评论,所以当我们调用 Comments.find({postId: this._id})commentsCount helper,Meteor 找不到必要的客户端数据为我们提供计数值。

处理这一问题的最佳办法是非规范化的评论计数加到帖子中(如果你不明白这意味着什么,不用担心,接下来的附录会详解!)。正如我们将看到的,代码中复杂性稍微增加,但从不发布帖子列表的_全部_评论中,得到的执行性能改善是值得的。

我们将通过在 post 数据结构中增加一个 commentsCount 属性来实现这一目标。首先,更新帖子的测试数据(用 meteor reset 去重载它们 —— 不要忘了之后重新创建你的帐户):

  1. // 测试数据
  2. if (Posts.find().count() === 0) {
  3. var now = new Date().getTime();
  4. // create two users
  5. var tomId = Meteor.users.insert({
  6. profile: { name: 'Tom Coleman' }
  7. });
  8. var tom = Meteor.users.findOne(tomId);
  9. var sachaId = Meteor.users.insert({
  10. profile: { name: 'Sacha Greif' }
  11. });
  12. var sacha = Meteor.users.findOne(sachaId);
  13. var telescopeId = Posts.insert({
  14. title: 'Introducing Telescope',
  15. userId: sacha._id,
  16. author: sacha.profile.name,
  17. url: 'http://sachagreif.com/introducing-telescope/',
  18. submitted: new Date(now - 7 * 3600 * 1000),
  19. commentsCount: 2
  20. });
  21. Comments.insert({
  22. postId: telescopeId,
  23. userId: tom._id,
  24. author: tom.profile.name,
  25. submitted: new Date(now - 5 * 3600 * 1000),
  26. body: 'Interesting project Sacha, can I get involved?'
  27. });
  28. Comments.insert({
  29. postId: telescopeId,
  30. userId: sacha._id,
  31. author: sacha.profile.name,
  32. submitted: new Date(now - 3 * 3600 * 1000),
  33. body: 'You sure can Tom!'
  34. });
  35. Posts.insert({
  36. title: 'Meteor',
  37. userId: tom._id,
  38. author: tom.profile.name,
  39. url: 'http://meteor.com',
  40. submitted: new Date(now - 10 * 3600 * 1000),
  41. commentsCount: 0
  42. });
  43. Posts.insert({
  44. title: 'The Meteor Book',
  45. userId: tom._id,
  46. author: tom.profile.name,
  47. url: 'http://themeteorbook.com',
  48. submitted: new Date(now - 12 * 3600 * 1000),
  49. commentsCount: 0
  50. });
  51. }

像往常一样,更新测试数据文件时,你必须用 meteor reset 重置数据库,以确保它再次运行。

然后,我们要确保所有新帖子的评论计数从0开始:

  1. //...
  2. var post = _.extend(postAttributes, {
  3. userId: user._id,
  4. author: user.username,
  5. submitted: new Date(),
  6. commentsCount: 0
  7. });
  8. var postId = Posts.insert(post);
  9. //...

当我们创建一个新的评论时,使用 Mongo 的 $inc 操作(给一个数字字段值加一)更新相关的 commentsCount

  1. //...
  2. comment = _.extend(commentAttributes, {
  3. userId: user._id,
  4. author: user.username,
  5. submitted: new Date()
  6. });
  7. // 更新帖子的评论数
  8. Posts.update(comment.postId, {$inc: {commentsCount: 1}});
  9. return Comments.insert(comment);
  10. //...

最后只需简单地删除 client/templates/posts/post_item.jscommentsCount helper,因为该值可以从帖子中得到。

现在用户可以互相对话,如果他们错过了新的评论,这将是不可原谅的。接下来的章节将告诉你如何实现通知,以防止这个情况发生!

转载本站内容时,请务必注明来自W3xue,违者必究。
 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号