2012-04-01
enchant.js + play2.0 + CoffeeScript
ちょっと気になって、enchant.jsを調べ始めた。
ExtJSもそうだけど、結局クライアント側のソースがJavaScriptだけ記述すれば良いのであれば、
JavaScriptを呼び出すだけのHTMLって余計というかいらないんだよね。
ということで、JavaScriptだけ指定して、実行できる環境をPlay2.0で作ってみた。
要は、こんな感じで指定すると、どこか知らないHTMLからXXX.jsを呼び出してくれるという感じ。
http://localhost/enchant/XXX.js
・conf/routes
GET /enchant/:script controllers.Application.enchant(script:String)
・app/controllers/Application.scala
package controllers import play.api._ import play.api.mvc._ object Application extends Controller { def enchant(script:String) = Action { Ok(views.html.enchantmain(script)) } }
・app/views/enchantmain.scala.html
@(scriptpath: String) <!DOCTYPE html> <html> <head> <title>@scriptpath</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,user-scalable=no"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-var-style" content="black-translucent"> <style type="text/css"> body { margin: 0; } </style> <link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")"> <script src="@routes.Assets.at("javascripts/enchant/enchant.js")" type="text/javascript"></script> <script src="@routes.Assets.at("javascripts/enchant/plugins/ui.enchant.js")" type="text/javascript"></script> <script src="@routes.Assets.at("javascripts/enchant/enchant.js")" type="text/javascript"></script> <script src="@routes.Assets.at("javascripts/" + scriptpath)" type="text/javascript"></script> </head> <body> </body> </html>
・app/assets/javascripts/test001_bear.coffee
enchant() window.onload =-> game = new Game(320,320) imagePath = "/assets/javascripts/enchant/images/chara1.gif" game.preload(imagePath) game.onload = -> bear = new Sprite(32,32) bear.image = game.assets[imagePath] bear.frame = 4 bear.addEventListener Event.ENTER_FRAME , -> this.x += 3 if this.x > 320 this.x = -32 game.rootScene.addChild bear game.start()
んで、http://localhost:9000/enchant/test001_bear.js にアクセス
いろいろ気づいたところ
・apps/assets/javascriptsにはJavaScript,CoffeeScriptの両ファイルは混在できる
・JavaScript,CoffeeScript内では@routes.asset.Atは使えない(Scalaテンプレートじゃないのであたりまえか)
・CoffeeScript作ればすぐに動かせるのでかなり楽チン
2012-03-29
sbtプラグインを追加
sbt でeclipseコマンドを使えるようにする
・project/plugins.sbtに追加
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.0.0")
・eclipsifyコマンドとどう違うのかは永遠の謎。
Jsonを返却する その3
いちいちJSON変換クラスなんか作っていられるか!!とお怒りの方に。
Gsonで変換してしまいましょう。
・project/Build.scalaにGsonの追加
val appDependencies = Seq(
// Add your project dependencies here,
"com.google.code.gson" % "gson" % "1.7.1"
)
・sbtコンソールにてupdate
・sbtコンソールにてeclipse(要SBT-Eclipseプラグイン)
・Eclipseでプロジェクトをリフレッシュ
・コード作成(関数の外にcase class を作らないとダメ)
import com.google.gson._ def RenderJson(obj:AnyRef) = Ok(new Gson().toJson(obj)).as("application/json; charset=utf-8") case class ResultData2(name:String,zip:String,age:Int) def sample4 = Action { implicit request => val obj = Array(ResultData2("hoge","123-4567",17),ResultData2("fuga","123-4567",17)) RenderJson(obj) }
Jsonを返却する その2.1
・配列、リストもOK
def sample3 = Action { implicit request =>
val obj = Array(ResultData("hoge","123-4567",17),ResultData("fuga","123-4567",17))
Ok(toJson(obj))
}
・結果
[{"name":"hoge","zip":"123-4567","age":17},{"name":"fuga","zip":"123-4567","age":17}]
Jsonを返却する その2
Play1.Xならcase classはすんなり返せていたのに、Play2.0だとちとメンドクサイ。
こちらを参考にしたが、うまく動かなかったので、
trait Protocolではなくobject Protocolにしたところ、動作できました。
・case classと変換クラス
package models import play.api.libs.json._ import play.api.libs.json.Reads._ import play.api.libs.json.Writes._ case class ResultData(name:String,zip:String,age:Int) object Protocol { implicit object ResultDataFormat extends Format[ResultData] { def reads(json: JsValue): ResultData = ResultData( (json \ "name").as[String] ,(json \ "zip").as[String] ,(json \ "age").as[Int] ) def writes(p: ResultData): JsValue = JsObject(List( "name" -> JsString(p.name) ,"zip" -> JsString(p.zip) ,"age" -> JsNumber(p.age) )) } }
・呼び出し側
import play.api.libs.json.Json._ import models.ResultData import models.Protocol._ def sample3 = Action { implicit request => val obj = ResultData("hoge","123-4567",17) Ok(toJson(obj)) }
・結果
{"name":"hoge","zip":"123-4567","age":17}
Jsonを返却する
・toJsonってどこ?→ play.api.libs.json.Json
import play.api.libs.json.Json._ def sample3 = Action { implicit request => Ok(toJson( Map("name"->"hoge","zip"->"123-4567","age"->"17") )) }
extjsを組み込む
とりたてて難しい話ではないけれど、main.scala.htmlをコピって、extjsをインクルードする行を追加した
extjsmain.scala.htmlを用意して、テンプレートからはextjsmain()を呼ぶように変更。
@(title: String)(content: Html) <!DOCTYPE html> <html> <head> <title>@title</title> <meta charset="UTF-8" /> <link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")"> <script src="@routes.Assets.at("javascripts/jquery-1.7.1.min.js")" type="text/javascript"></script> <link rel="stylesheet" media="screen" href="@routes.Assets.at("javascripts/ext-4.0.7/resources/css/ext-all.css")""> <script src="@routes.Assets.at("javascripts/ext-4.0.7/ext-all.js")" type="text/javascript"></script> </head> <body> @content </body> </html>
@(ctrl : controllers.Sample.type)(implicit req:Request[AnyContent]) @extjsmain(ctrl.test) { <script src="@routes.Assets.at("javascripts/main.js")" ></script> <script type="text/javascript"> Ext.onReady(initComponent); function initComponent(){ Ext.create("Ext.Viewport",getViewport()); } function getViewport(){ var cfg = { layout:"border" ,items:[ {region:'center',title:'@ctrl.test'} ] }; return cfg; } </script> }
テンプレートにコントローラを渡す
MVC嫌いなので、テンプレートにコントローラを渡して、コントーラのメソッドを呼び出します(笑)
GET /sample2 controllers.Sample.sample2
・Sample.scala
def sample2 = Action { implicit request =>
val obj = controllers.Sample
Ok(views.html.Sample.sample2(obj))
}
def test = "Hello Template"
@(ctrl : controllers.Sample.type)(implicit req:Request[AnyContent])
@main(ctrl.test) {
<h1>@ctrl.test</h1>
}
汎用API的な何か
Play2.0のroutesの設定は以前よりもだいぶ厳格になっているので、以下のような記述はできないみたい。
/{contoller}/api/{method} {contoller}.{method}
Play1.xで作っていたアプリは、この記述でいろいろなAPI定義していたので、Play2.0でも似たような感じでできるようにしてみた。
/Application/api/:method Application.api(method:String)
Controllerから派生したクラスを定義して、いろいろゴニョゴニョして最終的に呼び出せるようにはなったものの、
はたしてこれって便利なの?と自問自答してみたところ、結論として「便利ではない」という判断をすることに(笑)
2012-03-27
テンプレートにrequestオブジェクトを渡す
・テンプレート側
@(title:String)(implicit request: play.api.mvc.Request[AnyContent])
・コントローラ側
def mainmenu = Action { implicit request =>
Ok(views.html.mainmenu(""))
}
implicitって初めて使った(笑)
ほんとは、明示的な方がよいのかも。
コントローラでRequestオブジェクトからパラメータを取り出す
以前(1.2.x)のよりはだいぶ書き方が楽になってますね。
同名のパラメータが複数あった場合?を想定しているので、
queryStringがMap[String,Seq[String となってますね。
ここだけちょっとメンドクサイけど、しょうがないね。
def mainmenu = Action { request =>
val uid = request.queryString.getOrElse("uid",Seq[String]())
println(uid.length)
println(uid)
Ok(views.html.mainmenu())
}
動的にテンプレートを変更する
もっとうまいやり方があるんだろうけど、今の私にはコレが限界…
・conf/routesに追加。:pに表示させたいページ名を指定する
GET /route/:p controllers.Application.route(p:String)
・Application.routeメソッドを追加
def route(p:String) = Action {
val pages = Map(
"mainmenu"-> views.html.mainmenu()
,"sample"-> views.html.Sample.sample()
)
Ok(pages( p ))
}
http://localhost:9000/route/mainmenu
http://localhost:9000/route/sample
・ほんのちょっぴり変更(fを使ってみた)
def route(p:String) = Action { implicit request =>
val pages = Map(
"mainmenu"-> views.html.mainmenu.f
,"sample"-> views.html.Sample.sample.f
)
Ok(pages(p).apply("")(request))
}
f.apply使っているので、implicit部分は明示しないといけないっぽい。
2012-03-26
appディレクトリ直下に適当なパッケージを作成して、Scalaクラスを作成
・コントローラ以外のScalaソースを作ってコンパイルしてくれるか実験
とくに、問題なくコンパイル&コントローラからの呼び出しはできました。
まあ、当たり前か。
Hello Play2.0
・conf/routeに追加
Play1.X系のように*を指定してみたところこれには対応できないっぽい。
指定できるのはGET/POST/PUT/DELETE/HEADだそうです。
GET /mainmenu controllers.Application.mainmenu
・Application.scalaにmainmenuメソッドを追加
def mainmenu = Action {
Ok(views.html.mainmenu())
}
Play2.0 + Eclipse
・まずはPJ作成
play new 新規PJ名
cd 新規PJ名 play
・とりあえず起動
[新規PJ名] $ run
ctrl-d で停止。
サーバーの起動ポートを変更するには、
conf/application.confかなと思ったけど、ちょっと見当たらず。あとで調査。
・Eclipseプロジェクトを作成
[新規PJ名] $ eclipsify
・Eclipseからデバッグするには、Playのデバッグ実行モードで実行させておいて、
EclipseからリモートデバッグとしてPlayのプロセスにアタッチする。
Debug Configuration -> Remote Java Application -> new
設定ダイアログにて、
Connection Property -> port -> 9999
に設定する。
play debug run
2011-09-23
Chrome14 + WebSocket + Jetty 7.5.1
Chromeが14にバージョンアップされたことで、WebSocketのバージョンがDraft-10になってしまい、
いままで利用していたWebSocketを利用したアプリが動かなくなってしまった。
Draft-10に対応しているサーバーは、Node.jsのものしかないとのことだったが、
Jettyの最新版7.5.1にすることで、Chrome14でもChrome13でも同様にWebSocketが使えるのを確認した。
ただし、若干のソース修正が必要となる。
今までは、WebSocketから継承したクラスを作成して、onConnectにて、
Outboundクラスのインスタンスを受け取っていたが、
class MySocket extends WebSocket { override def onConnect(outbound:Outbound) {} override def onMessage(frame:Int,message:String){} override def onDisconnect(){} }
Jetty7.5.1では、Outboundクラスの代わりにConnectionクラスになっている。
また、WebSocket.OnTextMessageから継承すればとりあえず今までどおりっぽいコードで
動作する。
class MySocket extends WebSocket.OnTextMessage { override def onOpen(connection:Connection) {} override def onMessage(message:String){} override def onClose(code:Int,message:String){} }
メッセージの送信は、以下のようになっている。
Connection.sendMessage(message:String)

