JSON - JavaScript Object Notation

JSON is a very popular data transmission format used in many XHR applications - the ever popular gmail application makes extensive use of it. The official web site has much more information on all the details at http://json.org.

While the majority of default XHR (ajax) behaviour provided by Tapestry uses XML as the exchange format - because of how easy it is to wrap existing html blocks - it does also provide a great deal of support/API functionality for you to utilize JSON data transmission formats in your own applications.

This document will go over some of the basics of using this part of the API on the client / server side as well as show examples of how it is used by Tapestry itself in components such as the Autocompleter.

JSON Basics

The basic idea behind the JSON format is that you can output something using it from the server and your browser can evaluate it and access the structures you define directly - like any other javascript object. For example, we could define a response listing the general attributes of a user in some theoretical system we are building:

{name:"Dr. No", occupation:"Super villain", age:52, email:"noknows@gmail.com"}

The above block of JSON can be interpreted and used on the client side in javascript with a couple simple statements:

var user=eval("{name:'Dr No', occupation: 'Super villain'}");
alert("User occupation:" + user.occupation + " name:" + user.name);

The format also supports returning array like structures as well as nesting of differnt kind of structures as in:

{users:[
    {name:"Dr. No", occupation:"Super villain", age:52, email:"noknows@gmail.com"},
    {name:"Henrietta Smith"},
    {name:"Lev Nikolayevich Myshkin", occupation:"Idiot"}
    ]
}

or just..:

["10", "30", "14", "5"]

You get the idea.. One of the more useful things provided by http://json.org is a sample java API for working with the format - http://www.json.org/java/index.html. Tapestry has incorporated this java code in to the API proper (with some minor improvements) so creating our first example JSON object output can be as simple as:

..
org.apache.tapestry.json.JSONObject json = new JSONObject();
json.put("name", "Dr. No");
json.put("occupation", "Super villain");
..
calling json.toString() should produce:
{"name":"Dr. No", "occupation":"Super villain"}

See also: Tapestry JSON API

IJSONRender: Writing JSON capable components

To support this new format we've added a new optional interface that you can implement in your components - IJSONRender:

public void renderComponent(IJSONWriter writer, IRequestCycle cycle)
{
}

The basic idea is the same as the typical IComponent.renderComponent(IMarkupWriter, IRequestCycle) call - except that in this case you are dealing with a IJSONWriter instance instead of the more familiar IMarkupWriter. This interface is really just a wrapper around the JSON api provided by Tapestry.

Once you have implemented this IJSONRender interface in one of your components that is pretty much all there is to do. The JSON method you implement will only be called if a JSON request is processed by Tapestry AND the request has specified your component as one of the components to update and capture the response of. Otherwise the normal html markup based methods will be called on your component.

Client Side Processing

Processing json response data on the client side isn't really something Tapestry can do for you, so you'll have to have your own consumer of this data set up to handle it beforehand. If you use the standard Tapestry API's then the global tapestry.loadJson javascript function will be invoked. Currently this function does nothing other than decrement the global tapestry.requestsInFlight javascript variable value. You can replace or do an event connection on this function to provide your own implementation. An example of doing an event connection would be:

dojo.event.connect(tapestry, "loadJson", function(type, data, http, kwArgs){
    // do your stuff...the data argument is your json object
});

Example: Tapestry's Autocompleter Component

One example of a core Tapestry component that implements the IJSONRender interface is the Autocompleter component. The method implementation of that component looks like this:

public void renderComponent(IJSONWriter writer, IRequestCycle cycle)
{
   IAutocompleteModel model = getModel();

   if (model == null)
     throw Tapestry.createRequiredParameterException(this, "model");

   List filteredValues = model.getValues(getFilter());

   if (filteredValues == null)
    return;

   Object key = null;
   String label = null;
   JSONObject json = writer.object();

   for (int i=0; i < filteredValues.size(); i++) {
    Object value = filteredValues.get(i);

    key = model.getPrimaryKey(value);
    label = model.getLabelFor(value);
    json.put(getDataSqueezer().squeeze(key), label );
   }     
}

This component actually makes use of some new base classes - like AbstractFormWidget - don't let this distract you from the IJSONRender interface portion. They are both mutually exclusive and totally unrelated.

That is pretty much it. This component hands off the majority of client side functionality to a Dojo widget and only provides the widget with a URL string produced by a pre-generated JSON request. You can find the full source and all of the gory details of the rest of that here.