JavaScriptのみ!Meteorで作る簡単リアルタイムWebアプリ このエントリをはてなブックマークに登録

2012年10月19日

takurutakuru / , , ,

こんにちは。
この春に無事大学を卒業したので、KRAYアルバイトから社員に転職しました、浅海です。

最近、JavascriptのリアルタイムWebアプリケーションフレームワークのMeteorで遊びました。
リアルタイムWebアプリケーションを簡単に作ることができますので「最近流行りのリアルタイムWeb、一度やってみたいなー、でも難しそうだなー」と思っている方におすすめです!

この記事ではグーグルマップ上で会話できるリアルタイムチャットの作り方を解説します。

完成品はこちら

目次

  1. 注意事項
  2. Meteorを始める
  3. リアルタイムチャットの作成
  4. Googleマップとの連携
  5. 作ったアプリケーションを公開する
  6. 宣伝

注意事項

・この記事を執筆時点のMeteorのバージョンは0.42です。

・「コマンド一発でインストール!!」とか書きましたが、これは自分が使っているMacでの話です。
windowsのcygwinなど、それらの環境でどうなるかは試してません。

・一部、コードをどこに書くかが非常にわかりづらいと思います。
完成品はgithubに公開しているので、そちらもよろしくお願いします。
https://github.com/asaumi/geochat

・「JavaScriptのみ!」と言いつつ、CoffeeScriptで書いています。

Meteorを始める

Meteorとは

昨年末に突如登場したリアルタイムwebアプリケーションをさくさく作れるというフレームワークです。
「コードを更新すると自動でブラウザがリロードされる!」
「リアルタイムチャットが5分で作れる!!」
「インストールやデプロイがコマンド一発!!!」
などと、大きな話題になりました。

とことん開発者の負担を減らそうという試みがされており、驚くほど短時間で、動作するリアルタイムWebアプリケーションができてしまいます。

しかし、現状のMeteorには、MongoDBの更新は誰でもできてしまう(ブラウザからデータ全削除のクエリなども簡単に送れる)ため、使いどころは限られてきます。
将来的に、Meteorの開発元は「Galaxy」という有償版を作るらしいので、憶測ですが、認証機能はそちらに入るのかもしれません。

Meteorのインストール

以下のコマンドを使うだけでインストールが済みます。
ちょろい。

$ curl https://install.meteor.com | /bin/sh

プロジェクトの作成

meteor createの後に、プロジェクト名を指定します。
今回は、geochatと命名しました。

$ meteor create geochat

すると、geochatというディレクトリが作られ、その中には

  1. geochat.css
  2. geochat.html
  3. geochat.js

という3つのファイルが作成されます。

CoffeeScriptとjQueryのインストール

普段Railsばかりやっている自分は、CoffeeScriptとjQueryがないと何もできないペーペーなので使います。
ちなみにsassも使えるようです。

$ meteor add coffeescript
$ meteor add jquery

Meteorの起動

起動はただ単にmeteorを入力するだけです。
それだけでは味気ないので、MPが余ってる人は声高らかに「メテオッッ!!」と唱えながらEnterを叩くことをおすすめします。

$ meteor

まっさらな状態にする

ここで、 http://localhost:3000/
にアクセスした結果は以下です。

あなたの予想に反して、このページが見えているでしょうか?

これは、「Click」というボタンを押すとJavaScriptのコンソールに文字が出力されるというサンプルプログラムです。
こんなものは全て消し去りましょう。

coffeeで開発するので、geochat.jsをgeochat.coffeeに改名します。

$ mv geochat.js geochat.coffee

そして、以下のように書き換えます。

if Meteor.isClient
  ; # クライアント側の処理

if Meteor.isServer
  ; # サーバ側の処理

geochat.htmlは以下のように書き換えます。

<head>
  <title>geochat</title>
</head>
<body>
</body>

これでまっさらなプロジェクトができました。

CSSの設定

この記事の最終的なCSSを当ててしまいます。
geochat.cssを以下に書き換えてください。

html { height: 100%; }
body { height: 100%; margin: 0px; padding: 0px; }
#chat{ height: 100%; width: 25%; float: left; padding-top: 10px; }
#chat ol{ margin: 0; }
#chat .user-count{ text-align: center; line-height: 2;}
#map_canvas { height: 100%; width: 75%; }
#controlls{ text-align: center; margin: 0px; padding: 0px; }
#controlls input{ margin-bottom: 20px; }
.message{ list-style: none; line-height: 150%; }

リアルタイムチャットの作成

ではまず、普通のリアルタイムチャットを作ってみます。
Meteorならお手軽です。

ユーザーのセッション管理

Meteorでのセッション管理は、公式のこのサンプルが参考になります。
http://meteor.com/examples/wordplay

まずはチャットにアクセスしたユーザ情報を格納するUserコレクションを作成します。

サーバのMongoDBにデータを保存するには、Meteor.Collectionクラスをnewします。
この宣言は、クライアント側でもサーバ側でも必要なので geochat.coffee の先頭に記述します。

User = new Meteor.Collection('users')

now_time = -> (new Date()).getTime() # 現在時刻取得

ついでに現在時刻を取得する関数もついかしました。

クライアント側

クライアント側のコードは以下のようにします。

if Meteor.isClient
  # Meteorの準備が整ったら実行される部分
  Meteor.startup ->
    unless User.find(_id: Session.get('user_id')).count() # Userが無かったら
      user_id = User.insert(last_keepalive: now_time()) # Userを新規作成
      Session.set('user_id', user_id) # user_idをSessionに記憶

  Meteor.setInterval ->
    # user_idの一致するUserの時間を更新する
    User.update {_id: Session.get('user_id')}, {$set: {last_keepalive: (new Date()).getTime()}}
  , 10 * 1000 # 10秒毎

データベースからデータを取り出すにはfind、新規に挿入するならinsert、更新するならupdate、削除するならremove。
どれもmongoDBのクエリと同じような形式になっています。

サーバ側

これだけでは、Userを作ったら作りっぱなしになってしまいます。
一定時間アクセスの無いUserは削除する処理をサーバ側に記述しましょう。
以下のようにします。

if Meteor.isServer
  batch_interval = 15*1000 # 15秒

  Meteor.setInterval ->
    # 変なデータ削除
    User.remove({last_keepalive: undefined})

    # 150秒以上更新の無いUserを削除
    User.remove({last_keepalive: {$lt: now_time() - batch_interval * 10}})

  , batch_interval

リアルタイムにブラウザがリロードされるので、開発のタイミング次第ではゴミデータが発生するかもしれません。
それの削除のための処理を入れています。

参加人数表示

HTMLを変更して画面に人数を出力するようにします。
geochat.htmlのbody以下を以下のように書き換えます。

<body>
  <div id="chat">
    {{> users}}
  </div>
</body>

<template name="users">
  <div class="user-count">参加者 {{count}} 人</div>
</template>

{{> }}という括弧の中で指定するのは、templateタグのname属性です。
こうすることにより、{{> }}の中は対応するtemplateタグの中で置き換わります。

templateタグの中に 「>」 の無い {{ }} という括弧があります。
これは、クライアント側のスクリプトと対応づいており、以下のようにCoffeeScriptに記述すると

# if Meteor.isClientの中に書く
  Template.users.count = ->
    User.find().count() # ユーザ数を返す

usersテンプレートのcountは、Template.users.countの戻り値、つまりユーザの総数に置き換わります。
Template.~~ の部分は、サーバのDBに更新があると自動で実行されるため、データの更新に紐付いたリアルタイムな処理を書く場合は、Templateを利用します。

発言できるようにする

チャットらしく、発言をできるようにします。
発言を保存するMessageコレクションを宣言するので、geochat.coffeeファイルの頭に以下を追加します。

Message = new Meteor.Collection('messages')

HTML

HTMLに発言の入力欄と発言を表示する場所を作ります。

bodyの適当な部分に

  {{> controlls}}
  {{> messages}}

を追加し、bodyタグの外に

<template name="controlls">
  <div id="controlls">
    <input id="input-message" type="text" placeholder="発言する" />
    <input id="submit-message" type="button" value="発言" />
  </div>
</template>

<template name="messages">
  <ol>
    {{#each messages}}
      {{> message}}
    {{/each}}
  </ol>
</template>

<template name="message">
  <li class="message">{{body}}</li>
</template>

を追加します。

clickイベントを扱う

文章を入力するフォームと、入力内容を送信するボタンを設置しました。
これを動作させるには、フォームのイベントを扱う必要があります。

Meteorでテンプレートのイベントを扱う場合の書き方は以下です。

  Template.controlls.events
    'click #submit-message': (e) ->
      # 発言内容をDBに挿入
      Message.insert
        body: $('#input-message').val()
        user_id: Session.get('user_id')
        created_at: now_time()

      $('#input-message').val('') # 入力フォームを空っぽに

「click #submit-message」は、見ての通り#submit-messageがクリックされた時の処理です。
このTemplate.controlls.eventsに複数のイベントを登録でき、clickの他にも色々と登録できます。
公式のドキュメントでは以下の部分です。
http://docs.meteor.com/#eventmaps

発言内容を表示する

他にgeochat.coffeeに何を書く必要があるかはここまで読んでいればわかると思います。

Template.messages.messages
Template.message.body

  Template.messages.messages = ->
    Message.find({}, {sort: {created_at: -1}}) # 発言順にMessage全部を返す

  Template.message.body = ->
    @body # thisはMessageのカーソル

を追加します。
Template.messages.message.bodyでは、thisはMessageのカーソルのため、「@body」とすれば発言本体を取得できます。

サーバ側

サーバ側にはある程度時間の経過したMessageを削除する処理を入れます。

最初に作ったMeteor.Intervalの中に

  # 60秒以上経過した発言を削除
  Message.remove({created_at: {$lt: now_time() - batch_interval * 4}})

を追加します。

ここまでやれば、下の画像のように匿名リアルタイムチャットが完成しているはずです。

簡単ですね。

Googleマップとの連携

簡単にチャットができてしまったので、次はGoogleマップを利用して少し凝ったことをしてみます。

Googleマップの表示

Googleマップの表示は、Meteor独自のことは特にしません。
ただ単にGoogle Maps APIを利用するだけです。

HTML

htmlのhead内でGoogle Maps APIを読み込みます。
位置情報を使う予定なので、sensor=true のオプションを付けておきます。

  <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true">
  <script language="javascript"></script>

googleマップの初期化

クライアント側のcoffeeにあるMeteor.startupの中で、以下を記述します。

  map = null # クライアント側スクリプトの直下で宣言

 #Meteor.startup -> # この行の下あたりに追加

    map_canvas = $('<div>').attr('id', 'map_canvas').appendTo('body')
    map = new google.maps.Map map_canvas[0],
      zoom: 6
      center: new google.maps.LatLng(36.031332,137.805908)
      mapTypeId: google.maps.MapTypeId.ROADMAP

座標は適当です。
画像のように、日本が見えていると思います。

位置情報を取得する

ボタンを押したら現在位置を取得するようにします。

ボタンの設置はhtmlのcontrollsテンプレートに以下を追加します。

    <input id="set-current-position" type="button" value="現在位置を公開する" />

発言フォームを作った時のように、Template.controlls.eventsへ先ほどのボタンをクリックしたときの動作を追加します。

  # Template.controlls.events の中に記述
    'click #set-current-position': ->
      navigator.geolocation.getCurrentPosition (geo) ->
        User.update {_id: Session.get('user_id')}, {$set: {lat: geo.coords.latitude, lng: geo.coords.longitude}}

現在位置にマーカーを表示する

マーカーとはgoogleマップでよくみるコレです。

このマーカーを、チャットに来たユーザの現在地点に出すわけです。

html

マーカーの表示はUserのデータが更新された時に変更するべきなので、Template内でリアルタイム処理をします。
HTMLのusersテンプレートを以下の様に書き換えます。

<template name="users">
  <div class="user-count">参加者 {{count}} 人</div>
  {{#each users}}
    {{> user}}
  {{/each}}
</template>

<template name="user">
  {{marker}}
</template>

マーカーの表示

HTMLにuserテンプレートを作成したので、Userが更新された時に、Template.user.markerが実行されるようになりました。
なので以下のコードをクライアント側に書き加えます。

  markers = {} # マーカー保存用

  Template.users.users = ->
    User.find()

  Template.user.marker = ->
    # mapの初期化が終わる前に実行されることがあるためここでチェック
    return unless map and @lng? and @lat?

    position = new google.maps.LatLng(@lat, @lng)
    if markers[@_id] # マーカーが無ければ新規作成
      markers[@_id].setPosition(position) if position
    else # マーカーが既にあれば移動する
      markers[@_id] = new google.maps.Marker(position: position, map: map)
    ''

これで、最初のほうで設置した「現在位置を公開する」ボタンを押すとマーカーが地図に表示されます。

Userの削除を検知する

一定時間アクセスの無いユーザは、サーバ側で削除しています。
なので、ユーザが削除されたことを検知してマーカーも削除しなければいけません。
それが以下の処理です。

  Template.user.destroyed = ->
    return unless markers[@data._id]?
    markers[@data._id].setMap(null)
    markers[@data._id] = null

発言を地図上に表示する

これが最後のステップです。
チャットで発言した言葉を、以下のようにポップアップで出してみましょう。

google maps apiではInfoWindowと呼ばれているので、info_windowとします。
クライアント側のコードに下の位置行をいれましょう。

  info_windows = {}

Template.user.markerに一行書き加えます。

      # この行の次にいれる
      # markers[@_id] = new google.maps.Marker(position: position, map: map)
      info_windows[@_id] = new google.maps.InfoWindow

発言を表示している部分で、info_windowを設定します。
Template.message.bodyを以下のように書き換えましょう。

   Template.message.body = ->
    if info_windows[@user_id]
      info_windows[@user_id].setContent(@body)
      info_windows[@user_id].open(map, markers[@user_id])
    @body # body返すの忘れずに!

  # messageが時間経過で削除されたらinfo_windowを閉じる
  Template.message.destroyed = ->
    info_windows[@data.user_id].close() if info_windows[@data.user_id]?

データが削除されたときにだけ実行したいときは、Templateのdestroyedに関数を登録します。

それと一応、userが削除されたときに発言の表示も削除します。

# Template.user.destroyed = -> の最後に追加
    return unless info_windows[@data._id]?
    info_windows[@data._id].close()
    info_windows[@data._id] = null

完成!!

お疲れ様です!これでリアルタイムの地図チャットができました!
こんな感じになっているはずです。

リアルタイムですね。

作ったアプリケーションを公開する

Meteorはアプリケーションの公開方法も極限まで簡略化されています。

デプロイ

デプロイするにはMeteorのdeployコマンドを実行するだけです。
以下のコマンドでデプロイ後は、geochat.meteor.comで見れるようになります。

誰でも同じサブドメインでデプロイされてしまってはこまるので、パスワード認証を付けたい場合は、–passwordオプションを付けます。

$ meteor deploy --password geochat

デプロイが非常に簡単ですが、残念なことにこのmeteor.comドメインを作ったデプロイは、将来的には廃止されるようです。

完成品

完成品はこちらで公開しています。
http://geochat.meteor.com

自前のサーバにデプロイする方法は公式のドキュメントに載っています。
http://docs.meteor.com/#deploying
どうやら、

$ meteor bundle myapp.tgz

とすると、必要なパッケージ含む全てをtgzとして出力してくれます。
これを自前のサーバに持っていって、解凍、実行するようです。

おまけ

Meteorで遊んで、好き勝手に改造したバージョンです。
縮尺を調整したり、エンターキーで発言できたりします。

http://geochat_ex.meteor.com

ソースコード

githubにコードを公開しました。
ブログで紹介したものはmasterブランチ、機能追加したものはexブランチにあります。
https://github.com/ttakuru88/geochat

宣伝

ここからは宣伝です。

自社サービス Tripclip


http://kray.jp/news/tripclip/

旅のアルバムを簡単に作れるサービス Tripclipをリリースしました。
散歩や旅行で撮ったスナップ写真をブラウザにドラッグ&ドロップするだけで、きれいなアルバムができあがります。
http://tripclip.jp

東京Ruby会議10

東京Ruby会議10にて、弊社のダニーが「Inside Tripclip」というお題でTripclipの技術的な話を発表します。
http://tokyo10.rubykaigi.info/program.html
乞うご期待ください!

クレイについてもっと知りたい方は…

  1. クレイの3つの強みを見てみる。
  2. WEBシステムのことなら何でもご相談ください。

「いいね!」で応援よろしくお願いします!

このエントリーに対するコメント

  1. すごい良い!
    まだやってみてないけど
    完成とかおまけの使える!

    今度つくってみることにする

    ぼた餅

    2013年03月02日, 6:49 PM

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


トラックバック

we use!!Ruby on RailsAmazon Web Services

このページの先頭へ