-
Notifications
You must be signed in to change notification settings - Fork 32
/
08-editing-posts.md.erb
262 lines (199 loc) · 10.5 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: Editer des Posts
slug: editing-posts
date: 0008/01/01
number: 8
points: 5
photoUrl: http://www.flickr.com/photos/ikewinski/9473337133/
photoAuthor: Mike Lewinski
contents: Ajouter un formulaire pour modifier ses posts.|Mettre en place les permissions.|Empêcher certaines propriétés d'être modifiées.
paragraphs: 29
---
Maintenant que nous pouvons créer des posts, la prochaine étape est de pouvoir les modifier et les supprimer. Puisque l'interface est simple à mettre en œuvre, c'est le moment idéal de voir comment Meteor gère les permissions des utilisateurs.
Voyons d'abord le *router*. Nous lui ajoutons une route afin d'accéder à la page de modification des posts et lui donnons des données :
~~~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" %>
### La template de modification des posts
Concentrons nous maintenant sur la template. Notre template `postEdit` reste très classique :
~~~html
<template name="postEdit">
<form class="main form page">
<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="Votre 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="Nommez votre post" class="form-control"/>
</div>
</div>
<input type="submit" value="Submit" class="btn btn-primary submit"/>
<hr/>
<a class="btn btn-danger delete" href="#">Supprimer le post</a>
</form>
</template>
~~~
<%= caption "client/templates/posts/post_edit.html" %>
Et voilà le fichier `post_edit.js` qui va avec :
~~~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) {
// affiche l'erreur à l'utilisateur
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" %>
Normalement la majeure partie de ce code devrait vous être familier à présent.
Deux événements sont présents dans notre template : l'un pour l'événement `submit` du formulaire et l'autre pour l'événement `click` du lien de suppression du post.
L'événement de suppression est très simple : on empêche l'activation des événements par défaut et on demande une confirmation à l'utilisateur. Enfin si on l'obtient, on récupère l'ID du post actuel depuis les informations de la template, on supprime le post et on redirige l'utilisateur sur l'accueil.
L'événement de mise à jour du post est un peu plus long, mais pas plus compliqué. Après avoir, une fois de plus, empêché l'activation des événements classiques (lors de la soumission du formulaire) et récupéré l'ID du post concerné, on récupère les valeurs du formulaire depuis la page et on les sauvegarde dans l'objet `postProperties`.
Nous passons alors cet objet à la méthode `Collection.update()` de Meteor avec l'opérateur [`$set`](http://docs.mongodb.org/manual/reference/operator/update/set/) (qui remplace un ensemble de champs) et utilisons un callback pour afficher une erreur si la mise à jour est un échec ou renvoie l'utilisateur sur le post concerné si la mise à jour est un succès.
### Ajouter des liens
Nous devons bien évidemment rajouter un lien pour que les utilisateurs puissent modifier leurs posts :
~~~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">Discuter</a>
</div>
</template>
~~~
<%= caption "client/templates/posts/post_item.html" %>
<%= highlight "5~8" %>
De plus, nous ne devons pas afficher ce lien à n'importe qui. C'est pour cela que nous rajoutons un 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/templates/posts/post_item.js" %>
<%= highlight "2~4" %>
<%= screenshot "8-1", "Formulaire d'édition" %>
<%= commit "8-1", "Ajout des formulaire d'édition." %>
Notre formulaire pour modifier les posts parait correct, pourtant vous ne pourrez pas les modifier tout de suite. Que se passe-t-il ?
### Mettre en place les permissions
Depuis que nous avons supprimé le paquet `insecure`, toutes les requêtes de modifications provenant du client sont catégoriquement refusées.
Pour régler cela, nous devons fixer des permissions. Pour commencer, créez un nouveau fichier `permissions.js` dans le dossier `lib`. Nous serons ainsi sûr que nos permissions seront chargées en premier (et disponible dans les deux environnements) :
~~~js
// check that the userId specified owns the documents
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
~~~
<%= caption "lib/permissions.js" %>
Dans le chapitre [Créer des Posts](/chapter/creating-posts), nous n'avions pas utilisé la méthode `allow()` car nous insérions les nouveaux posts via des méthodes côté serveur (qui passent outre `allow()`).
Mais maintenant que nous éditons et supprimons des posts depuis le client, retournons dans le fichier `posts.js` et rajoutons la fameuse méthode `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", "Ajout de permissions basiques pour vérifier le propriétaire d'un posts." %>
### Limiter les éditions
Ce n'est pas parce que vous pouvez éditer vos propres posts que vous devez être capable d'éditer *toutes* les propriétés. Par exemple, nous ne voulons pas que l'utilisateur crée un post et l'assigne à quelqu'un d'autre.
Donc nous allons utiliser le callback `deny()` pour permettre à l'utilisateur d'éditer *seulement certains* champs :
~~~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) {
// may only edit the following two fields:
return (_.without(fieldNames, 'url', 'title').length > 0);
}
});
//...
~~~
<%= caption "lib/collections/posts.js" %>
<%= highlight "8~13" %>
<%= commit "8-3", "Accepter le changement de seulement certain champs." %>
Nous transmettons le tableau `fieldNames` qui contient la liste des champs modifiés et en utilisant la fonction `without()` d'[Underscore](http://underscorejs.org/) nous obtenons un tableau qui contient les champs qui *ne sont pas* `url` ou `title`.
Si tout se passe bien, le tableau sera vide et sa taille devra être de 0. Si quelqu'un essaie de jouer un peu avec le code, la taille du tableau vaudra 1 ou plus, et le callback retournera `true` (ce qui empêchera la mise à jour).
Vous aurez peut-être remarqué que nous ne vérifions nulle part dans notre code de modification des posts la présence de liens dupliqués. Cela veut dire qu'un utilisateur pourrait soumettre un lien et l'éditer afin de changer l'URL pour passer outre la vérification. La solution à ce problème serait d'utiliser une méthode de Meteor (`Meteor.methods()`) pour modifier les posts, mais nous avons voulu vous montrer cela pour le principe et vous exercer.
<% note do %>
### Les appels de méthode vs la manipulation de données *côté client*
Pour créer des posts, nous avons utilisé une méthode Meteor `postInsert`, par contre pour les modifier et les supprimer nous appelons `update` et `remove` directement depuis le client en utilisant `allow` et `deny` pour sécuriser les transactions de données.
Quand utiliser l'une ou l'autre méthode ?
Lorsque les choses sont relativement simple et que vous pouvez rapidement adapter votre sécurité avec `allow` et `deny`, il est plus simple de faire les opérations directement depuis le client.
Par contre, à partir du moment où vous devez faire des choses qui ne doivent pas être contrôlé par l'utilisateur (comme dater un nouveau post ou l'assigner au bon utilisateur), vous devriez utiliser une méthode Meteor `Meteor.methods`.
Les méthodes Meteor sont aussi plus adaptés dans certains cas :
- Quand vous devez connaître ou renvoyer des valeurs via un callback plutôt que d'attendre que la réactivité et la synchronisation prennent effet.
- Pour les fonctions opérant de grosses manipulations sur la base de données qui seraient trop lourdes à transmettre entre le client et le serveur.
- Pour des calculs sur la base de données (exemple : count, average, sum).
[Jetez un œil à notre blog](https://www.discovermeteor.com/blog/meteor-methods-client-side-operations/) pour une exploration plus en détail de ce sujet.
<% end %>