通知機能

11

本章で学ぶこと:

  • 他のユーザのアクションをユーザーに通知するための通知コレクションを追加します
  • ユーザと関連する通知のみを共有する方法を学びます
  • Mateorパブリケーションとサブスクリプションの詳細についてを学びます
  • これでユーザーはお互いの投稿にコメントできるようになったので、 会話が始まったことを知らせると良いでしょう。 

    そのようにするために、投稿にコメントが付いたことを投稿者に知らせて、 そのコメントを見るためのリンクを提供します。

    これは Meteor の機能がよく輝くところです: Meteor はデフォルトでリアルタイムなので、そうした通知は瞬時に表示されます。 ユーザーがページを再読み込みしたり、チェックしたりすることを私たちは待つ必要はありません。 特別なコードを書くことなく、新しい通知をポップアップできるからです。   

    通知を作成

    投稿に誰かがコメントをした時の通知を作っていきます。 将来的には、通知を多くのシナリオをカバーするように拡張できますが、 今のところはユーザーに何が起きているか知らせることで十分でしょう。

    では、Notificationsコレクションを作っていきましょう。 自分の投稿の新しいコメントごとにマッチする通知を挿入する createCommentNotification関数を作っていきます。

    クライアントからの通知を更新されますので、 allow呼び出しが防弾(?bulletproof)であることを確認する必要があります。:

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

    投稿やコメントと同じように、Notificationsコレクションはクライアントとサーバーの両方で共有されます。 一度ユーザーが通知を見た時に、私たちは通知を更新する必要があるので、アップデートできるようにします。 いつものように、更新許可をユーザー自身のデータにリダイレクトするようにします。

    私たちはシンプルな関数を作りました。これはユーザーがコメントしている投稿に目を向け、 誰がそこから通知されるか見つけて、新しい通知を挿入する関数です。

    私たちはすでに サーバーサイドのメソッドでコメントを作っているので、 関数を呼び出すために、このメソッドを増やすことができます。 新しく作ったコメントの_idを変数に保存するため、 return Comments.insert(comment);comment._id = Comments.insert(comment)に  取り替えます。それから、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;
      }
    });
    
    lib/collections/comments.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();
    });
    
    server/publications.js

    そしてクライアントでサブスクライブします。:

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

    コミット 11-1

    Added basic notifications collection.

    Displaying Notifications

    これで私たちは前に進むことができるようになり、ヘッダーに通知のリストを追加できます。

    <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

    そして、notifications テンプレートと notification テンプレートをつくります。 (この2つは1つの 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

    私たちはコメントされた投稿へのリンクがあるそれぞれの通知のための画面 とコメントされた投稿のユーザーの名前を見ることができます。 それぞれの通知がコメントした投稿へのリンク、 およびその上でコメントしたユーザーの名前を含むようにするための画面があることがわかります。

      次に、私たちはマネージャーで、通知の正しいリストを選んで、 ユーザーがポイントしているリンクをクリックした時に、通知を"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}});
      }
    });
    
    client/templates/notifications/notifications.js

    コミット 11-2

    Display notifications in the header.

    通知はエラーと違って、それほど難しくないと思うかもしれません。 確かにこの2つはとても似ている構造をしています。 しかし、1つだけキーとなる違いがあります: 私たちはクライアント-サーバー間を同期した適切なコレクションを作りました。 これは通知が永続的であるという意味であり、 私たちが同じユーザーアカウントを使う限り、 再読み込みするブラウザでも違うデバイスでもこの通知は健在です。

    試しにやってみます: 2つめのブラウザーを開きます。(Firefoxだとしましょう) 新しいユーザーアカウントを作って、あなたのメインアカウントで作った投稿にコメントします。 (Chromeを開いたままで)すると、このようになっているはずです。

    Displaying notifications.
    Displaying notifications.

    通知へのアクセスをコントロールします。

    通知はうまく動いています。しかし、少し問題があります:この通知は公開されているのです。

    2つめのブラウザーを開いたままだとしたら、ブラウザーコンソールで次のようなコードを動かしてみましょう。

     Notifications.find().count();
    1
    
    Browser console

    (コメントした)この新しいユーザーはすべての通知を持つべきではありません。 ユーザーがNotificationsコレクションで見ることができる通知は originalなユーザーに属しているはずです。

    潜在的なプライベートの問題はさておき、 私たちはすべてのユーザーがブラウザで読み込まれるすべてのユーザーの通知を保有する余裕はありません。 十分に大きなサイトでは、 これはブラウザが使用できるメモリをオーバーロードして、深刻なパフォーマンスの問題をもたらすことになります。

    この問題はパブリケーションで解決します。 各ブラウザーで共有させたいコレクションの部分を正確に規定するために、私たちはパブリケーションを使います。

    これをするために、私たちはNotifications.find()よりも パブリケーションで違ったカーソルを返す必要があります。 つまり、私たちは現在のユーザーの通知に対応するカーソルを返したいのです。 

    そうすることは、十分に簡単です。 publish関数はthis.userIdで使うことができる現在のユーザーの_idを持つためです。

    Meteor.publish('notifications', function() {
      return Notifications.find({userId: this.userId, read: false});
    });
    
    server/publications.js

    コミット 11-3

    Only sync notifications that are relevant to the user.

    私たちが 2つのブラウザーウィンドウをチェックすると、 2つの異なるnotificationsコレクションを見ることができます。:

     Notifications.find().count();
    1
    
    Browser console (user 1)
     Notifications.find().count();
    0
    
    Browser console (user 2)

    実際に、アプリにログインしたりログアウトした時に、通知のリストが変化するべきです。  それはユーザーアカウントが変化するたびに、 パブリケーションが自動的に再パブリッシュするためです。

    我々のアプリは、ますます機能的になりつつあり、多くのユーザーが参加し、 リンクの投稿を開始として、我々は決して終わることのないホームページで終わる危険性があります。 私たちは、ページネーションを実装することによって、次の章でこれに対処します。