上一章,我們已經(jīng)學會了創(chuàng)建帖子,下面來學習編輯和刪除它們。頁面的代碼非常簡單,讓我們在這個時候來談論一下 Meteor 是如何管理用戶權(quán)限。
讓我們先設置我們的路由器,添加一個可以訪問帖子編輯頁的路徑,并設置它的數(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
模板就包含一個相當標準的表單:
<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
來配合這個的模板:
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) {
// 向用戶顯示錯誤信息
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)對這些代碼都相當?shù)氖煜ち恕?/p>
我們有兩個事件回調(diào)函數(shù):一個用于表單的 submit
事件,一個用于刪除鏈接的 click
事件。
刪除鏈接的回調(diào)函數(shù)是非常簡單的:先防止默認點擊事件,然后提示確認窗口。如果確認刪除,它將從模板的數(shù)據(jù)上下文中獲得當前帖子的 ID ,然后刪除它,最后把用戶重定向到主頁。
更新的回調(diào)函數(shù)需要長一點時間,但并不復雜。在防止默認提交事件然后獲取了當前帖子之后,我們將從表單中獲取相關字段的值,并將它們存儲在一個 postProperties
的對象中。
然后,我們把該對象通過 $set
操作符(只更新指定字段的值,保留其他字段的值)傳遞給 Meteor 的 Collection.update()
方法,并通過回調(diào)函數(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}}
{{#if ownPost}}<a href="{{pathFor 'postEdit'}}">Edit</a>{{/if}}
</p>
</div>
<a href="{{pathFor 'postPage'}}" class="discuss btn btn-default">Discuss</a>
</div>
</template>
當然,我們不能讓你的帖子提供給其他用戶去編輯。這就要通過 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;
}
});
我們的帖子編輯表單看起來很好,但是目前還不能夠進行任何的編輯,這是為什么?
自從我們移除了 insecure
包,現(xiàn)在所有客戶端的修改都會被拒絕。
為了解決這個問題,我們需要建立一些權(quán)限規(guī)則。首先,在 lib
目錄下創(chuàng)建一個新的 permissions.js
文件。這樣做將會首先加載我們權(quán)限文件(它在服務端和客戶端都可以被加載到):
// check that the userId specified owns the documents
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
在創(chuàng)建帖子這個章節(jié),我們拋棄了 allow()
方法,因為我們只通過服務端方法去插入新的帖子(繞過了 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); }
});
//...
盡管你可以編輯自己的帖子,但并不意味著你可以允許去編輯帖子的每個屬性。例如,我們不允許用戶創(chuàng)建一個帖子之后,再將其分配給其他用戶。
我們用 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) {
// 只能更改如下兩個字段:
return (_.without(fieldNames, 'url', 'title').length > 0);
}
});
//...
代碼中的 fieldNames
數(shù)組,它包含了需要被修改的字段,并使用 Underscore 的 without()
方法返回一個不包含 url
和 title
字段的子數(shù)組。
正常情況下,這個數(shù)組應該是空的,它的長度應該是0。如果有人采取其他操作,這個數(shù)組的長度將變?yōu)?或更多,回調(diào)函數(shù)將返回 true
(因此禁止更新)。
你也許注意到了在我們的代碼中沒有檢查鏈接是否重復的代碼。這就意味著用戶成功添加一個鏈接后,再編輯時就會繞過檢查。這個問題同樣可以通過為編輯帖子表單使用 Meteor 內(nèi)置方法來解決,但是我們將它作為練習留給讀者。
創(chuàng)建帖子,我們使用的是 postInsert
的內(nèi)置方法,而編輯和刪除帖子,我們直接在客戶端調(diào)用 update
和 remove
,并通過 allow
和 deny
去限制使用權(quán)限。
我們該如何去選擇使用呢?
當操作相對比較直觀,你可以通過 allow
和 deny
去設置你的規(guī)則的時候,直接在客戶端進行操作通常會更簡單。
然而,一旦你需要做一些在用戶控制以外的事情(比如設置一個新帖子的時間戳,或者把帖子分配到正確的用戶),這種情況使用內(nèi)置方法會更好。
內(nèi)置方法也適用在其他的一些情景:
請閱讀我們的 blog 來深入了解這個話題。
更多建議: