Reputation: 23
case class Address(
address1: String,
city: String,
state: String,
postal: String,
country: String
)
Form(
mapping = mapping(
"address1" -> nonEmptyText,
"city" -> nonEmptyText,
"state" -> nonEmptyText,
"postal" -> nonEmptyText,
"country" -> nonEmptyText
)(Address.apply)(Address.unapply).verifying("Invalid Postal Code!", validatePostal _)
)
def validatePostal(address: Address): Boolean = {
address.country match {
case "US" | "CA" =>
val regex: Regex = ("^(\\d{5}-\\d{4}|\\d{5}|\\d{9})$|^([a-zA-Z]\\d[a-zA-Z]( )?\\d[a-zA-Z]\\d)$").r
regex.pattern.matcher(address.postal).matches()
case _ => false
}
}
Above form validation for postal code works fine with global error displayed on form for invalid US or Canadian postal codes.
I would like to show the error as field error right next to the field than as global error which in my case is shown on top of the form.
Is there a way to use inbuilt Form constraints or verification methods to achieve this instead of FormError's?
Upvotes: 2
Views: 1548
Reputation: 169
You can add the constraint to the field. Then update validatePostal to accept a tuple of the two values instead.
Form(
mapping = mapping(
"address1" -> nonEmptyText,
"city" -> nonEmptyText,
"state" -> nonEmptyText,
"postal" -> tuple(
"code" -> nonEmptyText,
"country" -> nonEmptyText
).verifying("Invalid Postal Code!", validatePostal _),
)((address1, city, state, postal) => Address(address1, city, state, postal._1, postal._2))((address: Address) => Some((address.address1, address.city, address.state, (address.postal, address.country))))
)
Template:
@inputText(
addressForm("postal.code"),
'_label -> "Postal code",
'_help -> "Please enter a valid postal code.",
'_error -> addressForm.error("postal")
)
Upvotes: 3
Reputation: 524
Defining the error like that you are creating a FormError("","Invalid Postal Code!")
object in a form, as it doesn't have key (the first parameter), the framework don't attach the error to the form element.
On form error when you are binding the request to the form, you will have to create a new form removing the FormError("","Invalid Postal Code!")
and replacing it with an error FormError("form.id","message")
In our project we created an implicit def for Form to replace the form errors (we couldn't find a way to create dynamic constraint validations) these are the 2 definitions we have:
def replaceError(key: String, newError: FormError): Form[T] = {
val updatedFormErrors = form.errors.flatMap { fe =>
if (fe.key == key) {
if (form.error(newError.key).isDefined) None
else {
if (newError.args.isEmpty ) Some(FormError(newError.key,newError.message,fe.args))
else Some(newError)
}
} else {
Some(fe)
}
}
form.copy(errors = updatedFormErrors.foldLeft(Seq[FormError]()) { (z, fe) =>
if (z.groupBy(_.key).contains(fe.key)) z else z :+ fe
})
}
def replaceError(key: String, message: String, newError: FormError): Form[T] = {
def matchingError(e: FormError) = e.key == key && e.message == message
val oldError = form.errors.find(matchingError)
if (oldError.isDefined) {
val error = if (newError.args.isEmpty) FormError(newError.key,newError.message,oldError.get.args) else newError
form.copy(errors = form.errors.filterNot(e => e.key == key && e.message == message)).withError(error)
}
else form
}
We have those in a class called FormCryptBind
(because we also improve the form object with some crypto stuff) and we define the implicit def like this:
implicit def formBinding[T](form: Form[T])(implicit request: Request[_]) = new FormCryptBind[T](form)
We do it like that because just importing the object having this implicit definition, you can use all the FormCryptBind
definitions as they were Form's
And we use it like this
import whatever.FormImprovements._
...
object SomeController extends Controller{
...
def submit = Action{ implicit request =>
form.bindRequest.fold(
formWithErrors => {
val newForm = formWithErrors.replaceError("", "formField.required", FormError("formField", "error.required")
BadRequest(someView(newForm)
},
formDetails => Redirect(anotherView(formDetails))
}
As I can't put actual live code from the app, I touched it a little bit :D so expect compile errors if you copy & paste
Upvotes: 2