2008-07-08 @ admin
I’m working on the AJAX chapter of the Stripes book. One exercise involves having a list of people, and a form, in a single page. The form gets submitted via AJAX, using jQuery.
If all is well, the list is refreshed and the form is hidden. On the other hand, if validation errors occur, the form is refreshed so that the error messages appear.
The initial page contains a div for the list of people and one for the form:
<div id=“people”>
<@include file=“people.jsp”>
</div>
<div id=“form”>
<@include file=“form.jsp”>
</div>
form.jsp:
<s:form beanclass=”…”>
<!— … —>
<s:submit name=“save” onclick=“return submitForm(this);”/>
</s:form>
The JavaScript submits the form with an AJAX post:
function submitForm(button) {
var form = button.form;
var params = $(form).serialize() + ‘&_eventName=’ + button.name;
$.post(form.action, params, function(data) {
$(’#people’).html(data);
});
return false;
}
The Action Bean is pretty standard:
public class FormActionBean extends BaseActionBean {
/* … */
public Resolution save() {
// save person info
// return refreshed list of people
return new ForwardResolution(“people.jsp”);
}
}
Now, the form properties in FormActionBean include @Validate annotations. This means that invalid input in the form will cause validation errors. Without doing anything, the default Stripes behavior is to return to form.jsp with error messages instead of executing the save() method. That’s fine, but when that happens, we want to put the data back in the '#form' div, not in the '#people' div:
function submitForm(button) {
var form = button.form;
var params = $(form).serialize() + ‘&_eventName=’ + button.name;
$.post(form.action, params, function(data) {
//
// The problem is: how do we determine ‘success’?
//
if (success) {
$(’#form’).hide();
$(’#people’).html(data);
}
else {
$(’#form’).html(data);
}
});
return false;
}
As commented in the code, how do we communicate a ‘success’ flag from the Action Bean to the AJAX response, while still using standard Resolutions to JSPs?
With some help from the friendly Stripes community, software development and some digging in the jQuery code, here is a reasonably elegant solution. Add a header in the response to indicate success, either in the Action Bean:
public class FormActionBean extends BaseActionBean {
/* … */
public Resolution save() {
// save person info
getContext().getResponse().setHeader(“Stripes-Success”, “true”);
return new ForwardResolution(“people.jsp”);
}
}
Or in people.jsp:
<% response.setHeader(“Stripes-Success”, “true”); %>
Next, retrieve the header in the AJAX response. I had trouble figuring out how to get the XmlHttpRequest object (which contains the response headers) and the HTML data in the same callback function. Turns out that the XHR object is returned from $.post, as well as its friends such as $.get and $.ajax.
So here is how to get the response header and use it to decide in which div to put the response data:
function submitForm(button) {
var form = button.form;
var params = $(form).serialize() + ‘&_eventName=’ + button.name;
var xhr = $.post(form.action, params, function(data) {
if (xhr.getResponseHeader(‘Stripes-Success’)) {
$(’#form’).hide();
$(’#people’).html(data);
}
else {
$(’#form’).html(data);
}
});
return false;
}
VoilĂ ! The form is AJAXified, validation errors show up correctly, and the list of people gets refreshed after a successful submit. All this with regular Action Beans and JSPs, all we had to do was add the
response header and retrieve it in the JavaScript. I find this solution much easier than returning raw data (e.g. JSON) in the response and having to reconstruct the HTML in JavaScript code! What’s
more, this way makes it much less work to maintain a form that works in both an AJAX and a non-AJAX context.
Cheers,
Freddy
This is great Freddy! I am wondering now how we could go about doing something like this without manually overriding public void execute().
It turns out that overriding execute() is not necessary. You can just set the header on the response and return a regular ForwardResolution. I’ve updated the code to reflect this.
Ahh, much simpler. Cool.