上一章,我們已經(jīng)學(xué)會(huì)了創(chuàng)建帖子,下面來學(xué)習(xí)編輯和刪除它們。頁面的代碼非常簡(jiǎn)單,讓我們?cè)谶@個(gè)時(shí)候來談?wù)撘幌?Meteor 是如何管理用戶權(quán)限。
讓我們先設(shè)置我們的路由器,添加一個(gè)可以訪問帖子編輯頁的路徑,并設(shè)置它的數(shù)據(jù)上下文:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() { return Meteor.subscribe('posts'); }
});
Router.route('/', {name: 'postsList'});
Router.route('/posts/:_id', {
name: 'postPage',
data: function() { return Posts.findOne(this.params._id); }
});
Router.route('/posts/:_id/edit', {
name: 'postEdit',
data: function() { return Posts.findOne(this.params._id); }
});
Router.route('/submit', {name: 'postSubmit'});
var requireLogin = function() {
if (! Meteor.user()) {
if (Meteor.loggingIn()) {
this.render(this.loadingTemplate);
} else {
this.render('accessDenied');
}
} else {
this.next();
}
}
Router.onBeforeAction('dataNotFound', {only: 'postPage'});
Router.onBeforeAction(requireLogin, {only: 'postSubmit'});
我們可以現(xiàn)在專注模板了。我們的 postEdit
模板就包含一個(gè)相當(dāng)標(biāo)準(zhǔn)的表單:
<template name="postEdit">
<form class="main form">
<div class="form-group">
<label class="control-label" for="url">URL</label>
<div class="controls">
<input name="url" id="url" type="text" value="{{url}}" placeholder="Your URL" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" id="title" type="text" value="{{title}}" placeholder="Name your post" class="form-control"/>
</div>
</div>
<input type="submit" value="Submit" class="btn btn-primary submit"/>
<hr/>
<a class="btn btn-danger delete" href="#">Delete post</a>
</form>
</template>
用 post_edit.js
來配合這個(gè)的模板:
Template.postEdit.events({
'submit form': function(e) {
e.preventDefault();
var currentPostId = this._id;
var postProperties = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
}
Posts.update(currentPostId, {$set: postProperties}, function(error) {
if (error) {
// 向用戶顯示錯(cuò)誤信息
alert(error.reason);
} else {
Router.go('postPage', {_id: currentPostId});
}
});
},
'click .delete': function(e) {
e.preventDefault();
if (confirm("Delete this post?")) {
var currentPostId = this._id;
Posts.remove(currentPostId);
Router.go('postsList');
}
}
});
相信你現(xiàn)在已經(jīng)對(duì)這些代碼都相當(dāng)?shù)氖煜ち恕?/p>
我們有兩個(gè)事件回調(diào)函數(shù):一個(gè)用于表單的 submit
事件,一個(gè)用于刪除鏈接的 click
事件。
刪除鏈接的回調(diào)函數(shù)是非常簡(jiǎn)單的:先防止默認(rèn)點(diǎn)擊事件,然后提示確認(rèn)窗口。如果確認(rèn)刪除,它將從模板的數(shù)據(jù)上下文中獲得當(dāng)前帖子的 ID ,然后刪除它,最后把用戶重定向到主頁。
更新的回調(diào)函數(shù)需要長(zhǎng)一點(diǎn)時(shí)間,但并不復(fù)雜。在防止默認(rèn)提交事件然后獲取了當(dāng)前帖子之后,我們將從表單中獲取相關(guān)字段的值,并將它們存儲(chǔ)在一個(gè) postProperties
的對(duì)象中。
然后,我們把該對(duì)象通過 $set
操作符(只更新指定字段的值,保留其他字段的值)傳遞給 Meteor 的 Collection.update()
方法,并通過回調(diào)函數(shù)去判斷如果更新失敗就顯示錯(cuò)誤信息;如果更新成功了,將自動(dòng)返回到該帖子的頁面。
我們還應(yīng)該添加一個(gè)編輯帖子的鏈接,以便用戶可以訪問到帖子編輯頁面:
<template name="postItem">
<div class="post">
<div class="post-content">
<h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
<p>
submitted by {{author}}
{{#if ownPost}}<a href="{{pathFor 'postEdit'}}">Edit</a>{{/if}}
</p>
</div>
<a href="{{pathFor 'postPage'}}" class="discuss btn btn-default">Discuss</a>
</div>
</template>
當(dāng)然,我們不能讓你的帖子提供給其他用戶去編輯。這就要通過 ownPost
helper 來幫忙:
Template.postItem.helpers({
ownPost: function() {
return this.userId === Meteor.userId();
},
domain: function() {
var a = document.createElement('a');
a.href = this.url;
return a.hostname;
}
});
我們的帖子編輯表單看起來很好,但是目前還不能夠進(jìn)行任何的編輯,這是為什么?
自從我們移除了 insecure
包,現(xiàn)在所有客戶端的修改都會(huì)被拒絕。
為了解決這個(gè)問題,我們需要建立一些權(quán)限規(guī)則。首先,在 lib
目錄下創(chuàng)建一個(gè)新的 permissions.js
文件。這樣做將會(huì)首先加載我們權(quán)限文件(它在服務(wù)端和客戶端都可以被加載到):
// check that the userId specified owns the documents
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
在創(chuàng)建帖子這個(gè)章節(jié),我們拋棄了 allow()
方法,因?yàn)槲覀冎煌ㄟ^服務(wù)端方法去插入新的帖子(繞過了 allow()
方法)。
但是現(xiàn)在我們要在客戶端編輯和刪除帖子!我們回到 posts.js
文件并添加 allow()
:
Posts = new Mongo.Collection('posts');
Posts.allow({
update: function(userId, post) { return ownsDocument(userId, post); },
remove: function(userId, post) { return ownsDocument(userId, post); }
});
//...
盡管你可以編輯自己的帖子,但并不意味著你可以允許去編輯帖子的每個(gè)屬性。例如,我們不允許用戶創(chuàng)建一個(gè)帖子之后,再將其分配給其他用戶。
我們用 Meteor 的 deny()
方法,以確保用戶只能編輯特定的字段:
Posts = new Mongo.Collection('posts');
Posts.allow({
update: function(userId, post) { return ownsDocument(userId, post); },
remove: function(userId, post) { return ownsDocument(userId, post); }
});
Posts.deny({
update: function(userId, post, fieldNames) {
// 只能更改如下兩個(gè)字段:
return (_.without(fieldNames, 'url', 'title').length > 0);
}
});
//...
代碼中的 fieldNames
數(shù)組,它包含了需要被修改的字段,并使用 Underscore 的 without()
方法返回一個(gè)不包含 url
和 title
字段的子數(shù)組。
正常情況下,這個(gè)數(shù)組應(yīng)該是空的,它的長(zhǎng)度應(yīng)該是0。如果有人采取其他操作,這個(gè)數(shù)組的長(zhǎng)度將變?yōu)?或更多,回調(diào)函數(shù)將返回 true
(因此禁止更新)。
你也許注意到了在我們的代碼中沒有檢查鏈接是否重復(fù)的代碼。這就意味著用戶成功添加一個(gè)鏈接后,再編輯時(shí)就會(huì)繞過檢查。這個(gè)問題同樣可以通過為編輯帖子表單使用 Meteor 內(nèi)置方法來解決,但是我們將它作為練習(xí)留給讀者。
創(chuàng)建帖子,我們使用的是 postInsert
的內(nèi)置方法,而編輯和刪除帖子,我們直接在客戶端調(diào)用 update
和 remove
,并通過 allow
和 deny
去限制使用權(quán)限。
我們?cè)撊绾稳ミx擇使用呢?
當(dāng)操作相對(duì)比較直觀,你可以通過 allow
和 deny
去設(shè)置你的規(guī)則的時(shí)候,直接在客戶端進(jìn)行操作通常會(huì)更簡(jiǎn)單。
然而,一旦你需要做一些在用戶控制以外的事情(比如設(shè)置一個(gè)新帖子的時(shí)間戳,或者把帖子分配到正確的用戶),這種情況使用內(nèi)置方法會(huì)更好。
內(nèi)置方法也適用在其他的一些情景:
請(qǐng)閱讀我們的 blog 來深入了解這個(gè)話題。
更多建議: