Play Framework 2.0.2 + AngularJS + Squeryl + lift-jsonでTODOアプリのサンプルを書いてみた
Play Framework 2.0 + AngularJS + Squeryl + lift-json を使って、AngularJSのTODOアプリのサンプルのバックエンドにPlay Framework2.0を使うような感じで、TODOアプリのサンプルを書いてみた。
tarhashi/Play-AngularJS-Sample
細かいことはソース見てください。コメント全然入ってないけど…。
Squerylのセットアップ
Squerylを使えるようにするために、以下のようなapp/GlobalSettings.scalaを作成する。今はドライバの初期化をH2 DBの場合しか書いてないが、実戦で使う場合にはPostgresなりMySQLなりを使えるようにする。
import org.squeryl.adapters.H2Adapter import org.squeryl.internals.DatabaseAdapter import org.squeryl.{Session, SessionFactory} import play.api.db.DB import play.api.Application import play.api.GlobalSettings object Global extends GlobalSettings { override def onStart(app: Application) { SessionFactory.concreteFactory = app.configuration.getString("db.default.driver") match { case Some("org.h2.Driver") => Some*1 case _ => sys.error("Database driver must be org.h2.Driver") } } def getSession(adapter: DatabaseAdapter, app: Application) = Session.create(DB.getConnection()(app), adapter) }
lift-json
どうもPlay標準のJSONモジュールは面倒な感じなので、@tototoshiさんのPlay20からlift-jsonを使えるモジュールを使ってみた。まあ、今回は大したことやってないので何使ってもよさそうでしたが。
javascriptRouter
Play Framework2.0にはjavascriptRouterという、ajax通信するときにjavascriptからroutesの定義をタイプセーフに使うための仕組みがあるので、これを使ってみる。コントローラにjavascriptRouterを出力するためのメソッドを作成。
def javascriptRoutes() = Action { implicit request => import play.api.Routes Ok( Routes.javascriptRouter("jsRouter", Some("jQuery.ajax"))( routes.javascript.Application.tasks, routes.javascript.Application.newTask, routes.javascript.Application.doneTask, routes.javascript.Application.undoneTask ) ).as("text/javascript") }
routesに以下を追加。
GET /javascript-routes controllers.Application.javascriptRoutes
そして、テンプレートに以下を追加。
<script src="@routes.Application.javascriptRoutes" type="text/javascript"></script>
で、こんな感じで使う。
jsRouter.controllers.Application.doneTask(id).ajax({ data: { //... }, success: function(data) { // ... }, error: function() { // ... } });
jsRouterをAngularJSで使う時の注意点
jsRouterを使ってデータの取得や更新をやろうとして、最初正常に反映されなくてハマった。最初、以下のようなコードで変更を反映しようとした。
function TodoCtrl($scope) { // ... $scope.addTodo = function() { jsRouter.controllers.Application.newTask().ajax({ data: { label: $scope.todoText }, success: function(data) { $scope.todos.push(data); $scope.todoText = ''; }, error: function() { alert("error:addTask"); } }); }; // ... }
通常AngularJSでajaxするときは$httpという専用のやつを使うのが推奨されているのだけど、jsRouterを使うとjQuery.ajaxを使って通信することになる。で、非同期で通信するとコールバック関数からの値の変更がAngularJSに検出されないようで、画面に変更が反映されない。
これを解決するための1つ目の方法はasync=falseとして同期通信にすること。でも全部を同期通信にしてしまうのはあまりにいけてない。で、もう1つの方法が、変更を通知する関数を使うこと。$scope.$applyという関数を使ってsuccessの処理を以下のように書き換える。
success: function(data) { $scope.$apply(function(){ $scope.todos.push(data); $scope.todoText = ''; }); },
感想
AngularJSっていうか、Javascript MVCフレームワークは使ったことなかったんだけど、なかなか便利。パフォーマンスについてどうなのか気になる所だけど、何かで使ってみたい。画面遷移を最小限にして、サーバサイドの処理のほとんどをJSONを返すAPIを実装していく形にできるのも良い。
AngularJSと組み合わせるときにajax通信をjavascriptRouterを使うべきか、AngularJS標準のでやるかは微妙なところ。AngularJSと使う以外の用途だったら普通に使えそう。
追記
Play2.1が出たので、2.1対応をやってみた。
*1:) => getSession(new H2Adapter, app