どうせ人生暇つぶし

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 にアクセス

f:id:Ohya6in:20120401220502p:image

いろいろ気づいたところ

・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()を呼ぶように変更。

・extjsmain.scala.html

@(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>

・sample2.scala.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嫌いなので、テンプレートコントローラを渡して、コントーラのメソッドを呼び出します(笑)

routes

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"

・sample2.scala.html

@(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から派生したクラスを定義して、いろいろゴニョゴニョして最終的に呼び出せるようにはなったものの、

はたしてこれって便利なの?と自問自答してみたところ、結論として「便利ではない」という判断をすることに(笑)

API定義(url関数のハンドリング)は、routesにいちいち書けばいいじゃんという事にした。

2012-03-27

テンプレートにrequestオブジェクトを渡す

groovyテンプレートって今思うと楽だったんだなと痛感。

テンプレート

@(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 ))
  }

以下のURLにてアクセス

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部分は明示しないといけないっぽい。

viewsフォルダの下にフォルダを作って、そこにテンプレートを配置

・views/Sample/sample.scala.htmlを配置して、コントローラからテンプレートを呼び出す

  def sample = Action {
    Ok(views.html.Sample.sample("Sample"))
  }

Eclipse上で修正しても最初はエラー表示となるので、一回Play上でテンプレートコンパイルさせておくと、エラーが除去される。

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())
    
  }

・views/mainmenu.scala.htmlを作成

http://localhost:9000/mainmenuにアクセス

Play2.0 + Eclipse

・まずはPJ作成

play new 新規PJ

・playコマンドでPJインスタンス?を起動

cd 新規PJ名
play 

・とりあえず起動

[新規PJ名] $ run

ブラウザlocalhost:9000にアクセス。

ctrl-d で停止。

サーバーの起動ポートを変更するには、

conf/application.confかなと思ったけど、ちょっと見当たらず。あとで調査。

Eclipseプロジェクトを作成

[新規PJ名] $ eclipsify

Eclipseから外部PJインポートを選ぶ。

Eclipseからデバッグするには、Playのデバッグ実行モードで実行させておいて、

EclipseからリモートデバッグとしてPlayのプロセスにアタッチする。

Eclipseにてデバッグ実行のConfigを作る。

Debug Configuration -> Remote Java Application -> new

設定ダイアログにて、

 Connection Property -> port -> 9999

に設定する。

ターミナルにて、Playをデバッグモードで実行しておく

play debug run

この状態でEclipseからデバッグ実行を行う。

Play2.0日記

今週はPlay2.0の勉強のため会社を1週間お休みしているところ(半分嘘)

メモがてら自分の調べたことをつらつらと書いていこうかと思います。

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)