目次

Version 27, last updated by hat at Nov 28 08:17 UTC

テンプレートとバインディング

Lift を使う場合、出力はさまざまな方法で生成することができますが、テンプレートを使うのが最も一般的です。テンプレートはアプリケーションの基本ページを構築するときによく使われます。RSS フィードのようなものについては、テンプレートではなくビューを使うことになるでしょう。テンプレートとビューの違いは、テンプレートでは、任意の数の XML ドキュメントと Scala クラス (スニペット) を介してクライアントへのレスポンスを作成するのに対し、ビューでは、レスポンスをすべてコードで作成する点にあります。

Mapper エンティティのための UI の作成」も参照してください。

テンプレート

次に示すのは、基本的な Lift テンプレートです。

<lift:surround with="default" at="content"> 
  <h2>Template introduction</h2> 
  <lift:mySnippet.example>
    <p>
      This is an introduction to templates. Though it's brief I've got no doubt you'll 
      be very <peer:status /> once you're done and will ready to dive into the many 
      cool aspects of Lift. 
    </p> 
  </lift:mySnippet.example>
</lift:surround>

非常にわかりやすいと思います。テンプレートは純粋な xhtml で、インラインコードもその他の余計なものも一切ありません。では、テンプレートの内容を順に見ていきましょう。<lift:mySnippet.example> は MySnippet のインスタンスのメソッド example を呼び出します。スニペットについては、次のセクションで説明します。example 関数はすべての子要素を取り、NodeSeq を処理して、別の NodeSeq を返します。<lift:surround with="default" at="content"> は、その子要素のすべてを、“default” という名前のテンプレートの content バインディングで囲みます。ただし、lift:surround には何も特別なところはないことに注意してください。これもまたスニペットで、あると便利で、どのプロジェクトでも使われる可能性が高いことから Lift にあらかじめ付属しているスニペットです。

<lift:surround with="default" at="content"> は、実際に使い勝手がよいものです。なぜなら、テンプレートを「ドライ」にしておくのに役立つからです。通常は、“master-template” を作成し、メニューやサイドバー、フッタなどのあるメイン構造を定義して、その上でサイト固有のコンテンツを独自のテンプレートで作成することになるでしょう。次に例を示します。

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:lift="http://liftweb.net/">
  <head>
    ..... ヘッダー部分 .....
  </head>
  <body>
    <div id="container">
      <div id="menu">
        <lift:Menu.builder />
      </div>
      <div id="content">
        <lift:bind name="content" />
      </div>
      <div id="footer">
        <lift:bind name="footer" />
      </div>
    </div>
  </body>
</html>

このようにすれば、多くの設定ポイントを持った汎用的なテンプレートを簡単に作成できます。上のテンプレートで囲まれるテンプレートを作成する場合、そのテンプレートは次のようなものになるでしょう。

<lift:surround with="default">
  <lift:bind-at name="content">
    .. スニペットを呼び出すか、またはここに HTML を記述 ...
  </lift:bind-at>
  <lift:bind-at name="footer">
    .. スニペットを呼び出すか、またはここに HTML を記述 ...
  </lift:bind-at>
</lift:surround>

スニペット – テンプレートへのコンテンツのバインディング

次に示すのは、最初のテンプレート用のスニペットクラスの例です。

import net.liftweb.util.Helpers._
...
class MySnippet {
  def example(xhtml: NodeSeq) = {
    bind("peer",xhtml, "status" -> "excited")
  }
}

メソッド example は NodeSeq を引数に取ります (NodeSeq になるのは、<lift:mySnippet.example> タグのすべての子ノードです)。メソッド example は bind 関数を呼び出すだけです。bind 関数は引数を 3 つ取り、最後の引数は任意の回数繰り返すことができます。最初の引数は、バインドするタグのプリフィックスで、2 番目の引数は、バインド対象の要素を探す場所を示す NodeSeq です。最後の引数は、適用する BindParam バインディングのリストです。

net.liftweb.util.BindPlus._ をインポートし、暗黙の変換を利用して、次のようなスタイルを使用できるようにすることもできます。

  xhtml.bind("peer",  "status" -> "excited")

この方法を使うと、bind の呼び出しをチェインすることで、複数のプリフィックスを簡単にバインドすることができます。

  xhtml.bind("peer",  "status" -> "excited").bind("otherprefix", "x" -> "y")

属性のバインディング

属性をバインドすることもできます。たとえば、画像の src 属性をバインドするには、次のようなテンプレートを作成します。

<lift:mySnippet.image>
  <img myImage:url="" />
</lift:mySnippet.image>

スニペットでは、bind 関数に渡す BindParam リストの一部を次のように記述します。

class MySnippet {
  def image(xhtml: NodeSeq) = bind("myImage",xhtml, AttrBindParam("url","http://url.to/image.jpg","src"))
}

属性の保存

指定した要素の属性を保存したい場合は、BindParam の中で -> の代わりに -%> を使う必要があることも知っておくと便利です。たとえば、<person:name id="yourname" /> のように記述している場合、スニペットでは bind("person",xhtml, "name" -%> Text("Mads")) のように bind ステートメントを記述します。-%> と記述しないと、そのテンプレートでは、要素の id は破棄されます。

AttrBindParam のシグニチャーは、final case class AttrBindParam(name: String, myValue: NodeSeq,newAttr: String) になっていることに注意してください。上で例を示したように、2 番目の引数に文字列を指定して AttrBindParam を呼び出すことができるのは、Lift には暗黙の型変換がたくさん用意されていて、net.liftweb.util.Helpers._ のようなバインドヘルパをインポートすると、こうした暗黙の型変換が利用可能になるためです。

既存の属性の値 (テンプレートですでに与えられている値) を変更する必要がある場合には、FuncAttrBindParam を使います。たとえば、テンプレートが次のようになっているとします。

<div somePrefix:data-item="existing-item-value-" />

ここで、次のようにします。

bind("somePrefix", xhtml, FuncAttrBindParam("data-item", ns => ns ++ Text("modified"), "data-item"))

結果は次のようになります。

<div data-item="existing-item-value-modified" />

XHTML の使用

バインディングをはじめて使うときは、手品のような印象を受けるかもしれません。しかし結局のところ、バインディングとは、タグで囲むことによって、スニペットに渡された NodeSeq (XHTML) 内部のコンテンツを置き換えるための便利な手段にすぎません。時には、自分で変換を行って、バインディングをバイパスしたりこともあるでしょう。

よくあるケースとして、既存の要素や属性にアクセスしたい場合があります。このような場合も、対象は単なる xml なので、Scala の強力な xml サポートを利用することができます。たとえば、div から class 属性のコンテンツを取得する場合を考えてみましょう。

  <lift:mySnippet.replace>
    <div class="stylestuff" >
    ...
    </div>
  </lift:mySnippet.replace>

スニペットでは、次のようにして class 属性にアクセスできます。

class MySnippet {
  def replace(xhtml:NodeSeq): NodeSeq = {
      val classcontent = xhtml \ "div" \ "@class"
      // 元の class 属性の値を保持して html をラップ.
      <div class={"menu" + classcontent}>{xhtml}</div>
  }
}

上の例からわかるように、使用できるのはバインドだけというわけではなく、バインドを使わない方が目的を簡単に実現できることもあります。

もう少し高度な例

Lift を使っていると、データのコレクションに対する繰り返し処理が必要になる場合が出てきます。Lift を使い始めたばかりだと、この処理をどうすればよいか、よくわかからないことがありますが (少なくとも筆者の場合はそうでした)。たとえば、前の晩にある人が飲んだお酒の種類を 1 行に 1 つずつ表示してテーブルを作成する場合を考えてみましょう。

<lift:surround with="default" at="content"> 
  <h2>Why my head hurts today</h2> 
  <lift:beerSnippet.count>
    <p>Hello <person:name /> here's a list of the alcoholic beverages you drank last night</p> 
    <table>
      <tr>
        <th>Name of beverage</th>
        <th>Count</th>
      </tr>
      <person:consumption>
        <tr>
          <td> <beverage:name /> </td>
          <td> <beverage:count /> </td>
        </tr>
      </person:consumption>
    </table>
  </lift:beerSnippet.count>
</lift:surround>

スニペットは次のようになります。

class BeerSnippet {
  def count( xhtml: NodeSeq ) = {
    val consumption = ("Tuborg Beer",2) :: ("Harboe pilsner",2) :: ("Red wine",1) :: ("Pan Galactic Gargle Blaster",1) :: Nil
    def bindConsumption(template: NodeSeq): NodeSeq = {
      consumption.flatMap{ case (bev, count) => bind("beverage", template,"name" -> bev,"count" -> count)}
    }
    bind("person",xhtml, "name" -> "Mads", "consumption" -> bindConsumption _)
  }
}

上では、count メソッドの内側で関数を定義しています。この bindConsumption 関数も NodeSeq を引数に取り、NodeSeq を返します。bindConsumption 関数は、テンプレートの要素のうち、beverage 名前空間に含まれる要素へのバインドを行うことによって、consumption リストの各タプルを Node に (フラット) マップしています。map の代わりに flatMap を使う必要があるのは、map では結果が Seq[NodeSeq] となり、テンプレートの 11 行目の <person:consumption> でバインドするために取得することを期待している NodeSeq (Seq[Node]) という結果が得られないからです。

応用例

すでに述べたように、bind 関数は NodeSeq を返します。これは、NodeSeq が期待される任意の場所で bind 関数を呼び出せることを意味します。こうした処理が特に便利なのは、クライアント側で複雑な JavaScript html ノードを構築するような場合です。net.liftweb.http.js パッケージの Jx 関数は NodeSeq を引数に取りますが、この関数が作成する JavaScript 関数をクライアント側で呼び出すと、クライアント側で XML を構築できます。

val xhtml = TemplateFinder.findAnyTemplate("path" :: "to" :: "file" :: Nil) match {
  case Full(template) => bind("hello", template, "world" -> "Mads says hi!")
  case _ => Text("Im sorry man, but the template file was nowhere to be found")
}
JsCrVar("createMarkup",Jx(xhtml).toJs)

JsCrVar は、var createMarkup = function(it){ ... } という JavaScript になります。ここでは、Lift の JavaScript 抽象化レイヤに深く立ち入ることはしませんが、非常に便利なことはわかってもらえると思います。詳細については、「JavaScript 抽象化レイヤ」を参照してください。この方法を使えば、まずいつもどおりマークアップを作成しておき、JavaScript を注入するか、その他のテンプレート同様に処理するかをあとで決めることができます。

Lift のタグ

<lift:surround with=""> タグと <lift:bind-at name=""> タグについてはすでに取り上げましたが、Lift にはこのほかにもいくつかタグがあります。

Embed

<lift:embed what="template" />

説明: テンプレートをほかのテンプレートに埋め込むことができます (SetHtml や ModalDialog といった JsCmd からテンプレートにアクセスすることができます)。リクエストに *-hidden を含む受信リクエストに対しては、サービスは提供されませんないことに注意してください。ただし、*-hidden という名前のディレクトリにあるテンプレートにアクセスすることはできます。したがって、AJAX テンプレートは webapps の /ajax-templates-hidden に置くことができます。

また、Lift の i18n サポートはテンプレートにも適用されるので、“/ajax-templates-hidden/welcome” を指定すると、適切にローカライズされたテンプレートが Lift によって提供されます。たとえば、現在のロケールが French Canadian になっている場合、Lift は /ajax-templates-hidden/welcome_fr_CA.html、/ajax-templates-hidden/welcome_fr.html、および /ajax-templates-hidden/welcome.html を探します。

使用例

<lift:embed what="/ajax-templates-hidden/welcome" />

欠点: (AJAX リクエストへのレスポントとして送信された) JsCmd によって作成されたテンプレートに含まれる JavaScript は実行されません。これには、Comet ウィジェットも含まれます。

Comet

<lift:comet type="ClassName" name="optional"/>

欠点: <lift:comet /> タグがあって、AJAX の要素を送り返す中でこのタグを使っている場合、期待どおり動作しないことがあります。

Ignore

<lift:ignore>children</lift:ignore>

用途: ローカルファイルが読み込まれた場合にはブラウザがパースできる子タグを含め、レンダリングされたコードにはこれらのタグを含める必要がない場合。

このタグが便利なのは、次の 2 つのケースです。

  • ブラウザにレンダリングする必要のないコメントをページに入れるケース。
  • ページがテンプレートで囲まれる場合でも、CSS やその他の要素をページに配置したいケース。

使用例

<lift:ignore>
<!-- The database info is scott/tiger -->
</lift:ignore>

Children

<lift:children>children</lift:children>

用途: XML ファイルが含むことのできるルート要素は 1 つだけです。したがって、次のような内容のファイルがあった場合、このファイルは正しくパースされません。

<div>This is the first DIV</div><div>This is the other DIV</div>

このような場合には、ファイルを <lift:children/> で囲むと、子ノードが返されるようになります。NodeSeq を引数に取る SetHtml とその他の JsCmd では、<lift:embed what="/ajax-hidden/comfirm-delete"/> とすることでスタンドアロンテンプレートを返すことができます。

使用例

<lift:children>
  <div>This is the first DIV</div>
  <div>This is the other DIV</div>
</lift:children>

Loc

<lift:loc id="What"/>

Head のマージ

バージョン 0.4 以降では、body (およびその子) 以下の任意の場所で複数の <head> セクションを宣言できます。この場合、各 <head> セクションはレンダリング時に /html/head の標準的な場所にマージされます。head セクションは html または (スニペットなどの) scala コードで作成されることがあります。

使用例

次のような内容の /webapp/template-hidden/default.html ファイルがあるとします。

<html>
  <head>
    <title>foo</title>
  </head>
  <body>
    <lift:bind name="content" />
  </body>
</html>

/webapp/index.html は次のようになっています。

<lift:surround with="default" at="content">
  <head>
    <script src="myscript.js"></script>
    <style>
      <!-- ここに css を記述 -->
    </style>
  </head>
  <h2>Welcome to the your project!</h2>
</lift:surround>

最終的なレンダリング結果は、次のようになります。

<html>
  <head>
    <title>foo</title>
    <script src="myscript.js"></script>
    <style>
      <!-- ここに css を記述 -->
    </style>
  </head>
  <body>
    <h2>Welcome to the your project!</h2>
  </body>
</html>

CSS

blueprint CSS スタイルを自動的に追加するときに使います。

使用例

<lift:CSS.blueprint/>

または

<lift:CSS.fancyType/>

Msg および Msgs

S.notice、S.warning、または S.error API を介して設定されたメッセージをレンダリングするときに使います。

Msg は、指定された ID に関連付けられたメッセージをレンダリングします。たとえば、S.error(“email_field_error”, Text(“Email is invalid”)) によって設定されたエラーは、ページ内で次のタグが現れる場所でレンダリングされます。

<lift:msg id="email_field_error"/>

errorClass や warningClass、noticeClass などの属性によってメッセージのスタイルを指定することもできます。次に例を示します。

<lift:msg id="email_field_error" errorClass="error_messages"/>

Msgs は、どの ID とも関連付けられていないメッセージをレンダリングします。たとえば、S.error(Text(“Session is invalid”)) は、ページ内で次のタグが現れる場所でレンダリングされます。

<lift:msgs />

属性 showAlltrue を設定すると、ID の有無にかかわらず、すべてのメッセージがレンダリングされます。このとき、ID に関連付けられたメッセージを表示するために Msg ビルトインスニペットも同時に使われていると、メッセージが重複して表示されます。

<lift:msgs showAll="true" />

msgs には次のようにしてスタイルを指定することもできます。

<lift:msgs>
  <lift:error_msg>Error!  The details are:</lift:error_msg>
  <lift:error_class>errorBox</lift:error_class>
  <lift:warning_msg>Whoops, I had a problem:</lift:warning_msg>
  <lift:warning_class>warningBox</lift:warning_class>
  <lift:notice_msg>Note:</lift:notice_msg>
  <lift:notice_class>noticeBox</lift:notice_class>
</lift:msgs>

HTML5

ページ内でこのタグを使うと、Lift は適切な DocType を設定して、有効な HTML 5 ページをレンダリングします。

<lift:html5/>

lazy-load

ページのフラグメントを非同期にレンダリングすることができます。次に例を示します。

.. マークアップ 1 ...
<lift:lazy-load>
   ... マークアップ 2 ...
</lift:lazy-load>
... マークアップ 3 ...

上の例で、lazy-load の内側のコードは、ページがレンダリングされた後にレンダリングされます。lazy-load の内側には、Lift のスニペットなど、任意の有効な xhtml コンテンツを置くことができます。スニペットの中で、リモートサービスなどから取得する情報を含むマークアップをレンダリグする場合には、lazy-load を使うと便利です。リモートサービスから情報を取得する処理に時間がかかる場合、lazy-load を使えば、リモートサービスの反応速度にかかわらず、ページのほかの部分がすばやくレンダリングされるので、ユーザーエクスペリエンスを向上させることができます。

test_cond

ユーザーがログインしている場合、またはログインしてない場合に、コンテンツをレンダリングするのに使います。

<lift:test_cond.loggedin>
  ... ここにマークアップを記述
</lift:test_cond.loggedin>

または

<lift:test_cond.loggedout>
  ... ここにマークアップを記述
</lift:test_cond.loggedout>

ユーザーがログインしているかどうかの判定は、LiftRules で設定したユーザー関数で行います。
たとえば、boot で次のように設定します。

LiftRules.loggedInTest = Full( 
  () => {
    // Boolean を返すコード
  }
)