2012年5月18日金曜日

Play framework 2.0のScalaテンプレートのユースケース意訳

Play framework2.0のScala templates common use casesを意訳しました。

先日テンプレートのシンタックス系のマニュアルを意訳しましたが、こちらはもう少し具体的にScalaテンプレートの使い方を説明しているものになります。

例のごとく、もし私の訳が間違っていても責任は負えませんのであしからず・・・
正確なものを読みたい方は原典をどうぞ。

http://www.playframework.org/documentation/2.0.1/ScalaTemplateUseCases

なお、日本Playframeworkユーザー会さんの方で翻訳作業が進行中のようですので、そちらを待たれるという手もあります。

それではどうぞ!

Scalaテンプレートの一般的なユースケース

テンプレートというシンプルな機能は、あなたがやりたい方法で構成することが出来ます。
以下に、いくつかの一般的なシナリオを示します。

レイアウト

メインのレイアウトテンプレートとして機能するviews/main.scala.htmlを宣言しましょう。

@(title: String)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
    <section class="content">@content</section>
  </body>
</html>

ご覧のとおり、このテンプレートは2つのパラメータを必要としています。
タイトルと、HTMLコンテンツです。
では、別のテンプレートであるviews/Application/index.scala.htmlからこのテンプレートを使ってみましょう。

@main(title = "Home") {
    
  <h1>Home page</h1>
    
}

Note:本ドキュメントでは、時々@main("Home")ではなく、@main(title = "Home")という形で、名前付き引数を使用します。
こうすることで、ソースをパッと見ただけで影響範囲がとても明確になります。

時々、サイドバーやパンくずリストといった個別のパーツが必要になるでしょう。
これは、パラメタを追加することで実現できます

@(title: String)(sidebar: Html)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
    <section class="sidebar">@sidebar</section>
    <section class="content">@content</section>
  </body>
</html>

このパラメタを追加したメインテンプレートを、さっき用意したviews/Application/index.scala.htmlから使ってみましょう。

@main("Home") {
  <h1>Sidebar</h1>

} {
  <h1>Home page</h1>

}

また、サイドバーの部分を別々に宣言することもできます。

@sidebar = {
  <h1>Sidebar</h1>
}

@main("Home")(sidebar) {
  <h1>Home page</h1>

}

※注意(公式にない私的メモ)
Play frameworkにおけるScalaテンプレートは関数だということを思い出しましょう。
さらに、テンプレートパラメータ(main.scala.htmlの先頭で宣言されている引数を受け取る部分)は、カリー化された関数になります。
だから、index.scala.htmlから呼び出す際に、コードブロックを{}で囲んで渡してあげることができるわけです。
カリー化されたmain.scala.htmlの方の引数の型が、Html型になっているので、コードブロックとして渡したHTMLはHtml型になるっぽいです。

タグ(これらは関数、でしょ?)

それでは、ちょっとしたインフォメーションを表示するシンプルなタグをviews/tags/notice.scala.htmlに書いてみましょう。

@(level: String = "error")(body: (String) => Html)
 
@level match {
    
  case "success" => {
    <p class="success">
      @body("green")
    </p>
  }

  case "warning" => {
    <p class="warning">
      @body("orange")
    </p>
  }

  case "error" => {
    <p class="error">
      @body("red")
    </p>
  }
    
}

これを別のテンプレートから呼び出してみましょう。

@import tags._
 
@notice("error") { color =>
  Oops, something is <span style="color:@color">wrong</span>
}

※注意(公式にない私的メモ)
テンプレートの方に@import tags._っていきなり出ていますね。
何の説明もなく、さも当たり前に書かれているので、なんじゃこりゃ!?と思いましたが、よくよく考えるとScalaテンプレートは結局ただの関数なので、上記で作成したviews/tags/notice.scala.htmlが、tags.notics()になっているということなんですね。(たぶん)

Includes

再度言いますが、特別なことは何もありません。
どんなテンプレートもどこからでも呼び出すことが出来ます。
普通の関数だってどこかから呼び出しているのと全く変わりありません。

<h1>Home</h1>
 
<div id="side">
  @common.sideBar()
</div>

moreScriptsとmoreStylesと同等の事

Play framework1.xの頃のmoreScriptsやmoreStylesと同じ環境をScalaテンプレートで宣言するには、mainテンプレートの以下のように宣言します。


(title: String, scripts: Html = Html(""))(content: Html)

<!DOCTYPE html>

<html>
    <head>
        <title>@title</title>
        <link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")">
        <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>
        @scripts
    </head>
    <body>
        <div class="navbar navbar-fixed-top">
            <div class="navbar-inner">
                <div class="container">
                    <a class="brand" href="#">Movies</a>
                </div>
            </div>
        </div>
        <div class="container">
            @content
        </div>
    </body>
</html>

mainテンプレート以外のテンプレートでのみ使いたいJavaScriptなどは以下のように定義します。

@scripts = {
    <script type="text/javascript">alert("hello !");</script>
}

@main("Title",scripts){

   Html content here ...

}

mainテンプレートに渡すべき特別なJavaScriptなどが無い場合は、以下のよう呼び出します。
@main("Title"){

   Html content here ...

}

※注意(公式にない私的メモ)
mainテンプレートのscripts引数を見てみると、デフォルト引数としてHtml=("")がセットされていますよね?
だから、mainテンプレートを呼び出す、別のテンプレートからscriptsを渡さなければ自動的にHtml("")がscriptsに格納されるわけですね。



以上で終了です。

2012年5月17日木曜日

Play framework 2.0のフォームの制御部分意訳

Play framework2.0のHandling form submissionを意訳しました。

ざっと概要を言うと、ブラウザから送られてくるフォームのデータを、どうやってScalaの値にマッピングするのか、ということです。
マッピングの前に、どうやって値を取得するんだ?という疑問もありますが、あまり深く考えずに取得できるようになっています。
言い方を変えると、マッピングする部分を書けば、後はPlayがうまいこと値をrequestから引っ張ってきてくれるということです。

ただ、Play frameworkらしからぬ感じなのですが、具体的なサンプルが少なくて、ちょっとマニュアルが分かりづらかったです。(私の諸々のスキルが低いというのもありますが)
なので、途中でサンプルソースを載せました。(背景が紫色の部分)

例のごとく、もし私の訳が間違っていても責任は負えませんのであしからず・・・
正確なものを読みたい方は原典をどうぞ。

http://www.playframework.org/documentation/2.0.1/ScalaForms

なお、日本Playframeworkユーザー会さんの方で翻訳作業が進行中のようですので、そちらを待たれるという手もあります。

それではどうぞ!

フォームの定義

play.api.dataパッケージは、HTTPフォームのデータを扱い、バリデーションも扱うヘルパーが含まれています。
フォームを扱う最も簡単な方法は、play.api.data.Form構造を定義することです。

import play.api.data._
import play.api.data.Forms._

val loginForm = Form(
  tuple(
    "email" -> text,
    "password" -> text
  )
)

以下のフォームは、結果としてMap[String,String]から、(String, String)の値を生成します。

val anyData = Map("email" -> "bob@gmail.com", "password" -> "secret")
val (user, password) = loginForm.bind(anyData).get

もし、requestスコープの中のソースをコーディングしている場合であれば、直接requestの中身をバインドすることが出来ます。

val (user, password) = loginForm.bindFromRequest.get

複雑なオブジェクトの構築

formは値を構築・分解する機能を使用することが出来ます。
例えば、既存のケースクラスにラップすることが出来ます。

import play.api.data._
import play.api.data.Forms._

case class User(name: String, age: Int)

val userForm = Form(
  mapping(
    "name" -> text,
    "age" -> number
  )(User.apply)(User.unapply)
)

val anyData = Map("name" -> "bob", "age" -> "18")
val user: User = userForm.bind(anyData).get

Note:tupleとmappingを使用することによる違いは、tupleを使用する場合、constructionとdeconstructionを定義する必要が無いということです。
(だって私たちはtupleの構築と分解方法を知っていますよね?)

mapping関数は、あなたに、あなたのカスタマイズされた関数を定義させます。
ケースクラスを構築したり分解したりしたい場合はデフォルトのapplyメソッドとunapplyメソッドを使用するだけです。

※注意(公式にない私的メモ)
ここの訳は良く分からない。
construction and deconstruction functionsをいったいなんと訳せばいいのか・・・

もちろん、Formシグネチャがケースクラスに正確に一致しないことも良くあります。
例として、利用規約に同意するために使われるチェックボックスを含んだformを使ってみましょう。
Userに値を与える必要はありません。
これはフォームのバリデーションに一度使用されますが、役に立つ情報を教えてくれないダミーのフィールドです。
私たちは自分自身の構築・分解のための機能を定義できるので、これを扱うのはとても簡単です。

val userForm = Form(
  mapping(
    "name" -> text,
    "age" -> number,
    "accept" -> checked("Please accept the terms and conditions")
  )((name, age, _) => User(name, age))
   ((user: User) => Some((user.name, user.age, false))
)

Note:formに既存のUserを代入したとき、deconstructionが使用されています。
これは、Userをデータベースからロードして、それを更新するためにフォームを用意する場合に便利です。

制約の定義

それぞれのmappingで、バインディングの過程で値をチェックする、追加のバリデーションの制約を定義することが出来ます。

import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._

case class User(name: String, age: Int)

val userForm = Form(
  mapping(
    "name" -> text.verifying(required),
    "age" -> number.verifying(min(0), max(100))
  )(User.apply)(User.unapply)
)

Note:こういう書き方もできます

mapping(
  "name" -> nonEmptyText,
  "age" -> number(min=0, max=100)
)

このconstructsは上の例と全く同じ制約になります。

また、フィールドにアドホックな制約を定義することもできます。

val loginForm = Form(
  tuple(
    "email" -> nonEmptyText,
    "password" -> text
  ) verifying("Invalid user name or password", fields => fields match { 
      case (e, p) => User.authenticate(e,p).isDefined 
  })
)

バインディングのエラーを制御する

制約を定義した場合には、バインディング時のエラーを制御できるようにする必要があります。
以下のように、foldを使うことが出来ます。

loginForm.bindFromRequest.fold(
  formWithErrors => // binding failure, you retrieve the form containing errors,
  value => // binding success, you get the actual value 
)

※注意(公式にない私的メモ)
実際のサンプルはこんな感じ

def debug = Action { implicit request =>
    // ************************************************************
    // フォームの定義
    // ************************************************************
    // nonEmptyTextやnumberといったプロパティ(とメソッド)はplay.api.data.Formsクラスの中
    val myForm = Form(
        tuple(
            "val1" -> nonEmptyText,
            "val2" -> number
        )
    )
    
    
    // ************************************************************
    // request(POSTされたデータ)を取得する(2パターン)
    // ************************************************************
    /**
     * 最もシンプルなフォームの値の取得方法
     * フォームの定義で 制約(constraints)を指定している値で、実際のフォームに値が
     * 入力されていない場合、この取得方法だと以下のエラーが発生する
     * [NoSuchElementException: None.get]
     * フォームの定義で制約を付けている場合は、必ずfoldを使ってエラーハンドリングをすること!
     * 値が入力されている場合は正常に動いてしまうので、正常系のテストでは問題なく
     * 動作してしまう為、注意すること。
     */
    val(val1, val2) = myForm.bindFromRequest.get
    Ok("bindFormRequest.get" + " -> " + (val1 + 1) + " : " +  (val2 + 1))
    
    
    /**
     * フォームの定義に制約を指定している場合は、foldを使って必ずエラーハンドリングするように!
     * 問題ない場合はタプルとして値が取得でき、型の制約も特に問題なく動作する
     */
    myForm.bindFromRequest.fold(
        errors => Ok("NOOOOO"),
        value => Ok("bindFormRequest.fold" + " -> " + (value._1 + 1) + " : " + (value._2 + 1))
    )
}


formをデフォルト値で初期化する

時々データを編集するために、formに既存のデータを代入したいこともあるでしょう。その場合は以下のようにします。

val filledForm = userForm.fill(User("Bob", 18))

ネストされた値

formのmappingでは、ネストされた値を定義することもできます。

case class User(name: String, address: Address)
case class Address(street: String, city: String)

val userForm = Form(
  mapping(
    "name" -> text,
    "address" -> mapping(
        "street" -> text,
        "city" -> text
    )(Address.apply)(Address.unapply)
  )(User.apply, User.unapply)
)

この方法でネストされたデータを使用する場合、ブラウザから送られるフォームの値は「address.street, address.city, etc」の様な命名にしなければなりません。

繰り返される値

formのmappingでは、繰り返される値(同名の複数の値)を定義することもできます。

case class User(name: String, emails: List[String])

val userForm = Form(
  mapping(
    "name" -> text,
    "emails" -> list(text)
  )(User.apply, User.unapply)
)

このような繰り返される値を使用する場合、ブラウザから送られるフォームの値は「emails[0], emails[1], emails[2], etc」のような命名にしなければなりません。

Optionな値

formのmappingでは、Optionな値を定義することもできます。

case class User(name: String, email: Option[String])

val userForm = Form(
  mapping(
    "name" -> text,
    "email" -> optional(text)
  )(User.apply, User.unapply)
)

Note:emailフィールドは、requestの中に存在しない場合と、ブランク値が入っている場合には、Noneがセットされて無視されます。

無視する値

送られてきた値を無視して、フィールドに静的な値を持たせたい場合は以下のようにします。

case class User(id: Long, name: String, email: Option[String])

val userForm = Form(
  mapping(
    "id" -> ignored(1234),
    "name" -> text,
    "email" -> optional(text)
  )(User.apply, User.unapply)
)


あなたは今、Optionな値、ネストされた値、同名の複数の値を混在させて、複雑なformを作成することが出来ます。


以上で終了です。

2012年5月9日水曜日

Play framework2.0のテンプレートエンジン部分意訳

Play framework2.0のThe template engineを意訳しました。

私は英語が苦手なタイプの人間なので間違っている部分は多々あると思います。
例のごとく、もし私の訳が間違っていても責任は負えませんのであしからず・・・
正確なものを読みたい方は原典をどうぞ。

http://www.playframework.org/documentation/2.0/ScalaTemplates

なお、日本Playframeworkユーザー会さんの方で翻訳作業が進行中のようですので、そちらを待たれるという手もあります。

それではどうぞ!

Play2.0のテンプレートエンジンはScalaがベースです

APS.NETのRazorにインスパイアされました。
コンパクトで簡単でどんなテキストエディタでも編集できます。
新しい言語を覚える必要はありません!

テンプレートはScalaだからコンパイルされます。
つまり、ブラウザで通常のプログラムと同じようにテンプレートのエラーを確認できます。

overview

Play2.0のテンプレートは、ちょっとしたScalaのコードを含んだシンプルなテキストファイルです。
HTMLやXML、CSVといった普通のテキストベースのフォーマットを生成できます。
テンプレートシステムは、デザイナが簡単に扱えるようにして、HTMLを快適に感じるようにしました。

テンプレートはコンパイルされて、普通のScalaの関数になります。
もし、「views/Application/index.scala.html」というテンプレートを作った場合、「views.html.Application.index」という関数が生成されます。

上記のディレクトリにファイルを作ったと仮定して、以下はシンプルなテンプレートの例です。

@(customer: Customer, orders: Seq[Order])
<h1>Welcome @customer.name!</h1>
<ul>
@orders.map { order =>
 
<li>@order.title</li>
}
</ul>

上記のテンプレートは、ただのテンプレートとしてだけではなく、どんなScalaコードからでも呼び出せます。
以下がサンプルです

val html = views.html.Application.index(customer, orders)

※注意(公式にない私的メモ)
Applicationというディレクトリがサンプルにあるけど、なんかこれはデフォルトコントローラのApplication.scalaと混同してしまって分かりにくかった。
viewsディレクトリの下に、「hoge/piyo.scala.html」というファイルを作った場合は、「views.html.hoge.piyo()」になります。
views.htmlは決め打ちで、後は通常のパッケージみたいに.区切りでディレクトリ階層を書いて行って、最後にテンプレートの先頭のファイル名(scala.htmlは不要)を書く感じ

@という魔法

Scalaテンプレートで、@を特殊な文字として扱います。
この@マークがある場所は、Scalaステートメントが始まる場所です。
@で始まるScalaステートメントをを態々閉じる必要はありません。
フレームワークがうまいことやってくれます。

Hello @customer.name!
       ^^^^^^^^^^^^^^^^^^^^^^^
        Scala code

テンプレートエンジンはあなたのコードを解析して、コードの終わりを自動的に検知してくれます。
ただし、もし複数のトークンを使うようなステートメントは明示的に括弧を使ってね。

Hello @(customer.firstName + customer.lastName)!
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                    Scala Code

さらに、普通のScalaコードのように中括弧を使ってステートメントを書くこともできます。

Hello @{val name = customer.firstName + customer.lastName; name}!
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                             Scala Code

そう!@は特殊な文字なのです。
時々@をエスケープしたいときもあるでしょう。
その時は@@を使ってください

My email is bob@@example.com


テンプレートパラメータ

テンプレートはシンプルな機能なので、引数が必要です。
テンプレートファイルの先頭行に宣言する必要があります。

@(customer: models.Customer, orders: Seq[models.Order])

さらにデフォルト引数も使えます。

@(title: String = "Home")

あと、いくつかのパラメータグループも使えます。

@(title:String)(body: => Html)

そしてimplictパラメータも使えます。

@(title: String)(body: => Html)(implicit request: play.api.mvc.Request)


イテレータ

Scalaのfor分は当然使えます。
ただし、以下の場合だとテンプレートコンパイラはコードブロックの上にyieldを置くことに注意してください。

<ul>
@for(p <- products) {
 
<li>@p.name ($@p.price)</li>

}
</ul>

恐らく分かるでしょうが、for分は典型的なmapのシンタックスシュガーになっています。


<ul>
@products.map { p =>
 
<li>@p.name ($@p.price)</li>

}
</ul>


ifブロック

ifブロックは特別なものではありません。
普通のScalaのifと同じように使い、以下のようになります。

@if(items.isEmpty) {
 
<h1>Nothing to display</h1>
} else {
 
<h1>@items.size items!</h1>
}


パターンマッチング

テンプレートの中でパターンマッチングを使うこともできます。

@connected match {
   
  case models.Admin(name) => {
    Connected as admin (@name)
  }

  case models.User(name) => {
    Connected as @name
  }
   
}


再利用可能なブロックの宣言

再利用可能なブロックを利用できます。

@display(product: models.Product) = {
  @product.name ($@product.price)
}

<ul>
@products.map { p =>
  @display(product = p)
}
</ul>

さらに、再利用可能なScalaによるコードブロックを宣言することもできます

@title(text: String) = @{
  text.split(' ').map(_.capitalize).mkString(" ")
}

<h1>@title("hello world")</h1>

Note:Scalaによるコードブロックをテンプレートの中で宣言するのは時々は有効な手段です。
しかし、複雑なロジックを記述するのに、テンプレートはベストな場所ではないということを覚えておいてください。
多くの場合、複雑なコードは純粋なScalaソースファイルに外出ししておいた方が良いです。
(views/パッケージの下にそういったソースファイルを格納することもできます。)

※注意(公式にない私的メモ)
つまり普通にテンプレートの中で関数を宣言できるということ。

PlayFramework2.0の決まりによって、再使用可能なコードはimplictという名前で定義されます。

@implicitFieldConstructor = @{ MyFieldConstructor() }


再利用可能な値の宣言

definingヘルパーを使って、スコープ内で再利用できる値を定義できます。

@defining(user.firstName + " " + user.lastName) { fullName =>
 
    Hello @fullName

}


ステートメントのインポート

テンプレートの先頭でインポートしたいものはどんなものでもインポートすることが来ます。
(もしくはサブテンプレート)

@(customer: models.Customer, orders: Seq[models.Order])

@import utils._

...


コメント

サーバサイドのみのコメントを@* *@を使って書くことが出来ます。

@*********************
 * This is a comment *
 *********************@  

テンプレートの最初の行にコメントを書くことで、Scala API docとして扱うことが出来ます

@*************************************
 * Home page.                        *
 *                                   *
 * @param msg The message to display *
 *************************************@
@(msg: String)

<h1>@msg</h1>


エスケープ

デフォルトでは、動的なコンテンツはテンプレートの種類に応じてエスケープされます。(HTMLとかXML)
もしエスケープさせずにそのまま出力したい場合には、出力したい生データをテンプレートのコンテントタイプでラップしてください。

  <p>@Html(article.content) <p> 


以上で、公式のテンプレートエンジン部分の意訳は終了!
PlayFramework1.2の頃に翻訳されたサイトを見ていましたが、チュートリアルが分かりやすいという点が本当に素晴らしいですね。
英語は苦手ですが、難しい表現もないようなので英語の勉強にももってこいなのでは?