Although a rich Javascript toolkit like Dojo can validate forms, your app should also support server-side validation for two reasons
- It's available if there are Javascript errors on the page. This is particularly important for pages composed of several loosely-related "widgets" where errors in one widget affect the others on the page.
- Security. If a page is saved and the validation is stripped, the server should gracefully handle the hacked version.
Flash Scope
Flash scope is a hybrid of request scope and session scope. Flash scope allows objects to be accessed outside of the traditional request scope, but isn't quite as "sticky" as session scope. The flash-scoped object is preserved through forwarding, but is cleared out when new requests are made from the browser.
This example backs up a form with a set of flash-scoped variables. This is to preserve the submitted contents in the case of a validation error. For example, if a form with an input text with a value of "ABCD" is submitted to a Controller expecting only 3 characters, the redisplayed form should retain the ABCD value with error messages and highlights from the view layer.
The View
This section of a form sets a Dojo ValidationTextBox from a flash-scoped variable 'tutorial_title_tx'. Notice that when the form is saved, it will submit the value -- possibly changed by the user -- using the name 'tutorial.tutorial_title_tx'.
View Code |
Successful Submission |
Invalid Form |
The Controller code supporting this form is divided into two methods: showTutorial() and saveTutorial(). showTutorial() is invoked on a GET request to display the initial form. The method is also called through forwarding when there is an error.
The showTutorial() method will retrieve an object from the database (if found). On the first pass, the flash variable 'tutorial_title_tx' will not be set, so rendering proceeds by passing the @Entity 'tutorial' to the render() call after setting the flash variable to the value currently stored in the DB.
GET Form - showTutorial() Controller Method |
If there were an error, then the flash.get() condition would be false and what was put on the flash scope from the preceding saveTutorial() call would be used.
There is special handling for the Public Flag which is displayed as a checkbox control. The Tutorial @Entity has a field public_fl that is a Boolean. The form will submit a checkbox named _public_fl whose type is a text value like "on" or null if the box is unchecked.
Saving
The saveTutorial() method first validates the arguments which are gathered using a POJO 'tutorial' and additional fields like 'topic_cd' and 'account_id'. Two validations are applied: required and maxlength. If the validation passes, entities coming from <select> controls are pulled in, attached to the form's value object, and saved. Note that the form's value object contains both the tutorialId primary key and the Hibernate version field.
If an error message were found, then the flash scoped variable is set which will be used.
POST Form - saveTutorial() Controller Method |
Design Notes
This form-handling code flattens out a nested @Entity object called Tutorial. Tutorial contains simple types like 'Long tutorial_id' and 'String tutorial_title_tx'. Play! can automatically assign these from the form. Dependent entities like 'Account creator' or 'Topic topic' are flattened, transporting only encoded values from the form. In the Controller, the actual entities are retrieved and hooked up to the Tutorial @Entity. Then, the Tutorial will be eligible to be saved. If you try to reconstitute the dependent entities (Account, Topic) from the form, you may run into problems with JPA object state.
Message Resources
This example uses some custom messages that override the default values of validation.required and validation.max. They follow the Play! documentation example, but I used a C printf technique for displaying an integer value with the max() function: %d rather than %s.
validation.required='%s' cannot be blank
validation.max='%s' cannot exceed %d characters
For a great UI, you'll want to use client-side validation, catching errors as the user is entering the data. This obviates the need for a big post-submission dissection of an error report. However, server side validation should still be used to produce a fault tolerant and secure application for cases when an unexpected browser or hacked-based form is sent.
very good article. Nevertheless I find the flash approach a little too verbose and redundant, besides you have the 4kb limitation (flash is saved to cookies)
ReplyDeleteI usually just render back to the form page (in this case showTutorial) passing as parameter the already populated object.
have a look at this sample application
https://github.com/opensas/events/blob/5-actions/app/controllers/Application.java
I just issue
public static void form(Long id) {
Event event;
if (id==null) {
event = new Event();
} else {
event = Event.findById(id);
}
render(event);
}
public static void save(@Valid Event event) {
if (validation.hasErrors()) {
render("@form", event);
}
event.save();
flash.success("Data successfully updated");
list();
}
With this approach you have a little inconsistency between the url and the page displayed. In case of error, you end up posting values to /save and it render the page /form...
Hi opensas,
ReplyDeleteThanks for the suggestion.
Maybe an overloaded form(Event event) method would eliminate the inconsistency? I'll try this with an @Valid on the POJO and post another article.
-Carl