Dashboard > Stripes 1.2 > ... > How To's > AJAX
Stripes 1.2 Log In   View a printable version of the current page.
AJAX
Added by Tim Fennell, last edited by Tim Fennell on Oct 10, 2005  (view change)

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, but none seems to provide a simple implementation that allows you to submit a form using AJAX. Attached to this article you can find stripes-ajax.js, which is a simple JavaScript library for interacting asynchronously with a Stripes application on a server.

stripes-ajax.js is free to use. As important, the source code is not obfuscated in any way, so if you already have a favourite AJAX library, or prefer to maintain your own JavaScript, you are free to examine the stripes-ajax functions and incorporate anything useful into your own environment and/or code.

There are two methods that we'll use. They are:

"Functions for talking to a Stripes Application using AJAX"
/**
 * Invokes a Stripes action using the contents of the supplied form. The named
 * event will be invoked on the server, and the callbackFunction will be
 * invoked. The callback function will only be invoked when the request is
 * completed, and (unlike when using the XMLHttpRequest directly) the
 * XMLHttpRequest will be supplied as the first parameter to the callback
 * function.
 *
 * @param form - a reference to a form object to be "submitted" using AJAX
 * @param event - the String name of the event to be fired when invoking the
 *                ActionBean. To invoke the default event pass either null or ""
 * @param callbackFunction - a function that takes a single parameter, the
 *        XMLHttpRequest, and will be invoked when the request has been processed
 * @return a reference to the XMLHttpRequest in case the caller would like to be
 *         able to manipulate it between sending the request and receive the
 *         callback (e.g. cancelling it before re-invoking).
 */
function invokeActionForm(form, event, callbackFunction) {
...
}

/**
 * Invokes a Stripes action bound to the specified URL. May specify a named
 * event, or if an event name is not supplied, will invoke the default event
 * handler.
 *
 * @param url - the URL for the http request, which will be accessed asynchronously
 * @param event - the String name of the event to be fired when invoking the
 *                ActionBean. To invoke the default event pass either null or ""
 * @param params - a JavaScript object. Each property on the object will be
 *        in the query string sent to the server. If a property is an array each
 *        value will be included, otherwise the property will be treated as a
 *        single value.
 * @param callbackFunction - a function that takes a single parameter, the
 *        XMLHttpRequest, and will be invoked when the request has been processed
 * @return a reference to the XMLHttpRequest in case the caller would like to be
 *         able to manipulate it between sending the request and receive the
 *         callback (e.g. cancelling it before re-invoking).
 */
function invokeActionUrl(url, event, params, callbackFunction) {
...
}

The first function will read all the fields in the form, and submit them to the server as part of an asynchronous XMLHttpRequest. The second method takes a URL to request, and optionally a set of parameters represented as a JavaScript object.

In both cases the methods take a parameter called callbackFunction. This should be a function object which will be called back when the response from the server has been received. There are a couple of differences from the callback function that is used if you are programming directly against the XMLHttpRequest object.

  1. The callback is only executed when the response has been fully received, not on each state change
  2. The callback method is provided the XMLHttpRequest object as a parameter

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" xml:space="preserve">
          function populateResult(xmlHttpRequest) {
              var td = document.getElementById("result");
              td.innerHTML = xmlHttpRequest.responseText;
          }
      </script>
      <script type="text/javascript"
              src="${pageContext.request.contextPath}/script/stripes-ajax.js"></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="/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:button name="Addition" value="Add"
                        onclick="invokeActionForm(this.form, this.name, populateResult);"/>
                    <stripes:button name="Division" value="Divide"
                        onclick="invokeActionForm(this.form, this.name, populateResult);"/>
                </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:button name="Addition" value="Add"
    onclick="invokeActionForm(this.form, this.name, populateResult);"/>
<stripes:button name="Division" value="Divide"
    onclick="invokeActionForm(this.form, this.name, populateResult);"/>

In the quick start the buttons were regular HTML submit buttons. Here we see buttons (not submit buttons) that trigger a javascript function instead, and pass it the form, the name of the button that was clicked (as the event name) and a reference to the populateResult function. Lastly we see:

<script type="text/javascript" xml:space="preserve">
    function populateResult(xmlHttpRequest) {
        var td = document.getElementById("result");
        td.innerHTML = xmlHttpRequest.responseText;
    }
</script>
<script type="text/javascript"
        src="${pageContext.request.contextPath}/script/stripes-ajax.js"></script>

The first script block defines a function that takes the text output of sending an AJAX request to the server and inserts it into the spot setup for the result of the calculation. The second script tag loads up the stripes-ajax set of functions.

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.HandlesEvent;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.StreamingResolution;
import net.sourceforge.stripes.action.UrlBinding;
import net.sourceforge.stripes.validation.Validate;
import net.sourceforge.stripes.validation.ValidationErrorHandler;
import net.sourceforge.stripes.validation.ValidationErrors;
import net.sourceforge.stripes.validation.ValidationError;

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
 */
@UrlBinding("/ajax/Calculator.action")
public class CalculatorActionBean implements ActionBean {
    private ActionBeanContext context;
    private double numberOne;
    private double numberTwo;
    public ActionBeanContext getContext() { return context; }

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

    @Validate(required=true)
    public double getNumberOne() { return numberOne; }
    public void setNumberOne(double numberOne) { this.numberOne = numberOne; }

    @Validate(required=true)
    public double getNumberTwo() { return numberTwo; }
    public void setNumberTwo(double numberTwo) { this.numberTwo = numberTwo; }

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

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

This looks very similar to the code from the quickstart example, other than the implementations of addNumbers and divideNumbers. 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 send the result back to the client as text using a StreamingResolution:

@HandlesEvent("Addition") @DefaultHandler
    public Resolution addNumbers() {
        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 might now 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)
  • 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");

            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>");
        }
    }
}

Summary

We've seen how to talk to ActionBeans asynchronously using AJAX techniques. All the usual Stripes services such as validation and deep-object binding work just the same as they do when invoking ActionBeans in the normal non-AJAX way. A few areas that the interested reader may want to explore are:

  1. Using JSPs to generate XML islands to be sent back to the browser
  2. Generating JSON representations of ActionBean or result properties

Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.2.7 Build:#524 Jul 28, 2006) - Bug/feature request - Contact Administrators