前回の続き。今回はクライアント側のWebSocketの記述を中心に見ています。
chatRoom.scala.html
@(username: String)
@main(username) {
<div class="page-header">
<h1>Welcome to the chat room
<small>You are chatting as @username</small></h1>
</div>
<div id="onError" class="alert-message error">
<p>
<strong>Oops!</strong> <span></span>
</p>
</div>
<div id="onChat" class="row">
<div class="span10" id="main">
<div id="messages">
</div>
<textarea id="talk"></textarea>
</div>
<div class="span4">
<h2>Members</h2>
<ul id="members">
</ul>
</div>
</div>
<script type="text/javascript" charset="utf-8"
src="@routes.Application.chatRoomJs(username)"></script>
}
main
のViewにはめ込んで表示している。
#onError
のdiv
にエラー表示、#onChat
のdiv
の左の列にチャットの内容、右の列にルーム内の人一覧(Members)を表示している。一番下でjavascriptを読み込んでいるが、指定の仕方が独特である。routes
ファイルを見てみると以下のようになっている。
GET /assets/javascripts/chatroom.js
controllers.Application.chatRoomJs(username)
JSのsrc指定の部分は、実行時には
src="/assets/javascripts/chatroom.js?username=a"
と変換されるため、クエリ文字列を渡しながらJSを取得しにいくことになる。routesファイルの定義により、/assets/javascripts/chatroom.js
にアクセスした場合はApplication.java
のchatRoomJs
アクションが呼ばれることになっている。ここでJSを生成し、ブラウザ側に返している。
public static Result chatRoomJs(String username) {
return ok(views.js.chatRoom.render(username));
}
ちなみにviews
パッケージ内にchatRoom.scala.js
という名前で配置したJSのテンプレートファイルは、コンパイルされるとviews.js
パッケージに配置され、views.js.chatRoom
という名前でアクセスできるようになる模様。
chatRoom.scala.js
@(username: String)
$(function() {
var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
var chatSocket =
new WS("@routes.Application.chat(username).webSocketURL(request)")
var sendMessage = function() {
chatSocket.send(JSON.stringify(
{text: $("#talk").val()}
))
$("#talk").val('')
}
var receiveEvent = function(event) {
var data = JSON.parse(event.data)
// Handle errors
if(data.error) {
chatSocket.close()
$("#onError span").text(data.error)
$("#onError").show()
return
} else {
$("#onChat").show()
}
// Create the message element
var el = $('<div class="message"><span></span><p></p></div>')
$("span", el).text(data.user)
$("p", el).text(data.message)
$(el).addClass(data.kind)
if(data.user == '@username') $(el).addClass('me')
$('#messages').append(el)
// Update the members list
$("#members").html('')
$(data.members).each(function() {
var li = document.createElement('li');
li.textContent = this;
$("#members").append(li);
})
}
var handleReturnKey = function(e) {
if(e.charCode == 13 || e.keyCode == 13) {
e.preventDefault()
sendMessage()
}
}
$("#talk").keypress(handleReturnKey)
chatSocket.onmessage = receiveEvent
})
ここがクライアント側のWebSocketを担っている要となっている模様。
MozWebSocket
が定義してある場合はそっちを使うようFireFox対応がしてあるが、FF11からはWebSocket
に統一された模様。
var chatSocket =
new WS("@routes.Application.chat(username).webSocketURL(request)")
という記述をしておくと、実行時に
var chatSocket = new WS("ws:\/\/localhost:9000\/room\/chat?username=a")
というWebSocket用プロトコルのURLに変換してくれる。ここではそのURLを用いてWebSocketオブジェクトを作成している。同時に、非同期で、サーバとのWebSocketコネクションの確立も行われているようである(The WebSocket API (日本語訳))。
routesファイルを見てみると以下のようになっており、この時Application.java
のchat
メソッドが呼ばれるのがわかる。
GET /room/chat controllers.Application.chat(username)
この時、サーバ側でもWebSocketオブジェクトを作成しブラウザ側に返している。これでサーバとのWebSocketコネクションが確立する。
public static WebSocket<JsonNode> chat(final String username) {
return new WebSocket<JsonNode>() {
// Called when the Websocket Handshake is done.
public void onReady(WebSocket.In<JsonNode> in, WebSocket.Out<JsonNode> out){
// Join the chat room.
try {
ChatRoom.join(username, in, out);
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
}
chatRoom.scala.js
ではWebSocketコネクションの確立の他、
- sendMessage
- receiveEvent
- handleReturnKey
の3つの関数を定義している。
sendMessage
は、WebSocketへのメッセージ送信を行っており、テキストエリアの文字列をJSON化した後、WebSocketの.send()
メソッドでサーバに送信している。送信が終わったらテキストエリアをクリアしている。
receiveEvent
は、chatSocket.onmessage
と紐付けられており、WebSocketでメッセージを受けとった際に行う処理が書いてある。
onmessage
の引数にはWebSocketの戻り値としてJSONオブジェクトが帰ってくるので、まずそれをパースしている。JSONにエラーが入っていた場合はエラーを表示する。
エラーがない場合は、メッセージ用のdiv
を追加してチャットメッセージ用のdiv
の一番下に追加、ルームメンバー一覧もul
を毎回クリアして新たにli
を組み直している。自分の発言にはそれとわかるようCSSのクラスを追加したりしている。
handleReturnKey
は、テキストエリアでのEnterキーの挙動を定義している。
長くなってきたのでここまで。次回はサーバ側の挙動について見ていく。
Playframework付属のサンプル'websocket-chat'を見てみる(1/4)
Playframework付属のサンプル'websocket-chat'を見てみる(3/4)
Playframework付属のサンプル'websocket-chat'を見てみる(4/4)