-
Notifications
You must be signed in to change notification settings - Fork 0
ScalaForms
The play.api.data package contains several helpers to handle HTTP form data submission and validation. The easiest way to handle a form submission is to define a play.api.data.Form structure:
import play.api.data._
import play.api.data.Forms._
val loginForm = Form(
tuple(
"email" -> text,
"password" -> text
)
)
This form can generate a (String, String) result value from Map[String,String] data:
val anyData = Map("email" -> "bob@gmail.com", "password" -> "secret")
val (user, password) = loginForm.bind(anyData).get
If you have a request available in the scope, you can bind directly to it from the request content:
val (user, password) = loginForm.bindFromRequest.getA form can use any functions to construct and deconstruct the value. So you can, for example, define a form that wraps an existing 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("email" -> "bob@gmail.com", "age" -> "18")
val user: User = userForm.bind(anyData).getNote: The difference between using
tupleandmappingis that when you are usingtuplethe construction and deconstruction functions don't need to be specified (we know how to construct and deconstruct a tuple right?).The
mappingmethod just let you define your custom functions. And when you want to construct and deconstruct a case class, you can just use its defaultapplyandunapplyfunctions as they do exactly that!
Of course often the Form signature doesn't match exactly the case class. Let's take an example a form that contain an additional checkbox field used to accept the terms and services. We don't need to fill this in our User value right? It's just a dummy field that serve for form validation but which doesn't carry any useful information once validated.
As we can define our own construction and deconstruction functions, it is easy to handle it:
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: The deconstruction function is used when we fill a form with an existing
Uservalue. It is useful if we want the load a user from the database and prepare a form to update it.
For each mapping you can also define additional validation constraints that will be checked during the binding phase:
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: That can be also written:
mapping( "name" -> nonEmptyText, "age" -> number(min=0, max=100) )That constructs the same mappings with additional constraints
You can also define ad-hoc constraints on the fields:
val loginForm = Form(
tuple(
"email" -> nonEmptyText,
"password" -> text
) verifying("Invalid user name or password", {
case (e, p) => User.authenticate(e,p).isDefined
})
)Of course if you can define constraints, then you need to be able to handle the binding errors. You can use the fold operation for that:
loginForm.bindFromRequest.fold(
formWithErrors => // binding failure, you retrieve the form containing errors,
value => // binding success, you get the actual value
)Sometimes you’ll want to fill a form with existing values, typically for editing:
val filledForm = userForm.fill(User("Bob", 18))A form mapping can define nested values:
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)
)Note: When you are using nested data this way, the form data sent by the browser need to be written as
address.street,address.city, etc.
A form mapping can also define repeated values:
case class User(name: String, emails: List[String])
val userForm = Form(
mapping(
"name" -> text,
"emails" -> list(text)
)(User.apply, User.unapply)
)
Note: When you are using repeated data this way, the form data sent by the browser need to be written as
emails[0],emails[1],emails[2], etc.
A form mapping can also define optional values:
case class User(name: String, email: Option[String])
val userForm = Form(
mapping(
"name" -> text,
"email" -> optional(text)
)(User.apply, User.unapply)
)
Note: The email field will be ignored and set to
Noneif the field
Now you can mix optional, nested and repeated mappings any way you want to created complex forms.
- HTTP programming
- Asynchronous HTTP programming
- The template engine
- HTTP form submission and validation
- Working with JSON
- Working with XML
- Handling file upload
- Accessing an SQL database
- Using the Cache
- Calling WebServices
- Integrating with Akka
- Internationalization
- The application Global object
- Testing your application