-
Notifications
You must be signed in to change notification settings - Fork 4
/
11-notifications.md.erb
282 lines (222 loc) · 12.1 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
282
---
title: Tính năng thông báo
slug: notifications
date: 0011/01/01
number: 11
points: 10
photoUrl: http://www.flickr.com/photos/ikewinski/8719868039/
photoAuthor: Mike Lewinski
contents: Thêm một collection thông báo để báo cho người dùng biết hoạt động của người dùng khác.|Học cách chỉ chia sẻ thông báo với một người dùng nhất định.|Học thêm về publish và subscribe của Meteor.
paragraphs: 25
---
Bây giờ khi mà người dùng đã có thể viết bình luận trên các bài viết khác, sẽ tốt hơn nếu chúng ta để họ biết rằng một hội thoại đã được bắt đầu.
Để làm điều này, chúng ta sẽ báo cho chủ của bài viết rằng có một bình luận mới trên bài viết của họ, và cung cấp đường dẫn để hiển thị bình luận đó.
Đây là một trong những tính năng nơi mà Meteor có thể toả sáng: bởi vì Meteor mặc định xử lý thời gian thực, chúng ta sẽ có thể hiển thị những thông báo đó _tức thời_. Chúng ta không cần người dùng phải làm mới trang hoặc kiểm tra lại, chúng ta có thể đơn giản thêm thông báo mới vào mà không cần phải viết thêm đoạn code đặc biệt nào.
### Tạo thông báo
Chúng ta sẽ tạo một thông báo mỗi khi ai đó viết bình luận vào bài viết của bạn. Trong tương lai, tính năng thông báo có thể mở rộng để đáp ứng nhiều tình huống, nhưng tạm thời nó sẽ chỉ đủ để giúp người dùng được cho biết điều gì đang diễn ra.
Hãy cùng tạo collection `Notifications`, và một hàm `createCommentNotification` mà nhiệm vụ của nó sẽ là chèn một thông báo khớp với mỗi bình luận trên mỗi bài viết của bạn.
Vì chúng ta sẽ cập nhật thông báo từ phía client, chúng ta cần phải chắc chắn rằng việc gọi `allow` có khả năng chống nhiễu bên ngoài. Do đó cần phải kiểm tra rằng:
- Người dùng gọi lệnh `update` sở hữu thông báo được sửa đổi.
- Người dùng chỉ cập nhật một trường đơn lẻ.
- Trường đơn lẻ đó là một thuộc tính `read` của thông báo.
~~~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" %>
Cũng giống như bài viết hoặc bình luận, collection `Notifications` sẽ được chia sẻ giữa cả client và server. Vì chúng ta cần phải cập nhật thông báo mỗi khi người dùng đã nhìn thấy chúng, chúng ta cũng cần phải kích hoạt tính năng cập nhật, chắc chắn rằng chúng ta hạn chế quyền cập nhật chỉ cho phép đối với dữ liệu của người dùng đó.
Chúng ta cũng vừa tạo một hàm đơn giản theo dõi bài viết người dùng đang tạo bình luận, khám phá ra xem ai sẽ nhận được thông báo, và thêm bản ghi thông báo mới.
Chúng ta đã tạo bình luận với Method phía server, nên chúng ta chỉ cần bổ sung Method đó để gọi hàm cần thiết. Chúng ta sẽ thay đổi `return Comments.insert(comment);` bằng `comment._id = Comments.insert(comment)` để lưu `_id` của bình luận mới được tạo vào biến số , sau đó gọi hàm `createCommentNotification` của chúng ta:
~~~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" %>
Hãy cùng publish thông báo:
~~~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" %>
Và subscribe phía client:
~~~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", "Added basic notifications collection." %>
### Hiển thị thông báo
Bây giờ chúng ta có thể tiếp tục và thêm một danh sách thông báo vào phần header.
~~~html
<template name="header">
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<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>
</div>
</nav>
</template>
~~~
<%= caption "client/templates/includes/header.html" %>
<%= highlight "15~22" %>
Và tạo template `notifications` và `notificationItem` (chúng dùng chung cùng một file đơn lẻ là `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" %>
Kế hoạch là để cho mỗi thông báo chứa một đường dẫn tới bài viết được viết bình luận, và tên của người dùng đã viết bình luận.
Tiếp đó, chúng ta cần chắc chắn rằng đã chọn đúng danh sách thông báo trong helper, và cập nhật những thông báo đó thành "read" (đã đọc) khi mà người dùng bấm vào đường dẫn đó.
~~~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", "Display notifications in the header." %>
Bạn có thể nghĩ rằng hệ thống thông báo không khác nhiều so với hệ thống hiển thị lỗi, và thực tế là cấu trúc của chúng rất giống nhau. Chỉ có một điểm khác biệt: chúng ta đã vừa tạo một collection đồng bộ thích đáng client-server. Điều này có nghĩa là hệ thống thông báo của chúng ta *ổn định*, và miễn là chúng ta dùng cùng một tài khoản, nó sẽ tồn tài giữa các lần làm mới trình duyệt và giữa các thiết bị khác nhau.
Hãy thử điều đó: mở một trình duyệt thứ hai (ví dụ như là từ Firefox), tạo một tài khoản người dùng, và viết bình luận vào bài viết bạn đã tạo với tài khoản chính thứ nhất (cái mà bạn đang mở với Chrome). Bạn sẽ thấy thứ gì đó như sau:
<%= screenshot "11-1", "Displaying notifications." %>
### Quản lý truy cập tới thông báo
Chức năng thông báo đã hoạt động tốt. Tuy nhiên vẫn còn một vấn đề nhỏ: hệ thống thông báo của chúng ta đang ở chế độ công khai.
Nếu bạn vẫn đang để trình duyệt thứ hai ở trạng thái mở, cố chạy đoạn code sau ở phía console trình duyệt:
~~~js
❯ Notifications.find().count();
1
~~~
<%= caption "Browser console" %>
Tài khoản mới này (là người đã viết bình luận) không nên thấy bất kỳ thông báo nào. Thông báo mà họ nhìn thấy trong collection `Notifications` thực tế thuộc về người dùng *đầu tiên* của chúng ta.
Ngoài vấn đề quyền riêng tư, chúng ta cũng không thể nào đủ sức để có dữ liệu thông báo nạp trên tất cả trình duyệt của người dùng. Trên một trang đủ lớn, điều này có thể dẫn đến vấn đề quá tải bộ nhớ và tạo ra những vấn đề nghiêm trọng về hiệu suất.
Chúng ta sẽ giải quyết bài toán này với **publish**. Chúng ta có thể dùng việc publish để đặc tả chính xác phần nào của collection sẽ được chia sẻ với các trình duyệt.
Để hoàn thành điều này, chúng ta cần phải trả về một con trỏ khác trong phần publish so với là `Notifications.find()` như hiện tại. Là, chúng ta muốn trả về một con trỏ tương ứng với thông báo hiện tại của người dùng.
Làm như vậy khá là hiển nhiên, vì hàm `publish` có được `_id` của người dùng hiện tại với `this.userId`:
~~~js
Meteor.publish('notifications', function() {
return Notifications.find({userId: this.userId, read: false});
});
~~~
<%= caption "server/publications.js" %>
<%= commit "11-3", "Only sync notifications that are relevant to the user." %>
Bây giờ, nếu kiểm tra hai cửa sổ trình duyệt, bạn sẽ thấy hai collection thông báo khác nhau:
~~~js
❯ Notifications.find().count();
1
~~~
<%= caption "Browser console (user 1)" %>
~~~js
❯ Notifications.find().count();
0
~~~
<%= caption "Browser console (user 2)" %>
Thực tế, danh sách thông báo nên được thay đổi khi bạn đăng nhập vào ứng dụng và đăng xuất khỏi ứng dụng. Điều này là do publish tự động thay đổi và xuất bản lại mỗi khi tài khoản của người dùng thay đổi.
Ứng dụng của chúng ta ngày càng trở nên chức năng hơn, và do ngày càng nhiều người dùng tham gia và bắt đầu viết bài nên chúng ta sẽ có nguy cơ trang chủ quá dài. Chúng ta sẽ bàn về vấn đề này ở chương tiếp theo với việc thực hiện phân trang.