社交新聞網(wǎng)站的目標(biāo)是創(chuàng)建一個用戶社區(qū),如果沒有提供一種方式讓人們互相交流,這將是很難做到的。因此在本章中,我們添加評論!
我們首先創(chuàng)建一個新的集來存儲評論,并在該集中添加一些初始數(shù)據(jù)。
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)
});
}
不要忘記來發(fā)布和訂閱我們這個新的集合:
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')];
}
});
請注意,為了使用新的數(shù)據(jù),你需要命令 Meteor reset
清除數(shù)據(jù)庫。不要忘了創(chuàng)建一個新的用戶,并重新登錄!
首先,我們在數(shù)據(jù)庫中創(chuàng)建了幾個(假的)用戶,并從數(shù)據(jù)庫中用他們的 id
選擇出來。然后給第一篇添加注釋,鏈接注釋到帖子(postId
)和用戶(userId
)。同時(shí)我們還添加了提交日期,評論內(nèi)容和一個非規(guī)范化的作者 author
項(xiàng)。
此外我們在路由器中增加等待一個含有評論和帖子訂閱的數(shù)組。
把評論存到數(shù)據(jù)庫,同時(shí)還需要在評論頁上顯示。
<template name="postPage">
{{> postItem}}
<ul class="comments">
{{#each comments}}
{{> commentItem}}
{{/each}}
</ul>
</template>
Template.postPage.helpers({
comments: function() {
return Comments.find({postId: this._id});
}
});
我們把 {{#each comments}}
塊放在帖子模板里面,所以在 comments
helper 里,this
指向的是當(dāng)前帖子。要找到相關(guān)的評論,我們可通過 postId
屬性找到鏈接到該帖子的評論。
我們已經(jīng)了解 helper 和 Spacebars 后,顯示一個評論是相當(dāng)簡單的。我們將在 templates
下,創(chuàng)建一個新的 comments
目錄和一個新的 commentItem
模板,來存儲所有的評論相關(guān)的信息:
<template name="commentItem">
<li>
<h4>
<span class="author">{{author}}</span>
<span class="date">on {{submittedText}}</span>
</h4>
<p>{{body}}</p>
</li>
</template>
讓我們編寫一個模板 helper 來幫助我們把 submitted
提交日期格式人性化的日期格式:
Template.commentItem.helpers({
submittedText: function() {
return this.submitted.toString();
}
});
然后,我們將在每個帖子中顯示評論數(shù):
<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>
添加 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();
}
});
現(xiàn)在您應(yīng)該能夠顯示初始的評論并看到如下的內(nèi)容:
讓用戶創(chuàng)建新的評論,這個過程將是非常類似過去我們允許用戶創(chuàng)建新的帖子。
首先我們通過在每個帖子底部增加一個提交框:
<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>
然后創(chuàng)建評論表單模板:
<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>
在 comment_submit.js
中調(diào)用 comment
方法,提交新的評論,這是類似于過去提交帖子的方法:
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('');
}
});
}
});
類似以前 post
服務(wù)器端的 Meteor 方法,我們將建立一個 comment
的 Meteor 方法來創(chuàng)建評論,檢查正確性后,插入到評論集合。
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);
}
});
這里沒做什么太花哨的,只是檢查該用戶是否已經(jīng)登錄,該評論有一個內(nèi)容,并且鏈接到一個帖子。
如同以往一樣,我們將發(fā)布屬于所有帖子的全部評論到每個連接的客戶端。這似乎有點(diǎn)浪費(fèi)。畢竟在任何給定的時(shí)間段,實(shí)際上只使用該數(shù)據(jù)的一小部分。因此讓我們提高發(fā)布和訂閱評論的精度。
如果仔細(xì)想想,我們需要訂閱 comments
評論發(fā)布的時(shí)間,是當(dāng)用戶訪問一個帖子的頁面,而此刻只需要加載這個帖子的評論子集。
第一步將改變我們訂閱評論的方式。目前是路由器級訂閱,這意味著當(dāng)路由器初始化時(shí),加載所有數(shù)據(jù)。
但是現(xiàn)在希望我們的訂閱依賴于路徑參數(shù),并且參數(shù)可以在任何時(shí)候改變。因此需要將我們的訂閱代碼從路由器級改到路徑級。
這樣做的另一個后果:每當(dāng)打開路徑時(shí)加載數(shù)據(jù),而不是初始化應(yīng)用時(shí)加載它。這意味著你在程序內(nèi)瀏覽時(shí),會等待加載時(shí)間。除非你打算加載全部內(nèi)容到客戶端,這是一個不可避免的缺點(diǎn)。
首先,在 configure
塊中通過刪除 Meteor.subscribe('comments')
,停止預(yù)裝全部評論(換句話說,要回以前的方法):
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() {
return Meteor.subscribe('posts');
}
});
我們將為 postPage
添加一個新的路徑級的 waitOn
函數(shù):
//...
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return Meteor.subscribe('comments', this.params._id);
},
data: function() { return Posts.findOne(this.params._id); }
});
//...
我們將 this.params._id
作為參數(shù)傳遞給訂閱。所以讓我們用這個新信息來確保限制評論屬于當(dāng)前帖子:
Meteor.publish('posts', function() {
return Posts.find();
});
Meteor.publish('comments', function(postId) {
check(postId, String);
return Comments.find({postId: postId});
});
這里有一個問題:當(dāng)我們返回主頁,顯示所有的帖子有0條評論:
這個問題的原因是:我們只裝載 postPage
路徑上的評論,所以當(dāng)我們調(diào)用 Comments.find({postId: this._id})
中 commentsCount
helper,Meteor 找不到必要的客戶端數(shù)據(jù)為我們提供計(jì)數(shù)值。
處理這一問題的最佳辦法是非規(guī)范化的評論計(jì)數(shù)加到帖子中(如果你不明白這意味著什么,不用擔(dān)心,接下來的附錄會詳解?。U缥覀儗⒖吹降?,代碼中復(fù)雜性稍微增加,但從不發(fā)布帖子列表的_全部_評論中,得到的執(zhí)行性能改善是值得的。
我們將通過在 post
數(shù)據(jù)結(jié)構(gòu)中增加一個 commentsCount
屬性來實(shí)現(xiàn)這一目標(biāo)。首先,更新帖子的測試數(shù)據(jù)(用 meteor reset
去重載它們 —— 不要忘了之后重新創(chuàng)建你的帳戶):
// 測試數(shù)據(jù)
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
});
}
像往常一樣,更新測試數(shù)據(jù)文件時(shí),你必須用 meteor reset
重置數(shù)據(jù)庫,以確保它再次運(yùn)行。
然后,我們要確保所有新帖子的評論計(jì)數(shù)從0開始:
//...
var post = _.extend(postAttributes, {
userId: user._id,
author: user.username,
submitted: new Date(),
commentsCount: 0
});
var postId = Posts.insert(post);
//...
當(dāng)我們創(chuàng)建一個新的評論時(shí),使用 Mongo 的 $inc
操作(給一個數(shù)字字段值加一)更新相關(guān)的 commentsCount
:
//...
comment = _.extend(commentAttributes, {
userId: user._id,
author: user.username,
submitted: new Date()
});
// 更新帖子的評論數(shù)
Posts.update(comment.postId, {$inc: {commentsCount: 1}});
return Comments.insert(comment);
//...
最后只需簡單地刪除 client/templates/posts/post_item.js
的 commentsCount
helper,因?yàn)樵撝悼梢詮奶又械玫健?/p>
現(xiàn)在用戶可以互相對話,如果他們錯過了新的評論,這將是不可原諒的。接下來的章節(jié)將告訴你如何實(shí)現(xiàn)通知,以防止這個情況發(fā)生!
更多建議: