Tính năng thông báo

11

Percentage Translated

In this chapter, you will:

  • 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.
  • 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.
    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
        });
      }
    };
    
    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:

    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;
      }
    });
    
    lib/collections/comments.js

    Hãy cùng publish thông báo:

    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();
    });
    
    server/publications.js

    Và subscribe phía client:

    Router.configure({
      layoutTemplate: 'layout',
      loadingTemplate: 'loading',
      notFoundTemplate: 'notFound',
      waitOn: function() {
        return [Meteor.subscribe('posts'), Meteor.subscribe('notifications')]
      }
    });
    
    lib/router.js

    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.

    <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>
    
    client/templates/includes/header.html

    Và tạo template notificationsnotificationItem (chúng dùng chung cùng một file đơn lẻ là 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>
    
    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 đó.

    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}});
      }
    });
    
    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:

    Displaying notifications.
    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:

     Notifications.find().count();
    1
    
    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:

    Meteor.publish('notifications', function() {
      return Notifications.find({userId: this.userId, read: false});
    });
    
    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:

     Notifications.find().count();
    1
    
    Browser console (user 1)
     Notifications.find().count();
    0
    
    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.