Only bind part of the Form mappings from request - scala

I am using Play 2.1.2 and I have a Form with the following mapping:
Form(
mapping(
"id" -> ignored(NotAssigned: Pk[Long]),
"name" -> nonEmptyText,
"stock" -> number(min = 0),
"initialAmount" -> number(min = 0),
"distUnitId" -> longNumber,
"categoryId" -> longNumber
)
(BarItem.apply)
(BarItem.unapply)
)
In the view, I want to use a form helper to edit only part of the values. In particular, the "initialAmount" value is meant to be only set once - when creating an item - and not be allowed to change on edit. So I just do not want to display it in the form and "keep" its previous value after form submission.
Currently the interesting part of the edit view looks like this:
#form(routes.Application.update(id), 'class -> "form-inline") {
<fieldset>
#inputText(barItemEditForm("name"), '_label -> "Produktbezeichnung")
#inputText(barItemEditForm("stock"), '_label -> "Lagermenge")
#select(
barItemEditForm("distUnitId"),
distUnits,
'_label -> "Einheit"
)
#select(
barItemEditForm("categoryId"),
categories,
'_label -> "Kategorie"
)
When navigating to the edit view, I fill the form with the fill() method of the Form class:
BarItem.findById(id).map {
item =>
Ok(html.edit(id, barItemEditForm.fill(item), DistUnit.selectOptions, Category.selectOptions))
}.getOrElse(NotFound)
In the action, that handles the form submission I bind the Form from request like so:
...
implicit request =>
barItemEditForm.bindFromRequest.fold(
formWithErrors => BadRequest(html.edit(id, formWithErrors, DistUnit.selectOptions, Category.selectOptions)),
item => {
...updateStuff...
}
)
...
So the problem is now, that as I do not have an input field for "initialAmount" in the view, I get an error when binding from request, saying it has no value.
Is it somehow possible to only apply part of the values from request and keep this one value from the call to fill() before?
Or can anyone suggest a proper way to handle this kind of "partial editing"?
I'm really looking forward to any suggestions!

Since you want to keep the value, that probably means a hidden input when you rerender the form. If the previous value comes from elsewhere, e.g. the database, you could define another form with an ignored mapping for that field.

Related

Assign random value to form

I am using this code to assign a default random value to a form field ("token"):
val userForm = Form(
mapping(
"token" -> default(text, (randomString("0123456789abcdef")(40))),
"username" -> optional(text),
"email" -> email,
"password" -> nonEmptyText,
"gender" -> nonEmptyText
)(User.apply)(User.unapply)
)
It seems like the "token" random value is generated only once and never changes after that. Any way of solving this issue?
Create your own Mapping that takes a thunk. The following is inspired by the Forms source:
def defaultThunk[A](mapping: Mapping[A], value: =>A): Mapping[A] =
OptionalMapping(mapping).transform(_.getOrElse(value), Some(_))
As value is used in a anonymous function it should be getting called every time, giving a different random number.
So instead of using
"token" -> default(text, (randomString("0123456789abcdef")(40))),
use instead:
"token" -> defaultThunk(text, (randomString("0123456789abcdef")(40))),
It is like this because your form is immutable.
As #Kigyo suggest in comment, I also think that you can try change it to function, but it will be not efficient when you want bind form data from request after that.
Another solution is to use fill method on form, something like that:
userForm.fill(User((randomString("0123456789abcdef")(40)), None, "", "")
It will return new form with filled data. That form you can put to your view template.
However, I am not sure if it is the best solution...
Alternatively you could write in your template as
#inputText(userForm("token").copy(value=Some(randomString("0123456789abcdef")(40))))
or pass the randomString as parameter to template and use as
#(userForm: Form[_], randStr)
#inputText(userForm("token").copy(value=Some(randStr)))

Play Framework 2 template Form None.get

Im new to Play 2 and Scala, and im getting a strange Exception in my template:
Execution exception
-------------------
[NoSuchElementException: None.get]
In /home/nic/workspaces/scala-ide/scims/app/views/persons/detailTabs/personal.scala.html at line 4.
1. #(personId: Long, personDetailTabForm: Form[dto.PersonDetailTab])(implicit formOptions: dto.PersonFormOptions)
2. #implicitFieldConstructor = #{ helper.FieldConstructor(support.bs3HorizField.f) }
3.
4. #persons.detail("personal", personDetailTabForm.get.firstName) {
The personDetailTabForm is an empty form object defined as:
val personalDetailTabForm: Form[PersonDetailTab] = Form(
mapping(
"firstName" -> text.verifying(nonEmpty),
"middleName" -> text,
"lastName" -> text.verifying(nonEmpty),
"gender" -> text,
"dateOfBirth" -> jodaDate("yyyy-MM-dd"),
"ethnicity" -> text,
"maritalStatus" -> text,
"password" -> text
)(PersonDetailTab.apply)(PersonDetailTab.unapply)
)
Any ideas as to what's wrong here?
I was under the impression a variable would have to be an Option to get a None?
Cheers
NFV
You are calling get on personDetailTabForm - Looking up it's ScalaDoc: http://www.playframework.com/documentation/2.2.x/api/scala/index.html#play.api.data.Form - it seems that .get returns the PersonDetailTab value that the form holds - IF, as the docs say, 'the submission was a success'.
You're seeing the None.get exception because most likely play.api.data.Form[T] simply uses Option[T] and get returns Some[T] when the form holds a valid value and None otherwise.
So on your line 4, in the scala template, you have something like
personDetailTabForm.get.firstName
That's a String, but you can expect a value only when the form's underlying PersonDetailTab itself has a value. I am not sure what you want to do, but you're dealing with a case where a value you want to render in a template might not be there, for whatever reason. In which case:
#personDetailTabForm.value.map{ personDetailTab =>
#persons.detail("personal", personDetailTab.firstName) // { ... whatever else
// anything else you want to render
} getOrElse { // errors in the form; personDetailTabForm cannot yield a valid personDetailTab
<h3> oops, what went wrong here? </h2>
}
It all depends on what you want to do in personal.scala.html. Form[T] is a good way
to deal with input and validation of some T thing, but if you are just displaying it,
and if you have a T (in your case PersonDetailTab) just pass that to the template as it is. If your PersonDetailTab may or may not exist, then just use Option[PersonDetailTab] instead Form[PersonDetailTab].

Play framework null checkbox list

I am new to play framework having some dificulties in accesing check box in controller. My view is:-
#(img:Form[Image])
#helper.form(action = routes.Application.abc) {
<li><input name="item[0]" value="pt" type=checkBox></li>
<li><input name="item[1]" value="sumit" type=checkBox></li>
<p>
<button type=submit id=imgButton>submit</button>
</p>
}
My conntroller is:-
def abc = Action{
implicit request =>
val values =ImageForm.bindFromRequest.get
println("mapinggg"+values)
Ok("hi")
}
My case class to handle checkbox is:-
case class Image (desc:List[String])
and form is
val ImageForm =Form(
mapping(
"desc" -> list(text)
)(Image.apply)(Image.unapply)
)
But it returns nill when I click on submit by selecting checkbox?
It gives output as Image(List()) but I want list of selected checkboxes
The names of your inputs ("item" - without the indices) needs to match the key of your list mapping (here given as "desc") for the binding to succeed. It should work if you change the ImageForm mapping to:
val ImageForm =Form(
mapping(
"item" -> list(text)
)(Image.apply)(Image.unapply)
)
Note that the actual field name in your case class - "desc" - should not matter here.

PlayFramework 2.x - Forms / Associating error message to one element of a tuple

I've got this form mapping:
val myElement = Form(
mapping(
"title" -> nonEmptyText,
"schedule" ->
tuple("startSchedule" -> jodaDate("dd/MM/yyyy HH:mm"),
"endSchedule" -> jodaDate("dd/MM/yyyy HH:mm"))
.verifying(MyValidator().checkForEndScheduleConsistency("error.schedule")),
)(MyElement.apply)(MyElement.unapply)
)
MyElement class:
case class MyElement(title: String, schedule: (Datetime, Datetime))
MyValidator class:
def checkForEndScheduleConsistency(errorMsg: String) =
Constraint[(DateTime, DateTime)]("constraint.schedule", errorMsg) {
schedule =>
MyDomainValidator().checkForEndScheduleConsistency(schedule._1, schedule._2, Messages(errorMsg)) match {
case Success(s) => Valid
case Failure(f) => Invalid(ValidationError("custom error string from `f`"))
}
}
Requirement: An error message must be associated to the field schedule.endSchedule(tuple's element) if the schedule is inconsistent according to MyValidator object.
However, in order to have both required elements (startSchedule and endSchedule) available for checkForEndScheduleConsistency method, I can't apply a verifying method directly on the nested tuple's element named endSchedule. Instead, I have to apply one on the whole tuple in order to include startSchedule variable, as shown in the code snippet.
The drawback is that the error is not mapped to endSchedule but to schedule (that doesn't represent anything in my HTML form), and so nothing is displayed to screen when an inconsistent schedule appears.
Therefore, I have to use this "workaround" to achieve my requirement using Form's withError method:
def create = Action {
implicit request =>
myForm.bindFromRequest.fold(
myFormWithErrors => {
myFormWithErrors.error("schedule") match { //check for the presence of potential schedule error
case Some(e) => {
BadRequest(views.html.create_element("Create an element", myFormWithErrors.withError("schedule.endSchedule", Messages("error.schedule"))))
}
case _ => BadRequest(views.html.create_element("Create an element", myFormWithErrors))
}
},
myForm => {
treatSubmittedMyForm(myForm)
}
)
}
=> Very ugly and anti-DRY.
Is there a way to apply verifying on the tuple and despite of that, apply the error message to a nested tuple's element? In my case, on endSchedule.
Probably the best solution is to replace the default helpers provided by Play when rendering the field by one of your own making, as per documentation:
#(elements: helper.FieldElements)
<div class="#if(elements.hasErrors) {error}">
<label for="#elements.id">#elements.label</label>
<div class="input">
#elements.input
<span class="errors">#elements.errors.mkString(", ")</span>
<span class="help">#elements.infos.mkString(", ")</span>
</div>
</div>
That way you can manually replace the error reference to the right element. Not extremely nice but you should be able to create a reusable tag from it.
The best solution is to precise the global (to schedule) "error" key into the more specific generated input directly, here the schedule.endSchedule input:
#inputText(hobbyForm("schedule.endSchedule"), 'id -> "schedule.endSchedule", '_error -> hobbyForm.error("schedule")
Notice the part: '_error -> hobbyForm.error("schedule")

How to display ad hoc constraints form validation messages?

ScalaForms
In the example linked here there is this example about form validation:
// You can also define ad-hoc constraints on the fields:
val loginForm = Form(
tuple(
"email" -> nonEmptyText,
"password" -> text
) verifying("Invalid user name or password", fields => fields match {
case (e, p) => User.authenticate(e,p).isDefined
})
)
Via binding errors some constraints are displayed in my form. (Like the nonEmptyText, that gets a extra line behind the field stating This field is required. See:
loginForm.bindFromRequest.fold(
formWithErrors => // binding failure, you retrieve the form containing errors,
value => // binding success, you get the actual value
)
If I do a .toString to the formWithErrors i get this for the nonEmptyText constraint:
Form(ObjectMapping2(<function2>,<function1>,(Email adress,FieldMapping(,List(Constraint(Some(constraint.required),WrappedArray())))),(Password,FieldMapping(,List(Constraint(Some(constraint.required),WrappedArray())))),,List(Constraint(None,List()))),Map(Password -> test, Email adress -> ),List(FormError(Email adress,error.required,WrappedArray())),None)
The latter part is a FormError List: List(FormError(Email adress,error.required,WrappedArray())),None) which is a case class: case class FormError (key: String, message: String, args: Seq[Any]) where key is defined as: The error key (should be associated with a field using the same key)..
The FieldConstructor picks this up and makes the 'Email adress' input box go red and add the error message ('This field is required').
Displaying the ad hoc constraints?
So when the form fields are all filled the username and password are checked:
verifying("Invalid user name or password", fields => fields match {
case (e, p) => User.authenticate(e,p).isDefined
})
But i dont know how to display the 'Invalid user name or password' to the user. The .toString of the FormError List of formWithErrors is:
List(FormError(,Invalid user name or password,WrappedArray())),None)
The Key part is empty.
Question
How do i display the ad hoc error?
I even tried:
BadRequest( views.html.test( formWithErrors ) ).flashing( "error" -> "Invalid user name or password." ) },
but for some reason its not working via the Flash either :(. This #flash.get("error").getOrElse("no 'error'") gives me 'no error' each time.
Actually, a form can have errors attached to it, so then when a bind from request fails validating constraint, a new form is created based on the given one, but filled in with errors.
Such errors list is composed of FormError which refers a Form Field and overrides toString to show its embed message.
This way if you wish to show all messages at once, you can simply formWithErrors.errors.mkString("<br>") for instance (in you template or flashing it).
There are a lot of ways and some common ones are described here http://www.playframework.org/documentation/2.0.2/ScalaFormHelpers. You might define your own field constructor as well.
To conclude, my advice would be that you should use helpers amap (for instance #inputText and co) because they already contain the logic to show helpers, tips and errors when available.
Edit
I missed the hint about the flash problem... you've probably forgotten to add (implicit flash: Flash) to you template param list. (don't forget to flag the request as implicit as well in your action definition)