XForms is a standardized technology, but it is not yet implemented by default in most browsers. This article uses the Mozilla XForms add-on. This is an add-on that can be used with any Mozilla Gecko-based browsers such as Firefox, Seamonkey, and Flock. All of the XForms code used here is standard, so any compliant XForms implementations should be similar, but here I only tested against the Mozilla XForms Add-on version 0.8 installed on Mozilla Firefox 2.0. Similarly, the JavaScript code was only tested on Firefox.
Any Ajax applications must request data from a back-end server. The server technology used in this article is Java™ technology. Thus Java 1.5+ is needed along with a Java Web container implementing the Servlet 2.4 specification. The application was tested against Apache Tomcat 5.5.
In this article you will develop an autosuggest-form field. This is a prototypical Ajax feature. One of the applications that first popularized it was Google Suggest. In Google Suggest, you start typing a keyword to search on and the application suggests search terms based on popularity. This is nice for users, as they don't have to type all of their search term and it helps prevents data-entry mistakes. This technique is used widely in Web applications now. Let's take a look at how this feature is typically implemented using Ajax.
As far as Ajax features go, the autosuggest field is fairly straightforward. You will use JavaScript to capture the event of a user entering data into your form field. You will make an asynchronous request to the server asking for suggestions based on what's been entered so far. The server will send back the suggestions. A JavaScript handler will then process these suggestions and modify the HTML DOM to show the suggestions and allow the end user to pick a suggestion. Let's take a look at each of these steps in creating an Ajax-based autosuggest field. You'll start with the HTML for your autosuggest field.
The HTML for your autosuggest field is fairly straightforward. It is shown in Listing 1.
Listing 1. Autosuggest field HTML
<input type="text" id="tBox" onkeyup="suggest();" autocomplete="off"/>
<div id="suggest"></div>
|
This is pretty simple. You have a simple text field, but its attributes are very important. First, you use the onkeyup attribute to declare that the JavaScript function called suggest should be invoked whenever the onkeyup event is fired. You'll take a look at this function in the next section. Notice also that the autocomplete attribute is set to Off. This is to prevent the browser from pre-filling the form field. Finally, notice that you create an empty div. You will fill this div with your suggestions. Let's look at how you request those suggestions.
JavaScript for sending request
You've seen that the your text box will call for the suggest function to be invoked when a user enters a character in the text box. That function is shown in Listing 2.
Listing 2. The
suggest JavaScript function
//This doesn't work in IE 6
var suggestReq = new XMLHttpRequest();
function suggest() {
if (suggestReq.readyState == 4 || suggestReq.readyState == 0) {
var str = escape(document.getElementById('tBox').value);
suggestReq.open("GET", '/xfsuggest/suggest?root=' + str, true);
suggestReq.onreadystatechange = handleSuggestions;
suggestReq.send(null);
}
}
|
The suggest function uses an XMLHttpRequest object to make a request to the /xfsuggest/suggest URL. The code in Listing 2 doesn't work in Internet Explorer 6 or earlier versions of Internet Explorer. For Internet Explorer 6 you would need to sniff the browser and then call new ActiveXObject("Microsoft.XMLHTTP") to get an XMLHttpRequest object. Once you have an XMLHttpRequest object, use JavaScript to retrieve what the user has entered so far in the autosuggest field. That becomes the root parameter on your HTTP GET to the server. Finally, notice that you set your response handler to the JavaScript function handleSuggestions. You'll see that later. Now let's take a look at how your server will handle this request.
One of the great things about client-side technologies such as Ajax (and XForms) is that they are independent of the server-side technology. You can use PHP or Ruby or whatever. Here you use a Java servlet. The code for your servlet is shown in Listing 3.
Listing 3. Suggest servlet
package org.developerworks.xfsuggest;
import java.io.IOException;
import java.util.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
public class SuggestServlet extends HttpServlet {
private final int listSize = 15;
private final Random gen = new Random(Calendar.getInstance().getTimeInMillis());
protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
String root = request.getParameter("root");
List<String> suggestions = this.getSuggestions(root);
request.setAttribute("suggestions", suggestions);
request.getRequestDispatcher("/suggestions.jsp").forward(request, response);
}
private List<String> getSuggestions(String str){
if (str.length() >= listSize){
return Collections.emptyList();
}
int size = listSize - str.length();
List<String> suggestions = new ArrayList<String>();
for (int i = 0; i<size; i++){
StringBuilder sb = new StringBuilder(str);
for (int j = 0; j < size; j++){
char ch = (char) ('a' + gen.nextInt(26));
sb.append(ch);
}
suggestions.add(sb.toString());
}
return suggestions;
}
}
|
Your servlet simply retrieves the root request parameter and calls the getSuggestions() method to get a list of suggestions. The implementation in Listing 3 creates a random list of strings for the suggestions. This mocked implementation creates less suggestions when the root is longer, to imitate the possibilities being narrowed by entering more input to the autosuggest-field. The list is then placed into the request object. The servlet then forwards to a JSP for rendering output. This is shown in Listing 4.
Listing 4. suggestions.jsp
<?xml version="1.0" encoding="ISO-8859-1"?>
<%@ page language="java" contentType="text/xml; charset=ISO-8859-1"
pageEncoding="ISO-8859-1" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<suggestions>
<c:forEach items="${suggestions}" var="str">
<word>${str}</word>
</c:forEach>
</suggestions>
|
The JSP creates an XML document to pass back to the client. It does this by simply iterating over the list of suggestions created by the servlet. Now let's look at the client-side JavaScript for processing this data.
JavaScript for handing the response
When you made the call to the server using the XMLHttpRequest object, you registered a response handler. Thus, when the server responds with XML as shown in Listing 4, that JavaScript handler is invoked. This kind of asynchronous flow is one of the most common pitfalls of Ajax programming. Let's take a look at the handleSuggestions JavaScript function that handles the response from the server, as shown in Listing 5.
Listing 5. The response handler:
handleSuggestions JavaScript function
function handleSuggestions() {
if (suggestReq.readyState == 4) {
var ss = document.getElementById('suggest')
ss.innerHTML = '';
var xml = suggestReq.responseXML;
var root = xml.getElementsByTagName('suggestions').item(0);
var suggestions = new Array();
var cnt = 0;
for (var i=0; i < root.childNodes.length; i++){
var node = root.childNodes.item(i);
if (node.childNodes.length > 0){
suggestions[cnt++] = node.childNodes.item(0).data;
}
}
for(i=0; i < suggestions.length; i++) {
var val = suggestions[i];
if (val.length > 0){
var suggest = '<div
onmouseover="javascript:suggestOver(this);" ';
suggest += 'onmouseout="javascript:suggestOut(this);" ';
suggest += 'onclick="javascript:setValue(this.innerHTML);" ';
suggest += 'class="suggest_link">' + val + '</div>';
ss.innerHTML += suggest;
}
}
}
}
|
There's two major parts to this function. First, you process the XML by walking the responseXML document and retrieving the suggestions. These are stored in the suggestions array. Next the code dynamically creates the list of suggestions for the user to pick from and places the list inside the suggest div created in the HTML. Take a look at Figure 1 to see what your autosuggest field looks like.
Figure 1. Autosuggest using Ajax
There's some extra CSS and JavaScript for creating the "suggest effect." This can be found in the full source code. Now let's take a look at how XForms can make this simpler.
Creating suggest with XForms and Ajax
Two of the key components of Ajax is that you can make requests to the server without changing what page you are viewing in the browser and you can get XML data back from the server. The former characteristic can be described as asynchronous requests, and puts the "A" in Ajax. The latter characteristic puts the "X" in Ajax. The XMLHttpRequest object is what's usually used, but it's definitely not the only way. XForms can accomplish the same thing and be used to put the "A" and "X" in Ajax.
XForms separates the model from the view in a Web application. The data is separated out so it can be modified more easily. All the data and the actions (requests to the server) are part of the model, as is common in typical Model View Control (MVC) architectures. For your autosuggest application, you'll have request data (the root being sent to the server), response data (the suggestions from the server), and an action (the HTTP request to the server) as part of your model. Let's take a look at the different parts of your model.
Setting up the XForm: Request model
The data you are going to use for your request to the server is the first part of your model. It's fairly simple. You just need the root parameter. Take a look at Listing 6.
Listing 6. XForms model: Request data
<xf:instance id="xfsuggestQuery">
<query xmlns="">
<root/>
</query>
</xf:instance>
|
This is just a simple XML document for storing the root parameter that you are going to send to the server. You'll see how this is bound to your view later. Now let's look at how you'll incorporate the response data into your model.
Setting up the XForm: Response model
The data for your response data is a little more complicated. You'll keep the structure you used with the all Ajax version, since it was nicely formed XML and perfectly compatible with XForms. However, when your application starts you have no suggestions, so you just need a placeholder to store it for now, as shown in Listing 7.
Listing 7. XForms model: Response data
<xf:instance id="xfsuggestResults">
<suggestions xmlns=""/>
</xf:instance>
|
You'll replace the suggestions node completely once you have a response from the server. For now you'll just leave the suggestions empty. That's all the data you need in your model; now you just need an action on your model.
Setting up the XForm: Server action
Data is one part of the model, but the actions you can take on the data is just as important. In this case your action is an HTTP request to the server. Listing 8 shows how easily this is done using an XForms submission node.
Listing 8. XForms Model: submission
<xf:submission id="get_suggest" action="/xfsuggest/suggest"
method="get" separator="&" ref="instance('xfsuggestQuery')"
replace="instance" instance="xfsuggestResults"/>
|
The action on your submission is the URL of the HTTP request you are going to make. The ref parameter tells the submission what to submit. In this case it indicates to pull the data from the xfsuggestQuery data object. That's your request data defined in Listing 6. The method on your submission is get and the separator is an ampersand. This tells XForms how to add the data to the HTTP request. It will create a parameter called root, because that is an element in the xfsuggestQuery/query node and the value of that parameter will be the value of the text node child of the "root" node from your XML data. If there were multiple parameters, they would be separated using an ampersand to create a nicely formed HTTP GET. You've defined your XForms model, so now you just need to define its view.
The view in your XForm is pretty simple. It's just a text field. It's not going to be an ordinary text field, though, since it will have your Ajax-ish autosuggest feature. It's declaration is shown in Listing 9.
Listing 9. The XForms view
<xf:input ref="instance('xfsuggestQuery')/root" incremental="true" id="tBox">
<xf:action ev:event="xforms-value-changed">
<xf:send submission="get_suggest" />
</xf:action>
</xf:input>
|
Let's examine what's going on here. First you define your input field. You bind it to your model using the ref attribute. This tells XForms that whatever value is entered into the input field should also be entered in the "root" node in the XML document called xfsuggestQuery. Next you have an action defined on your input. This action calls a "send" on the submission created in your model. This is all very typical XForms. It should look very familiar to developers with any exposure to XForms.
There are a couple of less-usual XForms capabilities you're using as well, though. The input has an attribute called incremental that is set to true. This fires an event whenever an incremental change is made to the input. This is actually superior to the onkeyup type of event shown in the HTML input in Listing 1. It lets XForms be more selective about firing the event, so it can throttle things when a user is typing rapidly. Many Ajax applications often have to add their own throttling mechanisms to accomplish what XForms gives you for free here.
The action you defined has an event attribute set to "xforms-value-changed." This is how you get your action to listen for the incremental events being fired by the input. Now whenever those events are fired, you'll call the submission action and thus make a request to your server for some suggestions for your autosuggest field. Now let's look at how your server code will handle this.
So how do you modify the server to handle these requests from your XForm? You don't! Your server was already sending XML back for Ajax, and that works perfectly with XForms. This demonstrates one of the many ways that XForms is complementary to XForms. The same server-side services used for one technology can be used with the other. Now you just need to handle the response from the server on your client.
In your Ajax application, you used a JavaScript function to process the responseXML from the server and to modify the HTML DOM to show the suggestions. With XForms, you can do things in a simpler manner by binding directly to the data. When the data gets sent to the server, your view updates itself automatically. So instead of having the empty suggest div, you add some bound data as shown in Listing 10.
Listing 10. Displaying results with bound XForm data
<div id="suggest">
<xf:repeat id="results" nodeset="instance('xfsuggestResults')/word">
<div onmouseover="javascript:suggestOver(this);"
onmouseout="javascript:suggestOut(this);"
onclick="javascript:setValue(this.innerHTML);" class="suggest_link">
<xf:output ref="."/>
</div>
</xf:repeat>
</div>
|
All you're doing here is binding to the "word" nodes in the xfsugggestResults document. There are no words at first, of course, but there are once you get a result from the server. Once you have "word" nodes, you iterate over them creating a div with the word in it. This div is just like the div created dynamically in JavaScript in the all-Ajax version. The suggestOver and suggestOut JavaScript functions it references are unchanged. The only thing you need to change is the setValue function. That function needs to work with XForms data instead of just mutating the data of the HTML input field. Take a look at setValue in Listing 11.
Listing 11. The new
setValue function
function setValue(value) {
var model = document.getElementById("xfsuggestModel");
var instance = model.getInstanceDocument("xfsuggestQuery");
var queryElement = model.getElementsByTagName("query")[0];
var rootElement = queryElement.getElementsByTagName("root")[0];
if (rootElement.childNodes.length == 0){
var textNode = document.createTextNode(value);
rootElement.appendChild(textNode);
} else {
rootElement.firstChild.nodeValue = value;
}
model.rebuild();
model.refresh();
document.getElementById('suggest').innerHTML = '';
}
|
In this function you access the XForms model, walk down to the xfsuggestQuery instance, and then walk to its root element and either add a text node to it or change its value if it already exists. This technique could have been used to access the xfsuggestResults data directly and display the suggestions just like you did earlier. JavaScript and XForms work very well together.
You've seen how XForms can be used to complement Ajax. It uses many of the same paradigms as Ajax, but provides several simplifications and optimizations. It's easier to send and receive data from the server using XForms, and you get "smarter" events from it. It can also simplify the JavaScript needed, providing a lot of "boilerplate" functionality that JavaScript must implement normally. In conclusion, it's often beneficial to let XForms be the "A" and the "X" in Ajax.
| Description | Name | Size | Download method |
|---|---|---|---|
| Article sample code | xforms_suggest_source.zip | 376KB | HTTP |
Information about download methods
Learn
-
Get a basic introduction to XForms in Introduction to XForms, Part 1: The new Web standard for forms (Chris Herborth, developerWorks, September 2006), Introduction to XForms, Part 2: Forms, models, controls, and submission actions (Chris Herborth, developerWorks, September 2006), and Introduction to XForms, Part 3: Using actions and events (Chris Herborth, developerWorks, September 2005).
-
See more ways to use XForms and Ajax in the tip Combining XForms and Ajax (Nicholas Chase, developerWorks, October 2006).
-
Learn more about using JavaScript and XForms together in the tip Call JavaScript from an XForms form (Nicholas Chase, developerWorks, January 2007).
-
Explore the full power of the repeat command used in this article, by reading the developerWorks article Make the most of XForms repeats (Jan J. Kratky and Steve K. Speicher, developerWorks, November 2006).
-
Want a faster way to create XForms? Read about Visual XForms Designer in Develop forms using the Visual XForms Designer (Jan J. Kratky, Keith Wells, and Kevin E. Kelly, developerWorks, June 2006).
-
Learn more ways that XForms can simplify Ajax in the blog by Kurt Cagle: Why XForms Matter, Revisited.
-
Learn more about XForms in the IBM developerWorks XML zone.
-
The best place for more Ajax information is the developerWorks Ajax resource center.
-
See where XForms is going.
-
Learn the essentials for creating the next generation of forms in XForms basics (Nicholas Chase, developerWorks, October 2006).
-
Visit the IBM XForms community.
-
Download the XForms extension for Mozilla.
-
IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
-
XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
-
developerWorks technical events and webcasts: Stay current with technology in these sessions.
-
Learn all about XML at the developerWorks XML zone.
Get products and technologies
-
The XForms Recommendation is maintained by the W3C.
Discuss
Comments (Undergoing maintenance)





