Play 2.3 scala forms - how to customise constraint messages - forms

I've created form in play framework with constraints:
val voucherForm = Form(
mapping(
"voucherName" -> nonEmptyText,
"voucherCode" -> optional(text(minLength = 6).verifying(pattern("""[a-zA-Z0-9]+""".r, error = "...")))
)(VoucherForm.apply)(VoucherForm.unapply)
)
when I display this form on a web page I have constraint messages (like Required, Minimum length: 6, constraint.pattern) shown near input boxes.
I want to customise this constraint messages per input field (i.e. two nonEmptyText constraints in same form will have different constraint message). How could I do it?

Instead of using nonEmptyText, could you not use text, and put your custom message in the verifying, along the lines of:
val voucherForm = Form(
mapping(
"voucherName" -> text.verifying(
"Please specify a voucher name", f => f.trim!=""),
...

These messages are taken from, well, messages. You can create your custom messages file and put there your custom text. Navigate through sources to check what is the valid string to put there.
For example nonEmptyText is declared as follows:
val nonEmptyText: Mapping[String] = text verifying Constraints.nonEmpty
and from there, Constraints.nonEmpty looks like this:
def nonEmpty: Constraint[String] = Constraint[String]("constraint.required") { o =>
if (o == null) Invalid(ValidationError("error.required")) else if (o.trim.isEmpty) Invalid(ValidationError("error.required")) else Valid
}
so the error string is "error.required"
now you can create a file messages in conf directory and put there a line
error.required=This field is required
ValidationError has apply method declared like this:
def apply(message: String, args: Any*)
which means you can also pass arguments there, in messages you can access them using {arg_num} syntax
If you for example created error like this
val format = ???
ValidationError("error.time", someFormat)
That will be returned with a bounded form, then play will use MessagesApi to find a message named "error.time" and format it accordingly, you could for example create a message like this:
error.time=Expected time format is {0}
My idea to have a custom message for each field is a custom method like this:
def nonEmptyTextWithError(error: String): Mapping[String] = {
Forms.text verifying Constraint[String]("constraint.required") { o =>
if (o == null) Invalid(ValidationError(error)) else if (o.trim.isEmpty) Invalid(ValidationError(error)) else Valid
}
}
probably not an ideal solution if you want to use many kids of constraints.

Related

how to do scala play forms validation for a field with different errors

I am trying to add some validation to our form for a field. It is the first .verifying below. At the moment even if it fails the new validation it then goes to check the rest of the .verifying and returns several error messages.
If the field fails my new validation I would like it to not do all the other verifying and just return. So basically if my field matches the regex for a temporary field, I want it to return an error message without continuing the rest of the validation.
val form = Form(
mapping(
"my_field" -> text
.verifying(Messages("error.field.temporary"), x => x.matches(TEMP_FIELD))
.verifying(Messages("error.mandatory", Messages("field")), _.length > 0)
.verifying(Messages("error.field.invalid"), x => x.length == 0 || MyField.isValid(x.toUpperCase()))
.verifying(Messages("error.field.invalid"), x => !MyField.isValid(x.toUpperCase()) || x.takeRight(1).toUpperCase().matches(FIELD_SUFFIX_REGEX))))
(MemberDetails.apply)(MemberDetails.unapply))
Any help appreciated.
I've solved this by using a constraint. Good information is available here: https://www.playframework.com/documentation/2.4.x/ScalaCustomValidations

Play(Scala) Framework 2.4.x - Form validations to validate equality of two fields in the same form at field-level

My application makes use of Play-2.4.2/Scala-2.11.6
I use PlayForms+HTML+Bootstrap to customize my views for User Registration module.
The form validation is a server-side validation , and I make use of ScalaCustomValidation to perform this.
https://www.playframework.com/documentation/2.4.x/ScalaCustomValidations
Below is an example of ScalaCustomValidation provided by the play docs.
val allNumbers = """\d*""".r
val allLetters = """[A-Za-z]*""".r
val passwordCheckConstraint: Constraint[String] = Constraint("constraints.passwordcheck")({
plainText =>
val errors = plainText match {
case allNumbers() => Seq(ValidationError("Password is all numbers"))
case allLetters() => Seq(ValidationError("Password is all letters"))
case _ => Nil
}
if (errors.isEmpty) {
Valid
} else {
Invalid(errors)
}
})
Now this validation can be called on the user form as below
val userFormConstraintsAdHoc = Form(
mapping(
"username" -> nonEmptyText,
"password" -> nonEmptyText(minLength = 12).verifying(passwordCheckConstraint),
"confirmpassword" -> nonEmptyText(minLength = 12).verifying(passwordCheckConstraint)
)(UserData.apply)(UserData.unapply)
)
This works fine and validates the strength constraint for both password and confirmPassword fields.
Now I need to define a constraint to validate the equality of password and confirmPassword .
Can someone help me on how to write this particular type of constraint , that actually reads the values of two(or more) fields of the same form and performs some the validation over the values of those fields
NOTE : I do not want to add the constraint at the case class level , I want to add it at field level only
I think the fact that you need information from more than one field prevents you from using a field-level constraint since they're supposed to be composable pure functions that don't have access to external state. You could use a Forms.tuple field for the password and confirmation and validate that separately, but I'm not sure what advantage that would confer over a form-level constraint:
val userFormConstraintsAdHoc = Form(
mapping(
"username" -> nonEmptyText,
"passwords" -> Forms.tuple(
"password" -> nonEmptyText(minLength = 12),
"confirm" -> nonEmptyText(minLength = 12)
).verifying("constraints.passwords.match",
passConfirm => passConfirm._1 == passConfirm._2)
)(UserData.apply)(UserData.unapply)
)

Scala Play upload file within a form

How would I upload a file within a form defined with Scala Play's play.api.data.Forms framework. I want the file to be stored under Treatment Image.
val cForm: Form[NewComplication] = Form(
mapping(
"Name of Vital Sign:" -> of(Formats.longFormat),
"Complication Name:" -> text,
"Definition:" -> text,
"Reason:" -> text,
"Treatment:" -> text,
"Treatment Image:" -> /*THIS IS WHERE I WANT THE FILE*/,
"Notes:" -> text,
"Weblinks:" -> text,
"Upper or Lower Bound:" -> text)
(NewComplication.apply _ )(NewComplication.unapply _ ))
is there a simple way to do this? By using built in Formats?
I think you have to handle the file component of a multipart upload separately and combine it with your form data afterwards. You could do this several ways, depending on what you want the treatment image field to actually be (the file-path as a String, or, to take you literally, as a java.io.File object.)
For that last option, you could make the treatment image field of your NewComplication case class an Option[java.io.File] and handle it in your form mapping with ignored(Option.empty[java.io.File]) (so it won't be bound with the other data.) Then in your action do something like this:
def createPost = Action(parse.multipartFormData) { implicit request =>
request.body.file("treatment_image").map { picture =>
// retrieve the image and put it where you want...
val imageFile = new java.io.File("myFileName")
picture.ref.moveTo(imageFile)
// handle the other form data
cForm.bindFromRequest.fold(
errForm => BadRequest("Ooops"),
complication => {
// Combine the file and form data...
val withPicture = complication.copy(image = Some(imageFile))
// Do something with result...
Redirect("/whereever").flashing("success" -> "hooray")
}
)
}.getOrElse(BadRequest("Missing picture."))
}
A similar thing would apply if you wanted just to store the file path.
There are several ways to handle file upload which will usually depend on what you're doing with the files server-side, so I think this approach makes sense.

store (binary) file - play framework using scala in heroku

I'm trying to store user-uploaded images in my application which is written by scala and play framework 2.2.x
I've deployed my app in heroku.
Heroku does not allow me to save my file in file system.
So I've tried to store my file in data base.
here is the code that I use for storing image :
def updateImage(id: Long, image: Array[Byte]) = {
val selected = getById(id)
DB.withConnection {
implicit c =>
SQL("update subcategory set image={image} where id = {id}").on('id -> id, 'image -> image).executeUpdate()
}
selected }
and here is the code that I use to retreive my image :
def getImageById(id: Long): Array[Byte] = DB.withConnection {
implicit c =>
val all = SQL("select image from subcategory where id = {id}").on('id -> id)().map {
case Row(image: Array[Byte]) => image
case Row(Some(image: Array[Byte])) => image
case Row(image: java.sql.Blob )=> image.getBytes(0 , image.length().toInt)
}
all.head
}
The problem is: when I use H2 database and blob column, I get the "Match Error" exception.
When I use Postgresql and bytea column, I got no error but when I retrieve the image, It's in hex format and some of the bytes in the beginning of the array are missing.
According to the PostgreSQL documentation, bytea stores the length of the array in the four bytes at the beginning of the array. These are stripped when you read the row, so that's why they seem to be "missing" when you compare the data in Scala with the data in the DB.
You will have to set the response's content-type to the appropriate value if you want the web browser to display the image correctly, as otherwise it does not know it is receiving image data. The Ok.sendFile helper does it for you. Otherwise you will have to do it by hand:
def getPicture = Action {
SimpleResult(
header = ResponseHeader(200),
body = Enumerator(pictureByteArray))
.as(pictureContentType)
}
In the example above, pictureByteArray is the Array[Byte] containing the picture data from your database, and pictureContentType is a string with the appropriate content type (for example, image/jpeg).
This is all quite well explained in the Play documentation.

How do you create a Playframework 2.0 Form with a field that is conditionally required?

Let's say I want to have a Form with a field, email, that is only required if they didn't put in their phone number in. Also the phone number is only required if they didn't put in their email, how would I do this?
I would like to do something like this, if requiredNoValid existed.
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._
case class User(email: Option[String] = None, age: Option[Int])
val userForm = Form(
mapping(
"email" -> email.verifying(requiredNoValid(phoneNumber)),
"phoneNumber" -> number.verifying(requiredNoValid(email))
)(User.apply)(User.unapply)
)
I have built my own solution for this in Play 1.X, but I would like to abandon most of that and use the Play 2 forms to do this for me if the functionality is there or if there is a way to do this by implementing a Validator or Constraint.
You can also add verifying on several fields. For a simple example:
val userForm = Form(
mapping(
"email" -> optional(email),
"phoneNumber" -> optional(number)
) verifying("You must provide your email or phone number.", {
case (e, p) =>
isValidEmail(e) || isValidPhoneNumber(p)
})(User.apply)(User.unapply)
)
Inside of the outer verifying now, you have access to both the email and phone number and can do cross validation.
This solution is for Java, but I'm sure you could so something similar if you're using scala
You could get the bound form data once you submit and validate it. If its not valid, you can reject the form with some error message. For example:
//Get filled form
Form<User> filledForm = userForm.bindFromRequest();
//Get the user object
User u = filledForm.get();
//If both are not empty
if(u.phoneNumber.isEmpty() && u.email.isEmpty()){
filledForm.reject("email", "You must provide a valid email or phone number");
}