-
Notifications
You must be signed in to change notification settings - Fork 39
/
08-editing-posts.md.erb
268 lines (203 loc) · 12.9 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
263
264
265
266
267
---
title: Редактирование постов
slug: editing-posts
date: 0008/01/01
number: 8
contents: Добавите форму редактирования постов.|Создадите права редактирования.|Ограничите поля для редактирования.
paragraphs: 29
---
Теперь, когда мы можем создавать посты, следующим шагом будет их редактирование и удаление. Хотя UI код для этого довольно прост, сейчас самое время будет поговорить о том, как Meteor работает с правами доступа.
Давайте взглянем на роутер. Мы добавим путь для страницы редактирования поста и зададим контекст:
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() { return Meteor.subscribe('posts'); }
});
Router.map(function() {
this.route('postsList', {path: '/'});
this.route('postPage', {
path: '/posts/:_id',
data: function() { return Posts.findOne(this.params._id); }
});
this.route('postEdit', {
path: '/posts/:_id/edit',
data: function() { return Posts.findOne(this.params._id); }
});
this.route('postSubmit', {
path: '/submit'
});
});
var requireLogin = function(pause) {
if (! Meteor.user()) {
if (Meteor.loggingIn())
this.render('loading')
else
this.render('accessDenied');
pause();
}
}
Router.onBeforeAction(requireLogin, {only: 'postSubmit'});
~~~
<%= caption "lib/router.js" %>
<%= highlight "14~17" %>
### Шаблон страницы редактирования поста
Теперь можем сосредоточится на шаблоне. Наш `postEdit` шаблон приобретёт стандартный вид:
~~~html
<template name="postEdit">
<form class="main">
<div class="control-group">
<label class="control-label" for="url">URL</label>
<div class="controls">
<input name="url" type="text" value="{{url}}" placeholder="Your URL"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" type="text" value="{{title}}" placeholder="Name your post"/>
</div>
</div>
<div class="control-group">
<div class="controls">
<input type="submit" value="Submit" class="btn btn-primary submit"/>
</div>
</div>
<hr/>
<div class="control-group">
<div class="controls">
<a class="btn btn-danger delete" href="#">Delete post</a>
</div>
</div>
</form>
</template>
~~~
<%= caption "client/views/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) {
// display the error to the user
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/views/posts/post_edit.js" %>
К этому моменту большая часть этого кода должна быть вам уже знакома.
У нас есть две функции-обработчика событий - одна для события `submit` на форме, и одна для клика на линке `delete`.
Обработчик события `delete` предельно прост - перехватить событие клика, спросить подтверждение на удаление поста. Если пользователь согласен продолжить, получить ID текущего поста из контекста `this` нашего шаблона, и, наконец, перенаправить пользователя на главную страницу.
Обработчик события редактирования поста слегка длиннее, но не намного сложнее. Мы получаем ID текущего поста из контекста шаблона через `this._id`. Затем мы считываем обновленный пост из полей формы, создаем новый объект `postProperties` и сохраняем обновленный пост в нем.
Затем мы передаем этот объект методу Meteor'a `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">Discuss</a>
</div>
</template>
~~~
<%= caption "client/views/posts/post_item.html" %>
<%= highlight "5~8" %>
Имеет смысл показывать линк редактирования поста только в том случае, если этот пост принадлежит вам. Для этого воспользуемся хелпером `ownPost`:
~~~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/views/posts/post_item.js" %>
<%= highlight "2~4" %>
<%= screenshot "8-1", "Форма редактирования поста" %>
<%= commit "8-1", "Добавлена форма редактирования поста" %>
Форма для редактирования поста готова и выглядит отлично. Но вы все еще не сможете отредактировать пост. Что же происходит?
### Создаем права доступа
Так как мы недавно удалили пакет `insecure`, все модификации со стороны клиента отвергаются сервером.
Чтобы это починить, создадим правила доступа. Для начала создайте новый `permissions.js` файл в папке `lib`. Это позволит Meteor'у загрузить логику прав доступа с самого начала (и сделать ее доступной на клиенте и сервере):
~~~js
// проверяем что userId на самом деле автор данного документа
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
~~~
<%= caption "lib/permissions.js" %>
В главе [Создание постов](/chapters/creating-posts) мы избавились от методов `allow()`, так как мы добавляли новые посты через Метод на сервере (который, в свою очередь, игнорирует `allow()`).
Но теперь, когда мы редактируем и удаляем посты на клиенте, давайте вернемся к файлу `posts.js` и добавим блок `allow()`:
~~~js
Posts = new Meteor.Collection('posts');
Posts.allow({
update: ownsDocument,
remove: ownsDocument
});
Meteor.methods({
...
~~~
<%= caption "collections/posts.js" %>
<%= highlight "3~6" %>
<%= commit "8-2", "Добавлены простые права доступа с проверкой автора поста" %>
### Ограничиваем редактирование
Только потому, что вам разрешено редактировать собственные посты еще не означает что вам можно редактировать *все* поля. Например, нам бы не хотелось дать пользователям возможность создать пост и затем сменить ID автора на кого-то другого.
Мы воспользуемся функцией `deny()` Meteor'a чтобы ограничить пользователей в редактировании полей:
~~~js
Posts = new Meteor.Collection('posts');
Posts.allow({
update: ownsDocument,
remove: ownsDocument
});
Posts.deny({
update: function(userId, post, fieldNames) {
// разрешаем редактировать только следующие два поля:
return (_.without(fieldNames, 'url', 'title').length > 0);
}
});
~~~
<%= caption "collections/posts.js" %>
<%= highlight "8~13" %>
<%= commit "8-3", "Разрешаем редактировать только определенные поля постов." %>
Мы берем массив `fieldNames` со списком редактируемых полей и используем метод библиотеки [Underscore](http://underscorejs.org/) `without()` чтобы пропустить через него наш массив и вернуть новый, уже *без* полей `url` или `title`.
Если все в порядке, новый массив будет пуст и его длина должна быть 0. Если же кто-то пытается отредактировать недопустимые поля, длина этого массива будет 1 или больше, и функция вернет `true` (и редактирование поста будет отвергнуто).
<% note do %>
### Вызовы Методов или редактирования данных на клиенте?
Для создания постов мы используем Метод `post`. Для редактирования и удаления постов мы вызываем `update` и `remove` прямо на клиенте, ограничивая доступ через `allow` и `deny`.
Когда же стоит использовать каждую из этих техник?
Когда вещи довольно просты и вы можете адекватно выразить правила редактирования данных через `allow` и `deny`, обычно все проще делать через клиента.
Прямой доступ к базе данных через клиента создает ощущение непосредственности, и может лучше порадовать пользователя отзывчивым интерфейсом, до тех пор пока вы помните правильно реагировать на ошибки сервера (то есть, когда сервер ответит что редактирование базы данных не удалось).
Однако как только вам понадобится совершать действия выходящие за рамки контроля пользователя (например, добавление времени создания поста или его авторства), стоит использовать Метод.
Вызов Метода также лучше подходит в следующих случаях:
- Когда вам нужно узнать или вернуть данные через функцию-коллбек вместо того, чтобы ждать пока сработает реактивность и синхронизация данных.
- Для массивных функций обработки базы данных
- Когда требуется просуммировать или агрегировать данные (например, вызов методов count, average, sum для массивов данных).
<% end %>