Wednesday, September 9, 2009

Integrating jQuery AJAX and Spring MVC with XStream/Jettison and GAE

I wrote a small howto about putting Spring 3.x to work with Google Appengine earlier the year.
This is the basic starting point for this extended howto.





jQuery is a powerful yet unobtrusive JavaScript library with a very readable syntax.
It's unobtrusiveness makes it easy to add rich behavior aka AJAX.


A very simple example
could be a search on parent child object relations (e.g Make and Model).
The user selects a make and the coresponding models are loaded.
You would do this with spring forms like this:

Make:
<form:select path="herstellerId" >
<form:options items="${herstellerValues}" itemValue="herstellerId"
itemLabel="label" />
</form:select>
<br />
Model:
<form:select path="modellId" >
<form:options items="${modelValues}" itemValue="modellId"
itemLabel="label" id="herstellerId" />
</form:select>


Now it is time to bring in the AJAX magic. All you need is jquery and its forms plugin.

<script type="text/javascript" src="/js/jquery/jquery-1.2.6.min.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.form.js"></script>


Now you need to bind an on change listener to the make select box and define the JSON query function.

<script type="text/javascript">
$(document).ready( function() {
// event listeners
$("select[name^=herstellerId]").bind('change', loadModels);

});

// When a make is selected, populate the models dropdown
function loadModels() {
$.getJSON("/models.html", // url
{
makeId :this.value
}, // request params
function(json) { // callback
var options = '';
$(json.list.model).each(
function() {
options += '<option value="' + this.modellId
+ '">' + this.label + '</option>';
});
$("select[name^=modellId]").html(options);
});
}
</script>

That is it. Now you are done with the frontend part. Next you need to have a look at your Spring MVC controller.
The /models.html should have a corresponding method in your controller.

@RequestMapping("/models.html")
public ModelAndView models(@RequestParam("makeId") Long makeId) {
ModelAndView mav = new ModelAndView(JsonView.instance);
mav.addObject(JsonView.JSON_ROOT, getModelList(makeId));
return mav;
}

All we do here is to instantiate a JsonView ModelAndView object and add a list of model objects to it.
The method signature getModelList could look like this:

private List getModelList(Long makeId)

all you have to do is to instantiate your CarModel domain objects and put them to a list. If you try
to use JPA based entities you will probably discover a problem. The XStream/Jettison way of transforming
Objects to JSONObjects is not able to handle the datanucleus enhanced entities. Therefore it will be best
to either write an own XStream converter or simply use value or transfer objects.

Now we need to add the JsonView:

public class JsonView implements View {

public static final JsonView instance = new JsonView();
public static final String JSON_ROOT = "root";

private XStream xstream = new XStream(new JettisonMappedXmlDriver());

private JsonView() {
// your DTO/VO
xstream.processAnnotations(Modell.class);
}

public String getContentType() {
return "text/html; charset=UTF-8";
}

@SuppressWarnings("unchecked")
public void render(Map model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
Object root = model.get(JsonView.JSON_ROOT);
if (root == null) {
throw new RuntimeException("JSON root object with key '"
+ JsonView.JSON_ROOT + "' not found in model");
}
PrintWriter writer = response.getWriter();
String json = xstream.toXML(root);
System.out.println("json: " + json);
writer.write(json);
}


That was all in terms of coding. Now you have to add the following jar files to your distribution.

- stax-api-1.0.1.jar
- xpp3_min-1.1.4c.jar
- xstream-1.3.jar
- jettison-1.0.1.jar


And start over with testing. If you are happy, you will see the json response logged with the console:

json: {"list":{"model":[{"modellId":19,"label":"GT","herstellerId":7},{"modellId":20,"label":"Astra C","herstellerId":7},{"modellId":39,"label":"Insignia","herstellerId":7}]}}


Happy coding.