Play Framework 2.3 For Java ことはじめ #8 フォームを扱う編
Play Framework 2.3 For Java 入門記事一覧
第8回はWebアプリケーションで重要な要素の一つであるフォームの話です。
フレームワークに乗っかれば、リクエストのマッピング、バリデーション、エラー表示などいろいろなことを面倒みてくれます。何気にこの仕組みって自分で作ろうとしたらすごく大変です。では、いってみましょう。
1. Playでのformの扱い方を見てみる
まず、DocumentationにFormの説明があるんですが、どこから始めればいいかというか、リクエストとかデータベースとの関わりみたいのがピンときません。
どうしようもんかとGoogleの海をサーフィンしてたら、そもそもPlayのトップページにチュートリアル動画があることに気づきました。
このチュートリアル動画がなんとまぁ分かりやすいこと。なぜ、今まで見なかったんだろうかw
ということでこの動画を見てみるとフォームはだいたいこんな感じで扱えます。
Person
クラスのモデルを作る(このチュートリアルではEBeanなのでModelを継承)
@Entity public class Person extends Model { @Id public String id; public String name; }
- コントローラーで、
play.data.Form
クラスの便利メソッドを使ってリクエストをモデルに変換してデータベースに保存
public static Result addPerson() { Person person = Form.form(Person.class).bindFromRequest().get(); person.save(); return redirect(routes.Application.index()); }
- viewで
<form>
を作る
<form action="@routes.Application.addPerson()" method="post"> <input type="text" name="name" /> <button>Add Person</button> </form>
うーん、分かりやすい。Form.form(クラス)
ってやるだけでリクエストを簡単にマッピングしてくれちゃうんですね。
じゃあ、その逆は?
編集画面なんかでは、逆にモデルからフォームへ変換したくなりますよね。それもちゃんと用意されてます。
Person person = Person.find.byId(id);
Form<Person> form = form(Person.class).fill(person);
モデルをとってきてfillに渡すだけ。この辺は苦労しなさそうです。
2. バリデーションとエラーメッセージ
それじゃ、バリデーションとエラーメッセージの仕組みはどうなってるのか。
いまどきのフレームワークなら当然モダンな感じで用意されてるんでしょう。
ということでJavaFormsを見てみる。
まず、バリデーションを入れたいフィールドに、アノテーションをつける。
さっきのPerson
クラスで名前を必須にしたい場合はこんな感じ。
@Entity public class Person extends Model { @Id public String id; @Constraints.required public String name; }
そして、コントローラー側では、formを取得して、hasErrors()
でバリデーションを検知できる。
そして今まではok
メソッドだったけど、badRequest
メソッドで返すようだ。
Form<Person> personForm = form(Person.class).bindFromRequest(); if(personForm.hasErrors()) { return badRequest(createForm.render(personForm)); }
ビュー側では、こんな感じでエラーメッセージを取得できる。
@for(error <- personForm("name").errors) { <p>@Messages(error.message)</p> }
ちなみに、このエラーメッセージは、conf/messages
というところに書いておけば上書きできる。
error.required=このパラメータは必須フィールドです
ふむふむ。雰囲気はなんとなく分かった。
3. ヘルパーメソッド
まず、基本的な@form
や@inputText
などは他のフレームワークでも一般的に用意されてるのでなんとなくタグを生成してくれるんだろうなっていうのは分かった。ただ、例えば、@form
に渡せるパラメータは何なのかAPIの見方がScalaの書き方なので、すぐに分からないのでまずはリファレンスの見方を理解する。
def apply(action: Call, args: (Symbol, String)*)(body: ⇒ Html): play.twirl.api.HtmlFormat.Appendable
APIのformのValue Members
のところにメソッドっぽいのあったのでこれのようだ。
def apply
っていうやつがあって、ググってみるとこのapply
というやつはScalaでは特別な意味を持ってるようです。apply
は省略しても呼ばれるメソッドということなので、だから、@form(){ ... }
で呼び出せたんですね。
ってことは、、ということで試しにやってみたら@form.apply(){ ... }
でもいけた。
そして、下の方に使い方の例と各引数の説明があった。
Generate an HTML form. Example: @form(action = routes.Users.submit, args = 'class -> "myForm") { ... } action The submit action. args Set of extra HTML attributes. body The form body.
うん。引数の説明とか分かりやすくてとてもいいです。
でも、使い方の例のところの、'class
の前についてる'
おまえ。お前はダメだ。一体君はどこにかかっているんですか?いつ閉じられるんですか?閉じないんですか?はっきりしてください。と不満が爆発しそうでしたが、そもそも文字列をくくるものではありませんでした。
シングルクオテーション scalaとかでググったらちゃんとみつかりました。
Scala文字列メモ(Hishidama's Scala String Memo)
シンボル(Symbol)も文字列を表す。[2011-02-07]
シンボルのリテラルは、文字の並びの前にシングルクォーテーションを1つ付ける('名前)。
もしくはSymbol("名前")でインスタンスを取得する。(Symbolオブジェクトのapply()メソッド) スペースの入ったシンボルや数字から始まるシンボルを取得するには後者の方法を使うしかない。
Stringとの違いは、Stringは同じ内容でも異なるインスタンスが存在しうる("abc"とnew String("abc")はインスタンスが異なる)のに対し、Symbolでは同じ内容ならインスタンスも等しい。
はぁーなるほど。シンボルでしたか。rubyでいう:
ね。シンボルって未だにあんまり概念が理解できないんだけど、要は絶対的な値というか、番号というかそんな感じでよかった気がする。
人間から見たら、classという5文字であることは、'class
でも、"class"
でも変わらないんだけど、コンピュータの世界では別ものであり、シンボルの方は絶対に変わらない値なので、生成のコストが低いみたいなメリットがあるって理解してる。
この調子で、API見れば、使い方分かりそうだ。Play For Javaと言えど、Scalaの文法は覚える必要があるなぁこれ。今回覚えたつまづきそうなJavaと違うScalaの文法を整理しとく。
_
: ワイルドカード。Javaでいう*
[]
: ジェネリクス。Javaでいう<>
apply
: メソッド名を省略した場合呼ばれるScalaにおける特別なメソッド'name
: シンボル。Javaにはないんじゃないかな?Rubyだと:name
4. FieldConstructor
あとは、このFieldConstructorというのが理解するのが大変だったけど、Scalaの文法調べたり、APIリファレンス見てるうちにやっと分かってきた。
つまり、FieldConstructorというやつは、@form() { }
の中括弧の中に書いてあるhelperで作った(直書きでinputタグ書いた場合は適用されなかった)inputタグに対して自動的に適用されるhtmlの装飾みたいなものってこと。
だって、どうせ、ただの<input>
タグだけとかそんなんないでしょ?<label>
とか必要になるでしょ?ってことで作られたんだと思う。
例えば、こう書くと
@form(routes.Application.index()) {
@inputText(computerForm("name"), '_label -> "name")
}
こんなふうに、FieldConstructorが適用されたhtmlが返ってくる。これは、defaultFieldConstructorとして定義されています。
<dl class=" " id="name_field"> <dt><label for="name">Computer name</label></dt> <dd> <input type="text" id="name" name="name" value=""> </dd> <dd class="info">Required</dd> </dl>
でも、今どきのbootstrapとか自分たちで作ってるサイトもそうだけど、全てがこんな形になってるわけじゃなくて、独自のhtmlタグの構造になってるはず。
それを、オーバーライドすることもできる。
例えば、Twitter Bootstrapで言えば、こういうテンプレートファイルを作っておいて、
@(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>
利用するフォームタグのhtmlで、このFieldConstructorを設定する。
@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) }
すると、さっきまでのhtmlと違い、bootstrap用のhtmlが出力されるようになる。
<div class="clearfix "> <label for="name">Computer name</label> <div class="input"> <input type="text" id="name" name="name" value=""> <span class="help-inline">Required</span> </div> </div>
ようは、FieldConstructorってフォームタグのところを整形するためのものだったんすね。やっと分かりました。
まとめ
個人的には、思いのほか難しかった。Scalaの文法が混じってるところが結構ハードル高いのかもしれない。 リクエストをモデルに変換する部分やバリデーションやエラーメッセージもフレームワークの機能として用意されてる。さらに、ヘルパーとしてform周りのタグを便利に生成してくれますよーということがなんとなく分かりました。
第9回は、FieldConstructorが結構難しいので、その出し分け方法について、さらに突っ込んでみることにしました。