We’ve looked at handling form data before – both manually and with the Form binding mechanism. We’ll take this form-binding one step further and use it to validate input.
Step 1: Create the skeleton application.
Lesson 5 details installing the Play framework and setting up Scala IDE,
and we created the skeleton application in lesson 1 but just to remind
you of the newer format using the activator command:
$ cd ~ $ activator new lesson6 $ cd lesson6 $ activator eclipse $ activator run
Step 2: An action for creating a Customer record.
We typically create a case class – an immutable type that represents data. We’ll do this in app/models/Customer.scala:
1 2 3 | package modelscase class Customer(name:String, creditLimit:Int) |
We’ll create a new route for our new Controller function createCustomer() – which will process the form. This is our conf/routes:
GET / controllers.Application.index POST / controllers.Application.createCustomer GET /assets/*file controllers.Assets.at(path="/public", file)
Here is our updated app/controllers/Application.scala:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package controllersimport play.api._import play.api.mvc._import models.Customerimport play.api.data._import play.api.data.Forms._object Application extends Controller { def customerForm = Form(mapping("Customer Name" -> nonEmptyText, "Credit Limit" -> number)(Customer.apply)(Customer.unapply)) def index = Action { Ok(views.html.index(customerForm)) } def createCustomer = Action { implicit request => customerForm.bindFromRequest().fold( formWithErrors => BadRequest(views.html.index(formWithErrors)), customer => Ok(s"Customer ${customer.name} created successfully")) }} |
There is enough here to warrant some explanation:
Here is the line that will map from Form inputs to the Customer class –
note the apply/unapply that does the binding from the (text, number) to
the (String, Int) in the Customer constructor.
1 2 | def customerForm = Form(mapping("Customer Name" -> nonEmptyText, "Credit Limit" -> number)(Customer.apply)(Customer.unapply)) |
Next is the updated call to app/views/index.scala.html. Note that the argument has changed to our form. We’ll see this later.
1 2 3 | def index = Action { Ok(views.html.index(customerForm))} |
Next is the action that processes the form and handles the 2 possible outcomes from fold – a form with errors or a Customer object. We’re trying to create a Customer from our request. This could quite easily fail as we have put validation into the form-mapping definition (remember the nonEmptyText?):
1 2 3 4 5 | def createCustomer = Action { implicit request => customerForm.bindFromRequest().fold( formWithErrors => BadRequest(views.html.index(formWithErrors)), customer => Ok(s"Customer ${customer.name} created successfully")) } |
When the form fails we resend the index view with the failed form which, helpfully, contains not only the original data but also the cause of the validation failure.
We need to use Play’s helpers to render our form though, which brings us to app/views/index.scala.html. I put the inputs in a different order to show they don’t matter:
1 2 3 4 5 6 7 8 9 10 11 12 | @(customerForm:Form[Customer])@import helper._@main("welcome") { <h1>Customer Form</h1> @form(action=routes.Application.createCustomer()) { @inputText(customerForm("Credit Limit")) @inputText(customerForm("Customer Name")) <input type="submit" value="Submit"> }} |
Step 3: Trying it out
We should now be able to see the form and try it out:
This is great – the “Numeric” and “Required” text just happens as the form knows there are constraints on both fields – the Credit Limit is a number, right?
Let’s submit the form with empty fields:
Great – some helpful information for the user, all off the back of an explicit and implicit constraint on user input.
Let’s fill in one of the fields, entering 100 for credit limit and submitting. What do we get back?
Perfect – we don’t need to re-render the previous credit limit input. That’s great. Let’s submit the form with ACME inc. as the Customer name:
Step 4: A little more validation
Not all integer values are valid for a Credit Limit. Let’s ensure it’s
between 0 and 100,000. We only modify the mapping in the form:
1 2 3 | def customerForm = Form(mapping("Customer Name" -> nonEmptyText, "Credit Limit" -> number(min = 0, max = 100000) )(Customer.apply)(Customer.unapply)) |
Is it that simple? You bet:
The output here is deliberately crude. I’m avoiding styling to show just the relevant parts. Trust me though, when integrated with Twitter Bootstrap the output looks completely professional.





I’m learning a lot with your examples !! I hope you continue with many more
LikeLike
Hi. I’m glad you like them. Any suggestions for topics?
LikeLike
Right now I’m following all your examples. Once finished all the tutorials I may suggest you some :)
Thanks!
LikeLike
This is the best set of tutorials I have found on play framework forms/twitter bootstrap integration (will be coming upon that soon) Thanks so much for posting this valuable info… Some of the things in scala lang itself are hard to absorb quickly but its good to have a framework ready and now i can always examine the smaller pieces with scala-lang.org documentation… thanks again so helpful
LikeLike