AJAX

AJAX is something that you must have been living under a rock if you haven't heard of before now, so I'm not going to do the "this is what AJAX is" routine. If you need that, I'd suggest you go here. Since AJAX is very much a client-side technology, and Stripes is a server side framework, this document will mainly focus on how to interact with Stripes using AJAX technologies. If you're looking for neat visual effects and general AJAX tips, you'd be better off searching google!

There are many different ways to write AJAX applications. At the simplest level you can imagine invoking some logic on the server (or even just fetching static content) and swapping the contents of visible elements on the screen without refreshing the page. More complex (and hence powerful) approaches involve passing back structured data as XML, or JSON constructs, which can then be manipulated in sophisticated ways on the client using JavaScript.

This "How To" will walk through:

  • How to invoke Stripes ActionBeans using AJAX
  • An AJAX version of the Calculator application from the Quick Start Guide
  • Some additional techniques for using AJAX with Stripes

Invoking ActionBeans using AJAX

Probably the first thing you'll need to do is invoke an ActionBean from the browser using JavaScript. There are a large number of AJAX frameworks out there and almost all of them contain helper methods and/or objects to accomplish this. Stripes does not require any specific AJAX framework, and will work equally well whether you choose Dojo, MochiKit, Prototype or any of the many other client side toolkits.

For the purpose of these examples we'll be using Prototype. Prototype is an excellent library that does a good job of abstracting away browser differences and adds a sane baseline over the often quirky JavaScript APIs.

AJAX Calculator Application

Although the calculator application is somewhat trivial, it serves as a good example for how to use Stripes with AJAX. The following is the JSP from the Quick Start Guide modified a little (if you haven't read the quick start, you might want to at least take a look at the example code for comparison):

/ajax/index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> 
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html> 
<head> 
<title>My First Ajax Stripe</title> 
<script type="text/javascript" 
src="${pageContext.request.contextPath}/ajax/prototype.js"></script> 
<script type="text/javascript" xml:space="preserve"> 
    /* 
    * Function that uses Prototype to invoke an action of a form. Slurps the values 
    * from the form using prototype's 'Form.serialize()' method, and then submits 
    * them to the server using prototype's 'Ajax.Updater' which transmits the request 
    * and then renders the response text into the named container.
    * 
    * NOTE: Requires Prototype version 1.6 or above. 
    * 
    * @param form reference to the form object being submitted 
    * @param event the name of the event to be triggered, or null 
    * @param container the name of the HTML container to insert the result into 
    */ 
    function invoke(form, event, container) { 
        if (!form.onsubmit) { form.onsubmit = function() { return false } }; 
        var params = Form.serialize(form, {submit:event}); 
        new Ajax.Updater(container, form.action, {method:'post', parameters:params}); 
    } 
</script> 
</head> 
<body> 
<h1>Stripes Ajax Calculator</h1> 

<p>Hi, I'm the Stripes Calculator. I can only do addition. Maybe, some day, a nice programmer 
will come along and teach me how to do other things?</p> 

<stripes:form action="/examples/ajax/Calculator.action"> 
    <table> 
        <tr> 
            <td>Number 1:</td> 
            <td><stripes:text name="numberOne"/></td> 
        </tr> 
        <tr> 
            <td>Number 2:</td> 
            <td><stripes:text name="numberTwo"/></td> 
        </tr> 
        <tr> 
            <td colspan="2"> 
                <stripes:submit name="add" value="Add" 
                    onclick="invoke(this.form, this.name, 'result');"/> 
                <stripes:submit name="divide" value="Divide" 
                    onclick="invoke(this.form, this.name, 'result');"/> 
            </td> 
        </tr> 
        <tr> 
            <td>Result:</td> 
            <td id="result"></td> 
        </tr> 
    </table> 
</stripes:form> 
</body> 
</html> 

There are a few changes to note. Working from the bottom up, we see:

<td>Result:</td> 
<td id="result"></td> 

In this case, we no longer insert the result from the ActionBean using EL, because the result isn't rendered into the JSP on the server side. Instead the place where the result will go is identified by an id so that we can reference it after the page has loaded. Next we see:

<stripes:submit name="add" value="Add" 
    onclick="invoke(this.form, this.name, 'result');"/> 
<stripes:submit name="divide" value="Divide" 
    onclick="invoke(this.form, this.name, 'result');"/> 

Here we see submit buttons that trigger a javascript function when they are clicked, and pass it the form, the name of the button that was clicked (as the Stripes event name) and the name of the HTML element into which to insert the result. Lastly we see:

 
<script
    type="text/javascript" 
    src="${pageContext.request.contextPath}/ajax/prototype.js"></script> 
<script type="text/javascript" xml:space="preserve"> 
    /* ... */ 
    function invoke(form, event, container) { 
        if (!form.onsubmit) { form.onsubmit = function() { return false } }; 
        var params = Form.serialize(form, {submit:event}); 
        new Ajax.Updater(container, form.action, {method:'post', parameters:params}); 
    } 
</script> 

The first script tag imports the Prototype JavaScript library. The second block defines a little utility function that uses Prototype to invoke an ActionBean on the server and update the contents of an HTML container when the server delivers the result. Before making the call to the server, it disables the normal form submission process by setting up the form's "onsubmit" event handler to return false.

That's it for the JSP. Assuming we have everything setup for the ActionBean to work right, the page will contact the server when a button is clicked and render the response into the page without refreshing the browser window.

Let's take a look at the ActionBean:

"AJAX CalculatorActionBean.java"
package net.sourceforge.stripes.examples.ajax; 

import net.sourceforge.stripes.action.ActionBean; 
import net.sourceforge.stripes.action.ActionBeanContext; 
import net.sourceforge.stripes.action.DefaultHandler; 
import net.sourceforge.stripes.action.Resolution; 
import net.sourceforge.stripes.action.StreamingResolution; 
import net.sourceforge.stripes.validation.Validate; 
import net.sourceforge.stripes.validation.ValidationError; 
import net.sourceforge.stripes.validation.ValidationErrorHandler; 
import net.sourceforge.stripes.validation.ValidationErrors; 

import java.io.StringReader; 
import java.util.List; 

/** 
* A very simple calculator action that is designed to work with an ajax front end. 
* @author Tim Fennell 
*/ 
public class CalculatorActionBean implements ActionBean, ValidationErrorHandler { 
    private ActionBeanContext context; 
    @Validate(required=true) private double numberOne; 
    @Validate(required=true) private double numberTwo; 

    public ActionBeanContext getContext() { return context; } 
    public void setContext(ActionBeanContext context) { this.context = context; } 

    @DefaultHandler public Resolution add() { 
        String result = String.valueOf(numberOne + numberTwo); 
        return new StreamingResolution("text", new StringReader(result)); 
    } 

    public Resolution divide() { 
        String result = String.valueOf(numberOne / numberTwo); 
        return new StreamingResolution("text", new StringReader(result)); 
    } 

    // Standard getter and setter methods 
    public double getNumberOne() { return numberOne; } 
    public void setNumberOne(double numberOne) { this.numberOne = numberOne; } 

    public double getNumberTwo() { return numberTwo; } 
    public void setNumberTwo(double numberTwo) { this.numberTwo = numberTwo; } 
} 

This looks very similar to the code from the quickstart example, other than the implementations of add and divide. Instead of setting an attribute on the ActionBean and forwarding the user to a JSP, the handler methods now perform the calculation and then simply sends the result back to the client as text using a StreamingResolution:

@DefaultHandler 
public Resolution add() { 
    String result = String.valueOf(numberOne + numberTwo); 
    return new StreamingResolution("text", new StringReader(result)); 
} 

That's it for the basics. Our page will now contact our ActionBean when one of the buttons is hit, and the result will get transferred back to the page and displayed in the appropriate place.

Handling Validation Errors

This all works great, until some validation errors occur. Then Stripes tries to forward the user back to the page that originated the request and render it with errors. With the code shown above, this results in the page getting embedded in itself! Oops. Luckily there is a way to handle this. Stripes provides an optional interface for ActionBeans called ValidationErrorHandler. This allows ActionBeans to intercept the flow of execution when validation errors occur, and tell Stripes what to do next.

At the top of our ActionBean we can add:

public class CalculatorActionBean implements ActionBean, ValidationErrorHandler { 
    ... 
    /** Converts errors to HTML and streams them back to the browser. */ 
    public Resolution handleValidationErrors(ValidationErrors errors) throws Exception { 
        StringBuilder message = new StringBuilder(); 

        for (List<ValidationError> fieldErrors : errors.values()) { 
            for (ValidationError error : fieldErrors) { 
                message.append("<div class=\"error\">"); 
                message.append(error.getMessage(getContext().getLocale())); 
                message.append("</div>"); 
            } 
        } 

        return new StreamingResolution("text/html", new StringReader(message.toString())); 
    } 
    ... 
} 

Because the ActionBean implements ValidationErrorHandler Stripes will invoke the handleValidationErrors method if any validation errors are generated. In this case the method loops through the set of errors constructing an HTML fragment with each error in a separate div. This is then returned to the client, again using the StreamingResolution. Now when validation errors occur they are displayed in the same place that the result would be displayed.

More Efficient Real-Time Streaming

The StreamingResolution works well when:

  • you are streaming data back to the client from a stream or reader object (e.g. streaming back a chunk of XML received from a web service invocation or the database)
  • you are generating a few kilobytes or less of information, programmatically in your ActionBean

If, however, you are generating a lot of output from your ActionBean and do not want to buffer it entirely to a String before starting to send it back to the client, one good way to approach the problem is to create an anonymous Resolution. Take a look at the following example:

Using an anonymous Resolution
@HandlesEvent("GetLotsOfData") 
public Resolution getLotsOfData() { 
    Map<String,String> items = getReallyBigMap(); 
    return new Resolution() { 
        public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception { 
            response.setContentType("text/xml"); 

            response.getOutputStream().print("<entries>"); 
            for (Map.Entry<String,String> entry : items.entries()) { 
                response.getOutputStream().print("<entry><key>"); 
                response.getOutputStream().print(entry.getKey()); 
                response.getOutputStream().print("</key><value>"); 
                response.getOutputStream().print(entry.getValue()); 
                response.getOutputStream().print("</value></entry>"); 
            } 
            response.getOutputStream().print("</entries>"); 
        } 
    } 
} 

Using ForwardResolution to return html fragments

If your ajax request expects an html fragment as a response, you can use a ForwardResolution to generate it. The benefit to this is that the html is created by a jsp.

  1. Ajax.Updater makes a request
  2. Your ActionBean event handler forwards to a jsp
  3. Ajax.Updater updates $(container) with whatever html fragment the jsp renders

For example, if we ever decide to add a button to our calculator to reveal a hidden drawer containing additional functionality, we could create an event handler 'showScientificCalc()' and add a new jsp 'scientificCalcControls.jsp'.

public Resolution showScientificCalc() { 
    return new ForwardResolution("/fictitious/scientificCalcControls.jsp"); 
} 

Returning more complex data to the browser

In some situations you may want to return structured data to the browser instead of HTML for display. In general there are two ways to do this:

  • return an XML island containing your data
  • return JSON or JavaScript to be evaluated by the browser

While both methods work, my preference is for returning JavaScript because it's easier to work with in the browser and can support more complex data (e.g. cyclical object graphs). Stripes has support for this kind of interaction through the JavaScriptResolution and JavaScriptBuilder classes.

The JavaScriptBuilder is a class that can take a Java object of any type and traverse it to create a set of JavaScript statements and JSON blocks that will, when evaluated, recreate the object's state in JavaScript. It can handle all the Java built in types, and can traverse arbitrary user types. It even correctly handles circular object graphs, ensuring that each object is serialized only once and that all object references are valid.

The JavaScriptResolution is a Resolution that uses the builder to serialize an item to JavaScript and then stream it back to the client. Although it's unlikely that we'd use this technique for returning a single number, a modified set of JavaScript for the caclulator example using Prototype might look like this:

Handling a JavaScriptResolution
/** Function that handles the update when the async request returns. */ 
function update(xhr) { 
    var output = eval(xhr.responseText); 
    $('result').innerHtml = output; 
} 

/** Function to submit a request and invoke a handler when it completes. */ 
function invoke(form, event, handler) { 
    var params = Form.serialize(form, {submit:event}); 
    new Ajax.Request(form.action, {method:'post', parameters:params, onSuccess:handler}}); 
}