-
Notifications
You must be signed in to change notification settings - Fork 4
/
08-editing-posts.md.erb
266 lines (201 loc) · 9.44 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
---
title: Edytowanie postów
slug: editing-posts
date: 0008/01/01
number: 8
contents: jak dodać formularz umożliwiający edytowanie postów.|jak ustawić prawa do edycji postóœ.|jak ograniczyć, które części posta mogą być edytowane.
paragraphs: 29
---
Teraz skoro możemy tworzyć posty, następnym krokiem będzie umożliwienie edycji i usuwanie postóœ. Kod UI który to umożliwi jest releatywnie prosty i jest to dobry moment na podjęcie tematu zarządzania praw użytkowników.
Zacznijmy od podłączenia routera. Dodamy ścieżkę, która umożliwi dostęp do strony modyfikacji posta i ustawi jej kontekst danych:
~~~js
Router.configure({
layoutTemplate: 'layout'
});
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() {
if (! Meteor.user()) {
if (Meteor.loggingIn())
this.render('loading')
else
this.render('accessDenied');
this.stop();
}
}
Router.before(requireLogin, {only: 'postSubmit'});
~~~
<%= caption "lib/router.js" %>
<%= highlight "12~15" %>
### Szablon edytowania posta
Możemy teraz skoncetrować się na szablonie. Nasz szablon `postEdit` będzie miał całkiem standardową formę:
~~~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" %>
A poniżej znajdziesz manager `post_edit.js` który z nim współgra:
~~~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" %>
Jak do tej pory większość kodu powinna się Tobie wydać znajoma. Na początku mamy helper szablonu, który pobiera bieżący post z bazy danych i przekazuje go do szablonu.
Następnie mamy dwo callbacki zdarzeń szablonu: jeden dla zdarzenia `submit` formularza, drugi dla usunięcia zdarzenia 'click' dla linka.
Callback usuwający zdarzenie click jest bardzo prosty: omija domyślne zdarzenie click i pyta o potwierdzenie. Jeżeli je dostanie, korzysta z post ID obecnego posta otrzymanego z kontekstu danych szablonu, usuwa go i ostatecznie przekierowuje użytkownika do strony domowej.
Callback uaktualniający dane 'update' jest nieco obszerniejszy, ale niewiele bardziej skomplikowany. Po ominięciu domyślnego zdarzenia i otrzymaniu bieżącego posta, otrzymujemy nowe dane formularza ze strony i zapisujemy je w obiekcie `postProperties`.
Następnie przekazujemy ten obiekt to metody Meteora `Collection.update()` i używamy callbacka który wyświetla błąd gdy uaktualnienie się nie powiedzie lub wysyła użytkownika z powrotem na stronę posta, gdy uaktualnienie przebiegnie pomyślnie.
### Dodawanie linków
Powinniśmy również edytować linki do naszych postów, aby użytkownicy mieli dostęp do strony edytowania posta:
~~~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" %>
Oczywiście nie chcemy pokazywać linku do edytowania posta osobom postronnym. Pomaga w tym helper `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", "Formularz edytowania posta." %>
<%= commit "8-1", "Dodany formularz edytowania posta." %>
Nasz formularz edytowania posta wygląda dobrze, ale właściwie nie możemy niczego teraz edytować. Co się dzieje?
### Ustawianie praw dostępu
Ponieważ poprzednio usunęliśmy pakiet `insecure`, wszystkie zmiany po stronie klienta są obecnie odrzucane.
Aby to naprawić, ustawimy zasady praw dostępu. Najpierw utwórz nowy plik `permissions.js` w folderze `lib`. Załaduje to logikę ustawiania praw dostępu jako pierwszą (i będzie dostępna w obu środowiskach, po stronie klienta i serwera).
~~~js
// sprawdz czy userId jest wlascicielem dokumentu
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
~~~
<%= caption "lib/permissions.js" %>
W rozdziale [Tworzenie postów](/chapter/creating-posts) pozbyliśmy się metod `allow()`, ponieważ wstawialiśmy nowe posty wyłącznie za pomocą metody serwerowej (która i tak omija `allow()`).
Teraz jednak edytujemy i usuwamy posty po stronie klienta, wróćmy zatem do pliku `posts.js` i dodajmy blok `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", "Dodanie podstawowych praw dostępu do sprawdzenia właściciela posta." %>
### Ograniczanie edycji
Fakt, że możesz edytować własne posty nie oznacza, że powinieneś edytować *każde* pole. Na przykład, nie chcemy aby użytkownicy mogli tworzyć posta i następnie przypisać go innej osobie.
Używamy callbacka Meteora `deny()` aby upewnić się, że użytkownicy mogę edytować wyłącznie niektóre pola:
~~~js
Posts = new Meteor.Collection('posts');
Posts.allow({
update: ownsDocument,
remove: ownsDocument
});
Posts.deny({
update: function(userId, post, fieldNames) {
// may only edit the following two fields:
return (_.without(fieldNames, 'url', 'title').length > 0);
}
});
~~~
<%= caption "collections/posts.js" %>
<%= highlight "8~13" %>
<%= commit "8-3", "Pozwól na zmianę tylko niektórych pól posta." %>
Używamy tablicy `fieldNames`, która zawiera listę zmienianych pól i używamy metody `without()` [Underscore](http://underscorejs.org/), aby zwrócić tablicę, która zawiera częściową tablicę zawierającą pola, te które *nie* są polami `url` czy `title`.
Jeżeli wszystko przebiegnie prawidłowo, ta tablica powinna być pusta i jej długość powinna być równa 0. Jeżeli ktoś stara się dokonać czegoś nieoczekiwanego, długość tablicy będzie równa 1 lub więcej i callback zwrócie `true` (odrzucając w ten sposób uaktualnienie).
<% note do %>
### Zawołania metod vs manipulacja danych po stronie klienta
Używamy metody `post` aby utworzyć nowe posty, natomiast do edycji i usuwania ich wołamy `update` i `remove` bezpośrednio po stronie klienta i ograniczamy dostęp przez `allow` i `deny`.
Kiedy odpowiednie jest używanie każdego z nich?
Gdy sytuacja jest relatywnie jasna i można konkretnie wyrazić zasady za pomocą `allow` i `deny`, zwykle prościej jest wykonywać je po stronie klienta.
Bezpośrednia manipulacja bazy danych ze strony klienta tworzy wyobrażenie natychmiastowej zmiany i służy lepszemu postrzeganiu aplikacji przez użytkownika, tak długo, jak pamiętasz o prawidłowym obchodzeniu się z przypadkami błędów (tj. wtedy gdy serwer zwraca informację, że zmiana się nie powiodła).
Jednakże, gdy masz potrzebę wykonania czynności, które powinny być poza kontrolą użytkownika (tak jak ustawianie znacznika czasu posta czy przypisywanie go do konkretnego użytkownika), lepiej jest użyć Metodę po stronie serwera.
Metody serwerowe są także odpowiednie w kliku innych przypadkach:
- Gdy musisz znać lub zwracać wartości za pomocą callbacka raczej niż czekać na synchronizację i propagację danych przez serwer.
- Dla funkcji obciążających bazę danych, dla których jest zbyt kosztowne przesyłanie całej kolekcji do klienta.
- Do sumowania lub zbierania danych (np. zliczanie, obliczanie średniej lub sumy).
<% end %>