Here s how I m doing it using Spring MVC + JQuery + Bootstrap, partially based on a recent blog post at SpringSource:
AddressController.java
@RequestMapping(value="/validate")
public @ResponseBody ValidationResponse processForm(Model model, @Valid AddressForm addressForm, BindingResult result) {
ValidationResponse res = new ValidationResponse();
if (result.hasErrors()) {
res.setStatus("FAIL");
for (ObjectError error : result.getAllErrors()) {
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
res.addError(fieldError.getField(), fieldError.getDefaultMessage());
}
}
}
else {
res.setStatus("SUCCESS");
}
return res;
}
AddressForm.java
public class AddressForm {
@NotNull
@Size(min=1, max=50, message="Address 1 is required and cannot be longer than {max} characters")
private String address1;
@Size(max=50, message="Address 2 cannot be longer than {max} characters")
private String address2;
// etc
}
ValidationResponse.java:
public class ValidationResponse {
private String status;
private Map<String,String> errors;
// getters, setters
}
address.jsp:
<f:form commandName="addressForm">
<div class="control-group">
<label for="address1">Address 1</label>
<div class="controls">
<f:input path="address1" type="text" placeholder="Placeholder Address 1" class="wpa-valid" />
<span class="help-inline"></span>
</div>
</div>
<!-- etc -->
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn">Cancel</button>
</div>
</f:form>
<script type="text/javascript">
function collectFormData($fields) {
var data = {};
for (var i = 0; i < $fields.length; i++) {
var item = $($fields[i]);
data[item.attr("id")] = item.val();
}
return data;
}
function clearErrors($fields) {
for (var i = 0; i < $fields.length; i++) {
var item = $($fields[i]);
$("#"+item.attr("id")).parents(".control-group").removeClass("error");
$("#"+item.attr("id")).siblings(".help-inline").html("");
}
}
function markErrors(errors) {
$.each(errors, function(key, val) {
$("#"+key).parents(".control-group").addClass("error");
$("#"+key).siblings(".help-inline").html(val);
});
}
$(document).ready(function() {
var $form = $("form.validate");
$form.bind("submit", function(e) {
var $fields = $form.find(".validate");
clearErrors($fields);
var data = collectFormData($fields);
var validationUrl = "validate";
$.get(validationUrl, data, function(response) {
$("#alert").removeClass();
if (response.status == "FAIL") {
markErrors(response.errors);
$("#alert").addClass("alert alert-error");
$("#alert").html("Correct the errors below and resubmit.");
} else {
$("#alert").addClass("alert alert-success");
$("#alert").html("Success!");
$form.unbind("submit");
$form.submit();
}
}, "json");
e.preventDefault();
return false;
});
});
</script>
It could use some refactoring, but this will do an ajax GET with the form data and update the page based on the result.