Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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.

...

Code Block
languagexml
title/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:

Code Block
xml
languagexml
 
<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:

Code Block
xml
languagexml
 
<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');"/> 

...

Let's take a look at the ActionBean:

Code Block
languagejava
title"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:

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

...

At the top of our ActionBean we can add:

Code Block
languagejava
 
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())); 
    } 
    ... 
} 

...

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:

Code Block
languagejava
titleUsing 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>"); 
        } 
    } 
} 

...

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'.

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

...

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:

Code Block
languagejava
titleHandling 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}}); 
}