-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy path08-editing-posts.md.erb
265 lines (201 loc) · 9.55 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: Editando Artigos
slug: editing-posts
date: 0008/01/01
number: 8
contents: Adicione um formulário par editar seus artigos.|Criar edição de permissões.| Restringir quais propriedades podem ser editadas.
paragraphs: 29
---
Agora que já conseguimos criar artigos, o próximo passo é conseguir edita-los e remove-los. Dado que o código de UI desta funcionalidade é bastante simples, esta é uma boa altura para falar sobre como Meteor gere permissões de utilizadores.
Vamos primeiro configurar o nosso roteador. Primeiro vamos adicionar uma rota para aceder à página de edição do artigo e vamos definir o seu contexto de dados:
~~~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" %>
### O Template De Edição de Artigos
Podemo-nos focar agora no template. O nosso template `postEdit` vai ser um formulário standard:
~~~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" %>
E aqui está o gestor `post_edit.js` que acompanha o template:
~~~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" %>
Por esta altura a maioria do código deve parecer familiar. Primeiro, temos o nosso ajudante de template que carrega o artigo atual e passa-o ao template.
Depois, temos duas callbacks de eventos do template: uma para o evento `submit` (submeter) do formulário, e outra para o `click` no link de apagar.
A callback do apagar é muito simples: suprimir o evento de click por omissão, e pedir ao utilizador para confirmar. Caso isso aconteça, obtém o ID do artigo atual do contexto de dados do Template, apaga o artigo, e finalmente redireciona o utilizador para a página inicial.
A callback de edição é ligeiramente maior, mas não muito mais complicada. Depois de suprimir o evento por omissão e de ir buscar o artigo atual, lemos os novos valores dos campos do formulário da página e guardamo-los num objecto `postProperties` (propriedades do artigo).
Depois passamos este objecto para o Método de Meteor `Collection.update()`, e utilizamos a callback que ou mostra um erro se a atualização falhou, ou envia o utilizador de volta para a página do artigo caso a atualização tenha sido feita com sucesso.
### Adicionando Links
Devemos ainda adicionar links de edição aos nossos artigos para que os utilizadores tenham uma forma de aceder à página de edição de artigos:
~~~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" %>
Naturalmente, não queremos mostrar links de edição para o formulário de outra pessoa. É aqui que o ajudante `ownPost` entra:
~~~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", "Formulário de edição de artigo." %>
<%= commit "8-1", "Formulário de edição de artigos adicionado." %>
O nosso formulário de edição de artigos está com bom aspeto, mas ainda não é possível editar nada. O que é que se passa?
### Definindo Permissões
Dado que anteriormente removemos o pacote `insecure`, todas as modificações feitas do lado do cliente estão a ser recusadas.
Para corrigir isto, vamos definir alguma regras de permissões. Primeiro, crie um novo ficheiro `permissions.js` dentro de `lib`. Isto faz com que a nossa lógica de permissões seja carregada primeiro (e que esteja disponível em ambos os ambientes):
~~~js
// check that the userId specified owns the documents
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
~~~
<%= caption "lib/permissions.js" %>
No capitulo [Criando Artigos](/chapter/creating-posts), nós vimo-nos livres dos Métodos `allow()` porque estávamos apenas a inserir novos artigos através de um Método de servidor (que de qualquer forma, ignora o `allow()`).
Mas agora que estamos a atualizar e apagar artigos a partir do cliente, vamos voltar ao `posts.js` e adicionar este bloco `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", "Permissão básica que verifica o dono do artigo adicionada." %>
### Limitando Edições
Só porque pode editar os seus próprios artigos, não quer dizer que deva poder editar *qualquer* propriedade. Por exemplo, nós não queremos que os utilizadores sejam capazes de criar um artigo e depois associá-lo a outro utilizador.
A callback `deny()` do Meteor é utilizada para garantir que apenas campos específicos podem ser atualizados:
~~~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", "Permitir que apenas certos campos do artigo possam ser alterados." %>
Estamos a pegar na lista `fieldNames` que contém os campos a serem modificados e usamos o Método `without()` do [Underscore](http://underscorejs.org/) para devolver uma sub-lista contendo apenas os campos que *não* são `url` ou `title`.
Se tudo está normal, essa lista deve estar vazia e o seu tamanho deve ser 0. Se alguem está a tentar alguma coisa manhosa, o tamanho dessa lista será 1 ou mais, e a callback vai devolver `true` (impedindo assim a atualização).
<% note do %>
### Chamadas a Métodos vs Manipulação de Dados No Lado do Cliente
Para criar artigos, estamos a utilizar o Método `post`, enquanto que para os editar e apagar, estamos a chamar `update` e `remove` diretamente no cliente e limitamos o acesso usando `allow` e `deny`.
Quando é adequado usar um e não o outro?
Quando as coisas são relativamente simples e as regras podem ser exprimidas por `allow` e `deny`, é normalmente mais simples fazer as coisas diretamente no cliente.
Manipular a base de dados diretamente a partir do cliente cria uma percepção de imediato, e pode fazer com que a experiência de utilização seja melhor desde que os casos de erro sejam tratados adequadamente (isto é, quando o servidor responde dizendo que a operação afinal falhou).
No entanto, a partir do momento em que é necessário fazer coisas que devem estar fora do controlo do utilizador (tais como dar timestamps a novos artigos ou associar um artigo ao utilizador correto), é provavelmente melhor usar um Método.
Chamadas a Métodos também são mais adequadas em alguns outros casos:
- Quando é preciso saber ou é preciso devolver valores através de uma callback em vez de esperar que a reatividade e sincronização propaguem as alterações.
- Para operações de base de dados pesadas que implicariam enviar uma coleção grande para o cliente.
- Para sumarizar ou agregar dados (por exemplo, contar, médias, somas).
<% end %>