-
Notifications
You must be signed in to change notification settings - Fork 12
/
11-notifications.md.erb
247 lines (193 loc) · 9.77 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
---
title: Notifications
slug: notifications
date: 0011/01/01
number: 11
contents: Eine Benachrichtigungs Collection (notifications) erstellen, die Benutzer über die Aktionen von anderen Benutzern auf dem Laufenden hält.|Lernen wie man relevante Informationen nur mit dem zuständigen Benutzer austauscht.|Mehr über Meteor Publications und Subscriptions erfahren.
paragraphs: 25
---
Da Benutzer jetzt Inhalte von anderen Benutzern kommentieren können, wäre es sinnvoll, diese wissen zu lassen, wann eine Konversation über einen von ihren Posts begonnen hat.
Um dies zu tun, werden wir den Verfasser eines Posts benachrichtigen, wenn ein anderer Benutzer einen Kommentar dazu gemacht hat und linken in der Benachrichtigung auf den Kommentar hinaus.
Dies ist eine Eigenschaft in der Meteor hinaussticht: Da Meteor standartmässig realtime ist, können wir diese Benachrichtigungen auch unmittelbar anzeigen. Der Benutzer braucht weder die Seite neu zu laden, noch sonst eine Aktion auszuführen, die neue Benachrichtigung erscheint sofort, ohne dafür zusätzlichen Code schreiben zu müssen.
### Benachrichtigungen kreieren
Wir erstellen eine neue Benachrichtigungen, sobald jemand deinen Post kommentiert. Künftig können diese Benachrichtigungen erweitert werden, um auch andere Szenarios abzudecken, aber für den Moment ist es ausreichend, Benutzer entsprechend zu informieren, wenn etwas passiert.
Wir erstellen unsere `Notifications` Collection plus eine `createCommentNotification` Funktion, welche eine Benachrichtigung erstellt sobald es ein neuer Kommentar gibt.
~~~js
Notifications = new Meteor.Collection('notifications');
Notifications.allow({
update: ownsDocument
});
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 "collections/notifications.js" %>
Genau wie bei Posts oder Comments, wird die `Notifications` Collection auf beiden Seiten (Server und Client) vorhanden sein. Da wir Benachrichtigungen aktualisieren wollen, sobald der Benutzer sie gelesen hat, müssen wir auch sicherzustellen, dass die Aktualisierungen nur auf Dokumenten ausgeführte werden können, die auch dem Benutzer gehören.
Wir erstellen auch eine einfache Funktion die auf den Post schaut, zu dem ein Kommentar erstellt wird, und daraus schliesst, welcher Benutzer von hier aus benachrichtigt werden soll.
Wir haben das Erstellen von Kommentaren schon serverseitig implementiert, folglich können wir diese Funktionalität einfach ein bisschen erweitern. Wir ersetzten `return Comments.insert(comment);` mit `comment._id = Comments.insert(comment)` um die _id des neuen Kommentars in eine Variabel zu speichern, um dann unsere `createCommentNotification` aufzurufen:
~~~js
Comments = new Meteor.Collection('comments');
Meteor.methods({
comment: function(commentAttributes) {
// [...]
// 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 "collections/comments.js" %>
<%= highlight "8~14" %>
Wir müssen die Benachrichtigungen auch noch publizieren (publish) und auf dem Client subscriben.
~~~js
// [...]
Meteor.publish('notifications', function() {
return Notifications.find();
});
~~~
<%= caption "server/publications.js" %>
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() {
return [Meteor.subscribe('posts'), Meteor.subscribe('notifications')]
}
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "5" %>
<%= commit "11-1", "Added basic notifications collection." %>
### Benachrichtigungen anzeigen
Jetzt können wir eine Liste mit Benachrichtigungen im Header anzeigen.
~~~html
<template name="header">
<header class="navbar">
<div class="navbar-inner">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="{{pathFor 'postsList'}}">Microscope</a>
<div class="nav-collapse collapse">
<ul class="nav">
{{#if currentUser}}
<li>
<a href="{{pathFor 'postSubmit'}}">Submit Post</a>
</li>
<li class="dropdown">
{{> notifications}}
</li>
{{/if}}
</ul>
<ul class="nav pull-right">
<li>{{loginButtons}}</li>
</ul>
</div>
</div>
</header>
</template>
~~~
<%= caption "client/views/includes/header.html" %>
<%= highlight "12~19" %>
Wir brauchen dazu noch die beiden Templates `notifications` und `notification` (Beide sind im selben `notifications.html` untergebracht):
~~~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}}
{{> notification}}
{{/each}}
{{else}}
<li><span>No Notifications</span></li>
{{/if}}
</ul>
</template>
<template name="notification">
<li>
<a href="{{notificationPostPath}}">
<strong>{{commenterName}}</strong> commented on your post
</a>
</li>
</template>
~~~
<%= caption "client/views/notifications/notifications.html" %>
Wir sehen, dass das Ziel ist, dass jede Benachrichtigung einen Link zu dem Post der kommentiert wurde und den Namen des Benutzers der kommentiert hat beinhält.
Als nächstes müssen wir noch sicherstellen, dass wir die richtige Liste von Benachrichtigungen auswählen und die Benachrichtigung als gelesen markieren, sobald ein Benutzer auf den korrespondierenden Link klickt.
~~~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.notification.helpers({
notificationPostPath: function() {
return Router.routes.postPage.path({_id: this.postId});
}
})
Template.notification.events({
'click a': function() {
Notifications.update(this._id, {$set: {read: true}});
}
})
~~~
<%= caption "client/views/notifications/notifications.js" %>
<%= commit "11-2", "Display notifications in the header." %>
Man könnte denken, dass Benachrichtigungen sich gar nicht so sehr von den Errors unterscheiden. Tatsächlich ist ihre Struktur sehr ähnlich, aber es gibt einen markanten Unterschied: Für die Benachrichtigungen haben wir eine eigene Client-Server synchronisierbare Collection erstellt. Das heisst unsere Benachrichtigungen sind persistiert und existieren in verschiedenen Browser-Instanzen gleichzeitig (sofern wir mit demselben Benutzer eingeloggt sind).
Versuch das mal aus: Öffne einen zweiten Browser (zB. Firefox), erstelle einen Benutzeraccount und kommentiere einen Post den du mit deinem Haupt-Benutzer erstellt hast (den wir in Chrome aktuell offen haben). Du solltest so etwas Ähnliches sehen:
<%= screenshot "11-1", "Displaying notifications." %>
### Zugriff auf Benachrichtigungen haben
Benachrichtigungen scheinen gut zu funktionieren. Es gibt allerdings ein Problem: die Benachrichtigungen sind öffentlich.
Wenn du den zweiten Browser immernoch offen hast, versuche in dessen Konsole folgenden Code auszuführen.
~~~js
❯ Notifications.find().count();
1
~~~
<%= caption "Browser console" %>
Dieser neue Benutzer (der den Kommentar verfasst hat) sollte keine Benachrichtigungen haben. Die Benachrichtigung die er sehen kann gehört eigentlich unserem Hauptbenutzer.
Mal abgesehen von Datenschutzgründen, ist es auch performance-technisch nicht sehr wirtschaftlich, jedem Benutzer alle `Notifications` aller anderen Benutzer zu laden. Auf einer grossen Seite kann das zur Überladung des verfügbaren Speichers und ernsthaften Performance Problemen führen.
Wir lösen diese Problem über die **Publications**. Wir können unsere Publications dafür gebrauchen, genau anzugeben welche Teile aus unserer Collection mit dem Browser geteilt wird
Um dies zu erreichen, müssen wir in unserer Publication einen anderen Cursor als `Notfications.find()` zurückgeben. Nämlich: Einen Cursor der nur die Benachrichtigungen des aktuellen Users beinhält.
Dies ist ziemlich einfach zu bewerkstelligen, da die `publish` Funktion die `_id` des aktuellen Benutzers als `this.userId` zur Verfügung hat.
~~~js
Meteor.publish('notifications', function() {
return Notifications.find({userId: this.userId});
});
~~~
<%= caption "server/publications.js" %>
<%= commit "11-3", "Only sync notifications that are relevant to the user." %>
Dies können wir jetzt in beiden Browser-Fenster überprüfen, wir sollten zwei verschiedenen Benachrichtigungs Collections sehen.
~~~js
❯ Notifications.find().count();
1
~~~
<%= caption "Browser console (user 1)" %>
~~~js
❯ Notifications.find().count();
0
~~~
<%= caption "Browser console (user 2)" %>
Die Liste der Benachrichtigungen sollte sogar ändern, während du dich aus- und einloggst. Das ist der Fall, weil die Publication automatisch neu-publiziert wird, sobald der User Account ändert.
Unsere App wird immer wie mehr funktional, und je mehr Benutzer sich einschreiben und Links posten, desto mehr laufen wir Gefahr, dass wir eine nie endende Homepage haben werden. Um genau das werden wir uns im nächsten Kapitel kümmern: wir implementieren eine Paginierung.