§フォームの送信
§フォームの定義
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" -> "[email protected]", "password" -> "secret")
val (user, password) = loginForm.bind(anyData).get
スコープ内にリクエストが存在する場合は、リクエストの内容から直接バインドすることができます。
val (user, password) = loginForm.bindFromRequest.get
§複雑なオブジェクトの構築
フォームはある値を構築したり、分解する関数を引数に取ることができます。そのため、例えば次のように case class をラップするフォームを定義することができます。
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
については構築および分解に使われる関数を指定する必要がないということです (タプルを構築、または分解する方法は明らかですよね) 。
mapping
メソッドには好きな関数を渡すことができます。ケースクラスを構築または分解する場合、デフォルトのapply
とunapply
関数がまさしくケースクラスの構築・分解を行う関数なので、それらを渡しておけば問題ありません。
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: 分解用の関数は
User
の値をフォームに設定するときに利用されます。これは、例えばユーザ情報をデータベースから読み込んで、更新のために予めフォームに埋め込んでおくような場合に便利です。
§制約の定義
各々のマッピングについて、フォームのバインド時に実行されるバリデーションを追加することができます。
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(nonEmpty),
"age" -> number.verifying(min(0), max(100))
)(User.apply)(User.unapply)
)
Note: 上記のコードは次のように書くこともできます。
mapping( "name" -> nonEmptyText, "age" -> number(min=0, max=100) )
このコードは、先程の例と同様に、バリデーションが追加された全く同じマッピングを表しています。
フィールドにアドホックなバリデーションを定義することもできます。
val loginForm = Form(
tuple(
"email" -> email,
"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,
BadRequest(views.html.login(formWithErrors)),
value => // binding success, you get the actual value
Redirect(routes.HomeController.home).flashing("message" -> "Welcome!" + value.firstName)
)
§フォームに初期値を設定する
よくあるケースとして、編集などのためにフォームに予め値を設定したい場合は、以下のようにします。
val filledForm = userForm.fill(User("Bob", 18))
§ネストした値
フォームはネストした値を扱うこともできます。
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
のような形式になっている必要があります。
§値の繰り返し
フォームは値の繰り返しを扱うこともできます。
case class User(name: String, emails: List[String])
val userForm = Form(
mapping(
"name" -> text,
"emails" -> list(email)
)(User.apply, User.unapply)
)
このようなデータの繰り返しを処理する場合には、ブラウザから送信されるフォーム値の名前は emails[0]
, emails[1]
, examils[2]
のような形式になっている必要があります。
§省略可能な値
フォームは省略可能な値を扱うこともできます。
case class User(name: String, email: Option[String])
val userForm = Form(
mapping(
"name" -> text,
"email" -> optional(email)
)(User.apply, User.unapply)
)
Note: リクエストパラメータに
None
がセットされます。
§無視された値
フィールド用の静的な値を持つフォームが必要な場合は:
case class User(id: Long, name: String, email: Option[String])
val userForm = Form(
mapping(
"id" -> ignored(1234),
"name" -> text,
"email" -> optional(email)
)(User.apply, User.unapply)
)
これまで説明した省略可能な値、値のネストや繰り返しに関するマッピングを組み合わせて、より複雑なフォームを定義することができます。
次ページ: フォームテンプレートヘルパーの利用