Skip to main content

Use XForms and Ajax to create an autosuggest form field

Michael Galpin (mike.sr@gmail.com), Software Engineer, eBay
Michael Galpin has been developing Web applications professionally since 1998. He is a software engineer at eBay in San Jose, CA. He holds a degree in mathematics from the California Institute of Technology.

Summary:  Web application development has been revolutionized by Ajax. What was once a new and flashy technology is now becoming ubiquitous. End users are coming to expect that certain interactions with a Web application will be done "with no refresh," in other words, using Ajax. The ubiquity of Ajax for users has not yet translated to client-side technologies. There are many Ajax frameworks out there that make it simpler to use Ajax, hiding some of the cross-browser issues, but building an Ajax-enabled Web application is still a non-trivial task, to say the least. XForms is a standardized technology that offers many benefits that are complimentary to Ajax. In this article you will see some of the benefits of using Ajax and XForms together by implementing an autosuggest field.

Date:  10 Jul 2007
Level:  Intermediate
Activity:  4364 views

Prerequisites

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.


Definition: Suggest

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.


Creating Suggest with 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.


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


Java for creating suggestions

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
Autosugget 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 paradigm

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 XForms view: The input

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.


Leverage server-side Java

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.


Handling the response

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.


Summary

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.



Download

DescriptionNameSizeDownload method
Article sample codexforms_suggest_source.zip376KB HTTP

Information about download methods


Resources

Learn

Get products and technologies

Discuss

About the author

Michael Galpin has been developing Web applications professionally since 1998. He is a software engineer at eBay in San Jose, CA. He holds a degree in mathematics from the California Institute of Technology.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML
ArticleID=239606
ArticleTitle=Use XForms and Ajax to create an autosuggest form field
publish-date=07102007
author1-email=mike.sr@gmail.com
author1-email-cc=ruterbo@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers