| Developer: J2EE
Enabling Data Exchange in Ajax Applications
by Andrei Cioroianu
Learn how to transfer data between Ajax clients and Java servers with
XML and JavaScript Object Notation.
Published June 2006
The Ajax core API known as XMLHttpRequest is all about sending HTTP
requests for the sole purpose of exchanging data between Web browsers
and servers. The JavaScript code running in a Web page can use XMLHttpRequest
to submit the request parameters to a server-side script such as a Servlet
or a JSP page. The invoked Servlet/JSP sends back a response containing
data that is typically used to update the content viewed by the user without
refreshing the whole page. This approach has both performance and usability
advantages since the network traffic is reduced and the Web UI behaves
almost like a desktop GUI.
Developing such a user interface is not an easy task, however, because
you have to implement data exchange, validation, and processing, using
JavaScript on the client side and Java (or something equivalent) on the
server side. Nevertheless, there are many cases when the extra effort
needed to build an Ajax-based interface is worthwhile, considering the
benefits that it brings to your users.
In this article I’ll present the main methods for exchanging data
between Ajax clients and servers, comparing the traditional Web application
model with the Ajax model. I will also discuss techniques for handling
and processing the data on both ends.
First, you’ll learn how to encode the parameters of a request
object on the client side, using JavaScript. You can use the so called
URL encoding, which is the default encoding used by Web browsers, or you
can include the request parameters in an XML document. The server will
process the request and will return a response whose data must be encoded
too. The article discusses JavaScript Object Notation (JSON) and XML,
which are the main options for the response’s data format.
Significant portions of the article’s content are dedicated to
the XML-related APIs that you typically use in an Ajax application. On
the client side, the XML API offer is very limited, but it is sufficient.
In many cases XMLHttpRequest does everything you need, but you can also
parse XML documents and serialize DOM trees in the Web browser, using
JavaScript. On the server side, you have lots of APIs and frameworks that
allow you to process XML documents. The article shows how to implement
basic tasks, using some of the standard Java APIs for XML, which offer
support for XML Schema, XPath, DOM, and many other useful standards.
The goal of this article is to provide everything you need for implementing
data exchange in Ajax applications, using the best techniques and the
most recent APIs. The included sample code is organized in three packages: util,
model, and feed. The util package contains classes that provide methods
for XML parsing, Schema-based validation, XPath-based querying, DOM serialization
and JSON encoding. The model package contains an example data model that
can be initialized from an XML document and then converted to the JSON
format. You’ll also find a Schema example (used for XML validation)
in the model directory. The feed package has classes that simulate a data
feed, whose information is retrieved every 5 seconds with Ajax in order
to refresh a Web page. The article explains how to avoid a memory leak
in the Web browser, by aborting unfinished Ajax requests and deleting
the XMLHttpRequest objects after using them.
The web directory contains the JSP and JavaScript samples. If you look
into ajaxUtil.js, you’ll find utility functions for sending an Ajax
request, aborting a request, and handling HTTP errors. The ajaxUtil.js
file also provides JavaScript utilities for XML and URL encodings, XML
parsing, and DOM serialization. The ajaxCtrl.jsp file acts as an Ajax
controller, receiving every Ajax request, forwarding the parameters to
the data model or feed for processing, and then returning the Ajax response.
The rest of the Web files are samples that show how to use the utility
methods.
Building Requests on the Client-Side
The easiest way to send data to a Web server is to encode the request
parameters in a query string, which can be either appended to a URL or
included in the request’s body, depending on the used HTTP method.
If you need to send complex data structures, a better solution is to encode
the information within an XML document. I’ll describe both methods
in this section.
Encoding the Request Parameters. When developing a
traditional Web application you don’t have to worry about encoding
the form data because the Web browser performs this operation automatically
when the user submits the data. In the case of an Ajax application, however,
it’s your job to encode the request parameters. JavaScript provides
a very useful function named escape() that replaces any character that
cannot be part of a URL with %HH where HH is the hexadecimal code. For
example, any space character is replaced with %20.
The sample code download contains a utility function named buildQueryString()
that concatenates the parameters retrieved from an array, separating the
name and value of each parameter with = and placing & characters between
the name-value pairs:
function buildQueryString(params) {
var query = "";
for (var i = 0; i < params.length; i++) {
query += (i > 0 ? "&" : "")
+ escape(params[i].name) + "="
+ escape(params[i].value);
}
return query;
}
Let’s say you want to encode the following parameters:
var someParams = [
{ name: "name", value: "John Smith" },
{ name: "email", value: "john@company.com" },
{ name: "phone", value: "(123) 456 7890" }
];
The buildQueryString(someParams) call will produce the result included
below:
name=John%20Smith&email=john@company.com&phone=%28123%29%20456%207890
If you want to use the GET method, you must append the query string
to the URL after the ? character. When using POST, the Content-Type header
should be set to application/x-www-form-urlencoded with setRequestHeader()
and the query string must be passed to the send() method of XMLHttpRequest,
which sends the HTTP request to the server.
Creating an XML Document. Using strings to build the
elements with their attributes and data is the simplest way to create
an XML document with JavaScript. If you adopt this solution you need a
utility method for escaping the & , < , > , " and ' characters:
function escapeXML(content) {
if (content == undefined)
return "";
if (!content.length || !content.charAt)
content = new String(content);
var result = "";
var length = content.length;
for (var i = 0; i < length; i++) {
var ch = content.charAt(i);
switch (ch) {
case '&':
result += "&";
break;
case '<':
result += "<";
break;
case '>':
result += ">";
break;
case '"':
result += """;
break;
case '\'':
result += "'";
break;
default:
result += ch;
}
}
return result;
}
To make the task easier, you would also need other utility methods such
as this:
function attribute(name, value) {
return " " + name + "=\"" + escapeXML(value) + "\"";
}
The following example builds an XML document from an array of objects
that have three properties: symbol, shares and paidPrice:
function buildPortfolioDoc(stocks) {
var xml = "<portfolio>";
for (var i = 0; i < stocks.length; i++) {
var stock = stocks[i];
xml += "<stock ";
xml += attribute("symbol", stock.symbol);
xml += attribute("shares", stock.shares);
xml += attribute("paidPrice", stock.paidPrice);
xml += "/>";
}
xml += "</portfolio>";
return xml;
}
If you prefer using DOM, you can use the Web browser’s APIs for
parsing XML and serializing DOM trees. IE lets you create an empty document
with new ActiveXObject("Microsoft.XMLDOM"). Then you may use
the loadXML() or load() methods to parse the XML from a string or from
a URL respectively. In the IE case, each node has a property named xml
that lets you get an XML representation of the node and all its descendants.
Therefore, you can parse an XML string, modify the DOM tree and then serialize
the DOM back to XML.
The Firefox and Netscape browsers allow you to create an empty document
with document.implementation.createDocument(...). Then you can create
DOM nodes with createElement(), createTextNode(), createCDATASection()
and so on. The Mozilla browsers also provide two APIs named DOMParser
and XMLSerializer. The DOMParser API contains the parseFromStream() and
parseFromString() methods. The XMLSerializer class has the corresponding
methods for serializing a DOM tree: serializeToStream() and serializeToString().
The following function parses an XML string and returns the DOM Document:
function parse(xml) {
var dom;
try {
dom = new ActiveXObject("Microsoft.XMLDOM");
dom.async = false;
dom.loadXML(xml);
} catch (error) {
try {
var parser = new DOMParser();
dom = parser.parseFromString(xml, "text/xml");
delete parser;
} catch (error2) {
if (debug)
alert("XML parsing is not supported.");
}
}
return dom;
}
The next function serializes a DOM Node and all its descendents, returning
the XML as a string:
function serialize(dom) {
var xml = dom.xml;
if (xml == undefined) {
try {
var serializer = new XMLSerializer();
xml = serializer.serializeToString(dom);
delete serializer;
} catch (error) {
if (debug)
alert("DOM serialization is not supported.");
}
}
return xml;
}
You can also use XMLHttpRequest as a parser or serializer. The response
to an Ajax request is parsed automatically when it is received from the
server. You can access both the text version and the DOM tree via the
responseText and responseXML properties of XMLHttpRequest. In addition,
a DOM tree is serialized automatically when it’s passed to the send()
method.
Sending Requests. In a previous article
I presented the XMLHttpRequest API and a utility function named sendHttpRequest(),
which you can find in the ajaxUtil.js file from the downloadable samples.
This function takes four parameters (the HTTP method, the URL, an array
of parameters and a callback), creates a XMLHttpRequest object, sets its
properties and calls the send() method. If the callback parameter is provided,
the request is send asynchronously and the callback function is called
when the response is received. Otherwise, the request is send synchronously
and you may process the response as soon as sendHttpRequest() returns.
As you can see, you must make some important choices when using XMLHttpRequest
- the HTTP method you’ll use (GET or POST)
- the format used to encode the request parameters (XML and URL encoding
have been discussed earlier in this article)
- whether the call will be made synchronously (waiting for the response)
or asynchronously (using a callback)
- the format of the response, such as XML, XHTML, HTML or JavaScript
Object Notation (JSON), which will be discussed later in this article.
Let’s suppose you want to get some share prices from a data feed.
This information will be refreshed periodically without any user intervention.
In this case, the HTTP request should be sent asynchronously because the
user interface doesn’t have to be blocked while retrieving the information.
The request parameters are an array of symbols, which can be encoded in
the URL. You don’t want to send XML documents when making frequent
requests because the server could be overloaded. Since you are interested
only in the latest share prices, any previous request that hasn’t
been completed yet should be aborted:
var ctrlURL = "ajaxCtrl.jsp";
var feedRequest = null;
function sendInfoRequest(symbols, callback) {
if (feedRequest)
abortRequest(feedRequest);
var params = new Array();
for (var i = 0; i < symbols.length; i++)
params[i] = {
name: "symbol",
value: symbols[i]
};
feedRequest = sendHttpRequest(
"GET", ctrlURL, params, callback);
}
Before calling the abort() method of the request object, the abortRequest()
function, which you can find in the ajaxUtil.js file, sets the onreadystatechange
property to a callback that does nothing. In addition, it is very important
to delete the request object to avoid a memory leak:
function abortRequest(request) {
function doNothing() {
}
request.onreadystatechange = doNothing;
request.abort();
delete feedRequest;
}
Let’s consider another case. When transmitting the whole user
data to be saved in a database, you should send the request synchronously
because you probably don’t want to let the user modify the data
while it is being saved. In this case, the XML format is preferred because
it is usually easier to encode an object model in a document instead of
using lots of string parameters. In addition, the requests for saving
the data aren’t very frequent and the server can handle the load
without any problems. The XML document could be encoded as a parameter
so that you can access it in a JSP page, using the EL syntax ( ${param.xml}).
Here is the function that sends the model’s data encoded in an XML
document:
function sendSaveRequest(xml) {
var params = [ { name: "xml", value: xml } ];
var saveRequest = sendHttpRequest("POST", ctrlURL, params);
if (saveRequest)
delete saveRequest;
}
You can also send a request synchronously if you need to restore the
object model, retrieving the data from the server. In this case, the server
should return a JSON response so that you can easily convert it to a JavaScript
object tree with eval(loadRequest.responseText):
function sendLoadRequest() {
var model = null;
var loadRequest = sendHttpRequest("GET", ctrlURL);
if (loadRequest) {
model = eval(loadRequest.responseText);
delete loadRequest;
}
return model;
}
The following two sections describe what you typically do with the XML
documents on the server and how you can respond to the Ajax requests.
Processing Requests on the Server-Side
The Servlet/JSP container parses each HTTP request and creates a ServletRequest
instance, which lets you obtain the request parameters with getParameter()
/ getParameterValues() or the request’s body with getInputStream().
In a JSP page, you can also get the parameters, using the EL syntax (${param...}
and ${paramValues...}). Note that you can get a request parameter with
getParameter() or ${param...} only if the Ajax client uses an utility
function like buildQueryString() to encode the data, using the application/x-www-form-urlencoded
format, which was described in the previous section. If you pass an XML
document or a DOM tree to the send() method of XMLHttpRequest on the client
side, you would have to use the getInputStream() method of ServletRequest
on the server side.
Data Validation. A typical Web application does a
lot of data validation. Most of the possible errors are fairly simple
such as missing request parameters, number format errors, and so on. The
user usually causes these errors, forgetting to enter the value of a form
element or providing an invalid value. Web frameworks, such as JSF and
Oracle ADF Faces are very good at handling the user errors. In the case
of an Ajax application, these errors should be caught and handled on the
client side, using JavaScript. For example, you can verify if a numeric
value is invalid with isNaN(new Number(value)).
For security and reliability reasons, data should be revalidated on
the server side and you shouldn’t assume that XML requests have
been formatted correctly. XML Schema is a good tool for validating complex
requests on the server side. The sample code download contains a class
named XMLUtil that provides methods for loading and using schema documents.
The following code fragment shows how to initialize a SchemaFactory:
import javax.xml.*;
import javax.xml.validation.*;
...
protected static SchemaFactory schemaFactory;
static {
schemaFactory = SchemaFactory.newInstance(
XMLConstants.W3C_XML_SCHEMA_NS_URI);
schemaFactory.setErrorHandler(newErrorHandler());
}
The newErrorHandler() method returns a SAX error handler:
import org.xml.sax.*;
...
public static ErrorHandler newErrorHandler() {
return new ErrorHandler() {
public void warning(SAXParseException e)
throws SAXException {
Logger.global.warning(e.getMessage());
}
public void error(SAXParseException e)
throws SAXException {
throw e;
}
public void fatalError(SAXParseException e)
throws SAXException {
throw e;
}
};
}
You can use getResourceAsStream() to locate and load a XSD file from
a directory or JAR specified in the CLASSPATH:
public static InputStream getResourceAsStream(String name)
throws IOException {
InputStream in = XMLUtil.class.getResourceAsStream(name);
if (in == null)
throw new FileNotFoundException(name);
return in;
}
Then, the SchemaFactory instance is used to obtain a Schema object with
the newSchema() method:
import javax.xml.validation.*;
...
public static Schema newSchema(String name)
throws IOException, SAXException {
Schema schema;
InputStream in = getResourceAsStream(name);
try {
schema = schemaFactory.newSchema(new StreamSource(in));
} finally {
in.close();
}
return schema;
}
You can also create an Oracle XMLSchema object with the following method:
import oracle.xml.parser.schema.XMLSchema;
import oracle.xml.parser.schema.XSDBuilder;
...
public static XMLSchema newOracleSchema(String name)
throws IOException, SAXException {
XMLSchema schema;
InputStream in = getResourceAsStream(name);
try {
XSDBuilder builder = new XSDBuilder();
schema = builder.build(new InputSource(in));
} catch (Exception e) {
throw new SAXException(e);
} finally {
in.close();
}
return schema;
}
The next thing you need to create is a DocumentBuilderFactory. The setSchema()
method, which was defined by JAXP 1.2, may throw an UnsupportedOperationException
if a JAXP 1.1 implementation is found in CLASSPATH, replacing the JAXP
1.2 implementation of Java SE 5.0. In this case, you can still create
a schema object using newOracleSchema() and you can set it with the setAttribute()
method:
import javax.xml.parsers.*;
import oracle.xml.jaxp.JXDocumentBuilderFactory;
...
public static DocumentBuilderFactory newParserFactory(
String schemaName) throws IOException, SAXException {
DocumentBuilderFactory parserFactory
= DocumentBuilderFactory.newInstance();
try {
parserFactory.setSchema(newSchema(schemaName));
} catch (UnsupportedOperationException e) {
if (parserFactory instanceof JXDocumentBuilderFactory) {
parserFactory.setAttribute(
JXDocumentBuilderFactory.SCHEMA_OBJECT,
newOracleSchema(schemaName));
}
}
return parserFactory;
}
Then, you create a DocumentBuilder object that can be used to validate
and parse an XML document:
import javax.xml.parsers.*;
...
public static DocumentBuilder newParser(
DocumentBuilderFactory parserFactory)
throws ParserConfigurationException {
DocumentBuilder parser = parserFactory.newDocumentBuilder();
parser.setErrorHandler(newErrorHandler());
return parser;
};
Let’s say you want to validate an XML document against the portfolio.xsd
schema example:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="portfolio" type="portfolioType"/>
<xsd:complexType name="portfolioType">
<xsd:sequence>
<xsd:element name="stock"
minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:attribute name="symbol"
type="xsd:string" use="required"/>
<xsd:attribute name="shares"
type="xsd:positiveInteger" use="required"/>
<xsd:attribute name="paidPrice"
type="xsd:decimal" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
The parsePortfolioDoc() method of the DataModel class uses XMLUtil to
validate and parse the xml parameter, returning a DOM Document:
private static final String SCHEMA_NAME
= "/ajaxapp/model/portfolio.xsd";
private static DocumentBuilderFactory parserFactory;
private static Document parsePortfolioDoc(String xml)
throws IOException, SAXException,
ParserConfigurationException {
synchronized (DataModel.class) {
if (parserFactory == null)
parserFactory = XMLUtil.newParserFactory(SCHEMA_NAME);
}
DocumentBuilder parser = XMLUtil.newParser(parserFactory);
InputSource in = new InputSource(new StringReader(xml));
return parser.parse(in);
}
Now that you have a DOM tree, the next thing to do is getting the data
you need form the DOM nodes.
Extracting the Needed Information. You can navigate
a DOM tree, using the DOM API, or you can use a query language such as
XQuery or XPath. Java provides a standard API for XPath, which is used
next. The XMLUtil class creates an XPathFactory that has a newXPath()
method:
import javax.xml.xpath.*;
...
protected static XPathFactory xpathFactory;
static {
xpathFactory = XPathFactory.newInstance();
}
public static XPath newXPath() {
return xpathFactory.newXPath();
}
The following methods evaluate XPath expressions in a given context,
returning the resulted values:
import javax.xml.xpath.*;
import org.w3c.dom.*;
...
public static String evalToString(String expression,
Object context) throws XPathExpressionException {
return (String) newXPath().evaluate(expression, context,
XPathConstants.STRING);
}
public static boolean evalToBoolean(String expression,
Object context) throws XPathExpressionException {
return ((Boolean) newXPath().evaluate(expression, context,
XPathConstants.BOOLEAN)).booleanValue();
}
public static double evalToNumber(String expression,
Object context) throws XPathExpressionException {
return ((Double) newXPath().evaluate(expression, context,
XPathConstants.NUMBER)).doubleValue();
}
public static Node evalToNode(String expression,
Object context) throws XPathExpressionException {
return (Node) newXPath().evaluate(expression, context,
XPathConstants.NODE);
}
public static NodeList evalToNodeList(String expression,
Object context) throws XPathExpressionException {
return (NodeList) newXPath().evaluate(expression, context,
XPathConstants.NODESET);
}
The setData() method of DataModel uses the XPath evaluation methods
to extract the information from a portfolio XML document:
public synchronized void setData(String xml)
throws IOException, SAXException,
ParserConfigurationException,
XPathExpressionException {
try {
ArrayList stockList
= new ArrayList();
Document doc = parsePortfolioDoc(xml);
NodeList nodeList = XMLUtil.evalToNodeList(
"/portfolio/stock", doc);
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
StockBean stock = new StockBean();
stock.setSymbol(
XMLUtil.evalToString("@symbol", node));
stock.setShares(
(int) XMLUtil.evalToNumber("@shares", node));
stock.setPaidPrice(
XMLUtil.evalToNumber("@paidPrice", node));
stockList.add(stock);
}
this.stockList = stockList;
} catch (Exception e) {
Logger.global.logp(Level.SEVERE, "DataModel", "setData",
e.getMessage(), e);
}
}
Once you have the data in an object model on the server side, you can
process it according to your application’s requirement. Then, you
must respond to the Ajax request.
Generating Responses on the Server-Side
Returning HTML as the response to an Ajax request is the easiest solution
because you can use the JSP syntax to build the markup and the Ajax client
just has to insert the HTML somewhere in the page, using the innerHTML
property of a <div> or <span> element. It is much more efficient,
however, to return only the data without any presentation markup to the
Ajax client. You can use an XML format or JSON.
Producing XML Responses. Java EE provides many options
for creating XML documents, which can be generated with JSP, created from
object trees with JAXB, or produced with javax.xml.transform.Transformer
as in the following example that serializes a DOM tree:
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
...
public static TransformerFactory serializerFctory;
static {
serializerFctory = TransformerFactory.newInstance();
}
public static void serialize(Node node, OutputStream out)
throws TransformerException {
Transformer serializer = serializerFctory.newTransformer();
Properties serializerProps = new Properties();
serializerProps.put(OutputKeys.METHOD, "xml");
serializer.setOutputProperties(serializerProps);
Source source = new DOMSource(node);
Result result = new StreamResult(out);
serializer.transform(source, result);
}
With so many standard options and open source frameworks for producing
XML on the server side, the only problem you have is choosing the one
that is right for you. On the client side, however, the situation is very
different since DOM is the only option for parsing the XML. Some browsers
also provide support for XPath and XSLT.
In a previous Ajax article,
you learned how to produce the XML with JSP and then process it on the
client side with JavaScript and DOM. Another solution is to use JSON instead
of XML as a data format for the response to the Ajax request. As mentioned
earlier, a JSON string can be converted to a JavaScript object tree with
the eval() function. This is much easier than extracting the information
from a DOM tree with JavaScript. All you need is a good utility class
for producing JSON on the server side.
JSON Encoding. The JSONEncoder class provides methods
for encoding literals, objects and arrays. The result is stored in a java.lang.StringBuilder:
package ajaxapp.util;
public class JSONEncoder {
private StringBuilder buf;
public JSONEncoder() {
buf = new StringBuilder();
}
...
}
The character() method encodes a single character:
public void character(char ch) {
switch (ch) {
case '\'':
case '\"':
case '\\':
buf.append('\\');
buf.append(ch);
break;
case '\t':
buf.append('\\');
buf.append('t');
break;
case '\r':
buf.append('\\');
buf.append('r');
break;
case '\n':
buf.append('\\');
buf.append('n');
break;
default:
if (ch >= 32 && ch < 128)
buf.append(ch);
else {
buf.append('\\');
buf.append('u');
for (int j = 12; j >= 0; j-=4) {
int k = (((int) ch) >> j) & 0x0f;
int c = k < 10 ? '0' + k : 'a' + k - 10;
buf.append((char) c);
}
}
}
}
The string() method encodes a whole string:
public void string(String str) {
int length = str.length();
for (int i = 0; i < length; i++)
character(str.charAt(i));
}
The literal() method encodes a JavaScript literal:
public void literal(Object value) {
if (value instanceof String) {
buf.append('"');
string((String) value);
buf.append('"');
} else if (value instanceof Character) {
buf.append('\'');
character(((Character) value).charValue());
buf.append('\'');
} else
buf.append(value.toString());
}
The comma() method appends a comma character:
private void comma() {
buf.append(',');
}
The deleteLastComma() method removes the last comma character if it
is found at the end of the buffer:
private void deleteLastComma() {
if (buf.length() > 0)
if (buf.charAt(buf.length()-1) == ',')
buf.deleteCharAt(buf.length()-1);
}
The startObject() method appends a { character, staring a JavaScript
object:
public void startObject() {
buf.append('{');
}
The property() method encodes a JavaScript property:
public void property(String name, Object value) {
buf.append(name);
buf.append(':');
literal(value);
comma();
}
The endObject() method appends a } character, ending a JavaScript object:
public void endObject() {
deleteLastComma();
buf.append('}');
comma();
}
The startArray() method appends a [ character, starting a JavaScript
array:
public void startArray() {
buf.append('[');
}
The element() method encodes an element of a JavaScript array:
public void element(Object value) {
literal(value);
comma();
}
The endArray() method appends a ] character, ending a JavaScript array:
public void endArray() {
deleteLastComma();
buf.append(']');
comma();
}
The toString() method returns the JSON string:
public String toString() {
deleteLastComma();
return buf.toString();
}
The clear() method clears the buffer:
public void clear() {
buf.setLength(0);
}
The JSONEncoder class is used by DataModel to encode the data that it
maintains:
public synchronized String getData() {
JSONEncoder json = new JSONEncoder();
json.startArray();
for (int i = 0; i < stockList.size(); i++) {
StockBean stock = stockList.get(i);
json.startObject();
json.property("symbol", stock.getSymbol());
json.property("shares", stock.getShares());
json.property("paidPrice", stock.getPaidPrice());
json.endObject();
}
json.endArray();
return json.toString();
}
The ajaxCtrl.jsp page sets the model’s data if the xml request
parameter is present. Otherwise, it outputs the JSON string returned by
getData(), using the ${dataModel.data} EL expression:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
...
<jsp:useBean id="dataModel" scope="session"
class="ajaxapp.model.DataModel" />
<c:choose>
...
<c:when test="${!empty param.xml}">
<c:set target="${dataModel}"
property="data"
value="${param.xml}" />
</c:when>
<c:otherwise>
${dataModel.data}
</c:otherwise>
</c:choose>
The job is not finished since the Ajax client must process the JSON
data.
Handling Responses on the Client-Side
In a typical Web application, you generate the content on the server-side,
using JSP, Web frameworks and tag libraries. Ajax applications fit very
well into this profile since Web frameworks such as JavaServer Faces and
Oracle ADF Faces can help you a lot when building Ajax applications. There
is however, a significant difference between Ajax and non-Ajax applications.
When using Ajax, you must handle data on the client side and generate
pieces of content dynamically with JavaScript in order to present the
data to the user.
If you use the JSON format for the data transfer, it is very easy to
convert the text to an object tree, using the eval() function provided
by JavaScript. If you prefer using XML, you’ll have more work to
do, but this format has its own advantages. For example, many types of
clients can consume XML while JSON is easy to parse only in a JavaScript
environment. In addition, errors can be spotted and fixed much faster
in the XML’s case, which reduces the debugging time.
Accessing DOM Trees with JavaScript. The DOM API for
JavaScript is very similar to Java’s org.w3c.dom package. The main
difference is the direct access to properties in the JavaScript’s
case while Java keeps the properties private, letting you access them
with get and set methods. For example, you can get a document’s
root element with dom.documentElement.
DOM is somehow a low-level API, giving you access to the structure of
the parsed documents. For example, you want to ignore the comments in
most cases and you probably wouldn’t expect to have adjacent text
nodes. Let’s take a simple example:
var xml = "<element>da<!--comment-->ta&"
+ "<![CDATA[cdata</element>";
You could parse the above XML string with a utility function that was
presented earlier:
var dom = parse(xml);
You can find the code of the parse() function in ajaxUtil.js. In this
example, it returns a DOM tree whose root element contains a text node,
followed by a comment, another text node and a character data node. If
you want the contained text without the comment, you have to iterate over
the element’s children, concatenating the values of the text and
character data nodes, whose types are 3 and 4 respectively:
var element = dom.documentElement;
var childNodes = element.childNodes;
var text = "";
for (var i = 0; i < childNodes.length; i++)
if (childNodes[i].nodeValue) {
var type = childNodes[i].nodeType;
if (type == 3 || type == 4)
text += childNodes[i].nodeValue;
}
When using DOM, you should build a small set of utility functions so
that you don’t have to deal with low-level details like those described
above.
Generating Dynamic Content with JavaScript. Web browsers
give you access to the DOM structure of the Web page via the document
object. For example, you can locate very easily an element with document.getElementById(...).
You can also create new elements and text nodes, which can be inserted
into the existing document. It is much simpler, however, to build the
HTML by concatenating strings like in the following example:
function updateInfo(request) {
var shares = eval(request.responseText);
var table = "<table border=1 cellpadding=5>";
table += "<tr>";
table += "<th>Symbol</th>";
table += "<th>Trend</th>";
table += "<th>Last Price</th>";
table += "</tr>";
for (var i = 0; i < shares.length; i++) {
var share = shares[i];
var symbol = escapeXML(share.symbol)
var trend = share.trend > 0 ? "+" : "-";
var lastPrice = new Number(share.lastPrice).toFixed(2);
table += "<tr>";
table += "<td>" + symbol + "</td>";
table += "<td>" + trend + "</td>";
table += "<td>" + lastPrice + "</td>";
table += "</tr>";
}
table += "</table>";
document.getElementById("table").innerHTML = table;
}
By setting the innerHTML property of the object returned by getElementById(),
the generated HTML can be inserted into an empty <div> element like
this:
<div id="table">
</div>
This article’s samples
use the updateInfo() function as a callback for handling the responses
to the Ajax requests that are sent to the server with sendInfoRequest
from the ajaxLogic.js file. If you want to update the information every
5 seconds, you can use the setInterval() function of JavaScript:
var symbols = [ ... ];
setInterval("sendInfoRequest(symbols, updateInfo)", 5000);
A class named DataFeed simulates a feed on the server side. The ajaxCtrl.jsp
page invokes the feed’s getData() method, returning the response
as a the JSON string. On the client side, the updateInfo() function parses
the JSON string with eval(request.responseText) as you’ve seen in
the code sample presented above.
Conclusion
In this article, you’ve learned how to transfer data from an Ajax
client to a server, using the URL encoding and XML formats. Then, you’ve
seen how to validate the data with XML Schema, extract the needed information
with XPath and produce XML or JSON responses on the server side. Finally,
you’ve found out how to handle the XML and JSON responses with JavaScript
and how to generate dynamic content that is used to update the Web page
in the browser.
Andrei Cioroianu (devtools@devsphere.com)
is the founder of Devsphere,
a provider of development, integration, and consulting services. Cioroianu
has written many Java articles published by Oracle Technology Network, ONJava,
JavaWorld, and Java
Developer’s Journal. He also coauthored the books Java XML
Programmer’s Reference and Professional Java XML (both
from Wrox Press).
|