まーぽんって誰がつけたの?

iOS→Scala→インフラなおじさん技術メモ

Play Framework 2.3 For Java ことはじめ #9 FieldConstructor編

f:id:masato47744:20140720125846p:plainPlay Framework 2.3 For Java 入門記事一覧

第9回はページャーとかセッションとかやろうと思ったけど、やっぱりフォーム編で話したFieldConstructorという概念がとても難しかったので、もうちょっと詳しく見ていこうと思います。

FieldConstructor@form(){}の中で、@inputText()などのフォームのヘルパーを使うと自動的に適用されるというものだってことは分かりました。でも、実際のWebアプリケーションで、フォームの中が全て、同じtypeのフォームばっかり(例えばテキストボックスだけ)みたいなことって絶対ないし、同じhtml構造をとれるわけがないと思います。

例えば、サンプルプロジェクトの一つの、computer-database-javaだと、こんな風に一つのフォームに対して、全て同じtwitterBootstrapInputFieldConstructorが適用されてしまいます。

テンプレートの部品の方のhtml : twitterBootstrapInput.scala.html

@(elements: helper.FieldElements)

@***************************************************
* Generate input according twitter bootstrap rules *
***************************************************@
<div class="clearfix @if(elements.hasErrors) {error}">
    <label for="@elements.id">@elements.label</label>
    <div class="input">
        @elements.input
        <span class="help-inline">@elements.infos.mkString(", ")</span> 
    </div>
</div>

formを使う側のhtml

@(computerForm: Form[Computer])

@import helper._

@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) } 

@main {
    
    <h1>Add a computer</h1>
    
    @form(routes.Application.save()) {
        
        <fieldset>
        
            @inputText(computerForm("name"), '_label -> "Computer name", '_help -> "")
            @inputDate(computerForm("introduced"), '_label -> "Introduced date", '_help -> "")
            @inputDate(computerForm("discontinued"), '_label -> "Discontinued date", '_help -> "")

            @select(
                computerForm("company.id"), 
                options(Company.options), 
                '_label -> "Company", '_default -> "-- Choose a company --",
                '_showConstraints -> false
            )
            

        </fieldset>
        
        <div class="actions">
            <input type="submit" value="Create this computer" class="btn primary"> or 
            <a href="@routes.Application.list()" class="btn">Cancel</a> 
        </div>
        
    }
    
}

これだと、type="text"も、type="date"も、selectも全部同じtwitterBootstrapInputというFieldConstructorが適用されちゃいますよね。
同じフォーマットで作るべきだというならそれはそれで正しいんですが、実際のプロジェクトではそうはいかないですよね。

そんなときに、どうやって別々の構造させるのを適用させるべきなのか。前置きが長くなりましたが、今回は、それについて考えてみました。

1. テンプレート部品側で場合分けする

素直に考えれば、テンプレート部品側twitterBootstrapInput.scala.htmlで、何かの値によって場合分けすればいいかなと思う訳です。

例えば、@inputTextで渡すパラメータにhtmlの属性として判断するための値を追加したり、渡ってくるinput要素をパースとかすれば、その文字列で出力するタグをif文で分けることはできました。

@if(elements.args.contains('paramA)){
    //Do something here
}else{
    //Do something else
}

でも、この方法ってなんかイケてないというか、せっかく型安全をうたってるのに、属性の文字列なんかで処理してしまったらこの素敵なテンプレートエンジンが台無しだなと思うわけです。他の方法があるはずだと。

2. 別々のFieldConstructorを指定するには

formの中の部品を呼び出す側のhtmlでは、FieldConstructorを指定してますが、こんな風に一つしか指定できません。

@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) } 

やっぱり、使い方をちゃんと知るには、リファレンスを見るしかないですよね。
そもそも@inputText(...)のところがどんなことをしているのかってのを確認してみます。

exampleを見ると、

@inputText(field = myForm("name"), args = 'size -> 10, 'placeholder -> "Your name")

と書いてあって、なんかフォームとhtmlの属性以外は渡せないんじゃないかなーという印象を受けます。

でも、Scalaといえばapplyということでそのメソッドの定義をちゃんと見てみると、、、

def apply(field: Field, args: (Symbol, Any)*)(implicit handler: FieldConstructor, lang: Lang)

ん?

なんか、最初のかっこ(field: Field, args: (Symbol, Any)*)の後ろに、(implicit handler: FieldConstructor, lang: Lang)がついてて、そこには、FieldConstructorの文字が!!

これは、色々なFieldConstructorが渡せそうだとなる訳です。
そんで、how to create different type fieldconstructor playframeworkとかでググってるとやっと分けてるやつが見つかりました。Play Framework 2.0 Templates - Part 1, Parameters - Virtual Void ちなみに、意外と日本語の情報って少ない印象。playってあんま流行ってないのかな。

これを見るとこんな風に書いてあって、やっぱり、好きなFieldConstructorをフォームヘルパーに渡しています。implicitly[Lang]というのは必要で、テンプレート側に暗黙的に渡ってくるLang型の変数を入れるみたいな意味です。

@inputDate(field = ...)(fieldConstructor = dateConstructor, implicitly[Lang])

3. 実際に別々のFieldConstructorを指定してみる

ってことで、例えばdefaultFieldConstructordivとか囲わなくていいっす。質素なやつでいいんです。みたいな場合には、こんなFieldConstructorを書いて、myTextInput.scala.htmlとして保存します。

@(elements: helper.FieldElements)

<label for="@elements.id">@elements.label</label>
@elements.input

あとは、呼び出し側で@inputTextのパラメータとしてそのFiledConstructorを渡してあげれば、

@inputText(computerForm("name"), '_label -> "Computer name")(myTextInput, implicitly[Lang])

こんなhtmlがでてきます。やった!

<label for="name">Computer name</label>
<input type="text" id="name" name="name" value="">

別々のFieldConstructorでformのヘルパーで出力させることができました!

Formオブジェクトを渡すだけでうまいことやってくれるヘルパーなんですから、なんとしてもFieldConstructorでやりたかった訳で、これでhtmlを直書きするなんて残念なことはしなくていいわけですね。

まとめ

Scalaimplicitみたいな単語ががよく出てきますが、これは日本語にすると暗黙的という意味です。
日本語にすれば至極簡単ですよね。その言葉通り、何もしなければ暗黙的に何かが適用されてるんだなと理解すればいいってことです。

FieldConstructorが別々のものが適用できることが分かったので、これでフォームヘルパーで怖いものはなくなりましたね!

第10回は、ログイン認証にしました。Play Framework 2.3 For Java ことはじめ #10 ログイン認証編