Play Framework 2 template Form None.get - scala

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].

Related

Optional checkbox in Scala Play - form does not validate

I just started with Play and I'm trying to create a form with an "optional checkbox". The user must agree to the general terms (don't share your login credentials, ...). The second checkbox is optional and the user can decide whether this hint should be shown in the future again when he logs in the next time:
I created some form validation code in my Scala controller:
case class AbuseStatus(abuseHintAccepted: Boolean, dismissAbuseHintInFuture: Boolean)
val abuseHintForm = Form(
mapping(
"abuseHintAccepted" -> checked("Please accept the general terms."),
"dismissHintInFuture" -> boolean
)(AbuseStatus.apply)(AbuseStatus.unapply) verifying ("you must accept the general terms", result => result match {
case abuseStatus =>
{
abuseStatus.abuseHintAccepted.booleanValue()
}
})
)
I'm using the following method to handle the POST request when the form has been sent.
def sendAbuseHintForm = Action {implicit request =>
abuseHintForm.bindFromRequest.fold(
formWithErrors =>
{
Logger.info("Form did not validate")
formWithErrors.errors.map(error => Logger.info(error.messages.mkString(",")))
Ok(views.html.abuseHint(formWithErrors))
},
abuseStatus =>
{
if(abuseStatus.dismissAbuseHintInFuture)
{
Logger.info(">>>dismissHintInFuture = true")
Ok(views.html.home()).withCookies(showAbuseHintCookie)
}
else
Ok(views.html.home())
}
)
}
The form does not validate if both checkboxes are set to true. I would like it to validate when at least the first checkbox is set to true (the second is optional). How can I achieve this and what is the difference between
"abuseHintAccepted"->checked("...")
and
"dismissHintInFuture"->boolean
They both return a Mapping[Boolean].
I have tried your example using Play 2.3.8 and it does appear to validate correctly when passing these parameters:
abuseHintAccepted:true
dismissHintInFuture:true
It would be worth making sure the form field names and values (true/false) that are posted are actually correct in all of the scenarios you describe (you haven't supplied that part of the code).
The difference between boolean and checked(msg) is that checked(msg) has validation applied ensuring the value is true - it is equivalent to boolean verifying (msg, _ == true) - (see Play framework source).
Finally, the checked() validation you have for abuseHintAccepted means that the verifying check on the whole form is not needed, but this shouldn't affect the behaviour of the form (it is just duplicate validation).

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)))

Only bind part of the Form mappings from request

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.

Cannot access the parameter of a Menu.param from a Lift Snippet

I'm trying to extract the parameter from a Lift Menu.param within a snippet so that I can use it to create a named Comet. However, I get a NullPointerException when I try to pass the parameter to the snippet using SnippetDisptach in my Boot.scala, as suggested here:
http://comments.gmane.org/gmane.comp.web.lift/44299
I've created the Menu item as follows:
object AnItemPage {
// create a parameterized page
def menu = Menu.param[Item]("Item", "Item",
s => fetchItem(s), item => item._id.toString) / "item"
private def fetchItem(s:String) : Box[Item] = synchronized {
ItemDAO.findById(ObjectId.massageToObjectId(s))
}
}
I've added the menu to SiteMap. I've also created a Snippet which I would like to pick up the Item parameter. (I'm using fmpwizard's InsertNamedComet library here):
class AddCometItemPage(boxedItem: Box[Item]) extends InsertNamedComet with DispatchSnippet{
val item : Item = boxedItem.openOr(null)
override lazy val name= "comet_item_" + item._id.toString
override lazy val cometClass= "UserItemCometActor"
def dispatch = null
}
My next step is to crate an instance of this class as demonstrated by David Pollak here:
http://comments.gmane.org/gmane.comp.web.lift/44299
This is what I have added to my Boot.scala:
LiftRules.snippetDispatch.append {
case "item_page" => new AddCometItemPage(AnItemPage.menu.currentValue)
}
My item.html references this snippet:
<div class="lift:item_page">
I get the following null pointer exception when I compile and run this:
Exception occurred while processing /item/5114eb4044ae953cf863b786
Message: java.lang.NullPointerException
net.liftweb.sitemap.Loc$class.siteMap(Loc.scala:147)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.siteMap(Menu.scala:170)
net.liftweb.sitemap.Loc$class.allParams(Loc.scala:123)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.allParams(Menu.scala:170)
net.liftweb.sitemap.Loc$class.net$liftweb$sitemap$Loc$$staticValue(Loc.scala:87)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.net$liftweb$sitemap$Loc$$staticValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.paramValue(Loc.scala:85)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.paramValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.currentValue(Loc.scala:114)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.currentValue(Menu.scala:170)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:107)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:106)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.common.Full.map(Box.scala:553)
net.liftweb.util.NamedPF$.applyBox(NamedPartialFunction.scala:97)
net.liftweb.http.LiftRules.snippet(LiftRules.scala:711)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.http.LiftSession.net$liftweb$http$LiftSession$$findSnippetInstance(LiftSession.scala:1505)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1670)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1669)
Has anybody any idea where I'm going wrong? I've not been able to find a lot of information on Menu.param.
Thank you very much for your help.
f
I have never tried what you are doing, so I am not sure the best way to accomplish it. The way you are using the Loc Param, you are extracting a variable from a URL pattern. In your case, http://server/item/ITEMID where ITEMID is the string representation of an Item, and which is the value that gets passed to the fetchItem function. The function call will not have a value if you just arbitrarily call it, and from what I can see you are requesting a value that is not initialized.
I would think there are two possible solutions. The first would be to use S.location instead of AnItemPage.menu.currentValue. It will return a Box[Loc[Any]] representing the Loc that is currently being accessed (with the parameters set). You can use that Loc to retrive currentValue and set your parameter.
The other option would be to instantiate the actor in your snippet. Something like this:
item.html
<div data-lift="AnItemPage">
<div id="mycomet"></div>
</div>
And then in your AnItemPage snippet, something like this:
class AnItemPage(item: Item) {
def render = "#mycomet" #> new AddCometItemPage(item).render
}
I haven't tested either of those, so they'll probably need some tweaking. Hopefully it will give you a general idea.

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)