I'm need render a Form with validators for this model:
Model:
case class Service (
name: String, description: String, unitcost: Long,
typo: Char, isactive: Char, modifiedby: String)
Controller:
import play.api.data.Form
import play.api.data._
import play.api.data.format.Formats._
import play.api.data.Forms._
object Services extends Controller {
....
....
private val servicesForm[Service] = Form(
mapping(
"name" -> nonEmptyText.verifying(
"validation.name.duplicate", Service.findByName(_).isEmpty),
"description" -> nonEmptyText,
"unitcost" -> longNumber,
"typo" -> of[Char],
"isactive" -> of[Char],
"modifiedby" -> nonEmptyText
) (Service.apply)(Service.unapply)
)
This code fails on every of[Char] saying that its needed import play.api.data.format.Formats._
but I was..
My second doubt is about how to put a radio button pair for each (typo and isactive)
thinking that typo has "M" and "A" like options and isactive has "Y" and "N".
PD: I think put this using a persistence model after...
The error indicates that the Form does not know how to handle the Char type. There is no default defined for the Char type.
To solve the problem you have two options:
Change the type from Char to String for which a default Formatter exists
Supply a Formatter for Char
A formatter would look something like this (note that it does not have the correct error handling)
implicit val charFormat = new Formatter[Char] {
def bind(key: String, data: Map[String, String]):Either[Seq[FormError], Char] =
data.get(key)
.filter(_.length == 1)
.map(_.head)
.toRight(Seq(FormError(key, "error.required", Nil)))
def unbind(key: String, value: Char) = Map(key -> value.toString)
}
Related
I'm using scala and Play framework and i want create a form with more than 22 fields so I share my field in 3 tuple like that:
val firstMapping = tuple(
"f1" -> text, "f2" -> text, ... "f18" -> text
)
val secondMapping = tuple(
"f19" -> text, "20"-> text ... "f25" -> text
)
val thirdMapping = tuple(
"f26" -> text, ... "f29" -> text
)
So after I regroup them in a form:
val createForm = From(tuple(
"general" -> firstMapping,
"specific" -> secondMapping,
"more_specific" -> thirdMapping
))
I think this is the good solution, but my question is about the view file (i'm in a MVC architecture)
In that view I want to pass my form like that:
#(formCreate: Form[])
But I don't know what I need to put in the " [] " (I'm french I don't know wath is the word for that in english) and how to create my field in HTML ?
Usually I use that kind html form:
#helper.form() {
<input type="text" name="id_metier" id="id_metier" maxlength="255"/>
}
So can I use that kind of field again or I need to use specific field from Play framework ? And what are the parameter for that #(formCreate: Form[]) ?
Thank you for your help
Your form is of type Tuple3 with some other tuples inside. Painful to read, write, use, maintain.
Form[((String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String), (String, String, String, String, String, String, String), (String, String, String, String))]
Refer to the docs: https://www.playframework.com/documentation/2.5.x/ScalaForms
and just create a case class that will contain 3 nested case classes for your data, name fields appropriately.
Here is the example from docs for nested case class
case class AddressData(street: String, city: String)
case class UserAddressData(name: String, address: AddressData)
val userFormNested: Form[UserAddressData] = Form(
mapping(
"name" -> text,
"address" -> mapping(
"street" -> text,
"city" -> text
)(AddressData.apply)(AddressData.unapply)
)(UserAddressData.apply)(UserAddressData.unapply)
)
When creating form you refer to nested fields with . notation
#helper.inputText(userFormNested("name"))
#helper.inputText(userFormNested("address.street"))
#helper.inputText(userFormNested("address.city"))
This is the example taken from the documentation:
import play.api.data._
import play.api.data.Forms._
case class User(name: String, age: Int)
val userForm = Form(
mapping(
"name" -> text,
"age" -> number
)(User.apply)(User.unapply)
)
val anyData = Map("name" -> "bob", "age" -> "18")
val user: User = userForm.bind(anyData).get
What is the Map instance (named anyData) doing here? I mean...is it used as a means of providing a default value for the user (in case the mapping done by the form fails)? or does it have any other purposes?
The anyData is just showing how the Map must be filled in order to be processed by the userForm and return the result value tuple (String,Int) with name and age.
The form generates a tuple from a Map and these lines just show how to do it.
val anyData = Map("name" -> "bob", "age" -> "18")
val user: User = userForm.bind(anyData).get
On a real application you will get the map implicitly from the request which contains the data filled in the HTML form by executing:
val user: User = loginForm.bindFromRequest.get
I try to fill a form with the values of System
def edit(id: Long) = IsAuthenticated { username => implicit request =>
User.findByEmail(username).map { user =>
System.findById(id).map { system =>
Ok(html.systems.editForm(id, systemForm.fill(system), Person.options, user))
}.getOrElse(NotFound)
}.getOrElse(Forbidden)
}
but some of 'system' values are java.math.BigDecimal
val systemForm = Form(
mapping(
"id" -> ignored(NotAssigned:Pk[BigDecimal]),
"sys_name" -> nonEmptyText,
"sys_desc" -> nonEmptyText,
"sys_owner1_id" -> longNumber,
"sys_owner2_id" -> optional(longNumber)
)(System.apply)(System.unapply)
)
and it says :
type mismatch; found : (anorm.Pk[java.math.BigDecimal], String, String, String, Option[String], java.math.BigDecimal, Option[java.math.BigDecimal]) => models.System required: (anorm.Pk[java.math.BigDecimal], String, String, Long, Option[Long]) => ?
how can i handle this?
Looks like problem in anorm or somewhere near.
As you may see from error description sys_owner1_id and sys_owner2_id are BigIntegers in query result, but declared as long in form.
I am not familiar with anorm, but solution is to declare these ids as long in anorm, or declare them as BigInteger in form mapping or convert from BigInteger to long in your query.
Perhaps I'm just overlooking something obvious but I can't figure out how to bind a form field to a double in a Play controller.
For instance, assume this is my model:
case class SavingsGoal(
timeframeInMonths: Option[Int],
amount: Double,
name: String
)
(Ignore that I'm using a double for money, I know that's a bad idea, this is just a simplified example)
And I wanted to bind it like so:
object SavingsGoals extends Controller {
val savingsForm: Form[SavingsGoal] = Form(
mapping(
"timeframeInMonths" -> optional(number.verifying(min(0))),
"amount" -> of[Double],
"name" -> nonEmptyText
)(SavingsGoal.apply)(SavingsGoal.unapply)
)
}
I realized that the number helper only works for ints but I thought using of[] might allow me to bind a double. However, I get a compiler error on this:
Cannot find Formatter type class for Double. Perhaps you will need to import
play.api.data.format.Formats._
Doing so doesn't help as there's no double formatter defined in the API.
This is all just a long way of asking what's the canonical way of binding a form field to a double in Play?
Thanks!
edit: 4e6 pointed me in the right direction. Here's what I did using his help:
Using the snippets in his link, I added the following to app.controllers.Global.scala:
object Global {
/**
* Default formatter for the `Double` type.
*/
implicit def doubleFormat: Formatter[Double] = new Formatter[Double] {
override val format = Some("format.real", Nil)
def bind(key: String, data: Map[String, String]) =
parsing(_.toDouble, "error.real", Nil)(key, data)
def unbind(key: String, value: Double) = Map(key -> value.toString)
}
/**
* Helper for formatters binders
* #param parse Function parsing a String value into a T value, throwing an exception in case of failure
* #param error Error to set in case of parsing failure
* #param key Key name of the field to parse
* #param data Field data
*/
private def parsing[T](parse: String => T, errMsg: String, errArgs: Seq[Any])(key: String, data: Map[String, String]): Either[Seq[FormError], T] = {
stringFormat.bind(key, data).right.flatMap { s =>
util.control.Exception.allCatch[T]
.either(parse(s))
.left.map(e => Seq(FormError(key, errMsg, errArgs)))
}
}
}
Then, in my form mapping:
mapping(
"amount" -> of(Global.doubleFormat)
)
You don't need to use the the format in the global if you have the version 2.1 up.
Just import:
import play.api.data.format.Formats._
and use as:
mapping(
"amount" -> of(doubleFormat)
)
Actually, there is predefined formatter for Double on master branch. So you should either switch to 2.1-SNAPSHOT play version or just copy the implementation.
In the play! framework, using scala,say that i have a form such as follows:
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._
case class User(someStringField: String, someIntField: Int)
val userForm = Form(
mapping(
"someStringField" -> text,
"someIntField" -> number verifying(x => SomeMethodThatReceivesAnIntAndReturnsABoolean(x))
)(User.apply)(User.unapply)
)
where SomeMethodThatReceivesAnIntAndReturnsABoolean is a method that performs some logic on the int to validate it.
However, i would like to be able to consider the value of the someStringField when validating the someIntField, is there a way to achieve this in play framework's forms? I know that i can do something like:
val userForm = Form(
mapping(
"someStringField" -> text,
"someIntField" -> number
)(User.apply)(User.unapply)
.verifying(x => SomeFunctionThatReceivesAnUserAndReturnsABoolean(x))
and then i would have the entire user instance available passed to the validation function. The problem with that approach is that the resulting error would be associated with the entire form instead of being associated with the someIntField field.
Is there a way to get both things, validate a field using another field and maintain the error associated to the specific field i wish to validate, instead of the entire form?
I have the same requirements with adding validation to fields depending on the value of other fields. I´m not sure how this is done in idiomatic PLAY 2.2.1 but I came up with the following solution. In this usage I´m degrading the builtin "mapping" into a simple type converter and apply my "advanced inter field" validation in the "validateForm" method. The mapping:
val userForm = Form(
mapping(
"id" -> optional(longNumber),
"surename" -> text,
"forename" -> text,
"username" -> text,
"age" -> number
)(User.apply)(User.unapply)
)
private def validateForm(form:Form[User]) = {
if(form("username").value.get == "tom" || form("age").value.get == "38") {
form
.withError("forename", "tom - forename error")
.withError("surename", "tom - surename error")
}
else
form
}
def update = Action { implicit request =>
userForm.bindFromRequest.fold({
formWithErrors => BadRequest(users.edit(validateForm(formWithErrors)))
}, { user =>
val theForm = validateForm(userForm.fill(user))
if(theForm.hasErrors) {
BadRequest(users.edit(theForm))
} else {
Users.update(user)
Redirect(routes.UsersController.index).flashing("notice" -> s"${user.forename} updated!")
}
})
}
Even though it works I´m urgently searching for a more idiomatic version...
EDIT: Use a custom play.api.data.format.Formatter in idiomatic play, more on http://workwithplay.com/blog/2013/07/10/advanced-forms-techniques/ - this lets you programmatically add errors to a form. My Formatter looks like this:
val usernameFormatter = new Formatter[String] {
override def bind(key: String, data: Map[String, String]): Either[Seq[FormError], String] = {
// "data" lets you access all form data values
val age = data.get("age").get
val username = data.get("username").get
if(age == "66") {
Left(List(FormError("username", "invalid"), FormError("forename", "invalid")))
} else {
Right(username)
}
}
override def unbind(key: String, value: String): Map[String, String] = {
Map(key -> value)
}
}
}
Registered in the form mapping like this:
mapping(
[...]
"username" -> of(usernameFormatter),
[....]
I believe what you're looking for is play.api.data.validation.Constraint.
Say you have a RegisterForm with a list of predefined cities and an otherCity field and you need either the cities or otherCity to be supplied, i.e., otherCity should be validated if cities is not provided:
case class RegisterForm(
email: String,
password: String,
cities: Option[List[String]],
otherCity: Option[String]
)
You can write a custom Constraint around this:
val citiesCheckConstraint: Constraint[RegisterForm] = Constraint("constraints.citiescheck")({
registerForm =>
// you have access to all the fields in the form here and can
// write complex logic here
if (registerForm.cities.isDefined || registerForm.otherCity.isDefined) {
Valid
} else {
Invalid(Seq(ValidationError("City must be selected")))
}
})
And your form definition becomes:
val registerForm = Form(
mapping(
"email" -> nonEmptyText.verifying(emailCheckConstraint),
"password" -> nonEmptyText.verifying(passwordCheckConstraint),
"cities" -> optional(list(text)),
"other_city" -> optional(text)
)(RegisterForm.apply)(RegisterForm.unapply).verifying(citiesCheckConstraint)
)
In this example emailCheckConstraint and passwordCheckConstraint are additional custom constraints that I defined similar to citiesCheckConstraint. This works in Play 2.2.x.
UPDATE:
Works on Play 2.3.8 as well.
if you don't mind having a prefix for you params you can group the related params:
val aForm = Form(
mapping(
"prefix" -> tuple(
"someStringField" -> text,
"someIntField" -> number
) verifying (tup => your verification)
)(tup => User.apply(tup._1, tup._2)(User.unapply...)
I use something similar just without the surrounding mapping.
You will have to adjust the apply/unapply a little and pass the arguments manually for it to compile.
The error will be registered to the "prefix" group.
I also find it weird that you cannot register errors on any field you'd like using FormError when verifying the form...
Thanks to Tom Myer, Here what I used
class MatchConstraint[A](val targetField:String, val map:(String, Map[String, String]) => A, val unmap:A => String) extends Formatter[A] {
override def bind(key: String, data: Map[String, String]): Either[Seq[FormError], A] = {
val first = data.getOrElse(key, "")
val second = data.getOrElse(targetField, "")
if (first == "" || !first.equals(second)) {
Left(List(FormError(key, "Not Match!")))
}
else {
Right(map(key, data))
}
}
override def unbind(key: String, value: A): Map[String, String] = Map(key -> unmap(value))
}
And here what's my form look like
val registerForm = Form(
mapping(
"email" -> email.verifying(minLength(6)),
"password" -> text(minLength = 6),
"passwordConfirmation" -> of(new MatchConstraint[String]("password", (key, data) => data.getOrElse(key, ""), str => str))
)(RegisterData.apply)(RegisterData.unapply)
)
I guess that they map the scala-code to JSR-Validation. There it's definitely not possible. There are some arguments to do this. Mainly that a validation should be simple and not make complex logic. How ever I still miss this too. OVal from play1 was better for me.
In the documentation:
Playframework Documentation
You can see the following code:
val userFormConstraintsAdHoc = Form(
mapping(
"name" -> text,
"age" -> number
)(UserData.apply)(UserData.unapply) verifying("Failed form constraints!", fields => fields match {
case userData => validate(userData.name, userData.age).isDefined
})
)
Mainly just verify after the unapply and you have all the fields mapped, so you can make a more complete validation.