How to represent a dynamic form with Play forms? - scala

I have a form which is like a quiz/questionnaire type design to it.
So say the page is a quiz, it will display a dynamic number of questions on the page, and each question will have a list of checkbox options (the user can select 1+ checkboxes).
How can I represent this type of form in play?
case class Quiz(id: Int, name: String)
case case Question(id: Int, quizId: Int, title: String)
case class QuestionOption(id: Int, questionId: Int, name:String)
So you have a quiz, that has many questions. And each question has multiple QuestionOptions.
So the form would be something like:
case class QuizForm(quiz: Quiz, questions: List[Question], options: List[QuestionOption])
For each question, you can select 1 or more QuestionOption.

I would nest the Questions within Quiz, and QuestionOptions within Question. That could make this a little more compact in the end.
case class Quiz(id: Int, name: String, questions: List[Question])
case class Question(id: Int, quizId: Int, title: String, options: List[QuestionOption])
case class QuestionOption(id: Int, questionId: Int, name: String)
A possible implementation of the Form would use lists of mapping:
val quizForm: Form[Quiz] = Form {
mapping(
"id" -> number,
"name" -> nonEmptyText,
"questions" -> list(mapping(
"id" -> number,
"quizId" -> number,
"title" -> nonEmptyText,
"options" -> list(mapping(
"id" -> number,
"questionId" -> number,
"name" -> nonEmptyText
)(QuestionOption.apply)(QuestionOption.unapply))
)(Question.apply)(Question.unapply))
)(Quiz.apply)(Quiz.unapply)
}
The very verbose part is the HTML form, which needs to contain all of these fields. Here is a sample of data a form would send (one question, multiple answers).
val data = Map(
"id" -> "1",
"name" -> "My Quiz",
"questions[0].id" -> "1",
"questions[0].quizId" -> "1",
"questions[0].title" -> "What?",
"questions[0].options[0].id" -> "1",
"questions[0].options[0].questionId" -> "1",
"questions[0].options[0].name" -> "red",
"questions[0].options[1].id" -> "2",
"questions[0].options[1].questionId" -> "1",
"questions[0].options[1].name" -> "green",
"questions[0].options[2].id" -> "4",
"questions[0].options[2].questionId" -> "1",
"questions[0].options[2].name" -> "blue"
)
The name fields of each of the question meta data / answers will need to have unique indices that bind them together. Here's a crude example:
<input type="hidden" name="id" value="1">
<input type="hidden" name="name" value="My Quiz">
<input type="hidden" name="questions[0].id" value="1">
<input type="hidden" name="questions[0].quizId" value="1">
<input type="hidden" name="questions[0].title" value="What?">
<input type="hidden" name="questions[0].options[0].id" value="1">
<input type="hidden" name="questions[0].options[0].questionId" value="1">
<input type="checkbox" name="questions[0].options[0].name" value="red">
<input type="hidden" name="questions[0].options[1].id" value="2">
<input type="hidden" name="questions[0].options[1].questionId" value="1">
<input type="checkbox" name="questions[0].options[1].name" value="green">
<input type="hidden" name="questions[0].options[2].id" value="3">
<input type="hidden" name="questions[0].options[2].questionId" value="1">
<input type="checkbox" name="questions[0].options[2].name" value="blue">
<input type="hidden" name="questions[0].options[3].id" value="4">
<input type="hidden" name="questions[0].options[3].questionId" value="1">
<input type="checkbox" name="questions[0].options[3].name" value="orange">
We can probably make this smaller by removing some of the title/name fields and ignoring them in the Form, since they shouldn't matter for persistence. But this gets rather verbose very quickly.
It's at this point I usually abandon Forms and go with pure javascript, as JSON is much simpler to handle (and read, in my opinion).

Related

Scala Play: How to render Form mappings with Repeated and Nested values?

I'm working on a Scala Play 2.7.x (you may checkout the project here play-silhouette-seed googleauth branch) and I have a form defined as:
object TotpSetupForm {
val form = Form(
mapping(
"sharedKey" -> nonEmptyText,
"scratchCodes" -> seq(mapping(
"hasher" -> nonEmptyText,
"password" -> nonEmptyText,
"salt" -> optional(nonEmptyText)
)(PasswordInfo.apply)(PasswordInfo.unapply)),
"scratchCodesPlain" -> optional(seq(nonEmptyText)),
"verificationCode" -> nonEmptyText(minLength = 6, maxLength = 6)
)(Data.apply)(Data.unapply)
)
case class Data(
sharedKey: String,
scratchCodes: Seq[PasswordInfo],
scratchCodesPlain: Option[Seq[String]],
verificationCode: String = "")
}
Where PasswordInfo comes from Play-Silhouette and looks like:
case class PasswordInfo(
hasher: String,
password: String,
salt: Option[String] = None
) extends AuthInfo
In my controller I populate the form and pass it as parameter to my view template as follows. Note that I have debugged it and totpInfo.scratchCodes has 5 values and the form is correctly populated:
val formData = TotpSetupForm.form.fill(TotpSetupForm.Data(totpInfo.sharedKey, totpInfo.scratchCodes, totpInfo.scratchCodesPlain))
Ok(views.html.someView(formData, ...)
I render the view as follows, please note that I did read the Scala Forms Repeated Values documentation note :)
#helper.form(action = controllers.routes.TotpController.submit()) {
#helper.CSRF.formField
#b3.text(totpForm("verificationCode"), '_hiddenLabel -> messages("verificationCode"), 'placeholder -> messages("verificationCode"), 'autocomplete -> "off", 'class -> "form-control input-lg")
#b3.hidden(totpForm("sharedKey"))
#helper.repeatWithIndex(totpForm("scratchCodes"), min = 1) { (scratchCodeField, index) =>
#b3.hidden(scratchCodeField, '_label -> ("scratchCode #" + index))
}
<div class="form-group">
<div>
<button id="submit" type="submit" value="submit" class="btn btn-lg btn-primary btn-block">#messages("verify")</button>
</div>
</div>
}
even though the form's scratchCodes sequence is correctly populated, each of the sequence values render as empty:
<input type="hidden" name="scratchCodes[0]" value="" >
<input type="hidden" name="scratchCodes[1]" value="" >
<input type="hidden" name="scratchCodes[2]" value="" >
<input type="hidden" name="scratchCodes[3]" value="" >
<input type="hidden" name="scratchCodes[4]" value="" >
The number of fields in the sequence is correct though.
I have also tried using the #helper.repeat alternative and even using the #helper.input instead of #b3.hidden just to be sure and the result is always the same ... I get empty valued PasswordInfo tuples rendered.
How can I fix this?
OK found the culprit: repeated + nested values require accessing each attribute separately like this:
#helper.repeat(totpForm("scratchCodes"), min = 1) { scratchCodeField =>
#b3.hidden(scratchCodeField("hasher"))
#b3.hidden(scratchCodeField("password"))
#b3.hidden(scratchCodeField("salt"))
}
then works fine and the post request populates the sequence of PasswordInfo UDTs correctly.

Displaying a list of checkboxes in my form

I want to display many checkboxes inline like:
<label class="checkbox-inline">
<input type="checkbox" id="inlineCheckboxA" value="optionA"> A
</label>
<label class="checkbox-inline">
<input type="checkbox" id="inlineCheckboxB" value="optionB"> B
</label>
<label class="checkbox-inline">
<input type="checkbox" id="inlineCheckboxC" value="optionC"> C
</label>
So I have my form like:
val form: Form[SearchForm] = Form(
mapping(
"letters" -> list(text)
)
)(SearchForm.apply)(SearchForm.unapply _)
Can I somehow pre-populate my form with the data or do I have to pass another variable with my view that has this 'letters' data?
val letters = List[String]("A", "B", "C", ...)
If I can't pass this letters val down with my view, then I guess I just pass another variable with my model and just iterate over it?
Have you looked at the helper inputCheckboxGroup ?
That will pre-populate the form elements from your model but you'll still have to provide the checkbox group with the set of possible values.
For example:
#views.html.helper.inputCheckboxGroup(
form("letters"),
options = Seq("A" -> "alpha", "B" -> "beta", "C" -> "gamma"),
'_label -> "My cool checkbox"
)
and the form filled with:
val form: Form[SimpleForm] = Form(mapping(
"letters" -> list(text)
)(SimpleForm.apply)(SimpleForm.unapply))
SimpleForm.form.fill(SimpleForm(List("A", "B")))
Would render a checkbox group like this:
[x] alpha [x] beta [ ] gamma

Nested form in Play! Scala 2.2

I have read several times the documentation but I still have problems with my nested form in Play! Scala 2.2 (detailed further).
Here is my form :
<form method="post" action="/admin/test2">
first name : <input type="text" name="firstname"> <br>
info 0 : <input type="text" name="label[0]"> <br>
info 1 : <input type="text" name="label[1]"> <br>
<input type="submit" value="test nested form" />
</form>
And the case classes corresponding :
case class Contact(firstname: String,
informations: Seq[ContactInformation])
case class ContactInformation(label: String)
val contactForm: Form[Contact] = Form(
mapping(
"firstname" -> nonEmptyText,
"informations" -> seq(
mapping(
"label" -> nonEmptyText
)(ContactInformation.apply)(ContactInformation.unapply)
)
)(Contact.apply)(Contact.unapply)
)
def saveContact = Action { implicit request =>
contactForm.bindFromRequest.fold(
formWithErrors => Ok("error"),
contact => {
println(contact)
Ok("Done")
}
)
}
I don't get any errors, but the contact that I get from the form (printed with println(contact)) has an empty informations field, i.e. it looks like this : Contact(h,List())
The error comes probably from the html part since I have followed to the letter the documentation from this page : play! scala forms documentation
but I can’t figure it out.
The field names of the inputs should use the full path in the Form. label appears within the informations Mapping, and informations is the sequence, not the label, so you should use informations[0].label instead of label[0].
Your view should look like this:
first name : <input type="text" name="firstname"> <br>
info 0 : <input type="text" name="informations[0].label"> <br>
info 1 : <input type="text" name="informations[1].label"> <br>

NoSuchElementException: None.get in play framework for scala

I need to build update method, but when i test show the error NoSuchElementException: None.get
UserController
object UserController extends Controller {
def update(id:Long) = DBAction { implicit rs =>
var user = simpleUserForm.bindFromRequest.get
user.id = Users.toOption(id)
Users.update(user)
Redirect(routes.UserController.list)
}
val simpleUserForm :Form[User] = Form {
mapping(
"firstName" -> nonEmptyText,
"lastName" -> nonEmptyText,
"email" -> email,
"birthDate" -> nonEmptyText,
"phone" -> nonEmptyText,
"username" -> text,
"password" -> nonEmptyText
)(UserForm.fromSimpleForm)(UserForm.toSimpleForm)
}
}
edit.scala.html
#import models.auth.Users
#(title: String, user:models.auth.User)
#main(title){
<form method="post" action="#controllers.auth.routes.UserController.update(Users.toLong(user.id))">
<input type="text" placeholder="First Name" name="firstName" value="#user.firstName"/><br/>
<input type="text" placeholder="Last Name" name="lastName" value="#user.lastName"/><br/>
<input type="email" placeholder="Email" name="email" value="#user.email" /><br/>
<input type="text" placeholder="Phone" name="phone" value="#user.phone" /><br/>
<input type="text" placeholder="Birthdate(dd/MM/yyyy)" name="birthDate" value="#user.birthDate" /><br/>
<input type="text" placeholder="Username" name="username" value="#user.username" /><br/>
<input type="submit" value="Update User" />
</form>
}
routes
POST /user/:id/ controllers.auth.UserController.update(id:Long)
I already done for create, read and delete, but for update i found error in line
var user = simpleUserForm.bindFromRequest.get
the error is NoSuchElementException: None.get
The Play page on Scala Forms is helpful here. That approach would be to send the populated form to the view as a parameter, then on submit use fold, which gives you options to deal with the error case as well as the "happy" case. Something like the following (adapted from the above page):
simpleUserForm.bindFromRequest.fold(
formWithErrors => {
// binding failure, you retrieve the form containing errors:
// in your form, test .hasErrors
BadRequest(views.html.user.edit(formWithErrors))
},
userData => {
/* binding success, you get the value. */
// .. do the update
...
//-- and return to list or home or...
Redirect(routes.Application.home(id))
}
)
If you don't want to use the form, then to back to your actual question, would .getOrElse not work?
did you import
import play.api.libs.concurrent.Execution.Implicits._

rendering and binding form with checkbox data

How can we create checkboxes and bind them with our form in play2.0 using scala.
If I have
val placeForm = Form(
mapping(
"id" -> ignored(NotAssigned: Pk[Long]),
"url_key" -> nonEmptyText,
"title" -> optional(text),
"page_id" -> optional(longNumber)
)(models.Place.apply)(models.Place.unapply)
)
and I have created form like this.
#form(routes.Page.save) {
#form(routes.Page.save) {
<fieldset>
#inputText(pageForm("title"), '_label -> "Title")
#inputText(pageForm("template"), '_label -> "Template") <label>Options:</label>
<div class="input">
<label>note <input type="checkbox" name="options[]" value="0">
</label> <label>About US <input type="checkbox" name="options[]"
value="0">
</label> <label>Facebook <input type="checkbox" name="options[]"
value="0">
</label> <label>Twitter <input type="checkbox" name="options[]"
value="0">
</label> <label>Hotmail <input type="checkbox" name="options[]"
value="0">
</label> <label>Something <input type="checkbox" name="options[]"
value="0">
</label>
</div>
</fieldset>
Now I want dont want simple html to create these checkboxes and bind these checkbox values into form
Can anyone help me with this
The structure of your val placeForm must match the form that you render in your template.
For example :
val placeForm = Form(
mapping(
"id" -> ignored(NotAssigned: Pk[Long]),
"title" -> optional(text),
"template" -> optional(text),
"checkbox1" -> text
"checkbox2" -> text
) // ... here construction and deconstruction functions
)
Your template can lookes like :
#form(routes.Page.save) {
<fieldset>
#inputText(pageForm("title"), '_label -> "Title")
#inputText(pageForm("template"), '_label -> "Template")
<label>Options:</label>
<div class="input">
<label> 1 <input type="checkbox" name="checkbox1" value="1"> </label>
<label> 2 <input type="checkbox" name="checkbox2" value="2"> </label>
</div>
</fieldset>
}
Now it's important to understand for wich purposes are these checkboxes. If you want to bind there values to your case class Place and your form structure match completely your case class, you can use apply and unaplly method, if no ... you must utilise your custom functions...
((title, template, checkbox1, checkbox2)=> Place(title, template, checkbox1, checkbox2)) //construct function
((place : Place) => Some((place.title, place.template, place.property_that_correspond_to_checkbox1_value,place.property_that_correspond_to_checkbox2_value)) // deconstruct function
Or you can use tuple instead of mapping for a form construction and then get your values simply as tuple values