こんにちは。
この春に無事大学を卒業したので、KRAYアルバイトから社員に転職しました、浅海です。
最近、JavascriptのリアルタイムWebアプリケーションフレームワークのMeteorで遊びました。
リアルタイムWebアプリケーションを簡単に作ることができますので「最近流行りのリアルタイムWeb、一度やってみたいなー、でも難しそうだなー」と思っている方におすすめです!
この記事ではグーグルマップ上で会話できるリアルタイムチャットの作り方を解説します。
目次
注意事項
・この記事を執筆時点のMeteorのバージョンは0.42です。
・「コマンド一発でインストール!!」とか書きましたが、これは自分が使っているMacでの話です。
windowsのcygwinなど、それらの環境でどうなるかは試してません。
・一部、コードをどこに書くかが非常にわかりづらいと思います。
完成品はgithubに公開しているので、そちらもよろしくお願いします。
https://github.com/asaumi/geochat
・「JavaScriptのみ!」と言いつつ、CoffeeScriptで書いています。
Meteorを始める
Meteorとは
昨年末に突如登場したリアルタイムwebアプリケーションをさくさく作れるというフレームワークです。
「コードを更新すると自動でブラウザがリロードされる!」
「リアルタイムチャットが5分で作れる!!」
「インストールやデプロイがコマンド一発!!!」
などと、大きな話題になりました。
とことん開発者の負担を減らそうという試みがされており、驚くほど短時間で、動作するリアルタイムWebアプリケーションができてしまいます。
しかし、現状のMeteorには、MongoDBの更新は誰でもできてしまう(ブラウザからデータ全削除のクエリなども簡単に送れる)ため、使いどころは限られてきます。
将来的に、Meteorの開発元は「Galaxy」という有償版を作るらしいので、憶測ですが、認証機能はそちらに入るのかもしれません。
Meteorのインストール
以下のコマンドを使うだけでインストールが済みます。
ちょろい。
[cc lang="bash"]
$ curl https://install.meteor.com | /bin/sh
[/cc]
プロジェクトの作成
meteor createの後に、プロジェクト名を指定します。
今回は、geochatと命名しました。
[cc lasn="bash"]
$ meteor create geochat
[/cc]
すると、geochatというディレクトリが作られ、その中には
- geochat.css
- geochat.html
- geochat.js
という3つのファイルが作成されます。
CoffeeScriptとjQueryのインストール
普段Railsばかりやっている自分は、CoffeeScriptとjQueryがないと何もできないペーペーなので使います。
ちなみにsassも使えるようです。
[cc lang="bash"]
$ meteor add coffeescript
$ meteor add jquery
[/cc]
Meteorの起動
起動はただ単にmeteorを入力するだけです。
それだけでは味気ないので、MPが余ってる人は声高らかに「メテオッッ!!」と唱えながらEnterを叩くことをおすすめします。
[cc lang="bash"]
$ meteor
[/cc]
まっさらな状態にする
ここで、 http://localhost:3000/
にアクセスした結果は以下です。
あなたの予想に反して、このページが見えているでしょうか?
これは、「Click」というボタンを押すとJavaScriptのコンソールに文字が出力されるというサンプルプログラムです。
こんなものは全て消し去りましょう。
coffeeで開発するので、geochat.jsをgeochat.coffeeに改名します。
[cc lang="bash"]
$ mv geochat.js geochat.coffee
[/cc]
そして、以下のように書き換えます。
[cc lang="coffeescript"]
if Meteor.isClient
; # クライアント側の処理
if Meteor.isServer
; # サーバ側の処理
[/cc]
geochat.htmlは以下のように書き換えます。
[cc lang="html"]
[/cc]
これでまっさらなプロジェクトができました。
CSSの設定
この記事の最終的なCSSを当ててしまいます。
geochat.cssを以下に書き換えてください。
[cc lang="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%; }
[/cc]
リアルタイムチャットの作成
ではまず、普通のリアルタイムチャットを作ってみます。
Meteorならお手軽です。
ユーザーのセッション管理
Meteorでのセッション管理は、公式のこのサンプルが参考になります。
http://meteor.com/examples/wordplay
まずはチャットにアクセスしたユーザ情報を格納するUserコレクションを作成します。
サーバのMongoDBにデータを保存するには、Meteor.Collectionクラスをnewします。
この宣言は、クライアント側でもサーバ側でも必要なので geochat.coffee の先頭に記述します。
[cc lang="coffeescript"]
User = new Meteor.Collection('users')
now_time = -> (new Date()).getTime() # 現在時刻取得
[/cc]
ついでに現在時刻を取得する関数もついかしました。
クライアント側
クライアント側のコードは以下のようにします。
[cc lang="coffeescript"]
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秒毎
[/cc]
データベースからデータを取り出すにはfind、新規に挿入するならinsert、更新するならupdate、削除するならremove。
どれもmongoDBのクエリと同じような形式になっています。
サーバ側
これだけでは、Userを作ったら作りっぱなしになってしまいます。
一定時間アクセスの無いUserは削除する処理をサーバ側に記述しましょう。
以下のようにします。
[cc lang="coffeescript"]
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
[/cc]
リアルタイムにブラウザがリロードされるので、開発のタイミング次第ではゴミデータが発生するかもしれません。
それの削除のための処理を入れています。
参加人数表示
HTMLを変更して画面に人数を出力するようにします。
geochat.htmlのbody以下を以下のように書き換えます。
[cc lang="html"]
[/cc]
{{> }}という括弧の中で指定するのは、templateタグのname属性です。
こうすることにより、{{> }}の中は対応するtemplateタグの中で置き換わります。
templateタグの中に 「>」 の無い {{ }} という括弧があります。
これは、クライアント側のスクリプトと対応づいており、以下のようにCoffeeScriptに記述すると
[cc lang="coffeescript"]
# if Meteor.isClientの中に書く
Template.users.count = ->
User.find().count() # ユーザ数を返す
[/cc]
usersテンプレートのcountは、Template.users.countの戻り値、つまりユーザの総数に置き換わります。
Template.~~ の部分は、サーバのDBに更新があると自動で実行されるため、データの更新に紐付いたリアルタイムな処理を書く場合は、Templateを利用します。
発言できるようにする
チャットらしく、発言をできるようにします。
発言を保存するMessageコレクションを宣言するので、geochat.coffeeファイルの頭に以下を追加します。
[cc lang="coffeescript"]
Message = new Meteor.Collection('messages')
[/cc]
HTML
HTMLに発言の入力欄と発言を表示する場所を作ります。
bodyの適当な部分に
[cc lang="html"]
{{> controlls}}
{{> messages}}
[/cc]
を追加し、bodyタグの外に
[cc lang="html"]
-
{{#each messages}}
{{> message}}
{{/each}}
[/cc]
を追加します。
clickイベントを扱う
文章を入力するフォームと、入力内容を送信するボタンを設置しました。
これを動作させるには、フォームのイベントを扱う必要があります。
Meteorでテンプレートのイベントを扱う場合の書き方は以下です。
[cc lang="coffeescript"]
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('') # 入力フォームを空っぽに
[/cc]
「click #submit-message」は、見ての通り#submit-messageがクリックされた時の処理です。
このTemplate.controlls.eventsに複数のイベントを登録でき、clickの他にも色々と登録できます。
公式のドキュメントでは以下の部分です。
http://docs.meteor.com/#eventmaps
発言内容を表示する
他にgeochat.coffeeに何を書く必要があるかはここまで読んでいればわかると思います。
Template.messages.messages
Template.message.body
[cc lang="coffeescript"]
Template.messages.messages = ->
Message.find({}, {sort: {created_at: -1}}) # 発言順にMessage全部を返す
Template.message.body = ->
@body # thisはMessageのカーソル
[/cc]
を追加します。
Template.messages.message.bodyでは、thisはMessageのカーソルのため、「@body」とすれば発言本体を取得できます。
サーバ側
サーバ側にはある程度時間の経過したMessageを削除する処理を入れます。
最初に作ったMeteor.Intervalの中に
[cc lang="coffeescript"]
# 60秒以上経過した発言を削除
Message.remove({created_at: {$lt: now_time() - batch_interval * 4}})
[/cc]
を追加します。
ここまでやれば、下の画像のように匿名リアルタイムチャットが完成しているはずです。
簡単ですね。
Googleマップとの連携
簡単にチャットができてしまったので、次はGoogleマップを利用して少し凝ったことをしてみます。
Googleマップの表示
Googleマップの表示は、Meteor独自のことは特にしません。
ただ単にGoogle Maps APIを利用するだけです。
HTML
htmlのhead内でGoogle Maps APIを読み込みます。
位置情報を使う予定なので、sensor=true のオプションを付けておきます。
[cc lang="html"]
[/cc]
googleマップの初期化
クライアント側のcoffeeにあるMeteor.startupの中で、以下を記述します。
[cc lang="coffeescript"]
map = null # クライアント側スクリプトの直下で宣言
#Meteor.startup -> # この行の下あたりに追加
map_canvas = $('
map = new google.maps.Map map_canvas[0],
zoom: 6
center: new google.maps.LatLng(36.031332,137.805908)
mapTypeId: google.maps.MapTypeId.ROADMAP
[/cc]
座標は適当です。
画像のように、日本が見えていると思います。
位置情報を取得する
ボタンを押したら現在位置を取得するようにします。
ボタンの設置はhtmlのcontrollsテンプレートに以下を追加します。
[cc lang="html"]
[/cc]
発言フォームを作った時のように、Template.controlls.eventsへ先ほどのボタンをクリックしたときの動作を追加します。
[cc lang="coffeescript"]
# 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}}
[/cc]
現在位置にマーカーを表示する
このマーカーを、チャットに来たユーザの現在地点に出すわけです。
html
マーカーの表示はUserのデータが更新された時に変更するべきなので、Template内でリアルタイム処理をします。
HTMLのusersテンプレートを以下の様に書き換えます。
[cc lang="html"]
{{#each users}}
{{> user}}
{{/each}}
{{marker}}
[/cc]
マーカーの表示
HTMLにuserテンプレートを作成したので、Userが更新された時に、Template.user.markerが実行されるようになりました。
なので以下のコードをクライアント側に書き加えます。
[cc lang="coffeescript"]
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)
''
[/cc]
これで、最初のほうで設置した「現在位置を公開する」ボタンを押すとマーカーが地図に表示されます。
Userの削除を検知する
一定時間アクセスの無いユーザは、サーバ側で削除しています。
なので、ユーザが削除されたことを検知してマーカーも削除しなければいけません。
それが以下の処理です。
[cc lang="coffeescript"]
Template.user.destroyed = ->
return unless markers[@data._id]?
markers[@data._id].setMap(null)
markers[@data._id] = null
[/cc]
発言を地図上に表示する
これが最後のステップです。
チャットで発言した言葉を、以下のようにポップアップで出してみましょう。
google maps apiではInfoWindowと呼ばれているので、info_windowとします。
クライアント側のコードに下の位置行をいれましょう。
[cc lang="coffeescript"]
info_windows = {}
[/cc]
Template.user.markerに一行書き加えます。
[cc lang="coffeescript"]
# この行の次にいれる
# markers[@_id] = new google.maps.Marker(position: position, map: map)
info_windows[@_id] = new google.maps.InfoWindow
[/cc]
発言を表示している部分で、info_windowを設定します。
Template.message.bodyを以下のように書き換えましょう。
[cc lang="coffeescript"]
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]?
[/cc]
データが削除されたときにだけ実行したいときは、Templateのdestroyedに関数を登録します。
それと一応、userが削除されたときに発言の表示も削除します。
[cc lang="coffeescript"]
# Template.user.destroyed = -> の最後に追加
return unless info_windows[@data._id]?
info_windows[@data._id].close()
info_windows[@data._id] = null
[/cc]
完成!!
お疲れ様です!これでリアルタイムの地図チャットができました!
こんな感じになっているはずです。
リアルタイムですね。
作ったアプリケーションを公開する
Meteorはアプリケーションの公開方法も極限まで簡略化されています。
デプロイ
デプロイするにはMeteorのdeployコマンドを実行するだけです。
以下のコマンドでデプロイ後は、geochat.meteor.comで見れるようになります。
誰でも同じサブドメインでデプロイされてしまってはこまるので、パスワード認証を付けたい場合は、--passwordオプションを付けます。
[cc lang="bash"]
$ meteor deploy --password geochat
[/cc]
デプロイが非常に簡単ですが、残念なことにこのmeteor.comドメインを作ったデプロイは、将来的には廃止されるようです。
完成品
完成品はこちらで公開しています。
http://geochat.meteor.com
自前のサーバにデプロイする方法は公式のドキュメントに載っています。
http://docs.meteor.com/#deploying
どうやら、
[cc lang="bash"]
$ meteor bundle myapp.tgz
[/cc]
とすると、必要なパッケージ含む全てをtgzとして出力してくれます。
これを自前のサーバに持っていって、解凍、実行するようです。
おまけ
Meteorで遊んで、好き勝手に改造したバージョンです。
縮尺を調整したり、エンターキーで発言できたりします。
ソースコード
githubにコードを公開しました。
ブログで紹介したものはmasterブランチ、機能追加したものはexブランチにあります。
https://github.com/ttakuru88/geochat
宣伝
ここからは宣伝です。
自社サービス Tripclip
http://intg.kray.jp/news/tripclip/
旅のアルバムを簡単に作れるサービス Tripclipをリリースしました。
散歩や旅行で撮ったスナップ写真をブラウザにドラッグ&ドロップするだけで、きれいなアルバムができあがります。
http://tripclip.jp
東京Ruby会議10
東京Ruby会議10にて、弊社のダニーが「Inside Tripclip」というお題でTripclipの技術的な話を発表します。
http://tokyo10.rubykaigi.info/program.html
乞うご期待ください!
- トラックバック
「いいね!」で応援よろしくお願いします!