Other the weekend I found a number of problems with the input validation code I had written, and I felt that I had to go back to the drawing board and start again. Part of the issue has been my unfamiliarity with Javascript, and part because I think I was trying to combine too many different behaviours into one action. So I started reading around, looking for examples of how other frameworks handle validation.
Client-side verses server-side validation
Any discussion of user-input in web applications must start with a discussion of client-side verses server-side validation. Of course, the correct answer is to do both. I'm focusing on the server-side because I think it's more important than client-side, a more interesting problem to solve, and keeps me away from nasty Javascript for a little longer.
In a real application I'd want to use both, and of course there are many useful validation steps which can happen on the browser - counting the number of characters in a password field, for instance. But I'll be focusing on server-side validation in this blog.
A basic AJAX-driven form submission and validation
Let's start with the desired workflow:
- A web form is displayed and the user enters some values
- The user clicks 'Submit' and an AJAX POST event is fired, sending the contents of the web form to the controller
- The controller parses each value in turn and validates it against some business rules - must not be empty, a minimum or maximum length, check that field B is filled in when checkbox A is ticked - etc etc.
- If there are no errors, the data is submitted (saved, emailed, whatever) and the user is shown a success message or page
- If there are errors, the form is re-shown with the errors highlighted in red. The values the user entered must be preserved
- The process repeats until the user gets it right or gives up in disgust.
Let's unpick steps 2 and 3 - submission and parsing. Consider the following simple AJAX POST request in Javascript:
function validateAndSubmit(formName, containerDiv) {
var serializedData = $('#' + formName).serialize();
// first, post to validator
$.ajax({
url: '/ajax/submit',
method: 'post',
data: serializedData,
success: function(response, statusText, xhr) {
// do something with the result...
}
)};
}
Here, I have asked jQuery to serialize the form data, which turns the form data into a string of query parameters - for instance, name=Liam&age=21
- and then POST them to the given URL /ajax/submit
. This is easily understood by the controller. It can get a little unwieldy if there are a lot of parameters, and the controller will need to convert the values into the right type (Integers, etc) before validation.
jQuery could also return the parameters as a JSON object, by calling $('#' + formName).serializeArray();
. This would produce something like [{name:"Liam",age:"21"}]
. However, the controller on the server will need to do more work to parse this JSON-like string, perhaps relying on a library to handle JSON parsing.
Furthermore, this JSON array string is beginning to look a lot like a data model. Perhaps we should go all-in and create a Java class to represent all the elements submitted in a form?
Indeed, that's what many of the big frameworks do. JSF requires a class to represent the form model, typically called a backing bean and annotated with @ManagedBean
. Validators are attached at the front-end but run on the server.
<h:inputText id="type" maxlength="10"
value="#{customer.type}"
label="Customer type"
validator="#{customer.validateType}"
<f:ajax />
<f:validateLength minimum="8" maximum="10" />
</h:inputText>
JSF then completely hides the stateless request-response nature of the web from the developer. It's a very compelling approach, but it's not one I want to replicate here.
Spring also expects form elements to be backed by a Java class. That class is typically annotated with the validation rules (for instance, @NotNull @Size(min=3,max=30) private String name;
. By the time the controller receives the user's input, Spring has already converted POST request data into the corresponding data model and validated the input.
@PostMapping("/submit")
public String submit(@Valid PersonForm personForm, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
// re-render the form
} else {
// submit the data
}
}
This is closer to the approach I want to take, though I doubt I can get spark-kotlin to perform that sort of annotation-driven request-to-model conversion happening automatically. It's definitely worth exploring, and Kotlin's data classes should make it quicker and easier to knock-up a backing form class than plain Java would.
As it is, I'm checking the queryParameters
received in the controller, and passing them to a validation function to parse and validate:
Spark.path("/ajax/") {
post("submit") {
val hours: String? = request.queryParams("hours")
val minutes: String? = request.queryParams("minutes")
val errorMap = validateHoursAndMinutes(hours, minutes)
if (errorMap.isEmpty()) {
// perform submission
} else {
model.put("errors",errorMap)
// return the updated form HTML to jQuery so it can update the DOM and display the error messages
}
}
}
If I go for the backing-bean data class approach, I'll probably create a constructor which takes a queryParams
object directly, and add a validator to a companion object on the data class.
Displaying validation results
Now that we have the basics of validation working, we need to display the results to the user. In the modern AJAX driven web, that means re-rendering the submission form with the error messages and the values the user entered.
The server needs to return a fragment of HTML containing the updated form. The success: function(response) {... }
will then update the web page with just the updated HTML, avoiding a reload of the entire web page.
The templating engine I'm currently using, Thymeleaf, allows page fragments to be re-rendered. Unfortunately, this isn't integrated into spark-kotlin's Thymeleaf dialect, which doesn't understand the template fragment syntax. So I have had to move the HTML form
out of the main HTML page and into a separate HTML file. It works fine, though it does lessen one of the key advantages of using Thymeleaf.
Other approaches
Of course, there are many other ways of tackling validation. There are plenty of Java and Javascript libraries that can handle it all. There's a myriad of approaches to defining validation rules in XML, JSON, Java annotations, in HTML tags. I'm not a fan of any of these approaches; in real life I always find validation requirements which don't fit into the standard approaches. So for now I'll just write custom Kotlin code for validation; perhaps I'll come up with a standard interface I can use across the project.