I've recently been developing a web project using the Struts2 framework. A problem that came up during development was validation. More specifically, the way error messages and page redirects are sent back to the user. In this blog post I'll explain the problems I encountered, and some solutions to those problems.
Struts' Validation Framework
Struts already has an excellent validation framework, using an XML-based solution to perform validation on specific fields. The pre-defined validation tests are robust and generally fit most validation cases. For example, take the follow excerpt, where we specify that the username is a required string:
We also define that the message should be looked up from a key (username.required) in a separate properties file, making string externalization very simple. There are a lot of pre-defined field validators to choose from, and they will generally fit most of your needs.
If they don't, adding a custom validation check is easy and intuitive. Simply implement the validate method on the action you wish to perform validation on and specify your logic there. For example, if we want to ensure that the username doesn't already exist in the database, we could implement validate like so:
String externalization isn't as simple, but we still get the ability to perform more complicated validation.
Struts' validation framework is excellent. Should you hit a validation error, the workflow interceptor will return the string "input", and avoid executing the action. It's pretty safe to say you can trust Struts to handle all your validation provided you've configured it correctly.
You start to run into problems when you try to report the error messages to the user. Struts will attempt to display field errors next to the fields of a form, or you can display the messages in the page like so:
The errors are added as HTML elements and can be styled with CSS fairly simply. However, I ran into the following issues with this system:
- If you want to do anything with the errors other than display them in static HTML elements, you have to work for it. For example, I wanted to use jQuery to animate the errors over the form they were for, and highlight the culprit fields red. Doable, but unnecessarily complicated. It'd require listening for any errors that're placed into the form, removing them, and then adding them to your own custom elements. DOM manipulation alarm bells should be ringing.
- Things get messy when your "input" result redirects to another action. Firstly, you'll lose the error messages. Wordpress user Glindholm outlined an excellent solution to this problem here.
- Secondly, any subsequent action calls (in the same request) after the validation error will have the result "input". So if your result page pulls in other pages by calling their actions, you'll have to define a result "input" for each of those too. Similarly, if you populate any fields in an action's execute method before returning the page, they won't be populated (execute is never called on an action during "input").
- Things get even messier when you don't want to perform a full page refresh every time a validation error occurs on one form. If you wanted to return the form again with the error messages, you'd have a problem as the messages won't be visible to anything other than the form, limiting where you can display them.
These problems motivated me into finding an alternative solution to display Struts' error messages whilst still using their validation framework.
- Intercept form submission with a submit handler.
- Mock the form submission with AJAX
- Analyse the return result.
- Display the errors if they exist, otherwise mock the form submission result
Intercepting and mocking form submission
Intercepting and mocking a form submission is easy, just define a handler like below using jQuery.live:
Analysing the return result
The result is, as we discussed before, whatever comes back from the "input" result. This is a bit of a problem, as we don't have any way of accessing the errors through the "input" page (it'll be a string representing the return page). The solution is to return a JSON object of the errors. This is very easy to do with the json return type in Struts. I modified my struts.xml to read:
Firstly, adding "json-default" to the package declaration gives me access to the json result type. I've then defined a global result for the type input, meaning that any uncaught input results will redirect to here.
The type is JSON, and I've specified the root to be fieldErrors (which is a bean property, and thus would map to getFieldErrors()). Finally, I've added a prefix and suffix to wrap the produced JSON in another object for nicer encapsulation. Using the example above, when calling the action with a username that already exists I receive the following object:
Mocking the form success behaviour
This is not a trivial task. Usually when a form submits, it redirects the user to a certain page. By preventing the default action to replace it with our own ajax request, we've lost that functionality.
All we have is the string representation of the result page. We could always just replace the body innerHTML with this string, but what if we only need to refresh part of the page? We won't be replacing the innerHTML of body anymore, but some element which varies depending on the form. Similarly, just loading the HTML wouldn't parse the page correctly, as well as prevent onload listeners from firing.
The solution will differ for some situations. If every form on your site does the same thing, you can just code that functionality straight into the ajax success. However, when you have multiple forms refreshing different parts of the page, you need some way of differentiating between them.
And then you can call the function from the success body like so:
Obviously, this solution poses obvious security risks. It wouldn't be too hard for the client to do something like:
and have direct access to the page. Don't do this! The solution is to have any actions that require entire page redirects return the page they want to redirect to. This would take the value of 'e' in the success function, which you could pass to the logInForm function.
My aim was to utilize the Struts built-in validation with a more flexible error message framework, which I believe I have achieved. I didn't want to go into the interceptors that handle the validation as they already work so well, I was mainly concerned with the error messages. The final stage, wherein you define a function for each form, is by no means perfect, and I am still looking for improvements to defining individual form submission behaviour.
The important thing to note is that, even though we're doing quite a lot on the client-side, all of the validation is done by the server. It's essentially safe behind closed doors, where it can evaluate the input and choose to execute the action or not. So even if there is a problem in the implementation of error messages being shown, you can be safe in the knowledge that your forms will still be validated by Struts. An interesting extension would be mixing in some client-side validation too.. but that's another blog post!