Meteorを利用した簡易的なチャットアプリの作成

昨年末に1.0がリリースされたMeteorを少し勉強してみたので備忘録代わりに記録します。

Meteorの魅力の一つはサーバとクライアントがリアルタイムに連携したアプリケーションを手軽に構築できることです
今回は簡単なチャットプログラムを作成してみました。

なお、筆者の環境は以下のとおりです。

* 開発OS :OS X バージョン10.10.2（Yosemite）
* ブラウザ:Google Chrome 40.0.2214.94 (64-bit)

説明はこの環境を前提に進めさせていただきます。

# Meteorのインストール

インストールは簡単です。
以下のコマンドを実行することで完了します。

> curl https://install.meteor.com | sh

# プロジェクトの作成

まずはアプリケーションのプロジェクトを作成します。
今回は「mychat」というプロジェクトを作成してみました。

> meteor create mychat

作成が終わるとmychatというフォルダが作成されています。
中を確認すると以下の３つのファイルが作成されているはずです。

+ mychat.css
+ mychat.html
+ mychat.js

# HTMLの作成

各ファイルの中身を見るとサンプルコードが自動生成されていますが、今回は一から作るのを目的としますので全部消しちゃいましょう。

その上でまずはmychat.htmlに以下のHTMLを記載します。
&lt;HTML&gt;タグがないのに注意です。

```mychat.html
<head>
  <title>MyChat</title>
</head>
<body>
  <h1>My Chat</h1>
  {{> commentList}}
  {{> inputForm}}
</body>
```

ここで一部見慣れない行があるかと思います。
例えばこれ
>  {{> commentList}}

これは、commentListというTemplateをこの位置に挿入するという意味を持ちます。
次にこのTemplateを作成します。

# Templateの作成

Templateを利用することでUIとそれに紐づくロジック（Javascript)を制御することができるので便利です。

今回は、以下の２つのTemplateを作成することにしました。

|Template名  |機能          |
|:-----------|:------------|
| commentList|入力されたコメントリストを表示|
| inputForm|コメント入力用フォーム |


まずはinputFormのTemplateを作成してみます。
先ほどのmychat.htmlに以下のコードを追加します。

```mychat.html
<template name="inputForm">
        <form>
                <input type="text" name ="inputComment">
                <input type="submit" value="Add Comment">
        </form>
</template>
```

TemplateとしてまとめたいHTML要素を&lt;template&gt;タグ内に記述します。
name属性でこのTemplateの名前を定義します。
これは特に難しくないですね。

同様にcommentListの方も定義します。

```mychat.html
<template name="commentList">
  {{#each comment}}
        <li class="comment">{{commentStr}}</li>
  {{/each}}
</template>
```

ここでまた見慣れない構文がでてきました。

>  {{#each comment}}
>  ...
>  {{/each}}

これは、Templateの動作を制御するブロックヘルパーと呼ばれるものの一種で、{{#each}}...{{/each}}は繰り返しを意味します。
この場合はcomment内に含まれる要素分繰り返すことを意味します。

では、このcommentはどこから来たのでしょうか？

実はTemplateに紐付けられたJavascriptコードの中に含まれているのです。
次はmychat.jsファイルを見てみましょう。

# Templateヘルパー及びイベントの定義

これからJavascriptコードを定義していきますが、説明の関係上、段階的に記載していきます。

まずは空のmychat.jsに以下のコードを追加します。

```mychat.js
if (Meteor.isClient) {
}
if (Meteor.isServer) {
}
```

Meteorの特徴は、Client側のアプリケーションもServer側のアプリケーションもJavascriptで記述できることです。
この点はClientとServerでコードを共有することが容易であるというメリットもありますが、ClientとServerで明示的に実行するコードが明確に異なる場合もあります。（というか、そういうケースがほとんどですね）

上記のMeteor.isClientもしくはMeteor.isServerで判定された内部のコードはそれぞれClient及びServerにおいてでしか実行されません。

>コードをファイル分割し、配置するフォルダで実行範囲を定義するという方法もあります。今回は説明の関係上１ファイルで進めたいのでこの手法で進めます。

先ほど定義した２つのTemplateに紐付いたロジックをisClient以下に記述します。

```mychat.js
if (Meteor.isClient) {
  Template.commentList.helpers({
    'comment': function () {
        //後ほど定義 
    }
  });
  Template.inputForm.events({
    'submit form': function (event) {
        //後ほど定義
    }
  });
}
```
Templateに紐づくロジックとしてヘルパーとイベントがあります。
ヘルパーは必要なデータを取得するための機能、イベントはイベント処理機能です。

上記のコードでは、commentListにはヘルパー、inputFormにはイベントを定義しています。
先ほどcommentListテンプレートで定義されていたcommentがここで出てきていますね。

しかし、まだこのコードは不完全です。
中身を定義していません。

# データアクセス処理の追加

ここで今回の目的を思い出してみましょう。
今回の目的はチャットアプリを作成することです。

そのためには、以下の仕組みが必要です。

+ inputFormで設定したコメントを保存
+ 上記で保存したコメントを取得しcommentListに表示

両者を紐づけるにはコメント保持する仕組みが必要です。

実はMeteorにはMongoDBが内包されています。
MongoDBはドキュメント指向のDBです。
データをJSONライクな形式で保持できるためJavascriptと親和性が高いです。

コメントを保存する領域としてcommentsというCollectionをMongoDBに作成します。
mychat.jsの先頭に以下のコードを追加してください。

```mychat.js
ChatComments = new Mongo.Collection('comments');
if (Meteor.isClient) {
   //省略
```

次にコメント追加する機能をinputFormのイベント処理に追加します。

```mychat.js
  Template.inputForm.events({
    'submit form': function (event) {
        event.preventDefault();
        var cmt = event.target.inputComment.value;
        ChatComments.insert({
                commentStr:cmt
        });
    }
  });
```
入力されたコメントを取得し、用意したCollectionにinsertしています。
event.preventDefault()はform処理の時に画面をリフレッシュしようとしてしまいますのでその防止です。

次にコメント取得です。
同様にcommentListのcommentヘルパーに追加します。

```mychat.js
  Template.commentList.helpers({
    'comment': function () {
        return ChatComments.find(); 
    }
  });
```
これで一通りの実装は終わりました。

# Meteorの起動

いよいよMeteorの起動です。
mychatフォルダに移動して以下のコマンドを実行してください。

> meteor run

起動完了後、ブラウザでlocalhost:3000にアクセスしてみましょう。

![スクリーンショット 2015-02-04 21.57.49.png](https://qiita-image-store.s3.amazonaws.com/0/64855/d3d64c4e-d65c-f946-cc3c-55210fc2d373.png)

おお！動いた！！
コメントを入力してAdd Commentボタンを押下するとコメントが追加されます。
これにてチャット作成完了！！
お疲れ様でした！

・・・なんでしょうか。
本当に？

このアプリにはユーザの概念がありません。
これではまるで寂しい独り言掲示板です。

ちゃんとしたチャットにするためにはユーザの概念が必要です。

# ログイン機能の追加

来ました、これ、ログイン。
めちゃめちゃ重そうな機能です。

+ ユーザ管理の仕組み
+ ログイン処理
+ UIの作成
+ その他

一体どれだけコードを書かないといけないのでしょうか？

でも安心してください。
たった数Stepのことをやるだけで完了します。

Meteorには非常にパワフルなパッケージが用意されています。
その中で今回は

+ accounts-password
+ accounts-ui

の２つを使います。

まずは準備のため実行中のMeteorをCtrl+Cで終了しましょう。

その後、パッケージの追加を行います。

>meteor add accounts-password

これでアプリケーションにユーザ管理機能が追加されました。
次にUI機能を追加します。

>meteor add accounts-ui

UIについてはこれだけでは利用できません。
最後にHTMLとaccounts-ui紐付けるために{{> loginButtons}}をbody内に追加します

HTMLを以下のとおり修正してください

```mychat.html
<body>
  {{> loginButtons}}
  <h1>My Chat</h1>
  {{> commentList}}
  {{> inputForm}}
</body>
<!--以下略-->
```

たったこれだけです。
再度Meteorを起動してブラウザで見てましょう。

![スクリーンショット 2015-02-04 22.55.40.png](https://qiita-image-store.s3.amazonaws.com/0/64855/d21334d1-2dbb-c871-9b60-9090d7acd384.png)

おお！ログインできるようになっています！

# アカウント情報に紐付いた機能の追加

最後に以下の機能を追加します。

+ ログインしていない状態ではチャットコメントとコメント入力UIは表示されないようにする
+ コメントの発言者がわかるようにE-mailアドレスを表示する

以降の修正はMeteorは終了せずにそのまま行ってください。


## ログイン状態による表示切り替え

まずは、ログインしないとUIが表示されないようにしましょう。
以下の通り、各Templateの開始点と終了点にコードを追加してください。

```mychat.html
<template name="commentList">
  {{#if currentUser}}
  <!--中略-->
  {{/if}}
</template>

<template name="inputForm">
  {{#if currentUser}}
  <!--中略-->
  {{/if}}
</template>
```

{{#if}}...{{/if}}は条件判定用のブロックヘルパーです。
今回はcurrentUserという値をチェックし、ログインの有無を確認しています。

## 発言者情報の表示

次に表示されるコメントに発信者情報としてE-mailアドレスを表示させましょう。
このためには、以下の２つの修正が必要です。

+ Collectionにデータを追加する際にE-mail情報も併せて付加する
+ コメント表示時にE-mail情報も表示するようにする

まずはデータ追加時の処理を変更します。
mychat.js上のsubmit formイベント処理を少し修正します。

```mychat.js
    'submit form': function (event) {
        //中略
        var eMail = Meteor.user().emails[0].address;
        ChatComments.insert({
                commentStr:cmt,
                userStr:eMail
        });
    }
```
これでE-mail情報も一緒に保存されるようになりました。
次に表示側の処理を修正しましょう。
commentListテンプレートを以下のとおり変更しましょう。

```mychat.html
<template name="commentList">
  {{#if currentUser}}
  {{#each comment}}
        <li class="comment">{{commentStr}}({{userStr}})</li>
  {{/each}}
  {{/if}}
</template>
```

Meteorを起動したまま修正を行っていたと思いますが、ファイルの変更が動的に表示中のブラウザに反映されていたことに気付いたでしょうか？
ブラウザをリロードすることなくアプリケーションが最新の状態になっている。
これはClientとServerが連携して動作するMeteorというフレームワークの強みです。
Serverにぶら下がるだけだったWebアプリケーションから大きな転換ではないでしょうか？

しかしながら、表示は変わりましたが以前登録していたコメントの発言者情報は空欄のままです。
これはロジックの問題ではなくデータの問題です。
過去に登録したデータにはuserStr要素が含まれていません。
過去のデータは一旦消去してしまいましょう。

Meteorにはデータを初期化するコマンドがあります。
一旦、Meteorを終了し、以下のコマンドを実行してください。

> meteor reset

その後、再度Meteorを起動し、ログイン後にコメントを追加してみてください。
更に別端末から別アカウントで同様にやってみてください。

投稿内容が即時にお互いの頁に即時に反映されています！
とりあえず簡単なチャットアプリが完成しました。

![スクリーンショット 2015-02-04 22.55.16.png](https://qiita-image-store.s3.amazonaws.com/0/64855/f2cf089a-5e76-6ccd-7261-d99ffcfc6d7f.png)

これまで修正してきたコードを最後に添付します。

```mychat.html
<head>
  <title>MyChat</title>
</head>
<body>
  {{> loginButtons}}
  <h1>My Chat</h1>
  {{> commentList}}
  {{> inputForm}}
</body>

<template name="commentList">
  {{#if currentUser}}
  {{#each comment}}
        <li class="comment">{{commentStr}}({{userStr}})</li>
  {{/each}}
  {{/if}}
</template>

<template name="inputForm">
        {{#if currentUser}}
        <form>
                <input type="text" name ="inputComment">
                <input type="submit" value="Add Comment">
        </form>
        {{/if}}
</template>
```

```mychat.js
ChatComments = new Mongo.Collection('comments');
if (Meteor.isClient) {
  Template.commentList.helpers({
    'comment': function () {
        return ChatComments.find(); 
    }
  });
  Template.inputForm.events({
    'submit form': function (event) {
        event.preventDefault();
        var cmt = event.target.inputComment.value;
        var eMail = Meteor.user().emails[0].address;
        ChatComments.insert({
                commentStr:cmt,
                userStr:eMail
        });
    }
  });
}

if (Meteor.isServer) {
}
```
長く説明しましたがコードとしてはこれだけです。
すごくシンプルに開発ができることがわかると思います。

# 次回について

機能的な部分については作成しましたが、Meteor.isServer以下は空のままです。
ここは不要だったのでしょうか？
実はこのアプリケーションにはセキュリティ面でいろいろと問題があります。
それらの問題の対策時にMeteor.isServerは使いますのでご心配なく。
次回は今回開発したアプリケーションをベースにその辺りを説明したいと思います。