May 10, 2021 Meteor
Now users can add comments to the post so they know it's a good idea that the discussion has begun.
We'll notify the author of the post that someone has already added a comment to his post and provide a link to see the comment.
This is one of the features of Meteor's true flash: because Meteor is real-time by default, we see these notifications in a flash. We don't need the user to refresh the page or do any other checks, we don't need to write any special code to get a simple notifications pop-up.
We'll generate a notification when someone adds a comment to your post. Later, notifications extend coverage in many other situations, but it's enough to let users know what's happening.
Let's start
Notifications
collection, and
createCommentNotification
which adds a notification to the collection when someone adds a comment under your post.
Since we will update notifications from the client, we need to
allow
method is bulletproof.
We check the following:
update
method
read
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
});
}
};
Like posts and comments,
Notifications
are shared on the server side and with clients. A
fter the user has read the notifications, we need to update them, so we need to allow the update operation.
As with other sections, only users who have notifications are allowed to update.
We wrote a simple program to find users who need notifications when they add comments to a post and insert a new notification.
We've created a comment object in the
comment._id = Comments.insert(comment)
the return Comments.insert with comment._id
return Comments.insert(comment);
。
This allows you to save the _id
_id
object, and
createCommentNotification
method:
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;
}
});
Next, release notifications:
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();
});
Subscribe to notifications on the client:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() {
return [Meteor.subscribe('posts'), Meteor.subscribe('notifications')]
}
});
Now let's add a notifications list to header.
<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>
Next,
notifications
notificationItem
templates (both templates are in
notifications.html
file):
<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>
We can see that each notification has a link to the post and contains the name of the comment author.
Next we need to make sure that the correct notifications list is selected in helper and mark the notifications as read after the user clicks on the link.
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}});
}
});
You might think that notifications are very similar to errors, yes, they are structurally similar. B ut there's one difference: We created a collection for notification. This means that notification is persistent and exists for the same user, whether browser refresh or cross-device.
Try it: Open a new browser (such as Firefox), create a new user, and then post a comment under the main user's post (in Chrome). You'll see the following:
Notifications works well. Then there's the little problem: everyone can see our notifications.
If your browser is still open, try typing the following js code in your browser console:
❯ Notifications.find().count();
1
A new user (when a post is commented on) should not receive any notifications.
Notifications
the Notifications collection actually belong to previous users.
In addition to possible privacy issues, we cannot let the browser display each notifications for the reason. For a site large enough, doing so can easily drain browser memory and cause serious performance problems.
We solve this problem through publications. We can use publications to specify exactly which part of the collection we want to share with other users.
To do this, we need to return a different cursor in publication instead
Notifications.find()
In other words, the cursor we return is related to the current user's notificatons.
This is straightforward
publish
function has the
_id
which
this.userId
Meteor.publish('notifications', function() {
return Notifications.find({userId: this.userId, read: false});
});
Now look at our two browser windows and we'll see two different notifications collections.
❯ Notifications.find().count();
1
❯ Notifications.find().count();
0
In fact, the Notifications collection changes when you log on and out. This is because publications are automatically re-published when the account changes.
Our app features are getting better and better, and as more and more people post, our home page will be infinitely long. In the next chapter we solve this problem by pedding.