Play Framework Form Error Handling - scala

This is my view file containing the form that has to filled in by the user:
#helper.form(call) {
#helper.input(resumeForm("surname"), '_label -> "Surname") { (id, name, value, args) =>
<input name="#name" type="text" value="#value" placeholder="Enter your surname">
}
}
This is my custom field constructor:
#(elements: helper.FieldElements)
#if(!elements.args.isDefinedAt('showLabel) || elements.args('showLabel) == true) {
<div class="input-with-label text-left">
<span>#elements.label</span>
#elements.input
</div>
} else {
#elements.input
}
Now I have a dilemma. When the entered value doesn't clear validation, I need to add the class field-error to the input and I need to add data-toggle, data-placement and title. However, I don't know of any way to check if there are errors for the specific field. What is the best way to implement this? I already looked at using inputText or something but that is basically the same as the base input thus also does not have access to any errors. I'm also unable to alter the HTML of the elements.input inside the field constructor.

Have a look at play documentation: Writing your own field constructor.
You can check on errors with #if(elements.hasErrors) within the template of your custom field constructor.
<div class="input-with-label text-left #if(elements.hasErrors){field-error}">
...
Edit:
You can pass the error state of your field via the args parameter to your input. From the play docs:
Note: All extra parameters will be added to the generated HTML, except for ones whose name starts with the _ character. Arguments starting with an underscore are reserved for field constructor argument (which we will see later).
You need to cast to the matching type though.
#input(resumeForm("surname"), '_label -> "Surname", 'hasErrors -> resumeForm("surname").hasErrors) { (id, name, value, args) =>
<input name="#name" type="text" value="#value" placeholder="Enter your surname"
class="#if(args.get('hasErrors).map(_ match { case x:Boolean => x}).get){field-error}">
}

Related

How to get reactive validation with array group?

I have set code this way
errorGroup: any = FormGroup;
this.errorGroup = this.formBuilder.group({
errors: this.formBuilder.array([])
});
For repeat/add new data in group I have add this function which works fine.
addErrorGroup() {
return this.formBuilder.group({
error_code: ['',[Validators.required ]]
})
}
Get controls by this way. I think hear I'm missing something.
get f() { return this.errorGroup.controls.errors; }
In HTML
<select formControlName="error_code" name="error_code" (change)="errorCodeChange($event.target.value , i)">
<option *ngFor="..." value={{...}}>{{...}}</option>
</select>
<span *ngIf="f.error_code.errors.required" class="error-msg">This is required field.</span>
I got this error.
ERROR TypeError: Cannot read property 'errors' of undefined
If that error is coming from HTML, it's because your *ngIf condition is trying to read a value from an undefined object.
At the point where the view is rendered, and checked, it's entirely possible that f (incidentally, you should change that variable name to something more descriptive, but 🤷🏻‍♂️) doesn't have any errors populated yet, so will be undefined.
You can do one of two things here, either, you can wrap the whole thing in another *ngIf to ensure the error_code part of f is populate before accessing it:
<span *ngIf="f && f.error_code">
<span *ngIf="f.error_code.errors.required" class="error-msg">This is required field.</span>
</span>
Or, you can use the safe navigation operator:
<span *ngIf="f?.error_code?.errors?.required" class="error-msg">This is required field.</span>
Note the ? after each object key. This bails out when it hits the first null value, but, the app continues to work as it fails gracefully.
You can read more about it here: https://angular.io/guide/template-syntax#the-safe-navigation-operator----and-null-property-paths
How about if you just do below?
<span *ngIf="errorGroup.get('error_code').errors.required" class="error-msg">
This is required field.
</span>
so by doing this way, you don't need the f() getter in your component file.

How to bind multiple snippets to one input field in a form

I'am trying to have auto complete and onBlur functionality attached to the same input field using Liftweb framework.
I have them working independently.
What I'am trying to do is have an auto complete input field and on selecting the value from the suggestion, some business logic is to be performed and another input field needs to be updated.
But only the auto complete feature is working.
This is the form
<form class="lift:CapitalOnBlur">
Country : <input id="countryNameOnBlur" type="text" name="countryNameOnBlur"/><br />
Capital: <input id="capitalNameOnBlur" type="text" name="capital"/>
</form>
This is the snippet
object CapitalOnBlur {
val capitals: Map[String, String] = Map(
"india" -> "New Delhi",
"uganda" -> "Kampala",
"japan" -> "Tokyo")
def render = {
def callback(countryName: String): JsCmd = {
val capital = capitals.getOrElse(countryName.toLowerCase, "Not Found")
SetValById("capitalNameOnBlur", capital)
}
val default = ""
def suggest(value: String, limit: Int) = capitals.filter(_._1.startsWith(value.toLowerCase)).keys.toSeq
def submit(value: String) = Unit
"#countryNameOnBlur" #> AutoComplete(default, suggest, submit) &
"#countryNameOnBlur [onBlur]" #> SHtml.onEvent(callback)
}
}
This is what I actually want to do. I tried this and only onBlur event is triggered.
According to my needs, When I start typing the country name in the first input field, it should show me the suggestions and on selecting the suggestion i.e.; onBlur from that input field, the corresponding capital should be rendered in the next input field.
And also is there a way to trigger an action on selecting a suggestion using the inbuilt Auto complete feature of lift.
I am adding this as a separate answer since the edit is essentially a separate question. The AutoComplete widget from Lift does not modify an existing element on the page, but rather replaces it with the following NodeSeq, as per the source.
<span>
<head>
<link rel="stylesheet" href={"/" + LiftRules.resourceServerPath +"/autocomplete/jquery.autocomplete.css"} type="text/css" />
<script type="text/javascript" src={"/" + LiftRules.resourceServerPath +"/autocomplete/jquery.autocomplete.js"} />
{Script(onLoad)}
</head>
<input type="text" id={id} value={default.openOr("")} />
<input type="hidden" name={hidden} id={hidden} value={defaultNonce.openOr("")} />
</span>
Since that has now replaced the original HTML, the second line where you add an onBlur handler is not applied to anything useful. However, the AutoComplete constructor does take an optional parameter for attributes and you can probably use that to add an onBlur attribute to the input tag.
You can try something like this:
"#countryNameOnBlur" #> AutoComplete(default, suggest, submit,
("onBlur", SHtml.onEvent(callback).cmd.toJsCmd))
The above should pass in a tuple which specifies the attribute name, and the string representation of the Javascript you want executed. This should accomplish what you are looking for as long as the AutoComplete library doesn't also rely on the onBlur event. That case is doable too, but a bit more work.
One other thing to note is that onBlur is fired when the input loses focus, ie: the user moves the cursor to another field. If you want it to fire any time the text changes, regardless of cursor position, you may prefer the onChange event.
If you are looking to bind to different events on the same element, so that you end up with something like: <input onblur="getCapitalName" onchange="autoComplete">, you can try using SHtml.onEvent. Something like this in your snippet should do the trick:
object CapitalOnBlur {
def render =
"* [onblur]" #> SHtml.onEvent(e => CapitalOnBlur.getCapitalName(e)) &
"* [onchange]" #> SHtml.onEvent(e => CapitalOnBlur.autoComplete(e)) &
...
}
And then call the snippet from your input, like this:
<form>
Country : <input id="countryNameOnBlur" data-lift="CapitalOnBlur" type="text" name="countryNameOnBlur"/><br />
</form>
I am not sure what any of the arguments your code takes, so the above is mostly illustrative - but will hopefully get you on your way.

PlayFramework 2.x - Forms / Associating error message to one element of a tuple

I've got this form mapping:
val myElement = Form(
mapping(
"title" -> nonEmptyText,
"schedule" ->
tuple("startSchedule" -> jodaDate("dd/MM/yyyy HH:mm"),
"endSchedule" -> jodaDate("dd/MM/yyyy HH:mm"))
.verifying(MyValidator().checkForEndScheduleConsistency("error.schedule")),
)(MyElement.apply)(MyElement.unapply)
)
MyElement class:
case class MyElement(title: String, schedule: (Datetime, Datetime))
MyValidator class:
def checkForEndScheduleConsistency(errorMsg: String) =
Constraint[(DateTime, DateTime)]("constraint.schedule", errorMsg) {
schedule =>
MyDomainValidator().checkForEndScheduleConsistency(schedule._1, schedule._2, Messages(errorMsg)) match {
case Success(s) => Valid
case Failure(f) => Invalid(ValidationError("custom error string from `f`"))
}
}
Requirement: An error message must be associated to the field schedule.endSchedule(tuple's element) if the schedule is inconsistent according to MyValidator object.
However, in order to have both required elements (startSchedule and endSchedule) available for checkForEndScheduleConsistency method, I can't apply a verifying method directly on the nested tuple's element named endSchedule. Instead, I have to apply one on the whole tuple in order to include startSchedule variable, as shown in the code snippet.
The drawback is that the error is not mapped to endSchedule but to schedule (that doesn't represent anything in my HTML form), and so nothing is displayed to screen when an inconsistent schedule appears.
Therefore, I have to use this "workaround" to achieve my requirement using Form's withError method:
def create = Action {
implicit request =>
myForm.bindFromRequest.fold(
myFormWithErrors => {
myFormWithErrors.error("schedule") match { //check for the presence of potential schedule error
case Some(e) => {
BadRequest(views.html.create_element("Create an element", myFormWithErrors.withError("schedule.endSchedule", Messages("error.schedule"))))
}
case _ => BadRequest(views.html.create_element("Create an element", myFormWithErrors))
}
},
myForm => {
treatSubmittedMyForm(myForm)
}
)
}
=> Very ugly and anti-DRY.
Is there a way to apply verifying on the tuple and despite of that, apply the error message to a nested tuple's element? In my case, on endSchedule.
Probably the best solution is to replace the default helpers provided by Play when rendering the field by one of your own making, as per documentation:
#(elements: helper.FieldElements)
<div class="#if(elements.hasErrors) {error}">
<label for="#elements.id">#elements.label</label>
<div class="input">
#elements.input
<span class="errors">#elements.errors.mkString(", ")</span>
<span class="help">#elements.infos.mkString(", ")</span>
</div>
</div>
That way you can manually replace the error reference to the right element. Not extremely nice but you should be able to create a reusable tag from it.
The best solution is to precise the global (to schedule) "error" key into the more specific generated input directly, here the schedule.endSchedule input:
#inputText(hobbyForm("schedule.endSchedule"), 'id -> "schedule.endSchedule", '_error -> hobbyForm.error("schedule")
Notice the part: '_error -> hobbyForm.error("schedule")

Getting value of a hidden element in Lift

I have a hidden input field in HTML, it contains SomeValue:
<input id="event_id" type="hidden"> SomeValue </input>
I need SomeValue in server-side.
Is there a SHtml method I can use? The following code should get the value on submit, I need the value once the page is loaded.
"event_id" #> SHtml.onSubmit(id = _)
In the template you can just write
<input id="event_id"></input>
And in the snippet you can use the SHtml.hidden method:
SHtml.hidden(() => println("hidden field"))

Bind a form field in Play 2.0 with a constant value?

I have a scala form with several fields.The fields in the form map to the member variables of a Java class. I want to bind one of the fields(say userId) with a value (I dont want the user to enter values for this field. Instead i want to pass this as a parameter to the scala template). However, i was unable to manually bind a form field. Any help is highly appreciated.
See the sample below for easier understanding :
`#(itemForm: Form[Item], user: User)
#import helper._
#main("Item list") {
#if(user != null) {
#form(routes.Application.newItem()) {
#itemForm("userId") = #user.id /**I want to bind the userId form field */
#inputText(itemForm("title"))
#inputText(itemForm("description"))
#inputText(itemForm("price"))
<input type="submit" value="Create">
}
}
}`
In this case it would be better to pass it as action's argument (remember to modify routes declaration)
#form(routes.Application.newItem(user.id)){
....
you can also just use common html
<input type="hidden" name="userId" value="#user.id" />
edit:
Validation in action.Note: it doesn't make sense to display errors on the page next to hidden field, so you do not need placeholders for error messages. It's up to you to pass VALID value into the hidden field. Displaying validation errors to user who can not change the value of hidden field is bad conception.
public static Result newItem(){
Form<ItemModel> itemForm = form(ItemModel.class).bindFromRequest();
if (itemForm.hasErrors(){
return badRequest(newItemView.render(itemForm));
}
itemForm.get().save();
return ok("Your new item is saved...");
}