2013年1月2日水曜日

Scala Lift lift_basic を読み解く View編

Scala Lift を理解するに当たり、一通りの機能を備えたサンプルであるlift_basicを元にその動作を追ってみる。

Liftのソースコードはこちら。これを書いている時点では2.5-M3が最新である。
Lift Download

ダウンロードしたzipなりを解凍すると中にlift_basic、lift_blankなどのフォルダがある。これらがそれぞれテンプレートとなっており、sbtのコマンドで動かすことができる(動かす方法はダウンロードページ参照)。

0.Liftの特徴
これは様々な解説サイトがあるのでそちらの方が詳しかろうが、「ビューファースト」であるという一言に尽きると思う。
Ruby On Railsなどの最近のフレームワークはモデルに合わせて画面を作るというイメージだが、Liftは画面を作りモデルをそこに適用する、というイメージである。

ユーザーの目に触れる画面は一刻も早く手を付け最も時間をかけたいところであるが、既存のフレームワークでは「モデルがないと画面がつくれない。待って。」とならざるを得ない。それを解決するのがビューファーストであり、それを支援するLiftフレームワークなのである。

(※ビジネスモデルに通ずるモデルの構成をまずきちんと考えるべきという説もあり、この辺りはケースバイケース)。


1.LiftのView
そんなわけで、Liftを読み解くに当たりまず確認するのはやはり画面である。

lift_basic/src/main/webapp/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
    <title>Home</title>
  </head>
  <body class="lift:content_id=main">
    <div id="main" class="lift:surround?with=default;at=content">
      <h2>Welcome to your project!</h2>
      <p>
<span class="lift:helloWorld.howdy">
 Welcome to your Lift app at <span id="time">Time goes here</span>
</span>
      </p>
    </div>
  </body>
</html>

Scalaは関数型言語の機能を持っているが、このページも関数のように処理される。アバウトに言ってしまうとclassの(lift:で始まる)値=「関数」、classの設定されているHTMLエレメント(divなど)=「引数」となっている。

1.1 スニペット
手近なところで以下の部分を見てみる。

<span class="lift:helloWorld.howdy">
  Welcome to your Lift app at <span id="time">Time goes here</span>
</span>

これは内部要素をHellowWorldクラスのhowdyメソッドで処理してください、という意味である。このように特定の要素を処理するためのクラス(とメソッド?)をスニペット(snippet)と呼ぶ。

lift_basic/src/main/scala/code/snippet/HelloWorld.scala のスニペットを見てみると・・・
class HelloWorld {

  lazy val date: Box[Date] = DependencyFactory.inject[Date] // inject the date

  // replace the contents of the element with id "time" with the date
  def howdy = "#time *" #> date.map(_.toString)

  /*
   lazy val date: Date = DependencyFactory.time.vend // create the date via factory

   def howdy = "#time *" #> date.toString
   */
}

(Boxなどはとりあえず無視し) defで始まるhowdyメソッドの定義に着目する。
"#time *" #> となっているのは、idがtimeのところだけ、という指定である。このほかにもCSSセレクタのような指定が可能である(CSS Selector Transforms 参照)。
このセレクタのようなメソッドはどうやらStringクラスのメソッドが拡張されているようで、セレクタに該当する部分を引数(この場合date.toString)に置換する関数(CssSel)を返しているようだ。

この部分を手動で書くなら、HTMLエレメント(NodeSeq)を受け取って(編集後の)HTMLエレメント(NodeSeq)を返す関数を定義することになる。以下がその例である。

//メソッド定義
 def howdy2(xhtml:NodeSeq):NodeSeq = bind("tag",xhtml,"param1" -> <span>Hellow</span>)

//htmlでの呼び出し
 <div class="lift:helloWorld.howdy2">
    <b><tag:param1 ></tag:param1></b>
 </div>
bind関数によって、対象HTMLエレメント内のtag:param1要素を<span>Hellow</span>に置換するという意味になる。
なお、置換するHTMLエレメントは上記のようにspanタグなどで囲まなくてもSHtmlという関数が用意されており、こちらを使用すると楽。 SHTMLのAPI


1.2 テンプレート
HTMLエレメントには、このスニペット以外にもテンプレートを適用することができる。これが以下の部分である。

    <div id="main" class="lift:surround?with=default;at=content">

surroundはページのテンプレートの指定である。このテンプレートは、lift_basic\src\main\webapp\templates-hidden に配置されており、ここではwith=default; default.htmlを使用すると宣言している。また、at=content の指定によりこのdiv内の要素をdefault.html内のid=content要素に適用するとしている。
これは、surroundという関数にwith、atというデフォルトテンプレート、置換先の要素を指定する引数を渡したかのようでもある。

この部分は <body class="lift:content_id=main"> によりいわば「このページのメイン関数はid=mainである」と指定されており、ページのレンダリングを行う際の処理の開始点となる(※誤りある可能性有。大体そんな感じととらえてください)。


LiftにおけるViewの概要はこんな感じとなる。


0 件のコメント:

コメントを投稿