-
Notifications
You must be signed in to change notification settings - Fork 79
/
Copy path08-editing-posts.md.erb
262 lines (199 loc) · 9.22 KB
/
08-editing-posts.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
---
title: 编辑帖子
slug: editing-posts
date: 0008/01/01
number: 8
points: 5
photoUrl: http://www.flickr.com/photos/ikewinski/9473337133/
photoAuthor: Mike Lewinski
contents: 添加一个帖子编辑表单。|设置可编辑权限。|限制哪些属性可以编辑。
paragraphs: 29
---
上一章,我们已经学会了创建帖子,下面来学习编辑和删除它们。页面的代码非常简单,让我们在这个时候来谈论一下 Meteor 是如何管理用户权限。
让我们先设置我们的路由器,添加一个可以访问帖子编辑页的路径,并设置它的数据上下文:
~~~js
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'});
~~~
<%= caption "lib/router.js" %>
<%= highlight "15~18" %>
### 帖子编辑模板
我们可以现在专注模板了。我们的 `postEdit` 模板就包含一个相当标准的表单:
~~~html
<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>
~~~
<%= caption "client/templates/posts/post_edit.html" %>
用 `post_edit.js` 来配合这个的模板:
~~~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');
}
}
});
~~~
<%= caption "client/templates/posts/post_edit.js" %>
相信你现在已经对这些代码都相当的熟悉了。
我们有两个事件回调函数:一个用于表单的 `submit` 事件,一个用于删除链接的 `click` 事件。
删除链接的回调函数是非常简单的:先防止默认点击事件,然后提示确认窗口。如果确认删除,它将从模板的数据上下文中获得当前帖子的 ID ,然后删除它,最后把用户重定向到主页。
更新的回调函数需要长一点时间,但并不复杂。在防止默认提交事件然后获取了当前帖子之后,我们将从表单中获取相关字段的值,并将它们存储在一个 `postProperties` 的对象中。
然后,我们把该对象通过 [`$set`](http://docs.mongodb.org/manual/reference/operator/update/set/) 操作符(只更新指定字段的值,保留其他字段的值)传递给 Meteor 的 `Collection.update()` 方法,并通过回调函数去判断如果更新失败就显示错误信息;如果更新成功了,将自动返回到该帖子的页面。
### 添加链接
我们还应该添加一个编辑帖子的链接,以便用户可以访问到帖子编辑页面:
~~~html
<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>
~~~
<%= caption "client/templates/posts/post_item.html" %>
<%= highlight "5~8" %>
当然,我们不能让你的帖子提供给其他用户去编辑。这就要通过 `ownPost` helper 来帮忙:
~~~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;
}
});
~~~
<%= caption "client/templates/posts/post_item.js" %>
<%= highlight "2~4" %>
<%= screenshot "8-1", "帖子编辑表单。" %>
<%= commit "8-1", "添加帖子编辑表单。" %>
我们的帖子编辑表单看起来很好,但是目前还不能够进行任何的编辑,这是为什么?
### 设置权限
自从我们移除了 `insecure` 包,现在所有客户端的修改都会被拒绝。
为了解决这个问题,我们需要建立一些权限规则。首先,在 `lib` 目录下创建一个新的 `permissions.js` 文件。这样做将会首先加载我们权限文件(它在服务端和客户端都可以被加载到):
~~~js
// check that the userId specified owns the documents
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
~~~
<%= caption "lib/permissions.js" %>
在[创建帖子](/chapter/creating-posts)这个章节,我们抛弃了 `allow()` 方法,因为我们只通过服务端方法去插入新的帖子(绕过了 `allow()` 方法)。
但是现在我们要在客户端编辑和删除帖子!我们回到 `posts.js` 文件并添加 `allow()` :
~~~js
Posts = new Mongo.Collection('posts');
Posts.allow({
update: function(userId, post) { return ownsDocument(userId, post); },
remove: function(userId, post) { return ownsDocument(userId, post); }
});
//...
~~~
<%= caption "lib/collections/posts.js" %>
<%= highlight "3~6" %>
<%= commit "8-2", "添加基本权限来检查帖子的拥有者。" %>
### 限制编辑
尽管你可以编辑自己的帖子,但并不意味着你可以允许去编辑帖子的**每个**属性。例如,我们不允许用户创建一个帖子之后,再将其分配给其他用户。
我们用 Meteor 的 `deny()` 方法,以确保用户只能编辑特定的字段:
~~~js
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);
}
});
//...
~~~
<%= caption "lib/collections/posts.js" %>
<%= highlight "8~13" %>
<%= commit "8-3", "只允许更改帖子的某些属性。" %>
代码中的 `fieldNames` 数组,它包含了需要被修改的字段,并使用 [Underscore](http://underscorejs.org/) 的 `without()` 方法返回一个*不包含* `url` 和 `title` 字段的子数组。
正常情况下,这个数组应该是空的,它的长度应该是0。如果有人采取其他操作,这个数组的长度将变为1或更多,回调函数将返回 `true` (因此禁止更新)。
你也许注意到了在我们的代码中没有检查链接是否重复的代码。这就意味着用户成功添加一个链接后,再编辑时就会绕过检查。这个问题同样可以通过为编辑帖子表单使用 Meteor 内置方法来解决,但是我们将它作为练习留给读者。
<% note do %>
### 内置方法的回调 vs 客户端数据操作
创建帖子,我们使用的是 `postInsert` 的内置方法,而编辑和删除帖子,我们直接在客户端调用 `update` 和 `remove`,并通过 `allow` 和 `deny` 去限制使用权限。
我们该如何去选择使用呢?
当操作相对比较直观,你可以通过 `allow` 和 `deny` 去设置你的规则的时候,直接在客户端进行操作通常会更简单。
然而,一旦你需要做一些在用户控制以外的事情(比如设置一个新帖子的时间戳,或者把帖子分配到正确的用户),这种情况使用内置方法会更好。
内置方法也适用在其他的一些情景:
- 当你需要通过内置方法的回调函数去获得返回值的时候,而不是等待响应和同步才传递的数据。
- 对于一些繁重的数据库操作,比如要提取大量的数据集合。
- 计算或者合计数据的时候(比如:计数、平均值、求和)。
[请阅读我们的 blog](https://www.discovermeteor.com/blog/meteor-methods-client-side-operations/) 来深入了解这个话题。
<% end %>