Developer: J2EE

 

Developing Smart Web UIs with Ajax, JSF, and ADF Faces

by Andrei Cioroianu

Learn how to build rich Web applications using Ajax techniques along with JavaServer Faces and ADF Faces.

Published February 2006

 

Downloads for this article:
 Sample code
 Oracle JDeveloper 10g Release 3 or later

Published February 2006

Asynchronous JavaScript and XML (Ajax) is a recent term for a group of mature APIs, technologies, and standards that can be used with all major browsers. Besides HTML/XHTML, CSS, JavaScript, XML, and DOM, the so-called Ajax applications use an API named XMLHttpRequest, which lets you request a URL without refreshing the current page of the Web browser. What makes this API particularly interesting is the ability to send the HTTP request asynchronously, meaning that the user doesn't have to wait for the response. You provide a callback function (written in JavaScript) that is invoked when the browser gets the response, which can be an XML document whose content is accessible with the help of DOM. This combination of features allows you to build highly interactive Web applications, such as Google Maps or Google Suggest.

 

Even if the Ajax code runs on the client side, you still need a server-side technology for your Web pages. JavaServer Faces (JSF) and ADF Faces (which Oracle has donated to the Apache Foundation as open source) are excellent candidates for building Ajax-based user interfaces because these two frameworks support JavaScript and can handle the form data on the server.

 

In this article, I will explain how to use Ajax with the existing components of the JSF and ADF Faces frameworks and how to create Web applications based on Ajax, JSF, and ADF Faces with the help of Oracle JDeveloper 10g. The article starts with a brief discussion of a Web application that is used to demonstrate the Ajax-ADF Faces and Ajax-JSF integration techniques. (There is nothing special about the backing beans of this application, but you have to take a quick look at them so that you can understand the article's samples.) Then, you'll learn how to build a simple Ajax controller and JSP pages that generate the response XML for the Ajax requests. After that, you'll find a full overview of the XMLHttpRequest API with code samples and usage patterns. A significant part of the article focuses on building the Web user interface with JDeveloper. Then, you'll find more details about the JavaScript code that runs on the client side. You'll learn how to use JavaScript with JSF and ADF Faces, how to invoke the Ajax controller, and how to implement an Ajax callback.

Developing the Application Logic

Suppose you need a smart Web mail client that should remember the email addresses of your correspondents. You might start a message with something like "Hi John," or "Hello John," or simply "John." If you've already sent an email to John, the application should remember John's email address and should automatically fill in the TO, CC, and BCC fields. You might also want to be able to open a sent message so that you can review it or resend it. Therefore, the user interface should have a drop-down list containing the sent messages. Here is the application's screen:

 

figure 1

Backing Beans and JSF Configuration. The example application of this article needs a couple of JavaBean components: MessageBean and SessionBean. The former bean encapsulates the data of a single message. The latter bean is kept in the JSP session scope and maintains the list of sent messages. Each MessageBean instance keeps a reference to a SessionBean object that is used in a JSF action method named sendAction():

// MessageBean.java

package jsfajax;

import java.io.Serializable;

public class MessageBean implements Serializable {

    private SessionBean ssnBean;
    private String msgSubject, msgContent, helloName;
    private String toAddr, ccAddr, bccAddr;

    public SessionBean getSsnBean() {
        return ssnBean;
    }

    public void setSsnBean(SessionBean ssnBean) {
        this.ssnBean = ssnBean;
    }

    public String getMsgSubject() {
        return msgSubject;
    }

    public void setMsgSubject(String msgSubject) {
        this.msgSubject = msgSubject;
    }

    ...

    public String sendAction() {
        ssnBean.sendMsg(this);
        return "sent";
    }

}
The SessionBean class provides the sendMsg() method that adds its msgBean parameter at the end of the list of sent messages ( msgList). This method also creates a JSF SelectItem that is added at the beginning of another list ( itemList) so that the user interface can show the sent messages in reverse order. The SessionBean class has two methods that look for a message bean by index or by the helloName property:
// SessionBean.java

package jsfajax;

import javax.faces.model.SelectItem;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class SessionBean implements Serializable {

    private List msgList;
    private List itemList;
    
    public SessionBean() {
        msgList = new ArrayList();
        itemList = new ArrayList();
    }
    
    public synchronized Object[] getMsgItems() {
        return itemList.toArray();
    }

    public synchronized void sendMsg(MessageBean msgBean) {
        msgList.add(msgBean);
        itemList.add(0, new SelectItem(
                msgBean.getMsgSubject()));
    }

    public synchronized MessageBean findMsgByIndex(int msgIndex) {
        if (msgIndex >= 0 && msgIndex < msgList.size())
            return (MessageBean) msgList.get(msgIndex);
        else
            return null;
    }
    
    public synchronized MessageBean findMsgByName(String helloName) {
        for (int i = msgList.size()-1; i >= 0; i--) {
            MessageBean msgBean = (MessageBean) msgList.get(i);
            if (msgBean.getHelloName().equals(helloName))
                return msgBean;
        }
        return null;
    }
    
}
If you want to call findMsgByIndex() or findMsgByName() from a scriptless JSP page, you can build a custom tag that receives the method's parameter as an attribute and stores the returned value in the JSP request scope. Creating a tag file is the easiest way to implement the custom tag:
<%-- findMsg.tag --%>

<%@ attribute name="msgIndex" required="false" %>
<%@ attribute name="helloName" required="false" %>

<jsp:useBean id="ssnBean" scope="session"
        class="jsfajax.SessionBean"/>
<%
    jsfajax.MessageBean msgBean = null;
    String msgIndex = (String) jspContext.getAttribute("msgIndex");
    String helloName = (String) jspContext.getAttribute("helloName");
    if (msgIndex != null && msgIndex.length() > 0)
        msgBean = ssnBean.findMsgByIndex(
                Integer.valueOf(msgIndex).intValue());
    else if (helloName != null && helloName.length() > 0)
        msgBean = ssnBean.findMsgByName(helloName);
    if (msgBean != null)
        request.setAttribute("msgBean", msgBean);
%>
(In a previous OTN article (" Reusability in Web Applications"), I explained how to create backing beans and tag files using JDeveloper's wizards.)

 

Both beans of the example application must be configured in the faces-config.xml file along with the navigation rules and other JSF settings:
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE faces-config PUBLIC
    "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"

<faces-config>

    <application>
        <default-render-kit-id>oracle.adf.core</default-render-kit-id>
        <locale-config>
            <default-locale>en</default-locale>
        </locale-config>
    </application>

    <managed-bean>
        <managed-bean-name>ssnBean</managed-bean-name>
        <managed-bean-class>jsfajax.SessionBean</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>

    <managed-bean>
        <managed-bean-name>msgBean</managed-bean-name>
        <managed-bean-class>jsfajax.MessageBean</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>ssnBean</property-name>
            <value>#{sessionScope.ssnBean}</value>
        </managed-property>
    </managed-bean>

    <navigation-rule>
        <from-view-id>/ajaxForm.jsp</from-view-id>
        <navigation-case>
            <from-outcome>sent</from-outcome>
            <to-view-id>/ajaxForm.jsp</to-view-id>
            <redirect/>
        </navigation-case>
    </navigation-rule>

</faces-config>
JDeveloper automatically creates the JSF configuration file when you add a JSF page to an existing project or when you crate a new Web project that uses JSF. JDeveloper also provides an easy-to-use interface for editing the faces-config.xml file:

 

figure 2

Ajax Controller and XML Response. All JSF requests are handled by a controller named FacesServlet that forwards the requests to the JSF pages. You can extend the JSF controller with a phase listener in order to handle the Ajax requests too. This solution is good when you build custom JSF-Ajax components, which can be packed together with the JSF listener in the same JAR file. When building a Web app, a simpler solution is to create a JSP controller and small JSP pages that generate the XML responses. The following example controller (ajaxCtrl.jsp) uses JSTL and several tag files:

<%-- ajaxCtrl.jsp --%>

<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>

<tags:response paramMap="${pageContext.request.parameterMap}">
    <tags:findMsg msgIndex="${param.msgIndex}"
            helloName="${param.helloName}"/>
    <c:if test="${msgBean != null}">
        <c:if test="${!empty param.msgIndex}">
            <jsp:include page="msgIndexResp.jsp"/>
        </c:if>
        <c:if test="${!empty param.helloName}">
            <jsp:include page="helloNameResp.jsp"/>
        </c:if>
    </c:if>
</tags:response>
The response.tag file sets the Content-Type header ( text/xml), sets the Cache-Control header ( no-cache), and generates a <response> element whose attributes are retrieved from a Map. When ajaxCtrl.jsp invokes response.tag, paramMap contains the request parameters. Here is the tag file's code:
<%-- response.tag --%>

<%@ attribute name="paramMap" required="true"
        type="java.util.Map" %>
<%
    response.setContentType("text/xml");
    response.setHeader("Cache-Control", "no-cache");
%>

<response
    <c:forEach var="attr" items="${paramMap}">
        ${attr.key}="<c:out value='${attr.value[0]}'/>"
    </c:forEach>
>
    <jsp:doBody/>
</response>
The property.tag file generates an XML element that contains the value of a bean property:
<%-- property.tag --%>

<%@ attribute name="name" required="true" %>
<%@ attribute name="target" required="true"
        type="java.lang.Object" %>

<${name}><c:out value="${target[name]}"/></${name}>
The msgIndexResp.jsp generates a group of XML elements that contain the data of a MessageBean instance:
<%-- msgIndexResp.jsp --%>

<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>

<tags:property name="msgSubject" target="${msgBean}"/>
<tags:property name="msgContent" target="${msgBean}"/>
<tags:property name="helloName" target="${msgBean}"/>
<tags:property name="toAddr" target="${msgBean}"/>
<tags:property name="ccAddr" target="${msgBean}"/>
<tags:property name="bccAddr" target="${msgBean}"/>
The response XML looks like this:
<response msgIndex="...">
    <msgSubject>...</msgSubject> 
    <msgContent>...</msgContent> 
    <helloName>...</helloName> 
    <toAddr>...</toAddr> 
    <ccAddr>...</ccAddr> 
    <bccAddr>...</bccAddr> 
</response>
The helloNameResp.jsp generates three XML elements that contain the email addresses:
<%-- helloNameResp.jsp --%>

<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>

<tags:property name="toAddr" target="${msgBean}"/>
<tags:property name="ccAddr" target="${msgBean}"/>
<tags:property name="bccAddr" target="${msgBean}"/>
The following section presents the API that is used to send HTTP requests to the Ajax controller.

Understanding the XMLHttpRequest API

When building real-world applications, you should look for an Ajax framework that wraps the XMLHttpRequest API, implementing additional features, such as error handling, form-XML bindings, and so on. Frameworks also hide browser differences, which are a common issue when using JavaScript. In our case here, however, no Ajax framework is used because one of the goals is to help you understand how XMLHttpRequest works.The code presented in this section can be found in the ajaxUtil.js file of the sample code download. This file contains a function named sendHttpRequest(), which takes four parameters: the HTTP method ( GET or POST), the URL of the Ajax controller, an array containing the request parameters, and a callback function that should be invoked when the XML response is retrieved from the server.

 

Creating the Request Object. In IE, which was the first browser that supported the API, you create a request object with the help of ActiveXObject(). All other browsers provide a XMLHttpRequest() constructor. Therefore, the Ajax code must detect the browser and then create the request object, using the appropriate method. The sendHttpRequest() function does that and uses a local variable for keeping a reference to the created object:
// ajaxUtil.js
...
function sendHttpRequest(method, url, params, callback) {
    var request;
    if (window.XMLHttpRequest)
        request = new XMLHttpRequest();
    else if (window.ActiveXObject)
        request = new ActiveXObject("Microsoft.XMLHTTP");
    else
        return null;
    ...
}
Initializing the Request. The XMLHttpRequest API provides a function named open() that takes at least two parameters: the HTTP method and the URL. An optional async parameter can be used to specify whether you want to send the request asynchronously or synchronously. The default value is true. (More details on this later.) The open() function accepts two additional parameters (user and password), which are useful for HTTP-based authentication. Before calling open(), you must append the request parameters to the URL if you use the GET method. The buildQueryString() function, which you can find in the ajaxUtil.js file, encodes the request parameters and returns the query string for the URL. After making sure that the HTTP method, the URL and the async flag are properly initialized, the sendHttpRequest() function passes them to open():
// ajaxUtil.js
...
function sendHttpRequest(method, url, params, callback) {
    ...
    if (method)
        method = method.toUpperCase();
    else
        method = "GET";
    var fullURL = url;
    if (params && method == "GET")
        fullURL += "?" + buildQueryString(params);
    var async = false;
    if (callback)
        async = true;
    request.open(method, fullURL, async);
    ...
}
The buildQueryString() function accepts an array as parameter. Each element of the array must have two properties (name and value) that are appended to the query string in a loop:
// ajaxUtil.js
...
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;
}
The open() function doesn't send the HTTP request. This happens when you call another function named send(). Between open() and send(), you can set the callback function and you can also set the request headers with setRequestHeader().

 

Setting the Callback. The request object has a property named onreadystatechange that lets you set the callback function, which the browser will call multiple times to indicate the various stages of the HTTP request. You can find out the current stage by examining another property named readyState, which may have one of the following values:

0 - uninitialized
1 - loading
2 - loaded>
3 - interactive
4 - completed

Initially, readyState is 0; it becomes 1 after calling open(); it becomes 2 after calling send(); at this point, you may get the response headers with getResponseHeader() and getAllResponseHeaders(); readyState is 3 while the browser is downloading the XML; you may get the partial result from the responseText property; once it gets the whole XML, the browser parses it and readyState becomes 4; if everything went well, you can get access to the DOM tree through the responseXML property; the status property indicates the HTTP status code and a message could be obtained via the statusText property. At any point, you may abort the request with the abort() function.

 

The browser doesn't pass any parameters to the callback function, but in most cases you would like to have access to the request object so that you can examine the readyState and status properties. Then, you would normally want to process the XML that is accessible through the responseXML property. You might be tempted to make request a global variable so that you can access it from your callback, but in this case you wouldn't be able to open multiple concurrent requests unless you use an array of XMLHttpRequest objects. A much more elegant solution is to define a nested function ( calbackWrapper()), which verifies the request and invokes your callback, passing the request object as a parameter:
// ajaxUtil.js
...
function sendHttpRequest(method, url, params, callback) {
    ...
    function calbackWrapper() {
        if (async && request.readyState == 4) {
            if (request.status == 200 && hasResponse(request))
                callback(request);
            else
                reportError(request, url, params);
        }
    }
    if (async)
        request.onreadystatechange = calbackWrapper;
    ...
}
Thanks to the wrapper, your callback can look like this:
function myCallback(request) {
    ...
    var response = request.responseXML.documentElement;
    ...
}
Since responseXML is a DOM Document, it has a documentElement property that lets you access the root element of the DOM tree. This property, however, might be null or might not contain what you expect if a parsing error occurs. The hasResponse() function of ajaxUtil.js verifies the responseXML, its documentElement property, and the tag name of the root element:
// ajaxUtil.js
...
var responseTagName = "response";
...
function hasResponse(request) {
    var responseXML = request.responseXML;
    if (responseXML) {
        var docElem = responseXML.documentElement;
        if (docElem) {
            var tagName = docElem.tagName;
            if (tagName == responseTagName)
                return true;
        }
    }
    return false;
}
If something goes wrong, the reportError() function of ajaxUtil.js signals the problem. For example, you'll get an "Internal Server Error" whose HTTP status is 500 if the Ajax controller throws an exception. As mentioned earlier, even if the HTTP status is OK (200), an XML parsing error can occur. In this case, some browsers, such as IE, return null when the documentElement property is accessed. Other browsers, such as FireFox, return an XML document that contains more details about the parsing error. No matter how the browser handles the error, any information on the parsing error is almost useless without seeing the actual XML that caused the error. Therefore, reportError() changes the browser's location property so that you can examine the error, which can be an internal server error, a parsing error or something else. Here is the code that reports the error:
// ajaxUtil.js
...
var debug = true;
...
function reportError(request, url, params) {
    if (debug) {
        if (request.status != 200) {
            if (request.statusText)
                alert(request.statusText);
            else
                alert("HTTP Status: " + request.status);
        } else
            alert("Response Error");
        if (params)
            url += "?" + buildQueryString(params);
        document.location = url;
    }
}
Sending the Request. At this point, the request object is initialized and the callback (if any) is set. If the HTTP method is POST, you still have to set the Content-Type header and build the request's body with the buildQueryString() function that was presented earlier. Then, you call the send() function defined by the XMLHttpRequest API:
// ajaxUtil.js
...
function sendHttpRequest(method, url, params, callback) {
    ...
    var body = null;
    if (method == "POST") {
        request.setRequestHeader("Content-Type",
            "application/x-www-form-urlencoded");
        if (params)
            body = buildQueryString(params);
    }
    request.send(body);
    if (!async && (request.readyState != 4
            || request.status != 200
            || !hasResponse(request))) {
        reportError(request, url, params);
        return null;
    }
    return request;
}
In case of an asynchronous call (i.e. you provided the callback parameter to sendHttpRequest()), the callback wrapper handles any possible error. If everything is okay, your callback function gets the request when the XML response is received from the server. Here is how you would invoke sendHttpRequest():
sendHttpRequest(method, url, params, myCallback);
The sendHttpRequest() function can also be used to send the request synchronously, meaning that the browser waits until it gets the XML response. The user interface is blocked during the HTTP request and the user can't do anything. In order to make a synchronous call, you don't provide the callback parameter to sendHttpRequest(). The callback wrapper won't be set in this case and the sendHttpRequest() function handles any possible errors before returning the request object. You can use the same function (named myCallback() in the samnple code) to process the request synchronously:
myCallback(sendHttpRequest(method, url, params));

Building the User Interface

The Ajax code presented in the previous section is used in the sample application of this article, which simulates a Web mail interface. In this section, you'll see how you can create a JSF page with JDeveloper. Then, you'll find out how to invoke the Ajax controller and how to process the XML response.

 

Creating the Web Form. After creating a new project, select File and New to open the New Gallery window. Expand the Web Tier category in the left panel and select JSF. Then go to the right panel, select JSF JSP and click OK:

 

figure 3

Skip the welcome page of the wizard and select the Servlet 2.4\JSP 2.0 (J2EE 1.4) option:

 

figure 4

Provide the ajaxForm.jsp name for the JSF page:

 

figure 5

Choose the binding style:

 

figure 6

Select an error page option:

 

figure 7

Select the JSP tag libraries that you want to use, moving them from the left panel (Available Libraries) to the right panel (Selected Libraries):

 

figure 8

Provide the page title:

 

figure 9

Click Finish. JDeveloper generates a JSF page that contains an empty form. Make sure that h:form is selected in the Structure navigator (lower left corner). Then, select JSF HTML and click Panel Grid in the Component Palette (upper-right corner):

 

figure 10

Skip the wizard's welcome page and enter 1 in the field labeled Number of Columns:

 

figure 11

Click Finish. JDeveloper will add a JSF grid panel within the page's form. Make sure h:panelGrid is selected in the Structure navigator. Then, select ADF Faces Core and click SelectOneChoice in the Component Palette:

 

figure 12

A new window opens. Enter #{ssnBean.msgItems} in the Value field. This is a binding to the list of message items maintained by the SessionBean. Here is what you should see on your screen:

 

figure 13

Click the Common Properties tab and enter Sent Messages: in the Label field:

 

figure 14

Click the Advanced Properties tab. Enter msgList as the value of the id property. Then, enter msgListChange() as the value of the onchange property. This is a JavaScript function presented later in the article.

 

figure 15

Click OK. JDeveloper inserts the list component into the panel that you added earlier. Because the list is an ADF Faces component, JDeveloper also replaces the <html>, <head>, and <body> tags with the corresponding ADF Faces tags: <afh:html>, <afh:head>, and <afh:body>. Select the Source editor to examine the generated source code:

 

figure 16

At any time you can change the properties of the UI components in the Property Inspector (lower right corner). For example, select h:form in the Structure navigator and change the id property to ajaxForm. If you look at the source, you'll notice that an id attribute was added to the <h:form> tag. You might also want to replace <h:form> with the <af:form> tag, which offers additional features, such as the support for file uploading. You can simply change the tag's prefix in the Source editor or you can use a JDeveloper wizard. The wizard is especially useful when you have a large page and you don't want to waste time looking for the start and end tags of the same form. Right-click h:form in the Structure navigator, click Convert, select ADF Faces Core, select Form and click OK:

 

figure 17

Of course, the wizard used above works for all tags of all libraries supported by JDeveloper. When you replace a tag with another tag, the wizard pops up windows that let you provide values for the attributes that weren't present in the old tag, but are required by the new tag. You'll also be warned if any attributes of the old tag will be removed because the new tag doesn't support them.

 

Let's go back to the main JDeveloper window. To insert a new component, just move the caret cursor where you want it and then select it from the Component Palette. You can also right click the page's elements in the Structure navigator and then use Insert before, Insert inside, or Insert after:

 

figure 18

Using JDeveloper's navigators, editors and wizards you can build full Web pages and much more. JDeveloper provides support for building EJBs, Web services, databases, Servlets, JSPs, client GUIs, XML schemas, UML diagrams and many others.

 

Using JavaScript with JSF and ADF Faces. The JavaScript code that you have to develop for a JSF page is no different from the code you would develop for a regular HTML page. Many JSF and ADF Faces components have attributes that let you set JavaScript event handlers that run on the client side. The ajaxForm.jsp page of the example application uses three such attributes: onload in <afh:body>, onchange in <af:selectOneChoice>, and onkeyup in <af:inputText>. They specify three JavaScript event handlers: the disableAutoComplete() function is called when the browser finishes the loading of the page, msgListChange() is called when the value of the form's list is changed, and msgContentKeyup() is invoked every time you press a key while the form's text area has the focus. The two files that contain the application's JavaScript code are imported with <script> within an <f:verbatim> that is placed in the page's header:
<%-- ajaxForm.jsp --%>

<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="afh" uri="http://xmlns.oracle.com/adf/faces/html" %>

<f:view>

<afh:html>

<afh:head title="Email">
    <f:verbatim>
        <script src="ajaxUtil.js">
        </script>
        <script src="ajaxLogic.js">
        </script>
    </f:verbatim>
</afh:head>

<afh:body onload="disableAutoComplete(formName)">

<af:panelPage title="Email">

<af:form id="ajaxForm">

    <h:panelGrid columns="1" border="0" cellspacing="5">

        <h:messages globalOnly="true"/>

        <af:selectOneChoice id="msgList"
                label="Sent Messages: "
                onchange="msgListChange()">
            <f:selectItems value="#{ssnBean.msgItems}"/>
        </af:selectOneChoice>

        <af:objectLegend name="required"/>

        <af:panelForm>

            <af:inputText id="msgSubject"
                    value="#{msgBean.msgSubject}"
                    label="Subject: " required="true"
                    columns="40" maximumLength="80">
                <f:validateLength minimum="1" maximum="80"/>
            </af:inputText>

            <af:inputText id="msgContent"
                    value="#{msgBean.msgContent}"
                    label="Content: " required="true"
                    rows="10" columns="40"
                    onkeyup="msgContentKeyup()">
            </af:inputText>

            ...

            <af:panelLabelAndMessage label=" ">
                <af:commandButton id="command" text="Send"
                        action="#{msgBean.sendAction}"/>
            </af:panelLabelAndMessage>

        </af:panelForm>

    </h:panelGrid>

    <af:inputHidden id="helloName"
            value="#{msgBean.helloName}"/>

</af:form>

</af:panelPage>

</afh:body>

</afh:html>

</f:view>
Web browsers try to help you complete forms by providing old values that you already entered when you used the form in the past. This feature can be annoying when using a Web app especially if you have Ajax code that updates the form too. The ajaxUtil.js file contains a function named disableAutoComplete(), which turns off the autocomplete attribute of a form:
// ajaxUtil.js
...
function disableAutoComplete(formName) {
    document.forms[formName].setAttribute("autocomplete", "off");
}
The getFormElem() function returns a JavaScript object that represents a form element:
// ajaxUtil.js
...
function getFormElem(formName, elemName) {
    return document.forms[formName].elements[elemName];
}
The getFormValue() function gets the value of a form element:
// ajaxUtil.js
...
function getFormValue(formName, elemName) {
    return getFormElem(formName, elemName).value;
}
The setFormValue() function sets the value of a form element:
// ajaxUtil.js
...
function setFormValue(formName, elemName, value) {
    getFormElem(formName, elemName).value = value;
}
Invoking the Ajax Controller. The ajaxLogic.js file contains the updateAjaxForm() callback and several other JavaScript functions, which are explained next. The invokeAjaxCtrl() function takes a list of parameters and a flag indicating whether the HTTP request should be sent asynchronously or synchronously. The sendHttpRequest() function, which was presented earlier does most of the work:
// ajaxLogic.js
...
var httpMethod = "POST";
var ctrlURL = "ajaxCtrl.jsp";
...
function invokeAjaxCtrl(params, async) {
    if (async)
        sendHttpRequest(httpMethod, ctrlURL, params, updateAjaxForm);
    else
        updateAjaxForm(sendHttpRequest(httpMethod, ctrlURL, params));
}
The msgListChange() function is an event handler that is called by the browser when the user changes the selection of the list that contains the sent messages. When computing the massage index parameter, msgListChange() takes into account the fact that the form's list shows the messages in reverse order:
// ajaxLogic.js
...
var formName = "ajaxForm";
...
function msgListChange() {
    var msgList = getFormElem(formName, "msgList");
    var msgIndex = msgList.options.length
            - msgList.selectedIndex - 1;
    var params = [ { name: "msgIndex", value: msgIndex } ];
    invokeAjaxCtrl(params, false);
}
The msgContentKeyup() function is called every time a keyboard event occurs while the form's text area has the focus. The value returned by the extractHelloName() function is stored into a hidden field and is also sent as a request parameter:
// ajaxLogic.js
...
var formName = "ajaxForm";
...
function msgContentKeyup() {
    var msgContent = getFormValue(formName, "msgContent");
    var helloName = extractHelloName(msgContent);
    if (helloName) {
        helloName = helloName.toUpperCase();
        var oldName = getFormValue(formName, "helloName");
        if (oldName != helloName) {
            setFormValue(formName, "helloName", helloName);
            var params = [ { name: "helloName", value: helloName } ];
            invokeAjaxCtrl(params, true);
        }
    }
}
The extractHelloName() function gets the message's content and returns the last word of the first line that contains letters:
// ajaxLogic.js
...
function extractHelloName(content) {
    var name = null;
    var i = 0;
    while (i < content.length && !isLetter(content.charAt(i)))
        i++;
    if (i < content.length) {
        var j = content.indexOf("\n", i);
        if (j != -1) {
            while (j > i && !isLetter(content.charAt(j-1)))
                j--;
            i = j - 1;
            while (i > 0 && isLetter(content.charAt(i-1)))
                i--;
            name = content.substring(i, j);
        }
    }
    return name;
}
The isLetter() function returns true if the parameter is a letter and returns false otherwise.
// ajaxLogic.js
...
function isLetter(c) {
    return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
}
Implementing the Ajax Callback. The Ajax callback must process the response XML. For example, the data contained by the response document can be used to update the Web form. The updateFormValue() function of ajaxUtil.js takes three parameters: a DOM tree that starts with the root element of the document, the form name, and the common name of the form element and the XML element that contains the data. The following JavaScript function gets the character data contained by the XML element and sets the value of the form element:
// ajaxUtil.js
...
function updateFormValue(xmlRoot, formName, elemName) {
    var xmlElem = xmlRoot.getElementsByTagName(elemName);
    if (xmlElem && xmlElem.length > 0) {
        var childNodes = xmlElem[0].childNodes;
        if (childNodes && childNodes.length > 0) {
            var value = "";
            for (var i = 0; i < childNodes.length; i++)
                if (childNodes[i].nodeValue)
                    value += childNodes[i].nodeValue;
            setFormValue(formName, elemName, value);
        }
    }
}
The updateAjaxForm() function is the callback that updates the form of the example application with the data extracted from the response XML:
// ajaxLogic.js
...
var formName = "ajaxForm";
...
function updateAjaxForm(request) {
    if (request == null)
        return;
    var response = request.responseXML.documentElement;
    var helloNameParam = response.getAttribute("helloName");
    var helloNameValue = getFormValue(formName, "helloName");
    if (helloNameParam && helloNameParam != helloNameValue)
        return;
    updateFormValue(response, formName, "msgSubject");
    updateFormValue(response, formName, "msgContent");
    updateFormValue(response, formName, "helloName");
    updateFormValue(response, formName, "toAddr");
    updateFormValue(response, formName, "ccAddr");
    updateFormValue(response, formName, "bccAddr");
}
It is possible to send multiple concurrent Ajax requests and the order in which they are completed might not be the same as the order in which they were sent to the server. In the case of the example application, this means that the user could modify the helloName before the completion of one or more requests that were sent to get the email addresses for some older names. Therefore, the updateAjaxForm()callback compares the current helloName value (maintained in a hidden field) with the value of the helloName request parameter (returned as an attribute of the <response> tag). When building your own Ajax application, you have to take into account the fact that you might get the XML responses out of order. Some of the responses might even get lost in the network traffic.

Summary

The Ajax techniques presented here let you build Web applications that are not possible with the traditional Web model. Ajax isn't a perfect solution, however, because asynchronous data exchange tends to be less reliable and less predictable than synchronous communication. You should consider Ajax when other factors, such as network traffic minimization and user-perceived performance, are more important. In other cases, you need to use Ajax together with Web frameworks such as JSF and ADF Faces, which make a very good combination. You should also keep in mind that XMLHttpRequest / Microsoft.XMLHTTP can be used to send the request synchronously, which allows you to build reliable JSF components that are updated without a full page-refresh. In fact, some of the ADF Faces components do use this browser API in the JavaScript code that runs on the client side.


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

Send us your comments