play framework: how to repopulate form on validation-failure when using datamodel? - model-view-controller

I'm building some crude CMS-like functionality (to get acquinted with Play Framework). For this test-case I've build 2 pages, 1 for listing tags and 1 for creating/editing/saving tags.
The flow is like this (routes-file):
#list tags
GET /tags Application.listTags
#view/edit existing tag
GET /tag/{<(?!new$)(.+)>name} Application.showTag
#new tag
GET /tag/new Application.showTag
the create/view/edit page displays a form which gets it's values from a tagDTO.
The normal flow works without problems, but when the form gives validation-errors (e.g: the tag-name must exist) I want to display the page again, repopulating the form with the edited values.
For this (following the Play Framework conventions) I could use the 'flash'-object which contains these last values, but the form is already bound to the tagDTO (which is null on redirect) instead of the 'flash'-object.
First the code:
Application.java
.....
public static void showTag(String name) {
TagDTO tagDTO = TagDTO.buildDTOFromModelOrNew(name);
render(tagDTO);
}
/**
* Save tag and redirect to Show
*
* #param name
* #param displayname
* #param isnew
*/
public static void saveTag(
#Required(message="Name is required") String name,
String displayname,
boolean isnew)
{
checkAuthenticity();
if(validation.hasErrors()) {
params.flash();
validation.keep();
showTag(null);
}
//fetch tagDTO based on backend or create new if not exist
TagDTO tag = TagDTO.buildDTOFromModelOrNew(name);
// Append / Overwrite values
tag.displayname = displayname;
tag.name = name;
//save result to model
TagDTO.buildAndSaveModelFromDTO(tag);
flash.success("Thanks for " + (isnew?"creating":"updating") + " tag " + tag.name);
//redirect to show
showTag(tag.name);
}
And ShowTag.html
#{extends 'main.html' /}
#{if flash.success}
<p class="success">${flash.success}</p>
#{/if}
#{ifErrors}
<p class="errors">Oops...</p>
#{/ifErrors}
#{form #Application.saveTag()}
#{authenticityToken /}
<p>
<label for="name">Name: </label>
<input type="text" name="name" id="name" value="${tagDTO.name}" />
<span class="error">#{error 'name' /}</span>
</p>
<p>
<label for="displayname">Displayname: </label>
<input type="text" name="displayname" id="displayname" value="${tagDTO.displayname}" />
<span class="error">#{error 'displayname' /}</span>
</p>
<p>
<input type="hidden" name="isnew" value="${tagDTO.isnew}" />
<input type="submit" value="Submit your comment" />
</p>
#{/form}
Now I could think of some ways to make it work, but none really elegant:
bind the form to the flash-object (or params-object) and populate the flas/params- object from the tagDTO
on validation-failure, refetch the tagDTO (not avail anymore so DB-call necessary) and overwrite values in tagDTO with values available in flash-object, bind form to tagDTO.
like 2, but using some sort of cache to quickly fetch tagDTO (so no need for db-call)
Some general mechanism to (de)serialize tagDTO from/to the session.
In short, I don't like any of them really.
What would you consider to be a best practice in this situation? Or is there any functionality in the Play Framework that I'm missing?

This is where the explicit render calls comes handy. Retain the form values from previous submission and give it back (if validation fails) as follows,
checkAuthenticity();
if(validation.hasErrors()) {
render("#showTag", name, displayname, isnew);
}
This will avoid the extra redirect (307 in case of Play!) that would have happened if you had called 'action from another action'.

Render the form again and avoid the redirect is a solution. I think it's OK if a user press F5 he will get the error again. But I think you should create a reload/cancel button, so the user can dismiss all the information.
To have always the correct URL you can do the following in the routes.conf:
GET /tag/create TagController.create
POST /tag/create TagController.insert
The flash solution has the disadvantage that your cookie can get really big.

Related

Dynamically created elements are not validated on the client

I have a table in my web application, which is populated from the model, where properties have attribute for validation:
[Required(ErrorMessage = "Please enter amount!")]
[DisplayFormat(NullDisplayText = "", ApplyFormatInEditMode = true)]
public decimal? Amount { get; set; }
When I press Submit, the field is properly validated on the client, displaying an error message, if the amount is left empty.
Now the user can add new TRs to the table using jquery. The new record completely imitates the existing records, e.g. when I inspect the Amount field for an existing TR:
<input class="form-control" type="text" data-val="true" data-val-number="The field Amount must be a number." data-val-required="Please enter amount!" id="Financials_1__Amount" name="Financials[1].Amount" value="1834.09"><span class="text-danger field-validation-valid" data-valmsg-for="Financials[1].Amount" data-valmsg-replace="true"></span>
Cf. to the same field of the dynamically added TR:
<input class="form-control" type="text" data-val="true" data-val-number="The amount must be a number." data-val-required="Please enter amount!" id="Financials_77e9f261-010a-4c7c-ae50-e3f6587a8c4e__Amount" name="Financials[77e9f261-010a-4c7c-ae50-e3f6587a8c4e].Amount" value="33"><span class="text-danger field-validation-valid" data-valmsg-for="Financials[77e9f261-010a-4c7c-ae50-e3f6587a8c4e].Amount" data-valmsg-replace="true"></span>
The records look very similar. Yet when I leave the required field empty and press submit, the value is not validated on the client, and the execution comes to the controller's action method. There ModelState.IsValid is false though. This is my first issue.
Here is the action method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(int contractorId, ContractorDetailsDto dto)
{
if (ModelState.IsValid)
{
...
}
return View(_contractorRepository.GetContractorDetailsViewModelByDto(dto));
}
As our ModelState is invalid, the same view is loaded. There the error is displayed in the validation summary, BUT NOT UNDER THE FIELD WITH THE INVALID VALUE. This is the second issue.
I have on my view:
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
How can I find the cause of my problems?
Answering for your main question. Can you try to execute next javascript code after you add new row
function refreshValidators(formSelector) {
var targetForm = $(formSelector);
targetForm.removeData('validator');
targetForm.removeData('unobtrusiveValidation');
targetForm.removeAttr('novalidate');
$.validator.unobtrusive.parse(targetForm);
}
Regarding your second problem I think you should create separate question for it as it unrelated to your main question. You need to find proper way how to add new rows in ASP.NET Core. For ASP.NET MVC we had next solution how to solve this task http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ . After googling BeginCollectionItem I found core analog solution, but it is pretty old and I didnt use it personaly.

Spring form with thymeleaf not binding right, throwing exception: Parameter is null

I have two views, one to book appointments and one to show them in a calendar view. After the booking was successful, there's a confirmation shown. The confirmation then forwards to the calendar view. I want to pass the booking infos as parameters to the calendar view so it can display the new booking accordingly in the calendar, but a null exception is thrown in that step.
I copied the template of a working form. I inspected the web request, all the necessary data is there, I think it's just not binding right.
data class EventAppointmentSearchRequest (val startDateTime: LocalDateTime, val endDateTime: LocalDateTime, val rooms: List<Room>)
/**
* Gets called when confirming a booking to add it to the DB.
*/
#PostMapping("/roomBookingConfirmation")
fun roomBookingConfirmation(model: Model, #ModelAttribute roomBookingRequest: RoomBookingRequest): String {
makeBooking(roomBookingRequest)
val date = roomBookingRequest.datetimeFrom
val start = roomBookingRequest.datetimeFrom.minusDays(date.dayOfWeek.value.toLong())
val end = roomBookingRequest.datetimeFrom.plusDays(7 - date.dayOfWeek.value.toLong())
model.addAttribute("eventAppointmentSearchRequest", EventAppointmentSearchRequest(
startDateTime = start,
endDateTime = end,
rooms = listOf(roomRepository.findByRoomName(roomBookingRequest.roomNr))
))
return "roomBookingConfirmation"
}
/**
* Displays the appointments in the calendar view according to the request
*/
#PostMapping("/calendarView")
fun calendarView(model: Model, #ModelAttribute eventAppointmentSearchRequest: EventAppointmentSearchRequest): String {
// THIS THROWS THE EXCEPTION: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method ...requests.EventAppointmentSearchRequest.<init>, parameter startDateTime
...
}
<!-- /*#thymesVar id="eventAppointmentSearchRequest" type="de.tudarmstadt.pvw.tulpe.soonToBeArtifactory.requests.EventAppointmentSearchRequest"*/ -->
<form th:action="#{/calendarView}" method="post" th:object="${eventAppointmentSearchRequest}" id="forwardToCalendar" style="grid-column: span 4">
<H1 th:text="#{roomBooking.bookingConfirmed}">
Booking confirmed.
</H1>
<div class="links">
<a href="#" th:text="#{roomBooking.nowRedirecting}" onclick="forwardToCalendar()">Redirecting to
calendarView in </a> <b id="secondsLeft">7</b>
<input type="hidden" th:field="${eventAppointmentSearchRequest.startDateTime}" th:name="startDateTime" th:value="${eventAppointmentSearchRequest.startDateTime}">
<input type="hidden" th:field="${eventAppointmentSearchRequest.endDateTime}" th:name="endDateTime" th:value="${eventAppointmentSearchRequest.endDateTime}">
<input type="hidden" th:field="${eventAppointmentSearchRequest.rooms}" name="rooms[]" th:each="room: ${eventAppointmentSearchRequest.rooms}" th:value="${room.RoomId}">
</div>
...
</form>
I expect the form to just be bound correctly, I can see all the necessary data to use the constructor of EventAppointmentSearchRequest in the web inspector of my browser. Actual output is this error message:
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method ...requests.EventAppointmentSearchRequest., parameter startDateTime
Something like this should suffice for the two date hidden inputs:
<input type="hidden" th:field="*{endDateTime}">
As for the third, th:field takes precedence over name and value attributes, if you look closely into the generated HTML, you'll see that the value is identical for each room hidden input, and it's the toString() on the list of Rooms. This is clearly wrong, and you need to specify each attribute of Room that you want to submit, have a look at the following article: https://www.baeldung.com/thymeleaf-list
I don't see the point in transmitting the details of the booking 2 more times between the client and server, I'd just pass a booking ID to the calendar page and have it load all the details...

MVC matching ModelState keys to ViewModel collection

Is it possible to match a ViewModel property to the matching ModelState.Key value when the ViewModel is a (has a) collection?
Example: To edit a collection of viewmodel items, I am using the extension found here.
That adds a GUID to the id of the fields on the page.
example:
class Pets
{
string animal;
string name;
}
For a list of Pets, the generated html source is like this:
<input name="Pets.index" autocomplete="off" value="3905b306-a9..." type="hidden">
<input value="CAT" id="Pets_3905b306-a9...__animal" name="Pets[3905b306-a9...].animal" type="hidden">
<input value="MR. PEPPERS" id="Pets_3905b306-a9...__name" name="Pets[3905b306-a9...].name" type="hidden">
<input name="Pets.index" autocomplete="off" value="23342306-b4..." type="hidden">
<input value="DOG" id="Pets_23342306-b4...__animal" name="Pets[23342306-b4...].animal" type="hidden">
<input value="BRUTICUS" id="Pets_23342306-b4...__name" name="Pets[23342306-b4...].name" type="hidden">
So when this gets bound on post, the ModelState gets loaded with all the form fields.
In ModelSTate.Keys, there is:
Pets[23342306-b4...].name
Pets[23342306-b4...].animal
Pets[3905b306-a9...].name
Pets[3905b306-a9...].animal
Everything good so far, but I am doing some business logic validation, things like, cant add new animal if one exists with the same name. In that case, I want to be able to highlight the input field that is in error.
So if my create function fails, it will return an error/key value pair like this:
{ error = "Duplicate Name", key="name" }
So I at least will now what property caused the problem.
But since my repository functions don't know about the view field ids, how can I match the key "name" to the appropriate ModelState key (in this case, either Pets[23342306-b4...].name or Pets[3905b306-a9...].name)?
If you used the built in functionality of MVC for displaying collections (Html.DisplayFor(m => m.Pets) or Html.EditorFor(m => m.Pets)) with appropriate display/editor template, MVC would render something like this:
Pets[0].name
Pets[0].animal
Pets[1].name
Pets[1].animal
This maps to IEnumerable<Pets> and you know that first item has index of 0, second item 1 etc.
So if the second item has an error, you can set error for the ModelState key "Pets[1].name" for example.
If you are using the Html.BeginCollectionItem extension method, like I was, I was able to get around this by not using the GUID. I need the dynamic add and delete, but I was always looking up known items, persons that have an ID, which I had in my editor. So instead of using the GUID, I just assign the ID (uniqueId) in the code below. I could then find the key because I knew it was Person[234232]. Of course if you are adding new items and not displaying selected items, it might not work for you.
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName, string uniqueId)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : uniqueId;
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}

Internationalized Labels for Form Components in Wicket

How do I correctly create internationalized labels for my form components so that when displaying feedback messages an internationalized field name is displayed instead of the name of the field in the java code?
I've read this:
https://cwiki.apache.org/WICKET/everything-about-wicket-internationalization.html
as well as the documentation for wicket's xhtml tags:
https://cwiki.apache.org/WICKET/wickets-xhtml-tags.html
<label wicket:for="name">
<wicket:label>
<wicket:message key="label.name"/>
</wicket:label>
</label>
<input wicket:id="name" type="text" wicket:message="placeholder:label.name" />
This results in the following error:
Last cause: Expected close tag for '<wicket:label>' Possible attempt to embed
component(s) '<wicket:message key="label.name"/>' in the body of this
component which discards its body
If I replace the wicket:message with some arbitrary text it displays the text in any associated feedback messages.
(There's a related jira issue: https://issues.apache.org/jira/browse/WICKET-3903 however I still do not understand what has been done to fix this and what I must do ...)
Just found out there is a way to do this in java:
add(new TextField<String>("name").setRequired(true).setLabel(new Model<String>(getString("label.name"))));
Is it possible to somehow do this in a more comfortable way?
I just tested the following:
<form wicket:id="form">
<label for="input"><wicket:message key="input">some input</wicket:message></label>
<input wicket:id="input" type="text" name="input">
<input type="submit" value="submit">
</form>
And in the java class:
Form<HomePage> form = new Form<HomePage>("form"
, new CompoundPropertyModel<HomePage>(this));
wmc.add(form);
TextField textField = new TextField("input");
textField.setRequired(true);
form.add(textField);
In the property file I provided:
input=SomeInputField
This led to the following screen (if I leave the requiered field empty and press submit.
Is this what you are looking for?
Here is an alternative approach to #bert's that has always worked for me (wasn't aware of <wicket:label>)
The text shown for a FormComponent when a validation error occurs can be specified by means of FormComponent.setLabel(IModel). The shown text will be the result of the IModel's getObject().
TextField comp = new TextField("comp");
// Use internationalized text from XML resource file
comp.setLabel(new StringResourceModel("formResources.comp.label", this, null));
Notice this has nothing to do with <label> nor FormComponentLabel. FormComponentLabel is a component that can be used to model <label> tags.
You could even subclass FormComponentLabel to provide the label text based on FormComponent.getLabel(), and maybe output an extra mark when the field is required:
public class MyLabel extends SimpleFormComponentLabel{
private boolean required;
public MyLabel (String id, LabeledWebMarkupContainer labelProvider) {
super(id, labelProvider);
if (labelProvider instanceof FormComponent){
required = ((FormComponent)labelProvider).isRequired();
}
}
protected void onComponentTagBody(final MarkupStream markupStream,
final ComponentTag openTag) {
String mark = "";
if (required){
// could be for instance "*"
mark = getString("formResources.requiredField");
}
String text = getModelObjectAsString() + mark;
replaceComponentTagBody(markupStream, openTag, text);
}
}
{
TextField component = new TextField("component");
component.setRequired(true);
component.setOutputMarkupId(true);
IModel labelModel = new StringResourceModel("formResources.component.label",
this, null);
component.setLabel(labelModel);
add(component);
add(new MyLabel("componentLabel", component);
}
<label wicket:id="componentLabel"/>
<input type="text" wicket:id="component"/>
This way you would have clean way of
Setting the FormComponent's text to an internationalized resource string
Reusing exactly the same resource string transparently for the <label> tag and even adding custom marks to it based on FormComponent's properties.
Another alternative is to use the key attribute of <wicket:label/>, like so:
<label wicket:for="name">
<wicket:label key="label.name">Placeholder label</wicket:label>
</label>
<input wicket:id="name" type="text"/>
Unfortunately this attribute is not documented on the wiki page describing wicket's xhtml tags. All attributes supported are documented using JavaDoc in the class handling the tag (org.apache.wicket.markup.html.form.AutoLabelTextResolver).
The advantage of this alternative is that there is no additional coding required.
Wicket throws an exception to tell you that your <wicket:message> tag will be removed because the body of the <wicket:label> tag is replaced. The problem is you cannot nest the <wicket:message> tag inside the <wicket:label> tag (and shouldn't need to).
either this (Option 1):
<label wicket:for="name">
<wicket:label key="label.name"/>
</label>
<input wicket:id="name" type="text />
or this (Option 2):
<label wicket:for="name">
<wicket:message key="label.name"/>
</label>
<input wicket:id="name" type="text />
should work for you and result in HTML something like the following (assuming the properties file contains label.name=Name):
<label for="someMarkupId">
Name
</label>
<input id="someMarkupId" type="text" />
The difference is that if you set the label for the component through the Java code like so:
component.setLabel(new Model("value set in code"));
then using the Option 1 will result in the label being set to "value set in code", while using Option 2 will still result in the label set to "Name". Also if the label is set through Java code, and the key is missing from the properties file the Option 2 will throw an exception, while Option 1 will simply use the value set in the code.
I prefer this:
<label wicket:for="name"><wicket:label />:</label>
<input type="text" wicket:id="name"></input>
Just make sure to set the label in the FormComponent using setLabel, so the only java needed is:
add(new TextField("name", nameModel).setLabel(Model.of("i18n.name")));
This will be rendered as (in Dutch):
<label id="name63-w-lbl" for="name63">Naam:</label>
<input type="text" value="" name="name" id="name63">

How to rerender part of page after ajax submit of form in Lift (Scala)

this is probably a stupid question but I cannot figure out how to do it.
So I'm new to Scala/Lift and I read the ajax form chapter in http://simply.liftweb.net/index-4.8.html#toc-Section-4.8 but the "RedirectTo" in the example does not seem to be very "ajaxian" to me. Often in case of submitting a form via ajax, you would just partially rerender the same page, right?
So that's what I'm trying to do and am completely failing right now.
How do I let Lift rerender just a part of the same page after I submit the form via ajax?
Any hints would be appreciated. Thanks.
Basically, what I have looks like this:
<div id="main" class="lift:surround?with=default;at=content">
<h2>Welcome to your project!</h2>
<div class="lift:Test">
<div>
<form class="lift:form.ajax">
<fieldset>
<label for="name">Name:</label>
<input id="name" name="name" type=text>
<p></p>
<input id="save" type="submit" value="Save">
</fieldset>
</form>
</div>
<div>
<span id="theName">Name</span>
</div>
</div>
</div>
class Test {
def render = {
var name = ""
def process(): JsCmd = {
Thread.sleep(500)
S.notice("Entered name is: %s".format(name))
Noop
}
"#theName " #> "This shall be updated with the name given in the form above" &
"#name" #> (SHtml.text(name, name = _) ++ SHtml.hidden(process))
}
}
How would I update "theName" when submitting the form?
Have a look at http://lift.la/shtmlidmemoize-simple-ajax-updating (Example Code). There is SHtml.memoize and SHtml.idMemoize which automatically caches the HTML code. Not sure why it is not used in this example in the Simply Lift book.
You have a 2 step form right? The above poster is correct.
Save your transformation in a RequestVar.
in your above example, the method you want to save is render, so 1st memoize the transform:
private def renderTest= SHtml.memoize { render }
Then, you can save this memoized transformation in a RequestVar (lasts for 1 request), or maybe a TransientRequestVar depending on your needs.
private object testTemplate extends RequestVar(renderTest)
When you want to replay the transform, from an ajax event - testTemplate.is.applyAgain.
I might have misunderstood the original question, b/c if you want to do a 2 step form, you don't really need the memoize. The memoize is if something changes on your current form, and you want to update it via an ajax event, i.e. on click or on change, b/c normally the form wouldn't update unless you did an ajax submit.

Resources