-
Notifications
You must be signed in to change notification settings - Fork 30
/
11-notifications.md.erb
281 lines (221 loc) · 10.3 KB
/
11-notifications.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
---
title: Notificaciones
slug: notifications
date: 0011/01/01
number: 11
points: 10
photoUrl: http://www.flickr.com/photos/ikewinski/8719868039/
photoAuthor: Mike Lewinski
contents: Añadiremos una colección para notificar acciones de otros usuarios.|Aprenderemos cómo compartir sólo notificaciones relevantes con un usuario.|Aprenderemos cómo cargar solo los comentarios del post actual.
paragraphs: 25
---
Ahora que los usuarios pueden comentar los posts de otros usuarios, sería bueno hacerles saber que alguien ha comenzado una conversación.
Para ello, notificaremos al autor, de que ha habido un comentario en su post, y le proporcionaremos un enlace para poder comentar.
En este tipo de funcionalidad es en la que Meteor brilla. Como por defecto, Meteor trabaja en tiempo real, vamos a poder mostrar notificaciones instantáneamente. No necesitamos esperar a que el usuario actualice la página, podemos mostrar nuevas notificaciones sin tener que escribir ningún código especial.
### Creando notificaciones
Crearemos una notificación cuando alguien comente uno de nuestros posts. En el futuro, las notificaciones podrían extenderse para cubrir muchos otros escenarios, pero, por ahora será suficiente con esto para mantener a los usuarios informados sobre lo que está pasando.
Vamos a crear la colección `Notifications` y la función `createCommentNotification` que insertará una notificación para cada comentario que se haga en uno de nuestros posts.
Puesto que estamos actualizando las notificaciones desde el lado del cliente, necesitamos asegurarnos que nuestra llamada `allow` es a prueba de balas. Por lo que deberemos comprobar que:
- El usuario que hace la llamada `update` es el dueño de la notificación modificada.
- El usuario solo está intentando modificar un solo campo.
- El campo a modificar es la propiedad `read` de nuestra notificación.
~~~js
Notifications = new Mongo.Collection('notifications');
Notifications.allow({
update: function(userId, doc, fieldNames) {
return ownsDocument(userId, doc) &&
fieldNames.length === 1 && fieldNames[0] === 'read';
}
});
createCommentNotification = function(comment) {
var post = Posts.findOne(comment.postId);
if (comment.userId !== post.userId) {
Notifications.insert({
userId: post.userId,
postId: post._id,
commentId: comment._id,
commenterName: comment.author,
read: false
});
}
};
~~~
<%= caption "lib/collections/notifications.js" %>
Al igual que con los posts o los comentarios, esta colección estará compartida por clientes y servidor. Como tendremos que actualizar las notificaciones cuando un usuario las haya visto, permitimos hacer `update` siempre que se trate de los datos del propio usuario.
También creamos una función que mira qué post está comentando el usuario, averigua qué usuario debe ser notificado e inserta una nueva notificación.
Ya tenemos un método en el servidor para crear comentarios, por lo que podemos ampliarlo para que llame a nuestra nueva función. Para guardar el `_id` del nuevo comentario en una variable, cambiamos `return Comments.insert(comment);`, por `comment._id = Comments.insert(comment)` y llamamos a la función `createCommentNotification`:
~~~js
Comments = new Mongo.Collection('comments');
Meteor.methods({
commentInsert: function(commentAttributes) {
//...
comment = _.extend(commentAttributes, {
userId: user._id,
author: user.username,
submitted: new Date()
});
// update the post with the number of comments
Posts.update(comment.postId, {$inc: {commentsCount: 1}});
// create the comment, save the id
comment._id = Comments.insert(comment);
// now create a notification, informing the user that there's been a comment
createCommentNotification(comment);
return comment._id;
}
});
~~~
<%= caption "lib/collections/comments.js" %>
<%= highlight "17~123" %>
Tenemos que publicar las notificaciones:
~~~js
Meteor.publish('posts', function() {
return Posts.find();
});
Meteor.publish('comments', function(postId) {
check(postId, String);
return Comments.find({postId: postId});
});
Meteor.publish('notifications', function() {
return Notifications.find();
});
~~~
<%= caption "server/publications.js" %>
<%= highlight "10~12" %>
Y suscribirnos en el cliente:
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() {
return [Meteor.subscribe('posts'), Meteor.subscribe('notifications')]
}
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "6" %>
<%= commit "11-1", "Añadida la colección de comentarios." %>
### Mostrando las notificaciones
Ahora podemos seguir y añadir una lista de notificaciones a nuestra cabecera:
~~~html
<template name="header">
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navigation">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{pathFor 'postsList'}}">Microscope</a>
</div>
<div class="collapse navbar-collapse" id="navigation">
<ul class="nav navbar-nav">
{{#if currentUser}}
<li>
<a href="{{pathFor 'postSubmit'}}">Submit Post</a>
</li>
<li class="dropdown">
{{> notifications}}
</li>
{{/if}}
</ul>
<ul class="nav navbar-nav navbar-right">
{{> loginButtons}}
</ul>
</div>
</nav>
</template>
~~~
<%= caption "client/templates/includes/header.html" %>
<%= highlight "14~21" %>
Y crear las plantillas `notifications` y `notificationItem` (que pondremos en el archivo `notifications.html`):
~~~html
<template name="notifications">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Notifications
{{#if notificationCount}}
<span class="badge badge-inverse">{{notificationCount}}</span>
{{/if}}
<b class="caret"></b>
</a>
<ul class="notification dropdown-menu">
{{#if notificationCount}}
{{#each notifications}}
{{> notificationItem}}
{{/each}}
{{else}}
<li><span>No Notifications</span></li>
{{/if}}
</ul>
</template>
<template name="notificationItem">
<li>
<a href="{{notificationPostPath}}">
<strong>{{commenterName}}</strong> commented on your post
</a>
</li>
</template>
~~~
<%= caption "client/templates/notifications/notifications.html" %>
Podemos ver que para cada notificación, tendremos un enlace al post que ha sido comentado junto con el usuario que lo ha hecho.
A continuación, hay que asegurarse de que se selecciona la lista de notificaciones correcta desde nuestro ayudante, y actualizar las notificaciones como "leídas" cuando el usuario hace clic en el enlace al que apuntan.
~~~js
Template.notifications.helpers({
notifications: function() {
return Notifications.find({userId: Meteor.userId(), read: false});
},
notificationCount: function(){
return Notifications.find({userId: Meteor.userId(), read: false}).count();
}
});
Template.notificationItem.helpers({
notificationPostPath: function() {
return Router.routes.postPage.path({_id: this.postId});
}
});
Template.notificationItem.events({
'click a': function() {
Notifications.update(this._id, {$set: {read: true}});
}
});
~~~
<%= caption "client/templates/notifications/notifications.js" %>
<%= commit "11-2", "Mostrar las notificaciones en la cabecera." %>
Como podemos ver, las notificaciones no son muy diferentes de los errores, y su estructura es muy similar. Solo hay una diferencia clave: hemos creado una colección sincronizada cliente-servidor. Esto significa que nuestras notificaciones son persistentes y, siempre y cuando se utilice la misma cuenta de usuario, persistirá en distintos navegadores y dispositivos.
Abre un segundo navegador, crea una nueva cuenta de usuario, y añade un comentario en un post del usuario anterior. Deberías ver algo así:
<%= screenshot "11-1", "Mostrando las notificaciones." %>
### Controlando el acceso a las notificaciones
Las notificaciones van bien. Sin embargo, hay un pequeño problema: nuestras notificaciones son públicas.
Si ejecutamos el siguiente comando en la consola del segundo navegador:
~~~js
❯ Notifications.find().count();
1
~~~
<%= caption "Consola del navegador" %>
El nuevo usuario (el que ha *comentado*) no debería tener notificaciones. Las que vemos son las de los demás usuarios.
Aparte de los posibles problemas de privacidad, simplemente no podemos permitirnos el lujo de cargar las notificaciones de todos los usuarios. Con un sitio lo suficientemente grande, esto podría sobrecargar la memoria disponible en el navegador y empezar a causar graves problemas de rendimiento.
Resolveremos este problema mediante las **publicaciones**. Podemos usar las publicaciones para especificar qué parte de nuestra colección queremos compartir con el navegador.
Para lograrlo, tenemos que cambiar `Notifications.find()`. Es decir, tenemos que devolver el cursor que correspondiente a las notificaciones del usuario actual.
Hacer esto es bastante sencillo puesto que la función `publish` tiene el `_id` del usuario actual disponible en `this.userId`:
~~~js
Meteor.publish('notifications', function() {
return Notifications.find({userId: this.userId, read: false});
});
~~~
<%= caption "server/publications.js" %>
<%= highlight "2" %>
<%= commit "11-3", "Sincronizar solo las notificaciones relevantes al usuario." %>
Si ahora se busca en las consolas de los dos navegadores, deberíamos ver dos colecciones distintas de notificaciones:
~~~js
❯ Notifications.find().count();
1
~~~
<%= caption "Consola del navegador (usuario 1)" %>
~~~js
❯ Notifications.find().count();
0
~~~
<%= caption "Consola del navegador (usuario 2)" %>
De hecho, la lista de notificaciones cambiará si accedes y sales de la aplicación. Esto se debe a que las publicaciones se republican automáticamente cada vez que cambia el estado del usuario.
Nuestra aplicación es cada vez más funcional, y a medida que cada vez más usuarios entran y empiezan a publicar enlaces, corremos el riesgo de acabar con una página de inicio demasiado larga. Vamos a abordar este problema en el próximo capítulo: la paginación.