The sole purpose of the Ajax core API (so-called XMLHttpRequest) is to send HTTP requests to exchange data between the Web browser and the server. 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 JSP page. The calling Servlet/JSP will send back a response that contains data typically used to update the user's view without refreshing the entire page. This approach offers unique advantages in terms of performance and usability because network traffic is reduced and the Web UI is almost as usable as the desktop GUI.
However, developing such a user interface is not simple, as you must implement data exchange, validation, and processing using JavaScript on the client side and Java (or equivalent language) on the server side. However, in many cases, the extra effort to build an Ajax-based interface is worth it, considering the benefits that will be gained.
In this article, I'll introduce one of the primary methods for transferring data between Ajax clients and servers, and compare the differences between the traditional Web application model and the Ajax model. In addition, the article will also explore techniques for processing data on the server side and client side.
First, you'll learn how to use JavaScript to encode the parameters of a request object on the client side. You can use so-called URL encoding (the default encoding used by web browsers), or you can include the request parameters in the XML document. The server will process the request and return a response whose data must also be encoded. This article explores JavaScript Object Notation (JSON) and XML, which are the main response data format options.
Most of this article will focus on XML-related APIs commonly used in Ajax applications. On the client side, the XML API is very limited but sufficient. In most cases, all necessary operations can be accomplished using XMLHttpRequest. Additionally, JavaScript can be used to parse XML documents and serialize DOM trees in a Web browser. On the server side, there are many APIs and frameworks available for processing XML documents. This article describes how to perform basic tasks using the standard Java API for XML, which supports XML Schema, XPath, DOM, and many other standards.
Through this article, you can learn about the best techniques and the latest APIs for data exchange in Ajax applications. The sample code involved is in three packages: util, model, and feed. Classes in the util package provide methods for XML parsing, schema-based validation, XPath-based querying, DOM serialization, and JSON encoding. The model package contains sample data models that can be initialized from XML documents and then converted to JSON format. There is also a Schema example in the model directory that can be used for XML validation. The classes in the feed package can be used to simulate a data feed, which retrieves information via Ajax every 5 seconds to refresh the Web page. This article explains how to avoid memory leaks in your Web browser by terminating outstanding Ajax requests and deleting the XMLHttpRequest object when you are finished using it.
JSP and JavaScript samples are included in the web directory. ajaxUtil.js contains utility functions for sending Ajax requests, terminating requests, and handling HTTP errors. This file also provides JavaScript utilities for XML and URL encoding, XML parsing, and DOM serialization. The ajaxCtrl.jsp file acts as an Ajax controller, receiving each Ajax request, forwarding parameters to the data model, or providing processing, and then returning an Ajax response. The rest of the Web files are examples that demonstrate how to use this practical method.
The simplest wayto construct a request on the client side
to send data to a Web server is to encode the request as a query string, which can either be appended to the URL or included in the request body, depending on the HTTP method used. If you need to send complex data structures, a better solution is to encode the information in an XML document. I'll cover both methods in this section.
Encode request parameters. When developing traditional Web applications, you don't need to worry about encoding form data because the Web browser does this automatically when the user submits the data. However, in an Ajax application, you must encode the request parameters yourself. JavaScript provides a very useful function escape(), which replaces any characters that cannot be part of the URL with %HH (where HH is the hexadecimal code). For example, any whitespace characters are replaced with %20.
The sample code download provides a utility function, buildQueryString(), which concatenates parameters retrieved from an array, separating the name and value of each parameter by = and placing the & character between each name-value pair :
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;
}
Suppose you want to encode the following parameters:
var someParams = [
{ name: "name", value: "John Smith" },
{ name: "email", value: " [email protected] " },
{ name:"phone", value: "(123) 456 7890" }
];
A call to buildQueryString(someParams) will produce results containing:
name=John%20Smith&[email protected]&phone=%28123%29%20456%207890
If you wish to use the GET method, you must append the query to the URL after the ? character. When using POST, the Content-Type header should be set to application/x-www-form-urlencoded via setRequestHeader(), and the query string must be passed to the send() method of XMLHttpRequest, which will send the HTTP request to server.
Create an XML document. Using strings to build elements from their attributes and data is the simplest way to create XML documents with JavaScript. If you adopt this solution, you will need a utility method to escape 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 simpler, some additional utility methods are required, such as:
function attribute(name, value) {
return " " + name + "="" + escapeXML(value) + """;
}
The following example builds an XML document from an array of objects with 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 working with DOM, you can use your web browser's API to parse XML and serialize the DOM tree. With IE, you can create an empty document with a new ActiveXObject("Microsoft.XMLDOM"). The XML can then be parsed from a string or URL using the loadXML() or load() methods respectively. In the case of IE, each node has an attribute called xml that allows you to obtain an XML representation of the node and all of its child nodes. Therefore, you can parse an XML string, modify the DOM tree, and then serialize the DOM back to XML.
Firefox and Netscape browsers allow you to create an empty document using document.implementation.createDocument(...). DOM nodes can then be created using createElement(), createTextNode(), createCDATASection(), etc. Mozilla browser also provides two APIs named DOMParser and XMLSerializer. The DOMParser API contains parseFromStream() and parseFromString() methods. The XMLSerializer class has corresponding methods for serializing the DOM tree: serializeToStream() and serializeToString().
The following function parses an XML string and returns a 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 second function serializes a DOM node and all its child nodes, 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. After a response to an Ajax request is received from the server, the response is automatically parsed. The text version and DOM tree can be accessed through the responseText and responseXML properties of XMLHttpRequest respectively. Additionally, the DOM tree is automatically serialized when passed to the send() method.
Send a request. In a previous article, I introduced the XMLHttpRequest API and a utility function, sendHttpRequest(), which you can find in the ajaxUtil.js file in the sample provided for download. This function takes four parameters (HTTP method, URL, a parameter array, and a callback), creates an XMLHttpRequest object, sets its properties, and calls the send() method. If a callback parameter is provided, the request is sent asynchronously and the callback function is called after the response is received. Otherwise, the request is sent synchronously and you can handle the response as soon as sendHttpRequest() returns.
As you can see, you have to make some important choices when using XMLHttpRequest
. Which HTTP method is to be used (GET or POST)?
The format used to encode request parameters (XML and URL encoding were discussed earlier in this article)
Whether to make the call synchronously (waiting for a response) or asynchronously (using a callback) The format of the response, such as XML, XHTML, HTML, or JavaScript Object Notation (JSON) (discussed later in this article). Let's say you want to get some stock price information from a data feed and refresh the information periodically without user intervention. In this case, the HTTP request should be sent asynchronously so that the user interface is not blocked while the information is retrieved. The request parameter is an array of symbols that can be encoded in the URL. Because the server may be overloaded, you do not want to send XML documents when frequent requests are made. Since you are only interested in the latest stock price, any outstanding previous requests should be terminated:
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 request object's abort() method, the abortRequest() function (found in the ajaxUtil.js file) sets the onreadystatechange property to a callback that does nothing. Additionally, it is critical to delete the request object to avoid memory leaks:
function abortRequest(request) {
function doNothing() {
}
request.onreadystatechange = doNothing;
request.abort();
delete feedRequest;
}
Let's consider another case: when transferring the entire user data to be saved in the database, the request should be sent synchronously because you probably don't want the user to modify it while saving this data is in progress. In this case, the XML format is preferred because it is often simpler to encode the object model in the document than to use many string parameters. Additionally, requests to save data are infrequent and the server handles the load without any problems. An XML document can be encoded as a parameter so that you can access it in a JSP page using EL syntax (${param.xml}). Here is the function that sends model 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;
}
If you need to restore the object model, you can also send a request synchronously to retrieve 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 using 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 operations typically performed on XML documents on the server and how to respond to Ajax requests.
Handling requests on the server side
The Servlet/JSP container analyzes each HTTP request and creates a ServletRequest instance, which allows you to get the request parameters through getParameter() / getParameterValues() or the request body through getInputStream(). In JSP pages, these parameters can also be obtained using EL syntax (${param...} and ${paramValues...}). Note that passing getParameter is only possible if the Ajax client uses a utility function like buildQueryString() to encode the data in the application/x-www-form-urlencoded format (described in the previous section of this article). () or ${param...} to get request parameters. If you pass an XML document or DOM tree to the send() method of XMLHttpRequest on the client side, you must use the getInputStream() method of ServletRequest on the server side.
Data validation. A typical web application performs many data validation operations. Most possible errors are fairly simple, such as missing request parameters, incorrect number formats, and so on. These errors are usually caused by the user forgetting to enter a value for a form element or providing an invalid value. Web frameworks such as JSF and Oracle ADF Faces are very good at handling these user errors. In Ajax applications, these errors can be caught and handled on the client side using JavaScript. For example, you can use isNaN(new Number(value)) to verify that a numeric value is not valid.
For security and reliability reasons, data should be re-validated on the server side, and XML requests should not be assumed to be well-formatted. XML schemas are a useful tool for validating complex requests on the server side. The sample code download includes a class called XMLUtil that provides methods for loading and using schema documents. The following code snippet shows how to initialize 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 find and load an XSD file in a directory or a 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, use the SchemaFactory instance to obtain the Schema object through 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 using:
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;
}
Next, you need to create a DocumentBuilderFactory. If a JAXP 1.1 implementation is found in the CLASSPATH, the setSchema() method defined by JAXP 1.2 may throw an UnsupportedOperationException, in which case the JAXP 1.1 implementation needs to be replaced with the JAXP 1.2 implementation of Java SE 5.0. In this case, you can still create a schema object using newOracleSchema() and set it via 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, create a DocumentBuilder object and use it to validate and parse the XML document:
import javax.xml.parsers.*;
...
public static DocumentBuilder newParser(
DocumentBuilderFactory parserFactory)
throws ParserConfigurationException {
DocumentBuilder parser = parserFactory.newDocumentBuilder();
parser.setErrorHandler(newErrorHandler());
return parser;
};
Suppose 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 parameters and return 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, you need to get the data needed to form the DOM nodes.
Extract the required information. You can use the DOM API or a query language (such as XQuery or XPath) to browse the DOM tree. Java provides a standard API for XPath, which will be used later. The XMLUtil class creates an XPathFactory with 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 an XPath expression in a given context and return the resulting value:
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 DataModel's setData() method uses the XPath solver method to extract information from the combined 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 the data is available in the server-side data model, it can be processed according to the requirements of the application. Then, you must respond to the Ajax request.
Generating the response on the server side
Returning HTML as a response to an Ajax request is the simplest solution because you can build the markup using JSP syntax and the Ajax client simply uses the <div> or <span> element. The innerHTML property inserts HTML somewhere on the page. However, it is more efficient to return data to the Ajax client without any presentation markup. You can use XML format or JSON.
Generate XML response. Java EE provides many options for creating XML documents: generated via JSP, created from an object tree via JAXB, or generated using javax.xml.transform. The transformer in the following example will serialize 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);
}
There are so many standard options and development source frameworks for generating XML on the server side that the only thing you have to do is choose the one that works for you. However, on the client, the situation is very different since XML can only be parsed using the DOM. Some browsers also support XPath and XSLT.
In previous Ajax articles, you learned how to generate XML via JSP and then parse it on the client using JavaScript and the DOM. Another solution is to use JSON instead of XML as the data format for responding to Ajax requests. As mentioned earlier, a JSON string can be converted into a JavaScript object tree using the eval() function. This is simpler than using JavaScript to extract information from the DOM tree. All you need is a good utility class that generates JSON on the server side.
JSON encoding. The JSONEncoder class provides methods for encoding literals, objects, and arrays. The results are stored in 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 :
buf.append( \ );
buf.append( );
break;
case
:
buf.append( \ );
buf.append(
);
break;
case
:
buf.append( \ );
buf.append(
);
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 ? + k :a + k - 10;
buf.append((char) c);
}
}
}
}
The string() method encodes the entire string:
public void string(String str) {
int length = str.length();
for (int i = 0; i < length; i++)
character(str.charAt(i));
}
literal() method encodes 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 will remove the last comma character at the end of the buffer (if any):
private void deleteLastComma() {
if (buf. length() > 0)
if (buf.charAt(buf.length()-1) == ,)
buf.deleteCharAt(buf.length()-1);
}
The startObject() method appends a { character to indicate the beginning of a JavaScript object:
public void startObject() {
buf.append({);
}
The property() method encodes JavaScript properties:
public void property(String name, Object value) {
buf.append(name);
buf.append(:);
literal(value);
comma();
}
The endObject() method appends a } character to indicate the end of a JavaScript object:
public void endObject() {
deleteLastComma();
buf.append(});
comma();
}
The startArray() method appends a [ character to indicate the beginning of a JavaScript array:
public void startArray() {
buf.append([);
}
The element() method encodes the elements of a JavaScript array:
public void element(Object value) {
literal(value);
comma();
}
The endArray() method appends a ] character to indicate the end of a JavaScript array:
public void endArray() {
deleteLastComma();
buf.append(]);
comma();
}
toString() method returns JSON string:
public String toString() {
deleteLastComma();
return buf.toString();
}
clear() method clears the buffer:
public void clear() {
buf.setLength(0);
}
DataModel uses the JSONEncoder class to encode the data 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();
}
If xml request parameters are provided, the ajaxCtrl.jsp page will set the model's data. Otherwise, the page will use the ${dataModel.data} EL expression to output the JSON string returned by getData():
< %@ 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>
This job is not complete because the Ajax client must process the JSON data.
Processing responses on the client side
In a typical Web application, you generate content on the server side using JSPs, Web frameworks, and tag libraries. Ajax applications are ideal for this situation because Web frameworks such as JavaServer Faces and Oracle ADF Faces are very useful in building Ajax applications. However, there are still significant differences between Ajax and non-Ajax applications. When using Ajax, you must process the data on the client side and use JavaScript to dynamically generate content to provide the data to the user.
If you are using the JSON format for data conversion, it is very easy to convert text into a tree of objects using the eval() function provided by JavaScript. If you prefer to use XML, there are a lot of other things you need to do, but this format also has its own advantages. For example, XML can be used by many types of clients, while JSON is only easy to parse in a JavaScript environment. In addition, when using XML, errors can be found and fixed faster, thus reducing debugging time.
Use JavaScript to access the DOM tree. JavaScript's DOM API is very similar to Java's org.w3c.dom package. The main difference is access to properties. In JavaScript you can access properties directly, whereas Java treats properties as private and you need to access them through get and set methods. For example, you can obtain the root element of a document through dom.documentElement.
The DOM is a low-level API that provides access to the structure of the parsed document. For example, you want to ignore comments in most cases, and may not want to have adjacent text nodes. Consider the following simple example:
var xml = "< element>da< !--comment-->ta&"
+ "< ![CDATA[cdata< /element>";
You can parse the above XML string using the utility function introduced earlier:
var dom = parse(xml);
You can find the code for the parse() function in ajaxUtil.js; in this case, the function 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 to include text without comments, you must iterate over the element's child elements, concatenating the values of the text and character data nodes (which have types 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 working with the DOM, you should build a small set of utility functions to avoid dealing with these low-level details.
Use JavaScript to generate dynamic content. Web browsers allow you to access the DOM structure of a Web page through document objects. For example, you can use document.getElementById(...) to find an element very easily. You can also create new elements and text nodes that can be inserted into existing documents. However, it is simpler to build HTML by concatenating strings as shown below:
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;
}
The generated HTML can be inserted
into an empty element by setting the innerHTML property of the object returned by getElementById(), for example:
<div id="table">
< /div>
The example in this article uses the updateInfo() function as a callback to handle responses to Ajax requests sent to the server through sendInfoRequest in the ajaxLogic.js file. If you want to update information every 5 seconds, you can use JavaScript's setInterval() function:
var symbols = [ ... ];
setInterval("sendInfoRequest(symbols, updateInfo)", 5000);
A class called DataFeed simulates a server-side feed. The ajaxCtrl.jsp page calls the feed's getData() method, returning the response as a JSON string. On the client side, the updateInfo() function parses the JSON string using eval(request.responseText), as shown in the preceding code example.