-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy path10-comments.md.erb
450 lines (357 loc) · 15 KB
/
10-comments.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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
---
title: Commenti
slug: comments
complete: 100
date: 0010/01/01
number: 10
contents: Visualizzare commenti esistenti.|Aggiungere un modulo di invio dei commenti.|Imparare come caricare i commenti del post corrente.|Aggiungere il totale dei commenti a un post.
paragraphs: 34
---
Lo scopo di un sito di social news è creare una comunità di utenti, e sarebbe davvero impossibile farlo se questi utenti non avessero la possibilità di parlare tra di loro. Dunque, in questo capitolo, aggiungiamo i commenti!
Iniziamo col creare una nuova collezione dove salvare i commenti, e aggiungendo un pò di dati di esempio a questa collezione.
~~~js
Comments = new Meteor.Collection('comments');
~~~
<%= caption "collections/comments.js" %>
~~~js
// Fixture data
if (Posts.find().count() === 0) {
var now = new Date().getTime();
// create two users
var tomId = Meteor.users.insert({
profile: { name: 'Tom Coleman' }
});
var tom = Meteor.users.findOne(tomId);
var sachaId = Meteor.users.insert({
profile: { name: 'Sacha Greif' }
});
var sacha = Meteor.users.findOne(sachaId);
var telescopeId = Posts.insert({
title: 'Introducing Telescope',
userId: sacha._id,
author: sacha.profile.name,
url: 'http://sachagreif.com/introducing-telescope/',
submitted: now - 7 * 3600 * 1000
});
Comments.insert({
postId: telescopeId,
userId: tom._id,
author: tom.profile.name,
submitted: now - 5 * 3600 * 1000,
body: 'Interesting project Sacha, can I get involved?'
});
Comments.insert({
postId: telescopeId,
userId: sacha._id,
author: sacha.profile.name,
submitted: now - 3 * 3600 * 1000,
body: 'You sure can Tom!'
});
Posts.insert({
title: 'Meteor',
userId: tom._id,
author: tom.profile.name,
url: 'http://meteor.com',
submitted: now - 10 * 3600 * 1000
});
Posts.insert({
title: 'The Meteor Book',
userId: tom._id,
author: tom.profile.name,
url: 'http://themeteorbook.com',
submitted: now - 12 * 3600 * 1000
});
}
~~~
<%= caption "server/fixtures.js" %>
Non dimentichiamoci di pubblicare e sottoscrivere la collezione che abbiamo appena creato:
~~~js
Meteor.publish('posts', function() {
return Posts.find();
});
Meteor.publish('comments', function() {
return Comments.find();
});
~~~
<%= caption "server/publications.js" %>
<%= highlight "5,6,7" %>
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() {
return [Meteor.subscribe('posts'), Meteor.subscribe('comments')];
}
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "4~6" %>
<%= commit "10-1", "Added comments collection, pub/sub and fixtures." %>
È bene osservare che, perché il codice sopra sia eseguito, devi prima lanciare `meteor reset` per svuotare il tuo database. Dopo aver eseguito il reset, non dimenticare di creare un nuovo utente ed effettuare di nuovo il login!
Prima di tutto, abbiamo creato un paio di (finti) utenti, li abbiamo inseriti nel database e abbiamo usato i loro `ids` per selezionarli dal database stesso subito dopo. Dopodichè, abbiamo aggiunto dei commenti al primo post, uno per ciascuno degli utenti, collegando il commento al post (con `postId`) e all'utente (con `userId`). Abbiamo anche aggiunto una data di inserimento e un testo per ciascun commento, insieme a un valore per `author`, un campo non normalizzato.
Inoltre, abbiamo modificato in nostro router per attendere sulle collezioni comments e posts.
### Visualizzare i commenti
È già un risultato l'aver inserito i commenti nel database, ma dobbiamo anche mostrarli nella pagina di discussione. La procedura dovrebbe ormai esserti familiare, e ti sei già fatto un'idea di quali sono i passi necessari:
~~~html
<template name="postPage">
{{> postItem}}
<ul class="comments">
{{#each comments}}
{{> comment}}
{{/each}}
</ul>
</template>
~~~
<%= caption "client/views/posts/post_page.html" %>
<%= highlight "3~7" %>
~~~js
Template.postPage.helpers({
comments: function() {
return Comments.find({postId: this._id});
}
});
~~~
<%= caption "client/views/posts/post_page.js" %>
<%= highlight "2~4" %>
Inseriamo il blocco `{{#each comments}}` dentro al template post, così `this` si riferisce a un post dentro all'helper `comments`. Per selezionare quali commenti mostrare, filtriamo la collezione selezionando quelli collegati allo specifico post tramite l'attributo `postId`.
Considerato quanto abbiamo già imparato riguardo agli helpers e ad Spacebars, visualizzare un commento è abbastanza semplice. Creiamo una nuova cartella `comments` dentro a `views` per salvare il template del dettaglio commenti:
~~~html
<template name="comment">
<li>
<h4>
<span class="author">{{author}}</span>
<span class="date">on {{submittedText}}</span>
</h4>
<p>{{body}}</p>
</li>
</template>
~~~
<%= caption "client/views/comments/comment.html" %>
Creiamo un template helper per formattare la data (campo `submitted`) in modo che sia leggibile (a meno che tu non sia una di quelle persone che riesce a leggere con naturalezza timestamp UNIX e codici colore esadecimali?)
~~~js
Template.comment.helpers({
submittedText: function() {
return new Date(this.submitted).toString();
}
});
~~~
<%= caption "client/views/comments/comment.js" %>
Mostriamo anche il numero totale di commenti per ciascun post:
~~~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}},
<a href="{{pathFor 'postPage'}}">{{commentsCount}} comments</a>
{{#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 "6,7" %>
E, per fare questo, aggiungiamo un helper chiamato `commentsCount` al template manager `postItem`:
~~~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;
},
commentsCount: function() {
return Comments.find({postId: this._id}).count();
}
});
~~~
<%= caption "client/views/posts/post_item.js" %>
<%= highlight "9,10,11" %>
<%= commit "10-2", "Display comments on `postPage`." %>
Dovresti essere ora in grado di visualizzare i commenti di esempio, qualcosa del genere:
<%= screenshot "10-1", "Visualizzazione dei commenti" %>
### Inserire commenti
Ora inseriamo la possibilità per i nostri utenti di creare nuovi commenti. La procedura che seguiremo sarà abbastanza simile a quella che abbiamo usato per la funzionalità di inserimento di nuovi post.
Incominciamo con il creare il modulo di inserimento alla fine di ciascun post:
~~~html
<template name="postPage">
{{> postItem}}
<ul class="comments">
{{#each comments}}
{{> comment}}
{{/each}}
</ul>
{{#if currentUser}}
{{> commentSubmit}}
{{else}}
<p>Please log in to leave a comment.</p>
{{/if}}
</template>
~~~
<%= caption "client/views/posts/post_page.html" %>
<%= highlight "11~15" %>
E poi create il template del modulo di inserimento:
~~~html
<template name="commentSubmit">
<form name="comment" class="comment-form">
<div class="control-group">
<div class="controls">
<label for="body">Comment on this post</label>
<textarea name="body"></textarea>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn">Add Comment</button>
</div>
</div>
</form>
</template>
~~~
<%= caption "client/views/comments/comment_submit.html" %>
<%= screenshot "10-2", "Il modulo di inserimento di commenti" %>
Per inserire il commento, chiameremo un metodo `comment` nel manager `commentSubmit` che funziona in maniera simile a quello nel manager `postSubmit`:
~~~js
Template.commentSubmit.events({
'submit form': function(e, template) {
e.preventDefault();
var $body = $(e.target).find('[name=body]');
var comment = {
body: $body.val(),
postId: template.data._id
};
Meteor.call('comment', comment, function(error, commentId) {
if (error){
throwError(error.reason);
} else {
$body.val('');
}
});
}
});
~~~
<%= caption "client/views/comments/comment_submit.js" %>
Nello stesso modo in cui abbiamo precedentemente creato un _Method_ meteor lato server chiamato `post`, ora ne creiamo uno chiamato `comment` per creare i commenti; controlliamo che tutto sia in regola, e inseriamo il nuovo commento nella collezione _comments_.
~~~js
Comments = new Meteor.Collection('comments');
Meteor.methods({
comment: function(commentAttributes) {
var user = Meteor.user();
var post = Posts.findOne(commentAttributes.postId);
// ensure the user is logged in
if (!user)
throw new Meteor.Error(401, "You need to login to make comments");
if (!commentAttributes.body)
throw new Meteor.Error(422, 'Please write some content');
if (!post)
throw new Meteor.Error(422, 'You must comment on a post');
comment = _.extend(_.pick(commentAttributes, 'postId', 'body'), {
userId: user._id,
author: user.username,
submitted: new Date().getTime()
});
return Comments.insert(comment);
}
});
~~~
<%= caption "collections/comments.js" %>
<%= highlight "3~25" %>
<%= commit "10-3", "Created a form to submit comments." %>
Non stiamo facendo nessun controllo particolare, solo verificando che l'utente sia autorizzato, che il commento non sia vuoto, e che sia collegato a un post.
### Ottimizzare la sottoscrizione Comments
Allo stato attuale, Meteor manda tutti i commenti di tutti i post a tutti gli utenti collegati; ci sembra uno spreco, perché alla fin fine, ciascun utente usa solo una piccola parte di tutti questi dati (i commenti del post che sta visualizzando). Ottimizziamo la pubblicazione e la sottoscrizione così da controllare meglio quali commenti sono inviati.
A pensarci bene, l'applicazione ha bisogno di sottoscrivere la pubblicazione `comments` solo quando un utente accede alla pagina che visualizza un post, e ha bisogno di caricare solo i commenti relativi a quel particolare post.
Il primo passo sarà cambiare il modo in cui effettuiamo la sottoscrizione alla collezione comments. Finora, l'abbiamo effettuata a livello di *router*, il che significa che carichiamo tutti i dati quando il router viene inizializzato.
Ma ora vogliamo che la nostra sottoscrizione dipenda dal particolare indirizzo della pagina che stiamo visualizzando, e tale indirizzo può ovviamente cambiare. Dunque dobbiamo spostare il codice che effettua la sottoscrizione dal livello di _router_ a quello di _route_.
Questo ha un'altra conseguenza: invece di caricare tutti i dati quando inizializziamo l'applicazione per la prima volta, questi verranno caricati quando la particolare *route* viene chiamata; questo significa che ora l'applicazione sarà più lenta in fase di navigazione (perché i dati vengono caricati man mano che servono), ma è inevitabile, a meno che non si vogliano precaricare **tutti** i dati all'avvio.
Prima di tutto, eviteremo di precaricare tutti i commenti all'interno del blocco `configure` rimuovendo `Meteor.subscribe('comments')`:
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() {
return Meteor.subscribe('posts');
}
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "5" %>
E aggiungeremo una nuova funzione `waitOn` a livello di route:
~~~js
Router.map(function() {
//...
this.route('postPage', {
path: '/posts/:_id',
waitOn: function() {
return Meteor.subscribe('comments', this.params._id);
},
data: function() { return Posts.findOne(this.params._id); }
});
//...
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "7~9" %>
Noterete che stiamo passando `this.params._id` come parametro della pubblicazione. Usiamo questo dato per far sì che i dati scaricati siano solo quelli riferiti al post corrente:
~~~js
Meteor.publish('posts', function() {
return Posts.find();
});
Meteor.publish('comments', function(postId) {
return Comments.find({postId: postId});
});
~~~
<%= caption "server/publications.js" %>
<%= highlight "5~7" %>
<%= commit "10-4", "Made a simple publication/subscription for comments." %>
Osserviamo ora un ultimo problema: quando torniamo alla pagina principale, l'applicazione dice che il nostro post ha 0 commenti:
<%= screenshot "10-3", "Tutti i commenti sono spariti!" %>
### Contare i commenti
La ragione di questo comportamento è semplice: noi carichiamo i commenti solo quando entriamo nella pagina di visualizzazione del post, dunque, quando viene eseguita l'istruzione `Comments.find({postId: this._id})` nell'helper `commentsCount` del manager `post_item`, Meteor non riesce a trovare il dato da mostrare.
La maniera migliore per risolvere il problema è _denormalizzare_, salvando il numero dei commenti dentro al post stesso (se non sei del tutto sicuro cosa significhi, non preoccuparti: questo argomento viene spiegato nel prossimo approfondimento). Anche se, come vedremo, il nostro codice diventerà un po' più complesso, questo aumento di complessità sarà giustificato dall'aumento delle prestazioni dell'applicazione, per non dover pubblicare _tutti_ i commenti già nella pagina che mostra i post.
Dobbiamo dunque aggiungere una proprietà `commentsCount` alla struttura dati `post`. Per cominciare, aggiorniamo i nostri dati di esempio (e ricordiamoci di lanciare `meteor reset` - non dimenticare anche di ricreare il tuo utente, dopo!):
~~~js
var telescopeId = Posts.insert({
title: 'Introducing Telescope',
..
commentsCount: 2
});
Posts.insert({
title: 'Meteor',
...
commentsCount: 0
});
Posts.insert({
title: 'The Meteor Book',
...
commentsCount: 0
});
~~~
<%= caption "server/fixtures.js" %>
Dopodichè, ogni post deve partire da 0 commenti:
~~~js
// pick out the whitelisted keys
var post = _.extend(_.pick(postAttributes, 'url', 'title', 'message'), {
userId: user._id,
author: user.username,
submitted: new Date().getTime(),
commentsCount: 0
});
var postId = Posts.insert(post);
~~~
<%= caption "collections/posts.js" %>
Ed incrementiamo il valore di `commentsCount` quando un utente aggiunge un nuovo commento utilizzando l'operatore `$inc` di Mongo (che serve a incrementare il valore di un campo numerico):
~~~js
// update the post with the number of comments
Posts.update(comment.postId, {$inc: {commentsCount: 1}});
return Comments.insert(comment);
~~~
<%= caption "collections/comments.js "%>
Possiamo dunque rimuovere l'helper commentsCount dal file client/views/posts/post_item.js, dal momento che il campo è disponibile nel post stesso.
<%= commit "10-5", "Denormalized the number of comments into the post." %>
Ora che gli utenti possono parlare l'un l'altro, sarebbe un peccato se la pagina non fosse aggiornata con i nuovi commenti via via che questi vengono inseriti; il prossimo capitolo ci spiega come utilizzare le notifiche per questo scopo.