การแจ้งเตือน
11แปลไปแล้ว
ในบทนี้ คุณจะได้
ตอนนี้ผู้ใช้ก็สามารถแสดงข้อคิดเห็นลงในข่าวของคนอื่นได้แล้ว ดังนั้นมันน่าจะดีไม่น้อย ถ้าจะทำให้พวกเค้ารู้ว่าการสนทนาได้เริ่มต้นขึ้นแล้ว
เพื่อให้เป็นอย่างนั้นได้ เราก็จะเตือนเจ้าของข่าวว่า มีข้อคิดเห็นใหม่เกิดขึ้นในข่าวแล้ว และเตรียมลิงก์ให้กดเข้าไปดูข้อคิดเห็นนั้นได้
คุณสมบัติแบบนี้เป็นจุดเด่นของ Meteor เพราะว่าตัว Meteor เองนั้นเป็นเรียลไทม์อยู่แล้ว ทำให้เราสามารถแสดงการแจ้งเตือนได้ ทันที ไม่จำเป็นต้องคอยให้ผู้ใช้รีเฟรชหน้าจอ หรือเช็คอินอะไรอีก ที่เราต้องทำก็แค่ แสดงกล่องข้อความเตือนโดยไม่ต้องใช้โค้ดพิเศษอะไรเพิ่มเติม
สร้างการแจ้งเตือน
เราจะสร้างการแจ้งเตือนเมื่อมีบางคนป้อนข้อคิดเห็นลงในข่าวของคุณ ซึ่งการแจ้งเตือนนี้สามารถขยายขอบเขตการใช้งานได้อีกหลากหลาย แต่สำหรับตอนนี้เราแค่เตือนให้ผู้ใช้รู้ว่าเกิดอะไรขึ้นก็พอ
เราจะสร้างคอลเลกชั่น Notifications
พร้อมกับฟังก์ชั่น createCommentNotification
ที่ใช้เพิ่มข้อมูลการแจ้งเตือน เมื่อมีข้อคิดเห็นใหม่ๆ เกิดขึ้นตรงกับข่าวใดข่าวหนึ่งของคุณ
เนื่องจากเราจะทำการอัพเดทการแจ้งเตือนจากฝั่งไคลเอนต์ เราก็ต้องแน่ใจว่าได้กำหนดสิทธิ์ allow
ที่เข้มงวดพอ โดยเราจะตรวจสอบในเรื่องต่อไปนี้
- ผู้ใช้ที่สั่ง
update
เป็นเจ้าของการแจ้งเตือนที่กำลังถูกแก้ไข - ผู้ใช้กำลังพยายามอัพเดทแค่หนึ่งฟิลด์
- หนึ่งฟิลด์นั้นก็คือ คุณสมบัติ
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
});
}
};
ก็เหมือนกับเรื่องข่าวและข้อคิดเห็น คอลเลกชั่น Notifications
จะถูกแชร์ให้ใช้งานได้ทั้งที่ฝั่งไคลเอนต์และเซิร์ฟเวอร์ และเราก็จำเป็นต้องอัพเดทข้อมูลการแจ้งเตือนหลังจากที่ผู้ใช้เห็นข้อความแล้ว โดยเราเปิดให้มีการอัพเดทแบบจำกัดสิทธิ์การใช้งานกับข้อมูลที่เป็นของผู้ใช้เท่านั้น
เรายังสร้างฟังก์ชันง่ายๆใช้ตรวจดูข่าวที่ผู้ใช้กำลังป้อนข้อคิดเห็น ค้นหาว่าจะต้องแจ้งเตือนใคร และสร้างการแจ้งเตือนขึ้นใหม่
เนื่องจากเราสร้างข้อคิดเห็นด้วยเมธอดที่รันบนเซิร์ฟเวอร์ ดังนั้นเราก็สามารถปรับให้เมธอดนั้นมาเรียกใช้ฟังก์ชันของเราได้ โดยเปลี่ยน return Comments.insert(comment);
ไปเป็น comment._id = Comments.insert(comment)
เพื่อเก็บค่า _id
ของข้อคิดเห็นใหม่ไว้ในตัวแปร แล้วก็เรียกฟังก์ชัน createCommentNotification
ของเราดังนี้
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;
}
});
และทำการเผยแพร่ข้อมูลการแจ้งเตือน ดังนี้
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();
});
โดยบอกรับข้อมูลที่ไคลเอนต์แบบนี้
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() {
return [Meteor.subscribe('posts'), Meteor.subscribe('notifications')]
}
});
แสดงการแจ้งเตือน
ตอนนี้เราก็พร้อมที่จะเพิ่มรายการแจ้งเตือนเข้าไปที่ header แล้ว
<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>
และสร้างเทมเพลท notifications
และ notificationItem
(โดยใช้ notifications.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>
โดยแผนที่วางไว้ก็คือ ในแต่ละการแจ้งเตือนจะมีลิงก์ไปยังหน้าข่าวที่ถูกป้อนข้อคิดเห็น และมีชื่อผู้ที่ป้อนแสดงอยู่ด้วย
ขั้นต่อไป เราก็ต้องทำให้แน่ใจว่า ตัวช่วยดึงข้อมูลการแจ้งเตือนที่ถูกต้องออกมา และทำการอัพเดทสถานะการแจ้งเตือนเป็น read
เมื่อผู้ใช้คลิ๊กที่ลิงก์
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}});
}
});
คุณอาจจะคิดว่า การแจ้งเตือนไม่ได้แตกต่างอะไรกับข้อผิดพลาด ซึ่งมันก็จริงที่ว่า โครงสร้างของพวกมันคล้ายคลึงกันมาก แต่มีสิ่งหนึ่งที่แตกต่างออกไปคือ เราได้สร้างคอลเลกชั่นที่ซิงโครไนซ์ข้อมูลระหว่างไคลเอนต์-เซิร์ฟเวอร์อย่างสมบูรณ์ขึ้นมา นั่นหมายความว่า ตราบใดที่เรายังใช้บัญชีผู้ใช้เดิม การแจ้งเตือนจะยังคงอยู่ต่อไป ไม่ว่าจะรีเฟรชสักกี่ครั้ง และบนอุปกรณ์ไหนก็ตาม
ลองทดสอบกันดูหน่อย ให้เปิดเบราว์เซอร์ตัวที่สอง (สมมุติว่า fiirefox) สร้างบัญชีผู้ใช้ใหม่ และป้อนข้อคิดเห็นลงในข่าวที่สร้างไว้ด้วยชื่อผู้ใช้คนแรก (ที่คุณป้อนไว้ใน Chrome) คุณก็ควรจะเห็นหน้าจอแบบนี้

ควบคุมการเข้าใช้การแจ้งเตือน
การแจ้งเตือนทำงานได้ดี แต่ทว่ามีปัญหาเล็กน้อย คือ การแจ้งเตือนของเราเป็นแบบสาธาณะ (public)
ถ้าคุณยังเปิดเบราว์เซอร์ตัวที่สองทิ้งไว้ ให้ลองรันคำสั่งต่อไปนี้ในคอนโซลของเบราว์เซอร์ดู
❯ Notifications.find().count();
1
ผู้ใช้รายใหม่นี้ (คนที่เพิ่ง ป้อนข้อคิดเห็น) ไม่ควรจะเห็นการแจ้งเตีอนตัวไหนเลย การแจ้งเตือนที่เค้ามองเห็นจากคอลเล็กชั่น Notifications
จริงๆแล้วเป็นของผู้ใช้ คนแรก
นอกเหนือจากปัญหาเรื่องความเป็นส่วนตัวแล้ว เราคงไม่สามารถทำให้การแจ้งเตือนของผู้ใช้ทุกคนถูกโหลดไปที่เบราว์เซอร์ทุกตัวของผู้ใช้ทั้งหมดได้ ในเว็บไซต์ขนาดใหญ่นั้น อาจทำให้หน่วยความจำของเบราว์เซอร์ไม่เพียงพอ และทำให้เกิดปัญหาใหญ่ที่ส่งผลเสียต่อประสิทธิภาพของระบบได้
เราแก้ปัญหานี้ได้ด้วยการ เผยแพร่ข้อมูล โดยเราสามารถเผยแพร่เฉพาะส่วนของคอลเลกชั่นที่ต้องการแชร์ระหว่างเบราว์เซอร์ได้
เพื่อให้สำเร็จตามนั้น เราจำเป็นต้องใช้ค่าเคอร์เซอร์ที่แตกต่างไปจาก Notifications.find()
ตัวเดิม สิ่งที่เราต้องการคือ ให้คืนเคอร์เซอร์ที่ตรงกับการแจ้งเตือนของผู้ใช้ปัจจุบัันเท่านั้น
ซึ่งสามารถทำได้ง่ายๆ เพราะฟังก์ชัน publish
นั้นมีค่า _id
ของผู้ใช้ปัจจุบันเก็บอยู่ที่ this.userId
แล้ว
Meteor.publish('notifications', function() {
return Notifications.find({userId: this.userId, read: false});
});
ตอนนี้ถ้าเราตรวจดูจากเบราว์เซอร์ทั้งคู่ เราก็น่าจะเห็นการแจ้งเตือนที่แตกต่างกัน
❯ Notifications.find().count();
1
❯ Notifications.find().count();
0
ในความเป็นจริง รายการแจ้งเตือนจะเปลี่ยนไปเมื่อคุณล็อกอินเข้าและออกจากแอพ นั่นก็เพราะการเผยแพร่ข้อมูลจะทำงานใหม่โดยอัตโนมัติ เมื่อใดก็ตามที่บัญชีผู้ใช้เปลี่ยนแปลง
จะเห็นว่าแอพของเราเริ่มจะทำอะไรได้มากขึ้นเรื่อยๆ เมื่อมีจำนวนผู้ใช้มากขึ้น และเริ่มป้อนข่าวเข้ามา เราก็มีความเสี่ยงที่จะเห็นหน้าโฮมแบบไม่รู้จบได้ ดังนั้นเราจะมาแก้ปัญหานี้ในบทต่อไป ด้วยการใช้การแบ่งหน้าเข้ามาช่วย
หรือพูดคุยเกี่ยวกับเนื้อหาในหนังสือที่นี่