Play2 forms and Bootstrap
Bootstrap gives developer grid layout, some css magic and components. As Play2 contains support for Bootstrap directly one can think that it's all easy no brainer to have some fields rendered with easy helpers.
Yeah, but...
Bootstrap 1.X, 2.X and 3.X
Play2 2.1 contains helpers, which render Bootstrap 1.4 markup. Since there's quite some changes between Bootstrap versions, Play2's built in support starts to look like a teaser.
If you want to study Bootstrap differences, it's all there
Built in standard field rendering (no bootstrap)
Let's see simple Scala template example without Bootstrap.
Here's our form template. Form type is defined as input, template imports helpers, form and inputText helpers are used to render html form.
@(userForm: Form[models.jpa.Users])
@* helper._ import is required to be able to use the @form tag *@
@import helper._
@helper.form(action = routes.UserController.save()) {
<fieldset>
@helper.inputText(userForm("id"))
@helper.inputText(userForm("username"))
@helper.inputText(userForm("password"))
</fieldset>
<input type="submit" class="btn primary">
}
}
When rendered to html result looks like this. Form has got url address and method, each field has label and id and values are retrieved from form.
<form action="/users" method="POST" >
<fieldset>
<dl class=" " id="id_field">
<dt><label for="id">id</label></dt>
<dd>
<input type="text" id="id" name="id" value="3" >
</dd>
</dl>
<dl class=" " id="username_field">
<dt><label for="username">username</label></dt>
<dd>
<input type="text" id="username" name="username" value="user1" >
</dd>
</dl>
<dl class=" " id="password_field">
<dt><label for="password">password1</label></dt>
<dd>
<input type="text" id="password" name="password" value="" >
</dd>
</dl>
</fieldset>
<input type="submit" class="btn primary">
</form>
Works, but not exactly eye-candy.
Documentation for templates can be found from here:
Built in Bootstrap field rendering (Bootstrap 1.4)
Taking bootstrap in use is really really simple. Just add import of Bootstrap helpers. Everything else stays same.
@(userForm: Form[models.jpa.Users])
@import helper._
@* helper.twitterBootstrap._ import is required for formatting *@
@import helper.twitterBootstrap._
When html is rendered you can see how form definition has not been changed, but fields are now defined with divs. Same happens to all fields.
<div class="clearfix " id="id_field">
<label for="id">id</label>
<div class="input">
<input type="text" id="id" name="id" value="3" >
<span class="help-inline"></span>
<span class="help-block"></span>
</div>
</div>
If it looks funny when rendered by your browser, remember that helpers and thus generated CSS is for Bootstrap 1.4 and your pages might use some other version. So; either get right Bootstrap files or abandon standard Bootstrap helpers and go on.
Customizing field rendering (Bootstrap 2.X and 3.X)
Play2 Input helpers and present Bootstrap (1.4) helpers might be enough for simple use cases, but if you want to have fields in any other layout than vertical, i.e. label with field name at top and field right under label, you probably need to implement your own rendering.
Possibilities how to proceed
- Write html as it is, no helpers, all tags are up to you
- Develop reusable code blocks and save them to any exiting template to use them later
- Develop reusable template files, which can be called directly
- Develop reusable template files, which are registered to be used during rendering
When you try to gain some reusability you get all the time more Scala on your bandwagon. Let's face it. Java is not enough here.
Example from "Play2 for Java" (Bootstrap 1.X)
"Play2 for Java" book is still work in progress, but MEAP is pretty good stuff. Example from book shows how standard Bootstrap helper is written.
@* Example from: play2 for java, meap 8, page 163 *@
@(elements: views.html.helper.FieldElements)
@import play.api.i18n._
@import views.html.helper._
* Generate input according twitter bootsrap rules *
<div class="clearfix @elements.args.get('_class)
@if(elements.hasErrors) {error}"
id="@elements.args.get('_id).getOrElse(elements.id + "_field")">
<label for="@elements.id">@elements.label(elements.lang)</label>
<div class="input">
@elements.input
<span class="help-inline">
@elements.errors(elements.lang).mkString(", ")
</span>
<span class="help-block">
@elements.infos(elements.lang).mkString(", ")
</span>
</div>
</div>
Reading sources of Play2 helps you to understand what you can do with FieldElements. At least I was bit surprised of FieldElements since input, for example, is just a parameter with html content.
Example of reusable registered rendering templates (Bootstap 2.X)
James Ward has published nice tips to us. James' tips help you directly if you use Bootstrap 2.X as Bootstrap 2.X has control groups and controls, but Bootstrap 3.X uses form-group instead of control-group. Maybe this clarification on naming between Bootstrap versions was needed, but at least it breaks your code.
James' example uses single form constructor, which is registered on page which uses it. Field constructor is like any other Scala template file.
@(elements: helper.FieldElements)
<div class="control-group @if(elements.hasErrors) {error}">
<label for="@elements.id" class="control-label">@elements.label</label>
<div class="controls">
@elements.input
<span class="help-inline">@elements.errors.mkString(", ")</span>
</div>
</div>
It's all explained here. No need to repeat.
Example of scala helpers (Bootstrap 2.X)
Here's simple example from Philip M. Johnson how to use single field constructor.
But life's not that simple. Here's another example from Philip that can bring you closer to understand the problem in depth.
Philip says: "The canonical recommendation for users of Twitter Bootstrap 2.x is to create a single "implicit" field constructor. The problem with this recommendation is that a single implicit field constructor cannot satisfy all of Twitter Bootstrap's layout requirements for form controls (for example, multiple checkboxes). This example illustrates a more general solution in which normal (i.e. "explicit") scala templates (i.e. field constructors) are defined in theviews.bootstrap package for each of the Twitter Bootstrap controls. IMHO, the code is significantly easier to understand and debug for Java-based Play framework users."
You should read this well, and understand it. After reading it's up to you to try to figure out if there's mismatch between your needs and what Play2 helpers provide. If you follow Philips advice you are suddenly starting to do all by yourself. Goodbye form helpers, welcome control. But is that right way to go? Maybe. It depends.
Philips field template for simple text field looks like this. Pretty clean.
@(field: Field, label:String, placeholder:String, help:String)
<div class="control-group @if(field.hasErrors) {error}">
<label class="control-label">@label</label>
<div class="controls">
<input
class="input-xlarge"
type="text"
id="@field.id"
name="@field.name"
value="@field.value.getOrElse("")"
placeholder="@placeholder" />
<p class="help-block">@help</p>
<p class="help-block">@{field.error.map { error => error.message }}</p>
</div>
</div>
Example from "Play2 for Scala" (Bootstrap 2.X)
"Play2 for Scala" book presents example FieldConstructor which is written for Bootstrap 2.X, but I'll add it here to give you idea.
@* Example from: play2 for scala, page 187 *@
@(elements: views.html.helper.FieldElements)
@import play.api.i18n._
@import views.html.helper._
<div class="control-group @elements.args.get('_class)
@if(elements.hasErrors) {error}"
id="@elements.args.get('_id).getOrElse(elements.id + "_field")" >
<label class="control-label" for="@elements.id">
@elements.label(elements.lang)
</label>
<div class="controls">
@elements.input
<span class="help-inline">
@if(elements.errors(elements.lang).nonEmpty) {
@elements.errors(elements.lang).mkString(", ")
} else {
@elements.infos(elements.lang).mkString(", ")
} </span>
</div>
</div>
You should read chapter 7.3.4 "Customizing generated HTML" to go deeper to topic. This might save your day, since I haven't found too many places where this topic would be well discussed.
My-2-Cents template (Bootstrap 3.X)
Twitter Bootstap 3.X needs right css class definition for form and fitting structure for fields also. I decided to write my template simple way, so: get class for label and for div thru element params.
@import play.api.i18n._
@import views.html.helper._
<div class="form-group @elements.args.get('_class) @if(elements.hasErrors) {error}">
<label for="@elements.id" class="@elements.args.get('_label_class)">@elements.label(elements.lang)</label>
@elements.input
<span class="help-block">
@if(elements.errors(elements.lang).nonEmpty) {
@elements.errors(elements.lang).mkString(", ")
} else {
@elements.infos(elements.lang).mkString(", ")
} </span>
</div>
My solution is just imitation of ones listed already, and it's about to be used like this
@(userForm: Form[models.jpa.Users])
@implicitFieldConstructor = @{ helper.FieldConstructor(bootstrapInput.render) }
@* helper._ import is required to be able to use the @form tag *@
@import helper._
@helper.form(action = routes.UserController.save(),
'class -> "form-horizontal", 'role -> "form") {
<fieldset>
@helper.inputText(userForm("id"),
'_label_class -> "col-md-2",
'class -> "col-md-2", '_label -> "ID")
@helper.inputText(userForm("username"),
'_label_class -> "col-md-2",
'class -> "col-md-2")
@helper.inputText(userForm("password"),
'_label_class -> "col-md-2", 'class -> "col-md-2")
</fieldset>
<input type="submit" class="btn primary">
}
Rendering looks like this. ID fields name is overwritten, class has went to input element and _label_class to label. If I'd used _class it would have went to div.
<form action="/users" method="POST" class="form-horizontal" role="form">
<fieldset>
<div class="form-group ">
<label for="id" class="col-md-2">ID</label>
<input type="text" id="id" name="id" value="3" class="col-md-2">
<span class="help-block">
</span>
</div>
<div class="form-group ">
<label for="username" class="col-md-2">username</label>
<input type="text" id="username" name="username" value="user1" class="col-md-2">
<span class="help-block">
</span>
</div>
Please see how this matches to Bootstrap 3.X documentation, and modify as needed.
Further steps
Java developers, like me, who have dreamed to get power of Play2 without any added complexity might feel like cheated after reading "Play2 for Java" and learning that forms and bootstrap support are all built-in, since it's not all that easy. You really need to go beyond examples of books and learn some Scala.
It's possible to implement integration libraries like one that is currently embedded to Play2, or leave implementation always for project. Important is to follow DRY principle - so: don't repeat yourself, use reusable templates. Whether these templates are made by you, copy pasted or acquired as dependencies is up to presence of such components and your needs.
And please read some more posts. Maybe learn some more Scala.