Building Ajax-enabled auto-complete and cascading drop-down controls

Using JSP TagLib, JSON, and Ajax

Build Asynchronous JavaScript + XML (Ajax) controls that can be used in business-line applications. These configurable JSP TagLib-based controls leverage JavaScript Object Notation (JSON), JavaScript, and CSS. Because they are standard JSP TagLib controls, find out how you can easily drop them into any application to provide more intuitive and responsive user interfaces.

Brian J Stewart (BrianJStewart@AquaDataTech.com), Principal Consultant, Aqua Data Technologies, Inc.

Photo of Brian StewartBrian J. Stewart is currently a principal consultant at Aqua Data Technologies, a company that he founded to focus on content management, XML technologies, and enterprise client/server and Web systems. He architects and develops enterprise solutions based on the J2EE and .NET platforms.



09 September 2008

Also available in Chinese Russian Japanese

Two key technologies enable next-generation Web sites: Ajax and JSON. Business-line applications can benefit from these technologies to provide more intuitive and responsive user interfaces. This article describes how to add Ajax and JSON to Java™ Platform, Enterprise Edition (Java EE) Web applications by building reusable JSP TagLib controls based on Ajax.

Within the article, I show how to build a cascading drop-down control that dynamically populates values in an HTML SELECT control based on other form field values. I also describe how to build an auto-complete control, similar to Google Suggest, that displays a suggestion list that is updated in real time as a user types. You'll build the controls by integrating JSON, JavaScript, CSS, HTML, and Java EE technologies.

Technical overview

The primary design goals of the controls developed in this article are:

  • Provide easy integration with existing Web applications. The controls should encapsulate all logic and JavaScript code to simplify the deployment process.
  • Be configurable.
  • Minimize data- and page-size overhead.
  • Leverage CSS and HTML standards.
  • Provide cross-browser support (Microsoft® Internet Explorer, Mozilla Firefox).
  • Leverage common design patterns/best practices to improve code maintainability.

To meet the goals of easy integration and configurable controls, the article's examples use configurable tag attributes where possible. In addition, you define interface/contracts to provide a straightforward way to integrate custom data/value providers with the controls.

The article uses an additional control to encapsulate common JavaScript functions, and thereby minimizing data and overhead. You use JSON to minimize data exchange when making asynchronous calls.

The articles's examples use Web standards, including CSS and HTML, for cross-browser support. The JavaScript, HTML, and CSS emitted by the controls are tested against Internet Explorer 7.x and Mozilla Firefox 2.x/3.x.

The data and value providers are built based on common Object-Oriented Programming design patterns and best practices, such as an n-tier architecture, the adapter design pattern, and interface-based programming.


Technical considerations for implementing the example controls

There are a few key technical considerations for the Ajax-enabled controls you develop in this article, including the mechanism to provide values to the Ajax controls, a data-exchange format for asynchronous communication, the class design, and the data model.

Mechanism to provide responses to asynchronous calls

You have three options to expose data asynchronously to Ajax-enabled controls:

  • JavaServer Pages (JSP)
  • Servlets
  • SOAP or RESTful Web services

This article uses Servlets due to their efficiency and minimal overhead. A JSP page is simpler to implement than a Servlet, but it isn't as clean from an implementation perspective.

Data-exchange format considerations

Data providers for Ajax-enabled controls can use XML or JSON as the data-exchange format. XML is generally more human-readable than JSON, but it has the following disadvantages:

  • Larger data size compared to JSON
  • Slightly more difficult to parse within JavaScript

For these reasons, this article uses JSON.

Data model

The data model for the sample application comprises two entities:

  • State, which contains state abbreviations and names
  • Location, which contains city, zip code, and other location data

Figure 1 shows the data model used for the sample pages in this article.

Figure 1. Data Model
Data model diagram

Class model

The example in this article consists of the Data Abstract Layer (DAL), Data Transfer Objects (DTOs), the Business Logic Layer (BLL), the Presentation Layer, and supporting helper classes. The following figures show UML class diagrams for these classes.

The helper classes provide database and presentation layer supporting classes (see Figure 2).

Figure 2. UML class diagram -- helper classes
UML class diagram -- helper classes

The Data Abstract Layer consists of a single class to provide location-related data to the business layer (see Figure 3).

Figure 3. UML class diagram -- Data Abstract Layer classes
UML class diagram -- data abstract layer classes

You use two DTOs to pass data through the three tiers (see Figure 4). StateDTO holds state-related data, and LocationDTO holds location-related data, including zip code, city name, state, latitude, and longitude.

Figure 4. UML class diagram -- Data Transfer Object classes
UML class diagram -- Data Transfer Object classes

The Business Logic Layer consists of the value providers that provide data to the Ajax-enabled controls (see Figure 5). Value providers for the auto-complete control must implement the IJsonValueProvider interface. The location service receives the collection of DTO objects from the data layer and generates the corresponding JSON data for use in the presentation layer.

Figure 5. UML class diagram -- Business Logic Layer classes
UML class diagram -- Business Logic Layer classes

The Servlets provide the interface to which the client-side asynchronous calls are made (see Figure 6). These Servlets interact with the value providers to provide JSON data to the Web browser.

Figure 6. UML class diagram -- Business Logic Layer, Servlet classes
UML class diagram -- Business Logic Layer, Servlet classes

JSP TagLib controls

You'll create the following Ajax-enabled controls:

  • Cascading drop-down control -- Dynamically populates value options in SELECT controls based on other form fields or business rules.
  • Auto-complete control -- Displays a suggestion list, similar to Google Suggest, in real time as a user types. The suggestions are dynamically displayed using asynchronous communication with a data provider servlet.

In addition to the two JSP TagLib controls, you need a third control to encapsulate all reusable JavaScript functions, such as clearing/populating values, handling keyboard/mouse events, and supporting asynchronous communication. Figure 7 illustrates these three control classes.

Figure 7. UML class diagram -- JSP TagLib control classes
UML class diagram -- JSP TagLib control classes

Click to see larger image

Figure 7. UML class diagram -- JSP TagLib control classes

UML class diagram -- JSP TagLib control classes

Build the data provider and data layer

The LocationDataService class is a data provider to retrieve location-related data from the database. It returns TreeMap objects containing LocationDTO and StateDTO objects. It's highly recommended that the data provider cache the results in memory for optimal performance, particularly because the data is consumed through asynchronous server calls.


Build a JSP TagLib control

You create a JSP TagLib control by extending TagSupport or TagBodySupport and overriding the doStartTag(), doAfterBody(), or doEndBody() method to render the control's content (HTML code, JavaScript) during page processing. Listing 1 shows an example of an overridden doStartTag method.

Listing 1. JdbcQuery class
/* (non-Javadoc)
* @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
*/
@Override
public int doStartTag() throws JspException {
	JspWriter out = pageContext.getOut();
	try {
		// An example of rendering output within a JSP page
		out.print("This is a string that will be rendered");
	 
		// A more practical example
		out.print("<h1 id='heading1'>This is a Heading</h1>");
	} catch (IOException e) {
		e.printStackTrace();
	}

After you create the implementation of the JSP TagLib control, you must define a TagLib Library Definition (TLD) in the /WEB-INF/tlds directory, as shown in Listing 2.

Listing 2. Sample JSP TagLib library definition file
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" 
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
	<tlibversion>1.0</tlibversion>
	<jspversion>1.1</jspversion>
	<shortname>ajax</shortname>
	<info>Ajax control library</info>

	<tag>
		<name>sample</name>
		<tagclass>com.testwebsite.controls.SampleJspTag</tagclass>
		<bodycontent>JSP</bodycontent>
		<info>
			This is a sample control
		</info>
		<attribute>
			<name>id</name>
			<required>false</required>
			<rtexprvalue>false</rtexprvalue>
		</attribute>
	</tag>
</tagLib>

You can place the control in any JSP page by adding the code from Listing 3.

Listing 3. Sample JSP page
<%@ page contentType="text/html; charset=ISO-8859-5" %>
<%@ taglib prefix="ajax" uri="/WEB-INF/tlds/ajax_controls.tld"%>
<html>
	<head>
		<title>This is a test page</title>

		<link href="core.css" rel="stylesheet" type="text/css" />
	 
	</head>

	<body>
		This is a test page.
		<ajax:sample/>
	</body>
</html>

Build the Ajax page JSP TagLib control

The <ajax:page/> control renders the standard JavaScript functions that are needed to add asynchronous support to JSP pages. It also renders helper functions for the <ajax:autocomplete/> and <ajax:dropdown/> controls. The helper functions are covered in the respective control sections Build the auto-complete JSP TagLib control and Build the cascading drop-down JSP TagLib control. Where possible, it's best to keep supporting JavaScript functions in the <ajax:page/> control rather than in individual controls because doing so reduces the page size. Alternatively, you can store them in an external JS file, but that slightly complicates deployment by reducing encapsulation within the control.

The XMLHttpRequest object, which is accessible in JavaScript, is central to asynchronous Web communication. Unfortunately, XMLHttpRequest isn't an approved standard, and vendor support varies slightly. For Opera, Mozilla Firefox, and Microsoft Internet Explorer 7.0 and later, it's a matter of using the new XMLHttpRequest() JavaScript syntax. For prior versions of Microsoft Internet Explorer, you create the object using new ActiveXObject('Microsoft.XMLHTTP'). Listing 4 shows how to initialize the XMLHttpRequest for cross-browser support.

Listing 4. Create the XMLHttpRequest object
var req;

function initializeXmlHttpRequest() {
	if (window.ActiveXObject) {
		req=new ActiveXObject('Microsoft.XMLHTTP');
	}
	else {
		req=new XMLHttpRequest();
	}
}

As mentioned, you render JavaScript code to a page by adding the code from Listing 5 to the tag-implementation class.

Listing 5. Render the JavaScript initialization function for the XMLHttpRequest object
/* (non-Javadoc)
 * @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
 */
@Override
public int doStartTag() throws JspException {
	StringBuffer html = new StringBuffer();
	html.append("<script type='text/javascript' language='javascript'>");
	html.append("var req;");
	html.append("var cursor = -1;");
	
	// Generate functions to support Ajax
	html.append("function initializeXmlHttpRequest() {");
	
	// Support for non-Microsoft browsers (and IE7+)
	html.append("if (window.ActiveXObject) {");
	
	// Support for Microsoft browsers
	html.append("req=new ActiveXObject('Microsoft.XMLHTTP');");
	html.append("}");
	html.append("else {");	 
	html.append("req=new XMLHttpRequest();");
	html.append("}");

	JspWriter out = pageContext.getOut();
	try {
		out.append(html.toString());
	} catch (IOException e) {
		e.printStackTrace();
	}

	return this.SKIP_BODY;
}

The req variable can now be used globally within the Web page. Listing 6 illustrates how to make an asynchronous call.

Listing 6. Use the XMLHttpRequest object
// If req object initialized
if (req!=null) {
	// Set callback function
	req.onreadystatechange=stateName_onServerResponse;
	
	// Set status text in browser window
	window.status='Retrieving State data from server...';
	
	// Open asynchronous server call
	req.open('GET',dataUrl,true);
	
	// Send request
	req.send(null);
}

When the ready state of a request changes, the function specified in req.onreadystatechange is invoked. req.readystate contains one of the following status codes:

  • 0=initialized
  • 1=Open
  • 2=Sent
  • 3=Receiving
  • 4=Loaded

Use XML as a data-exchange format

The XMLHttpRequest object also has a responseXML property to retrieve XML data responses. JavaScript's DOM can then be used for processing.

Normally, anything but Loaded is ignored because typically nothing needs to occur until the server response is complete. A value of Loaded for an asynchronous call doesn't guarantee success. As with any Web page request, it's possible that the page wasn't found or that another problem occurred. If the req.status is anything but 200, something went wrong. Listing 7 shows how to handle the server response.

Listing 7. Handle the response to the asynchronous request
function stateName_onServerResponse() {
	if(req.readyState!=4) return;
	
	if(req.status != 200) {
		alert('An error occurred retrieving data.');
		return;
	}
 
	// Obtain server response
	var responseData = req.responseText;
	... Processing of result
}

You've now had a good overview of how to make asynchronous calls and handle responses. The next step is to start building the first control: <ajax:autocomplete/>.


Build the auto-complete JSP TagLib control

The following steps are required to build the auto-complete control:

  1. Build a value provider to supply suggestions for the control.
  2. Create a Servlet interface to expose the value provider for asynchronous calls.
  3. Create a JSP TagLib control to encapsulate everything in a control that can be used in JSP pages.

The following sections explain these steps in detail.

Build the value provider to supply suggestions for the auto-complete control

The value provider supplies the suggestion list to the auto-complete control. A value provider must implement the IJsonValueProvider interface, which defines a single method getValues() that returns a JSONArray object containing the suggestion list. The interface is shown in Listing 8.

Listing 8. IJsonValueProvider interface
public interface IJsonValueProvider {
	JSONArray getValues(String criteria, Integer maxCount);
}

JSONObject and JSONArray

These objects are part of JSON for Java, an open source wrapper for using JSON in Java. Refer to Resources for additional information regarding this library.

The next step is to create CityValueProvider, the implementation of this interface, which provides city data for the <ajax:autocomplete/> control. Note the following key things about the getValues() implementation:

  • Data is retrieved from the Location Data Provider -- a Data Abstract Layer (DAL) component -- which caches all locations in memory.
  • A two-phase approach is required to process the data (TreeMap containing LocationDTO objects), because the Location Data Provider returns a TreeMap sorted by zip code. The results need to be sorted based on the city name for the CityValueProvider.

Listing 9 illustrates how this is done.

Listing 9. City value provider
package com.testwebsite.bll;

import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;
import org.json.JSONArray;
import com.testwebsite.dal.LocationDataService;
import com.testwebsite.dto.LocationDTO;
import com.testwebsite.interfaces.IJsonValueProvider;

/** @model
 *  @author Brian J. Stewart (Aqua Data Technologies, Inc. http://www.aquadatatech.com) */
public class CityValueProvider implements IJsonValueProvider {
	/* (non-Javadoc)
	 * @see com.testwebsite.interfaces.IJsonValueProvider#getValues(java.lang.String)
	 */
	@Override
	public JSONArray getValues(String criteria, Integer maxCount) {
		String cityName = "";
		// If city found, make the search case insensitive
		if (criteria != null && criteria.length() > 0) {
			cityName = criteria.toLowerCase();
		}
		// Get Location data from Data Provider
		TreeMap<Integer, LocationDTO> 
		locData = LocationDataService.getLocationData();	 
		// The LocationDataService Data Provider returns a TreeMap containing 
		// LocationDTO objects that are sorted by Zip Code.	 
		// First build a temporary TreeMap (sorted list) filtering with
		// only unique city names matching the specified cityName parameter
		TreeMap<String, String> cityData = this.getCityData(locData, cityName);
		// Finally iterate through sorted City list 
		// and create JSONArray containing
		// the number elements specified by the maxCount parameter
		JSONArray json = this.getJsonData(cityData, maxCount);		 
		return json;
	}
 
	/**
	 * The getCityData method returns a TreeMap containing Cities matching the 
	 * specified cityName criteria. The results are sorted by City Name and filter 
	 * out any duplicate city names.
	 * @param locData Location Data from which to retrieve cities
	 * @param cityName City Name prefix to which to search
	 * @return
	 */
	protected TreeMap<String, String> getCityData(
		TreeMap<Integer, LocationDTO> locData, String cityName) {	 
		TreeMap<String, String> cityData = new TreeMap<String, String>();
		// Iterate through all data looking for matching cities 
		// and add to temporary TreeMap
		Set<Integer> keySet = locData.keySet();
		Iterator<Integer> locIter = keySet.iterator();	 
		while (locIter.hasNext()) {
	    	// Get current state
			Integer curKey = locIter.next();
			LocationDTO curLocation = locData.get(curKey);
			// Get current location data
	    	if (curLocation != null) {
	    		String curCityName = curLocation.getCity().toLowerCase();
	    		// Add current item if it starts with the cityName parameter
	    		if (curCityName.startsWith(cityName)) {
	    			cityData.put(curLocation.getCity(), 
					curLocation.getCity());	    		 
	    		}
		    }	     
	    }	 
		return cityData;
	}
 
	/**
	 * The getJsonData method returns a JSONArray contain a list of strings
	 * with the city name specified with a maximum number of elements as specified
	 * by the maxCount parameter. 
	 * @param cityData TreeMap containing unique list of matching cities
	 * @param maxCount Maximum number of items to include in the JSONArray
	 * @return JSONArray contain sorted list of city names
	 */
	protected JSONArray getJsonData(TreeMap<String, String> 
		cityData, int maxCount) {	 
		int count = 1;
		JSONArray json = new JSONArray();	 
		// Get city name keys
		Set<String> citySet = cityData.keySet();
		// Iterate through query results
		Iterator<String> cityIter = citySet.iterator();
		while (cityIter.hasNext()) {
			// Get current item
			String curCity = cityIter.next();		 
			// Add item to JSONArray
			json.put(curCity);		 
			// Increment counter 
			count ++;	 
			// If maximum number of entries has been met, then exit loop
			if (count >= maxCount) break;
		}
		return json;
	}
}

Create a Servlet to handle asynchronous requests to the value provider

The next step is to build the AutoCompleteServlet Servlet, the interface for the Web browser to call the IJsonValueProvider implementations. The Servlet is straightforward, with one minor exception. To meet the goal of "easy integration/deployment," you should only need to worry about implementing a value provider, rather than the Servlet interface. To support this goal, you use reflection to instantiate the value provider at runtime using the classname attribute of the <ajax:autocomplete/> control. See Listing 10.

Listing 10. Auto-complete Servlet
package com.testwebsite.servlets;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONArray;

/**
 * @model
 * @author Brian J. Stewart (Aqua Data Technologies, Inc. http://www.aquadatatech.com)
 */
public class AutoCompleteServlet extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = -867804519793713551L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
	 
		String data = "";
		// Get parameters from query string
		String format = req.getParameter("format");
		String criteria = req.getParameter("criteria");
		String maxCountStr= req.getParameter("maxCount");
		String className = req.getParameter("providerClass");
		// If format is not null and it's 'json' 
		if (format != null &amp;&amp; format.equalsIgnoreCase("json")) {
			if (className != null &amp;&amp; className.length() > 0) {
				data = this.getJsonResultAsString(criteria, 
							maxCountStr, 
							className);
			}		 
			resp.setContentType("text/plain");
		}	 
		// Write response
		// Get writer for servlet response
		PrintWriter writer = resp.getWriter();
		writer.println(data);
		writer.flush();
	}

	public String getJsonResultAsString(String criteria, 
                                        String maxCountStr, 
                                        String className) {
		String data = "";
		Integer maxCount = 10;
		if (maxCountStr != null && maxCountStr.length() > 0) {
			maxCount = new Integer(maxCountStr); 	 
		}
		// Get dataprovider class using reflection		 
		// Construct class
		Class providerClass;
		try {
			// Get provider class
			providerClass = Class.forName(className);
			// Construct method and method param types
			Class[] paramTypes = new Class[2];
			paramTypes[0] = String.class;
			paramTypes[1] = Integer.class;
			Method getValuesMethod = providerClass.getMethod("getValues", 
							paramTypes);
			// Construct method param values
			Object[] argList = new Object[2];            
			argList[0] = criteria;
			argList[1] = maxCount;
			// Get instance of the provider class
			Object providerInstance = providerClass.newInstance();
			// Invoke method using reflection
			JSONArray resultsArray = (JSONArray) 
                                      getValuesMethod.invoke(providerInstance, 
                                                             argList);
			// Convert JSONArray result to string
			data = resultsArray.toString();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}		 
		return data; 
	}
}

Figure 8 shows the server response from AutoCompleteServlet Servlet.

Figure 8. Auto-complete Servlet Response
Auto-complete Servlet Response

Create a JSP TagLib control that can be used in a JSP page

The auto-complete control renders a standard INPUT tag, sets up the event handlers, and renders the Suggestion List Container DIV element and appropriate CSS for formatting. You need to add the following supporting JavaScript functions:

  • Handle keyboard events -- <ajax:page/>
  • Handle server response and post-asynchronous invocation processing -- <ajax:autocomplete/>, with helper functions rendered in <ajax:page/>
  • Highlight a specified item in the suggestion list -- <ajax:page/>
  • Hide the suggestion list -- <ajax:page/>
  • Handle the selection of an item in the suggestion list (when the user presses Enter) -- <ajax:page/>
  • Handle when the control loses focus -- <ajax:page/>

Let's start with the onSuggestionKeyDown function, where the Esc key, Enter key, and other control keys are handled. If the user presses Esc, the suggestion list should be hidden and subsequent events in the JavaScript event chain canceled (for example, the Key Up event shouldn't be processed because the event was already handled by hiding the suggestion list); see Listing 11.

Listing 11. Code fragment for handling Esc key
var keyCode = (window.event) ? window.event.keyCode : ev.keyCode;
switch(keyCode) {
	...	 
 
	// Handle ESCAPE key
	case 27: 
		hideSelectionList(curControl, suggestionList);
		ev.cancelBubble = true;
		// IE
		if (window.event) {
			ev.returnValue = false; 
		}
		// Firefox
		else { 
			ev.preventDefault(); 
		}
		break;

	...

If the user presses Enter, the current item should be copied to the input control and the suggestion list hidden. To hide/display the suggestion list, you use standard CSS for formatting in conjunction with JavaScript to change the class name. The display property is set to none to hide the control and to block to display the list. Listing 12 shows the JavaScript function, which you add to the <ajax:page/> control because it can be used with any <ajax:autocomplete/> control.

Listing 12. Code fragment for handling the Enter key
...

// Handle ENTER key
case 13: 
	handleSelectSuggestItem(curControl, suggestionList);
	ev.cancelBubble = true;
	// IE
	if (window.event) { 
		ev.returnValue = false; 
	}
	// Firefox
	else { 
		ev.preventDefault(); 
	}
	break;
 
...

The key-down event handler calls handleSelectSuggestItem, which is defined in <ajax:page/> (see Listing 13).

Listing 13. Handling the Enter key
function handleSelectSuggestItem(curControl, suggestionList) {
	// Get selected node
	// Cursor is a global variable that is incremented/decremented 
	// when the UP ARROW or DOWN ARROW key is pressed.
	var selectedNode = suggestionList.childNodes[cursor];
 
	// Get selected value
	var selectedValue = selectedNode.childNodes[0].nodeValue;
 
	// Set the value of the INPUT control
	curControl.value = selectedValue;
 
	// Finally hide the selection list
	hideSelectionList(curControl, suggestionList);
}

function hideSelectionList(curControl, suggestionList) {
	// If suggestion not found
	if (suggestionList == null || suggestionList == undefined) { 
		return; 
	}
 
	// Clear the suggestion list elements
	suggestionList.innerHTML='';
 
	// Toggle display to none
	suggestionList.style.display='none';
	curControl.focus();
}

There isn't much to handling the pressing of control keys (Shift, Alt, and Ctrl). You need to ignore these keyboard events by doing the following:

  • Preventing changes to the input control during the return by setting returnValue on the EVENT object to false (for Internet Explorer) and executing preventDefault() on the EVENT object (for Firefox)
  • Canceling the event chain for the keyboard event by setting the cancelBubble property on the EVENT object to true

The complete code for onSuggestionKeyDown appears in Listing 14.

Listing 14. Complete key-down event handler
function onSuggestionKeyDown(curControl, ev) {
	// Get suggestion list container
	var suggestionList= document.getElementById(curControl.id + '_suggest');

	// Get key code of key pressed
	var keyCode = (window.event) ? window.event.keyCode : ev.keyCode;
	switch(keyCode) {
		// Ignore certain keys
		case 16, 17, 18, 20: 
			ev.cancelBubble = true;
			// IE
			if (window.event) { 
				ev.returnValue = false; 
			}
			// Firefox
			else { 
				ev.preventDefault(); 
			}
			break;
	 
		// Handle ESCAPE key
		case 27: 
			hideSelectionList(curControl, suggestionList);
			ev.cancelBubble = true;
			// IE
			if (window.event) {
				ev.returnValue = false; 
			}
			// Firefox
			else { 
				ev.preventDefault(); 
			}
			break;
	 
		// Handle ENTER key
		case 13: 
			handleSelectSuggestItem(curControl, suggestionList);
			ev.cancelBubble = true;
			// IE
			if (window.event) { 
				ev.returnValue = false; 
			}
			// Firefox
			else { 
				ev.preventDefault(); 
			}
			break;
	}
}

The key-up event handler is slightly more interesting. If the user presses the Up Arrow or Down Arrow key, the highlighted selection should change. If the user has entered the minimum number of characters (default: 3), then the asynchronous call should be made to the server to populate the suggestion list.

If the user presses the Up Arrow or Down Arrow key, the global cursor variable is incremented/decremented accordingly. The cursor variable tracks the currently selected item. The highlightSelectedNode function is than called to highlight the value. See Listing 15.

Listing 15. Code fragment of the key-up event handler for handling the Up Arrow and Down Arrow keys
... 

switch(keyCode) {
	// Ignore ESCAPE
	case 27: 
 
	// Handle UP ARROW	 
	case 38: 
		if (suggestionList.childNodes.length > 0 && cursor > 0){
			var selectedNode = suggestionList.childNodes[--cursor];
			highlightSelectedNode(suggestionList, selectedNode);
		}
		break;
 
	// Handle DOWN ARROW
	case 40: 
		if (suggestionList.childNodes.length > 0 && 
			cursor < suggestionList.childNodes.length-1) {
				var selectedNode = suggestionList.childNodes[++cursor];
				highlightSelectedNode(suggestionList, selectedNode);
		}
		break;

...

Listing 16 shows the highlightSelectedNode function for highlighting an item. CSS rules are defined for selected and unselected items. The className is toggled using JavaScript. The highlight for the previously selected element is then removed.

Listing 16. Highlight an item in the suggestion list
function highlightSelectedNode(suggestionList, selectedNode) {
	if (suggestionList == null || selectedNode == null) { 
		return; 
	}
 
	// Iterate through all items searching for a node that 
	// matches the node selected
	for (var i=0; i < suggestionList.childNodes.length; i++) { 
		var curNode = suggestionList.childNodes[i];
		if (curNode == selectedNode){
			curNode.className = 'autoCompleteItemSelected'
		} else {
			curNode.className = 'autoCompleteItem';
		}
	}
}

If the user presses any other key and has entered at least the minimum number of characters, an asynchronous call to the server is made to retrieve a JSON array of suggestions. After the ready state changes, the function specified in the req.onreadystatechange property is invoked (see Listing 17).

Listing 17. Code fragment for handling any other key
// If control not found (shouldn't happen)
// or minimum number of characters not entered
if (curControl == null || 
	curControl.value.length < minChars) {
 
	// Hide selected item
	hideSelectionList(curControl, suggestionList);
	return;
}
		 
// Initialize XMLHttpRequest object
initializeXmlHttpRequest();

// If req object initialized
if (req!=null) {
	// Set callback function
	req.onreadystatechange=cityName_onServerResponse;

	// Set status text in browser window
	window.status='Retrieving State data from server...';

	// Open asynchronous server call
	req.open('GET',dataUrl,true);

	// Send request
	req.send(null);
}

When the server-response function is invoked, the readyState is checked to make sure it's Loaded. The status is also checked. If all is well, the string representation of the JSON array is converted to an array using the eval JavaScript function. The array is then passed to the populateSuggestionList function, which adds the elements to the Suggestion List. Listing 18 shows the server-response function.

Listing 18. Server-response handler (generated dynamically by the <ajax:autocomplete/> control)
function cityName_onServerResponse() {
	// If loaded
	if(req.readyState!=4) { 
		return; 
	}
 
	// If an error occurred
	if(req.status != 200) {
		alert('An error occurred retrieving data.');
		return;
	}
 
	// Get response and convert it to an array
	var responseData = req.responseText;
	var dataValues=eval('(' + responseData + ')');
 
	// Get current control
	var curControl = document.getElementById('cityName');
 
	/// Populate suggestion list for control
	populateSuggestionList(curControl, dataValues);
}

The populateSuggestionList function, which is rendered in the <ajax:page/> control, is responsible for populating the suggestion list with the values returned from the asynchronous server call. The array is then iterated through, and a DIV element is created for each item in the array. The DIV element is added to the Suggestion List. Listing 19 shows populateSuggestionList.

Listing 19. Populate the suggestion list (rendered in the <ajax:page/> control)
populateSuggestionList(curControl, dataValues) {
	// Get Suggest List Container for control
	var container = document.getElementById(curControl.id + '_suggest'); 
	// If container not found (shouldn't happen), then simply return
	if (container == null) { return; } 
	// Clear suggestion list container
	container.innerHTML = ''; 
	// If no values return, hide suggestion list
	if (dataValues.length < 1) { 
		container.style.display='none';
		return;
	} 
	// Show suggestion list
	container.style.display='block';
	container.style.top = 
		(curControl.offsetTop+curControl.offsetHeight) + 'px';
	container.style.left = curControl.offsetLeft + 'px'; 
	// Iterate through all values
	// 1. Create DIV element
	// 2. Set attributes and text node value
	// 3. Append new element to the container
	for(var i=0;i < dataValues.length;i++) {
		// Get current value
		var curValue= dataValues[i];	 
		// If value is not blank
		if (curValue != null && curValue.length > 0 ) {
			// Create DIV element
			var newItem = document.createElement('div');		 
			// Append current value as a text node
			newItem.appendChild(document.createTextNode(curValue));
			// Set attributes
			newItem.setAttribute('class', 'autoCompleteItem');
			// Finally append new element to container
			container.appendChild(newItem);
		}
	}
 
	// Set first item as the selected node
	cursor = 0;
	// Get first node 
	var selectedNode = container.childNodes[cursor]; 
	// If first node is equal to the first node, hide the selection list
	if (selectedNode.childNodes[0].nodeValue == curControl.value) {
		hideSelectionList(curControl, container);
	}
	else {
		// Highlight the first node
		highlightSelectedNode(container, selectedNode);
	}
}

Auto-complete TagLib library definition entry

Listing 20 contains the auto-complete control's TagLib library definition entry (with embedded comments for a description of each attribute).

Listing 20. Auto-complete TagLib library definition entry
<tag>
	<name>autocomplete</name>
	<tagclass>com.testwebsite.controls.AutoCompleteTag</tagclass>
	<bodycontent>JSP</bodycontent>
	<info>
		Auto-complete/suggest form input fields based on a specified value.
	</info>
	<!-- Unique identifier for control -->
	<attribute>
		<name>id</name>
		<required>true</required>
		<rtexprvalue>false</rtexprvalue>
	</attribute>
	<!--  Minimum string length before submitting asynchronous request -->
	<attribute>
		<name>minimumlength</name>
		<required>false</required>
		<rtexprvalue>true</rtexprvalue>
	</attribute>
	<!-- Maximum number of items to include in suggestion list -->
	<attribute>
		<name>maxcount</name>
		<required>false</required>
		<rtexprvalue>true</rtexprvalue>
	</attribute>
	<!-- Width of control -->
	<attribute>
		<name>width</name>
		<required>false</required>
		<rtexprvalue>true</rtexprvalue>
	</attribute>
	<!-- Value of control -->
	<attribute>
		<name>value</name>
		<required>false</required>
		<rtexprvalue>true</rtexprvalue>
	</attribute>
	<!-- Data Url for asynchronous call. A default Servlet has been created, 
	but for greater flexibility, a Web Service or another Servlet can be 
	specified-->
	<attribute>
		<name>dataurl</name>
		<required>false</required>
		<rtexprvalue>false</rtexprvalue>
	</attribute>
	<!-- Class that provides suggest value list for control 
		(Used if dataUrl not specified -->
	<attribute>
		<name>providerclass</name>
		<required>false</required>
		<rtexprvalue>false</rtexprvalue>
	</attribute>			 
</tag>

Build the cascading drop-down JSP TagLib control

Typically, business-line applications include selection lists whose values are dependent on other form fields (for example, a product name that depends on the product category).

Prior to Ajax and asynchronous Web programming techniques, you were forced to render all values to the Web page (typically in a JavaScript array) and to dynamically populate the values within JavaScript. The JavaScript arrays were either multidimensional or contained tokens, such as the | character to delimit cascading values. Alternatively, the entire page would be refreshed to retrieve the values for cascading selection lists. Neither of these approaches are appealing when you're dealing with large data sets or trying to build user-friendly Web applications. With Ajax and asynchronization techniques, you can now provide the same rich and intuitive user experience typically only found in desktop applications.

The following sections describe the steps to create the cascading drop-down control:

  1. Create a value provider/Servlet interface that can be called from JavaScript.
  2. Create the JSP TagLib control to encapsulate everything in a control that can be put in any JSP page.

Create the value provider and interface (Servlet)

Similar to the value provider you created for the auto-complete control, you create a Servlet to return a JSON array containing the values. Value providers for cascading controls take a little more effort, because the requirements and data often require separate Servlets or Web services to apply the business rules. Alternatively, you can use embedded JSP TagLib controls (controls included in the body of another tag), but that complicates things slightly. Using separate Servlets provides greater flexibility in terms of returning data to the client. The values can be dependent on other form fields or other complex business rules that are defined in the Servlet.

Listing 21 shows the two value providers for the cascading drop-down controls. The first is the City value provider, which is dependent on the State value. The second value provider is for County, which is dependent on the State and City values. Both Servlets return JSON arrays and use the Location Data Provider (a DAL component). The code is similar to developing a value provider for the auto-complete control; the key difference is that you use separate Servlets to keep the implementation simple and flexible.

Listing 21. CityServlet Servlet
package com.testwebsite.servlets;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.testwebsite.bll.LocationService;

public class CityServlet extends HttpServlet {

	private static final long serialVersionUID = 3231866266466404450L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException { 
		String data = null;

		// Get parameters from query string
		String format = req.getParameter("format");
		String cityName = req.getParameter("cityName");
		String stateName = req.getParameter("stateName");	 
		// If format is not null and it's 'json' 
		if (format != null &amp; format.equalsIgnoreCase("json")) {	 
			// Get city data based on state name and city name prefix
			data = LocationService.getCitiesAsJson(cityName, stateName);
			resp.setContentType("text/plain");
		}	 
		// Write response
		// Get writer for servlet response
		PrintWriter writer = resp.getWriter();
		writer.println(data);
		writer.flush();
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		super.doPost(req, resp);
	}
}

/**
 * @model
 * @author Brian J. Stewart (Aqua Data Technologies, Inc. http://www.aquadatatech.com)
 *
 */
public class CountyServlet extends HttpServlet {

	
	/**
	 * 
	 */
	private static final long serialVersionUID = 3231866266466404450L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {	
		String data = null;

		// Get parameters from query string
		String format = req.getParameter("format");
		String cityName = req.getParameter("cityName");
		String stateName = req.getParameter("stateName");
		String countyName = req.getParameter("countyName");
		
		// If format is not null and it's 'json' 
		if (format != null && format.equalsIgnoreCase("json")) {
			data = LocationService.getCountiesAsJson(countyName, 
                                                     stateName, cityName);
			resp.setContentType("text/plain");
		}		

		// Write response
		// Get writer for servlet response
		PrintWriter writer = resp.getWriter();
		writer.println(data);
		writer.flush();
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		super.doPost(req, resp);
	}
}

Create the JSP TagLib control

The cascading drop-down control works as follows:

  • The page is rendered with an empty SELECT control.
  • The user selects the SELECT control. When it receives focus, an asynchronous call is made to retrieve the values from the server.
  • The server sends the JSON array of values back to the client.
  • The client dynamically populates the SELECT control with the values in the JSON array.
  • After the user selects a value from the list and the control loses focus (upon the blur event), the controls that are dependent on the current field are cleared. This is done to preserve data integrity (if the State value is change, the City value likely won't be valid).

The <ajax:page/> control renders the common functions that are usable by all cascading drop-down controls on a page, and the <ajax:dropdown/> control renders the JavaScript specific for an individual control instance.

Render the SELECT control for the cascading drop-down control

The rendering of the SELECT control is straightforward. The event handlers for the onfocus and onblur events are rendered as shown in Listing 22.

Listing 22. Render the SELECT control for the cascading drop-down control
...

/**
 * The getSelectControlHtml method returns the html code to render the drop down (html
 * select) control. 
 * @return Html code for drop down (html select) control
 */
protected String getSelectControlHtml() {
	StringBuffer html = new StringBuffer(); 
	// Render dropdown/select control
	html.append("<select id='");
	html.append(this.getId()); 
	// Render on focus event handler
	html.append("' onfocus='");
	html.append(this.getId());
	html.append("_onSelect(this)'"); 
	// Render on change event handler
	html.append(" onChange='");
	html.append(this.getId());
	html.append("_onChange(this)'"); 
	// Render css class if specified
	if (this.getCssclass() != null && this.getCssclass().length() > 0) {
		html.append(" class='");
		html.append(this.getCssclass());
		html.append("'");
	}		 
	// Render width if applicable (not 0/default/auto-fit)
	if (this.getWidth() > 0) {
		html.append(" style='width:");
		html.append(this.getWidth());
		html.append("px'");
	} 
	html.append("/>");
 
	return html.toString();
}
...

Event handlers for the cascading drop-down control

In the onSelect event handler, the values for the controls, from which the current control cascades, are retrieved, and the URL is built to send the asynchronous request to the server. Upon receiving the response, the SELECT tag is populated with values returned in the JSON Array using JavaScript (see Listing 23).

Listing 23. On-select event handler
function stateName_onSelect(curControl) {
	if(curControl.options.length > 0) {
		return;
	}
 
	clearOptions(curControl);
 
	// Set waiting message in control
	var waitingOption = new Option('Retrieving values...','',true,true);
	curControl.options[curControl.options.length]=waitingOption;
 
	// The dataUrl is built dynamically based on the cascadeTo control
	var dataUrl = '/TestWebSite/State?format=json&stateName=' + 
		getSelectedValue('stateName');
 
	// Initialize the XMLHttpRequest object
	initializeXmlHttpRequest();
 
	// If initialization was successful
	if (req!=null) {
		// Set callback function
		req.onreadystatechange=stateName_onServerResponse;
		// Set status text in browser window
		window.status='Retrieving State data from server...';
		// Open asynchronous server call
		req.open('GET',dataUrl,true);
		// Send request
		req.send(null);
	}
}

The following things happen in the server-response handler CONTROL-NAME_onServerResponse, which is dynamically generated by the cascading drop-down control tag:

  • Ignore status changes unless Loaded
  • Notify the user if an error occurred during the asynchronous call
  • Get the current control, and clear all OPTION elements
  • Get the response data, and convert it to an array containing strings
  • Populate the SELECT control using the array

Listing 24 is dynamically rendered by the <ajax:dropdown/> control.

Listing 24. Server-response handler (generated dynamically by the control)
function cityName_onServerResponse() {
	// If not finished, then return
	if(req.readyState!=4) {
		return;
	}
 
	// If an error occurred notify user and return
	if(req.status != 200) {
		alert('An error occurred retrieving data.');
		return;
	}
 
	// Get current control
	var curControl = document.getElementById('cityName');
 
	// Clear options 
	clearOptions(curControl);
 
	// Get response data
	var responseData = req.responseText;
 
	// Convert to array
	var dataValues=eval('(' + responseData + ')');
 
	// Populate SELECT tag with OPTION elements
	populateSelectControl(curControl, dataValues);window.status='';
}

The populateSelectControl function, which is generated by the <ajax:page/> tag, adds a blank OPTION to the SELECT control, as well as an OPTION element for each value in the dataValues array. The dynamically generated code fragment is shown in Listing 25.

Listing 25. Populate the SELECT control
function populateSelectControl(curControl, dataValues) {
	// Append blank option 
	var blankOption= new Option('','',false,true);
	curControl.options[curControl.options.length]=blankOption;

	// Iterate through data value array
	for (var i=0;i<dataValues.length;i++) {
		// Create option
		var newOption= new Option(dataValues[i],dataValues[i],false,false);
	 
		// Add option to control options
		curControl.options[curControl.options.length]=newOption;
	}
}

In the onChange event handler, all controls that are dependent on the current control are cleared (see Listing 26).

Listing 26. On-change event handler
function stateName_onChange(curControl) {
	// Array dynamically generated by the control
	var toList=['cityName','countyName'];
 
	// If no controls are dependent on this function, simply return
	if (toList == null || toList.length == 0)  {
		return;
	}
	 
	// Iterate through list of controls that are dependent on 
	// the current control
	for (var i=0; i < toList.length; i++) {
		// Get current control name
		var curControlName = toList[i];
	 
		// Get current control
		var curToControl = document.getElementById(curControlName);
	 
		// If control not found, then exit
		if (curToControl == null) return;
	 
		// Clear the current control
		clearOptions(curToControl);
	}
}

The clearOptions function, which removes all items in the parent SELECT control, is rendered in the <ajax:page/> control (see Listing 27).

Listing 27. On-change event handler
function clearOptions(curControl) {
	// If current control is null then exit
	if (curControl == null)  {
		alert('Unable to clear control');
		return;
	}
 
	// Check if control is already blank and return if it is
	if (curControl.options.length < 1) {
		return;
	}
 
	// Clear the options 
	curControl.options.length = 0;
}

Listing 28 shows the cascading drop-down control's TagLib library definition entry (with embedded comments for a description of each attribute).

Listing 28. Cascading drop-down TagLib library definition entry
<tag>
	<name>dropdown</name>
	<tagclass>com.testwebsite.controls.DropDownTag</tagclass>
	<bodycontent>empty</bodycontent>
	<info>
		Populates Drop Down control asynchronously cascading values.
	</info>
	<!-- Unique identifier for control -->
	<attribute>
		<name>id</name>
		<required>true</required>
		<rtexprvalue>false</rtexprvalue>
	</attribute>
	<!-- Url for Value Provider -->
	<attribute>
		<name>dataurl</name>
		<required>true</required>
		<rtexprvalue>true</rtexprvalue>
	</attribute>	 
	<!-- Message displayed while retrieving values from Value Provider -->
	<attribute>
		<name>updatemessage</name>
		<required>false</required>
		<rtexprvalue>false</rtexprvalue>
	</attribute>
	<!-- CSS class name -->
	<attribute>
		<name>cssclass</name>
		<required>false</required>
		<rtexprvalue>false</rtexprvalue>
	</attribute>
	<!-- Current control value-->
	<attribute>
		<name>value</name>
		<required>false</required>
		<rtexprvalue>true</rtexprvalue>
	</attribute>
	<!-- Comma separated list of control id from which the current 
		control cascades -->
	<attribute>
		<name>cascadefrom</name>
		<required>false</required>
		<rtexprvalue>true</rtexprvalue>
	</attribute>
	<!-- Comma separated list of control id to which the current control cascades -->
	<attribute>
		<name>cascadeto</name>
		<required>false</required>
		<rtexprvalue>true</rtexprvalue>
	</attribute>
	<!-- Width of control -->
	<attribute>
		<name>width</name>
		<required>false</required>
		<rtexprvalue>true</rtexprvalue>
	</attribute>
</tag>

Build the test Web pages

The next step is to build sample pages to test the Ajax-enabled controls. You'll test the <ajax:autocomplete/> control with the Create New Contact page, and you'll test the <ajax:dropdown/> control with the Create New Employee page.

Create a new contact

Figure 9 shows the test Create New Contact page, which demonstrates how the auto-complete control looks from a user perspective.

Figure 9. Create New Contact page demonstrates how to use the auto-complete control
Create New Contact page demonstrates how to use the auto-complete control

The JSP code for this test page is shown in Listing 29.

Listing 29. Sample page to demonstrate using the auto-complete control
<%@ page contentType="text/html; charset=ISO-8859-5" %>
<%@ taglib prefix="ajax" 
	uri="/WEB-INF/tlds/ajax_controls.tld"%>
<html>
<head>
<title>New Contact Information</title>
 
<link href="core.css" rel="stylesheet" 
	type="text/css" />
<ajax:page/>
</head>
 
<body>
<div id="container">
<form>
<div class="dialog">
<div class="dialogTitle">
Contact Information
</div>						 
<div class="contentPane"> 
<div style="font-weight:bold">First Name:</div>
<div>
<input type="text" id="firstName" 
	size="40"/>
</div>
				 
<div style="font-weight:bold">Last Name:</div>
<div>
<input type="text" id="lastName" 
	size="40"/>
</div>
				 
<div style="font-weight:bold">Address:</div>
<div>
<input type="text" id="streetAddress" 
	size="40"/>
</div>

<div style="font-weight:bold">City:</div>
<div>
<ajax:autocomplete id="cityName" width="40"  
	providerclass="com.testwebsite.bll.CityValueProvider"/>
</div>

<div style="font-weight:bold">County:</div>
<div>
<input type="text" id="countyName" 
	size="40"/>
</div>
					 
<div style="font-weight:bold">Zip Code:</div>
<div>
<input type="text" id="zipCode" 
	size="40"/>
</div>
</div>
<div class="buttonPane">
<input type="reset" />&nbsp;
<input type="submit" value="Save"/>
</div>								 
</div>
</form>
</div>				 
</body>
</html>

The City Name field is now Ajax enabled. As the user types text in the City Name field, suggestions are dynamically displayed, similar to Google's auto-suggest.

Create a new employee

Figure 10 shows the Create New Employee page, which illustrates the cascading drop-down control from a user perspective.

Figure 10. Test page demonstrating the cascading drop-down control
Test page demonstrating the cascading drop-down control

The JSP code for this page is shown in Listing 30.

Listing 30. Sample page to demonstrate using the cascading drop-down control
<%@ page contentType="text/html; charset=ISO-8859-5" %>
<%@ taglib prefix="ajax" uri="/WEB-INF/tlds/ajax_controls.tld"%>

<html>
<head>
<title>New Employee</title>

<ajax:page/>	 

<link href="core.css" rel="stylesheet" 
	type="text/css" />
</head>

<body>
<div id="container">
<form>
<table class="dialog" cellspacing="0" 
	cellpadding="0">
<thead>
<tr>
<td class="dialogTitle" colspan="2">
Employee Information
</td>
</tr>
</thead>
<tbody>
<tr>
<td class="fieldLabel">
Last Name:					 
</td>
<td class="fieldValue">
<input type="text" id="lastName"
	size="40"/>
</td>
</tr>

<tr>
<td class="fieldLabel">
First Name:					 
</td>
<td class="fieldValue">
<input type="text" id="firstName" 
	size="40"/>
</td>
</tr>


<tr>
<td class="fieldLabel">
Address:					 
</td>
<td class="fieldValue">
<input type="text" id="streetAddress" 
	size="40"/>
</td>
</tr>

<tr>
<td class="fieldLabel">
State:					 
</td>
<td class="fieldValue">
<ajax:dropdown id="stateName" dataurl="/State" 
	width="240"
updatemessage="Retrieving State data from server..."
cascadeto="cityName,countyName" />
</td>
</tr>

<tr>
<td class="fieldLabel">
City:					 
</td>
<td class="fieldValue">
<ajax:dropdown id="cityName" dataurl="/City"
updatemessage="Retrieving City data from server..."
cascadeto="countyName" width="240"
cascadefrom="stateName" />
</td>
</tr>

<tr> 
<td class="fieldLabel">
County:					 
</td>
<td class="fieldValue">
<ajax:dropdown id="countyName" dataurl="/County" 
updatemessage="Retrieving County data from server..."
cascadefrom="stateName,cityName" width="240"/>
</td>
</tr>

<tr>
<td class="fieldLabel">
Zip Code:					 
</td>
<td class="fieldValue">						 
<input type="text" id="zipCode" 
	size="40" />
</td>
</tr>			 

</tbody>

<tfoot align="right" class="buttonPane">
<tr>
<td colspan="2">
<input type="reset" />&nbsp;
<input type="submit" value="Save"/>
</td>
</tr>
</tfoot>
</table>
</form>
</div>				 
</body>
</html>

Conclusion

In this article, you learned a few asynchronous communication techniques and how you can add JSON and Ajax to business-line applications through reusable JSP TagLib controls. Business-line applications can significantly benefit from Ajax-based controls thanks to the improved user experience and more responsive and intuitive user interfaces. The code isn't tremendously complex; you just need to integrate the key blocks (JavaScript, CSS, and J2EE technologies) to build the Ajax-enabled JSP controls.

You can extend the controls further to do the following:

  • Support cascading to/from other types of controls (in addition to SELECT controls)
  • Add mouse-event handling to the auto-complete control
  • Add encoding and checking for asynchronous requests

Downloads

DescriptionNameSize
Contains all source code for this article1ArticleCodeSample.zip50KB
Contains MySQL database scriptsfor this article2ArticleDatabaseScripts.zip50KB

Notes

  1. This ZIP file contains all source code for the article.
  2. This ZIP file contains all MySQL database scripts.

Resources

Learn

Get products and technologies

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=336424
ArticleTitle=Building Ajax-enabled auto-complete and cascading drop-down controls
publish-date=09092008