-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy path10-comments.md.erb
436 lines (345 loc) · 14.7 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
---
title: Comentários
slug: comments
complete: 100
date: 0010/01/01
number: 10
contents: Apresente comentários existentes.|Adicione um formulário para comentários.|Aprenda como carregar apenas os comentários de determinado artigo.|Adicione uma propriedade de contador de comentários nos artigos.
paragraphs: 34
---
A meta de um site social de notícias é criar uma comunidade de usuários, e será difícil fazê-la sem providenciar uma forma para as pessoas conversarem entre si. Então neste capítulo, vamos adicionar comentários!
Nós começaremos criando uma nova coleção para armazenar comentários, e adicionando alguma informação de exemplos básica na coleção.
~~~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" %>
Não vamos nos esquecer de publicar e fazer assinatura à nossa nova coleção:
~~~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." %>
Note que para ativar este código de exemplos, você precisará usar `meteor reset` para limpar o banco de dados. Após limpar, não esqueça de criar uma nova conta de usuário para logar de novo!
Primeiro, nós criamos alguns usuários (completamente falsos), inserindo-os no banco de dados e usando seus `id`s para selecioná-los no banco de dados mais tarde. Então nós adicionamos um comentário para cada usuário no primeiro artigo, ligando o comentário ao artigo (com `postId`), e ao usuário (com `userId`). Nós também adicionamos uma data de envio e um corpo a cada comentário, junto com `author`, um campo desnormalizado.
Também, nós melhoramos nosso roteador para esperar tanto os comentários quanto os artigos.
### Mostrando comentários
Está tudo certo em por comentários no banco de dados, mas nós também precisamos mostrá-los na page de discussão. Felizmente este processo deve ser familiar a você agora, e você tem uma idéia dos passos envolvidos:
~~~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" %>
Nós pomos o bloco `{{#each comments}}` dentro do template do artigo, então `this` é um artigo dentro do ajudante `comments`. Para encontrar comentários relevantes, nós checamos aqueles que estão ligados ao post através do atributo `postId`.
Dado que nós aprendemos sobre ajudantes e handlebars, renderizar um comentário é bem linear. Nós criaremos um novo diretório `comments` dentro de `views` para armazenar toda nossa informação de comentário:
~~~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" %>
Vamos configurar um simples ajudante de template para formatar nossa informação `submitted` para um formato legível por humanos (a menos que você seja uma daquelas pessoas que conseguem entender códigos de UNIX timestamps e cores hexadecimais fluentemente?)
~~~js
Template.comment.helpers({
submittedText: function() {
return new Date(this.submitted).toString();
}
});
~~~
<%= caption "client/views/comments/comment.js" %>
Então, nós mostraremos o número de comentários de cada artigo:
~~~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 adicionaremos o ajudante `commentsCount` ao nosso administrador `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`." %>
Nós devemos ser capazes de mostrar nossos comentários de exemplo e ver algo assim:
<%= screenshot "10-1", "Displaying comments" %>
### Enviando Comentários
Vamos adicionar uma forma dos nossos usuários criarem comentários. O processo que seguiremos será bem similar ao como nós criamos usuários para criar novos artigos.
Nós começaremos por adicionar uma caixa de envio no fim de cada artigo:
~~~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 então criar um template de formulário de comentário:
~~~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", "The comment submit form" %>
Para enviar nossos comentários, nós chamamos um Método `comment` no administrador `commentSubmit` que opera de uma forma similar ao administrador `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" %>
Assim como nós previamente configuramos um Método `post` do lado do servidor, nós configuraremos um Método Meteor `comment` para criar nossos comentários, cheque que tudo é legítimo, e finalmente insira o novo comentário na coleção de comentários.
~~~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." %>
Isto não está fazendo nada requintado, apenas checando se o usuário está logado, que o comentário tem um corpo, e que esteja ligado a um artigo.
### Controlando a Assinatura dos Comentários
Como as coisas estão, nós estamos publicando todos comentários de todos os artigos para todos os clientes conectados. Isso é um desperdício. Já que, nós estamos apenas utilizando um pequeno subconjunto da informação a qualquer momento que for. Vamos melhorar nossa publicação e assinatura para controlar exatamente quais comentários são publicados.
Se nós pensarmos sobre isso, o único momento que nós precisamos fazer a assinatura para a publicação dos nossos `comments` é quando o usuário acessa a página individual do artigo, e nós precisamos apenas ler um subconjunto dos comentários relacionados a este artigo em particular.
O primeiro passo será mudar a forma como nós fazemos a assinatura para os comentários. Até agora, nós temos feito a assinatura no nível do *roteador*, o que significa que nós lemos toda nossa informação quando o roteador é inicializado.
Mas nós agora queremos que a nossa assinatura dependa de um parâmetro path, e esse parâmetro pode obviamente mudar a qualquer momento. Então nós precisaremos mover nosso código de assinatura do nível do *roteador* para o nível da *rota*.
Isto tem outra conseqüência: ao invés de ler nossa informação quando nós inicializamos nosso aplicativo, nós agora a leremos toda vez que nós alcançamos nossa *rota*. Isto significa que você agora terá momentos de loading enquanto navega dentro do aplicativo, é um ponto vagamente negativo a não ser que você queira ler de vez o seu conjunto de informação todo para sempre.
Assim que a nossa nova função `waitOn` ao nível da rota se parece:
~~~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" %>
Você perceberá que nós estamos passando `this.params._id` como um argumento à assinatura. Então usamos essa nova informação para garantir que nós restrinjamos nosso conjunto de informação aos comentário pertencentes ao artigo atual:
~~~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." %>
Há apenas um problema: quando nós retornamos à página inicial, ela diz que todos nossos artigos tem 0 comentários:
<%= screenshot "10-3", "Our comments are gone!" %>
### Contando Comentários
A razão para isto ficará logo clara: nós apenas temos no máximo *um* dos nossos comentários do artigo lido, então quando nós chamamos `Comments.find({postId: this._id})` no ajudante `commentsCount` no administrador `post_item`, Meteor não consegue encontrar a informação necessária do lado do cliente para nos prover um resultado.
A melhor maneira para lidar com isto é *desnormalizar* o número de comentários do artigo (se você não está certo do que isso significa não se preocupe, a próxima barra lateral cobrirá isso!). Como veremos, há uma pequena adição de complexidade no nosso código, o benefício de performance que nós ganhamos de não ter que publicar _todos_ comentários para mostrar a lista de artigos vale a pena.
Nós conseguiremos isso ao adicionar uma propriedade `commentsCount` à informação de estrutura do `post.` Para começar, nós atualizamos nossos exemplos de artigo. (e `meteor reset` para relê-los -- não esqueça de recriar sua conta de usuário depois):
~~~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" %>
Então, nós garantimos que todos novos artigos comecem com 0 comentário:
~~~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" %>
E então nós atualizamos o `commentsCount` relevante quando nós fazemos um novo comentário usando o operador Mongo `$inc` (o qual incrementa o campo numérico por um):
~~~js
// update the post with the number of comments
Posts.update(comment.postId, {$inc: {commentsCount: 1}});
return Comments.insert(comment);
~~~
<%= caption "collections/comments.js "%>
Finalmente, nós podemos apenas remover o ajudante `commentsCount` do `client/views/posts/post_item.js`, já que o campo está agora diretamente disponível no nosso artigo.
<%= commit "10-5", "Denormalized the number of comments into the post." %>
Agora que nossos usuários podem conversar entre si, seria uma pena se eles não soubessem dos novos comentários. E sabe mais, o próximo capítulo mostrará a você como implementar notificações para previnir exatamente isto!