i have a table called system it has some columns mainly number / char types but there is one binary type for holding a document (word or excel etc)
ignore for now if we should hold the file inline or external i.e. bfile or blob... the question is more about how to handle it in the play framework
I have got the form working with the model view and controller... but i set it up without the file-field just to get it working:
I have a case class in the model
case class System(sys_id: Pk[Long] = NotAssigned
, sys_name: String
, sys_desc: String
, sys_owner1: Long
, sys_owner2: Long)
a form in the controller
val systemForm = Form(
mapping(
"sys_id" -> ignored(NotAssigned:Pk[Long]),
"sys_name" -> nonEmptyText,
"sys_desc" -> nonEmptyText,
"sys_owner1" -> longNumber,
"sys_owner2" -> longNumber
)(System.apply)(System.unapply)
)
and a form in the view
#form(routes.Systems.save(), 'class -> "form-horizontal") {
#inputText(systemForm("sys_name"), '_label -> "System Name")
#inputText(systemForm("sys_desc"), '_label -> "Description")
#select(
systemForm("sys_owner1"),
persons,
'_label -> "Primary Owner", '_default -> "-- Choose a person --",
'_showConstraints -> false
)
#select(
systemForm("sys_owner2"),
persons,
'_label -> "Secondary Owner", '_default -> "-- Choose a person --",
'_showConstraints -> false
)
<div class="control-group">
<div class="controls">
<input type="submit" value="Create System" class="btn btn-success"> or
Cancel
</div>
</div>
}
This all works well... but now i want to add the file in, what should i do?
I have seen this http://www.playframework.com/documentation/2.1.2/ScalaFileUpload
but it confuses me it makes no mention of if or how i should change the case class - and what variable is the file stored in (if it is) so that I can access it in my code?
should I modify my case class (if so what type should i add)?
should I modify the Form in the controller (again if so what type should I add?)
im not bothered whether I store it inline or as a bfile - but how can I handle it as asked above?
Thank you
There are some items to consider:
In your .scala.html file, you'll need a file upload field <input type="file" name="someName">.
You'll need to set the encoding type of the form as stated in the documentation: 'enctype -> "multipart/form-data".
You can then change your controller to to handle multipart form-data, so it looks like the one in the example. Use Action(parse.multipartFormData) to parse the form. I think you should then also be able to use your form with systemForm.bindFromRequest (from Form API), although I haven't tried that before.
Edit:
What I meant was to treat the uploaded file and the rest of the form data as separate items.
Try changing the example form the documentation to look like this:
def upload = Action(parse.multipartFormData) { request =>
request.body.file("picture").map { picture =>
import java.io.File
val filename = picture.filename
val contentType = picture.contentType
picture.ref.moveTo(new File("/tmp/picture"))
// this is the new part
val formContents = systemForm.bindFromRequest.value
// now you can do stuff with `formContents` and `picture`
Ok("File uploaded")
}.getOrElse {
Redirect(routes.Application.index).flashing(
"error" -> "Missing file"
)
}
}
Related
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)
)
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.
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.
I am new to play framework and i have a list of files and i wanna add check box so that i can delete the all the checked files on clicking the delete button, i am displaying the list of files using scala language, help is really needed
#form(action = routes.Application.delete, 'enctype -> "multipart/form-data") {
#for(order1 <- listfiles) {
#if(order1.isDirectory()) { }
else {
<input type="checkbox" name="#order1">#order1.getName()
}
}
<input type="submit" name="delete">
}
where #order1.getName() is the name of the file
You need to pass them as an array of params, take a look at the nico_ekito's answer to find how you can access them in the controller.
Optionally you can use ie. JavaScript to join all filenames/identifiers into one comma-separated String and then just split it in controller to List<String> which maybe will be even easier.
you have to use special syntax for name attributes [], to let to know play that you will send list of elements:
#for((value,index) <- listfiles.zipWithIndex ) {
<input type="checkbox" name="files[index]">#value.getName()</input>
}
and then you can bind them to list in you form mapping like this
files -> list(text)
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");
}