5.2. Building a Suggestion Field
Building the suggestion field is the most fun but also the most complicated part of the
Figure 5-2. The Ajax Customer Management page
When you type "M," the application finds all the
Figure 5-3. Ajax suggestion field lookup for one record
This application takes a few shortcuts. The first is the lookup: it doesn't go to the database and look up the matching set of names for every character that's typed. We could implement it that way, but it wouldn't improve the application and it would require more
Even though Ajax applications are more
The HTML code for the form is presented in Example 5-4. Example 5-4. The Ajax Customer Management code
A Cascading Style Sheet is used to set up the div that is needed for the suggestion field. Here's the code from oreillyajax.css :
div.suggestions {
position: absolute;
-moz-box-sizing: border-box;
box-sizing: border-box;
border: 1px solid blue;
}
div.suggestions div {
cursor: default;
padding: 3px;
}
div.suggestions div.current {
background-color: #3366cc;
color: white;
}
The suggestion div is set up for absolute positioning; that is, it is positioned according to fixed coordinates. Those coordinates are set in the oreillySuggest.js JavaScript file, based on the location of the username input field. So, the div's initial values are set in the CSS file, but the final values are set by the JavaScript functions. This strategy gives the program flexibility to adapt to changes in the HTML: the div remains anchored to the bottom of the username input field, even if that field moves around as the application grows. Figure 5-4 shows how the suggestion field looks on the screen. Figure 5-4. Suggestion field on the Customer Management page
Now the JavaScript file, oreillySuggest.js , comes into play. This file is loaded and the init( ) function is called to set up the ajax_username field:
window.onload = function ( ) {
init("ajax_username");
}
The
init( )
function does a lot of the
function init(field) {
inputTextField = document.getElementById(field);
cursor = -1;
createDebugWindow( );
fillArrayWithAllUsernames( );
inputTextField.onkeyup = function (inEvent) {
if (!inEvent) {
inEvent = window.event;
}
keyUpHandler(inEvent);
}
inputTextField.onkeydown = function (inEvent) {
if (!inEvent) {
inEvent = window.event;
}
keyDownHandler(inEvent);
}
inputTextField.onblur = function ( ) {
hideSuggestions( );
}
createDiv( );
}
5.2.1. Retrieving the UsernamesThe init( ) function loads the usernames by calling fillArrayWithAllUsernames( ) , which sets up the call to the server:
function fillArrayWithAllUsernames( ) {
var url = "/ajax-customer-lab5-1/lookup?username=*"+"&type="+ escape("3");
if (window.XMLHttpRequest) {
req = new XMLHttpRequest( );
}
else if (window.ActiveXObject) {
req = new ActiveXObject("Microsoft.XMLHTTP");
}
req.open("Get",url,true);
req.onreadystatechange = callbackFillUsernames;
req.send(null);
}
Nothing really new is happening here. We create a URL, get an
XMLHttpRequest
object to communicate with the server, register
callbackFillUsernames( )
as the callback function, and send the URL to the server. The URL
/ajax-customer-lab5-1/lookup?username=*&type=3
private String getAllUsers( ) {
Connection con = DatabaseConnector.getConnection( );
ResultSet result = null;
StringBuffer returnSB = null;
try {
Statement select = con.createStatement( );
result = select.executeQuery("SELECT USERNAME from USERS;");
returnSB = new StringBuffer( );
while (result.next( )) {
returnSB.append(result.getString("username") + ",");
}
returnSB.deleteCharAt(returnSB.length( ) - 1);
catch (SQLException e) {
// you could pop up a window with Ajax to let users know
// there is a problem
} finally {
if (con != null) {
try {
con.close( );
} catch(SQLException e) {
}
}
}
return returnSB.toString( );
}
}
The usernames are then collected into one long comma-separated string, using a StringBuffer . The resulting string is then sent back to the client as is: no XML, no JSON wrapping. It's simple and effective. When the server returns the data, the browser calls callbackFillUsernames( ) :
function callbackFillUsernames( ) {
if (req.readyState==4) {
if (req.status == 200) {
populateUsernames( );
}
}
}
The next step, the call to populateUsernames( ) , occurs only if the request has reached the ready state (i.e., the server has returned a result) and the request's status is 200 (success). populateUsernames( ) parses out the usernames from a comma-separated string and loads them into a JavaScript array. The String.split( ) JavaScript function does the conversion:
function populateUsernames( ) {
var nameString = req.responseText;
debugInfo('name array'+nameString);
var nameArray = nameString.split(',');
lookAheadArray = nameArray;
}
At this point, the
lookAheadArray
is loaded with usernames; the program is ready to interpret the
5.2.2. Creating the Divinit( ) 's final act is to call createDiv( ) , which sets up the div:
function createDiv( ) {
suggestionDiv = document.createElement("div");
suggestionDiv.style.zIndex = "2";
suggestionDiv.style.opacity ="0.8";
suggestionDiv.style.repeat = "repeat";
suggestionDiv.style.filter = "alpha(opacity=80)";
suggestionDiv.className = "suggestions";
suggestionDiv.style.visibility = "hidden";
suggestionDiv.style.width = inputTextField.offsetWidth;
suggestionDiv.style.backgroundColor = "white";
suggestionDiv.style.autocomplete = "off";
suggestionDiv.style.backgroundImage = "url(http://flylib.com/books/4/163/1/html/2/transparent50.png)";
suggestionDiv.onmouseup = function( ) {
inputTextField.focus( );
}
suggestionDiv.onmouseover = function(inputEvent) {
inputEvent = inputEvent window.event;
oTarget = inputEvent.target inputEvent.srcElement;
highlightSuggestion(oTarget);
}
suggestionDiv.onmousedown = function(inputEvent) {
inputEvent = inputEvent window.event;
oTarget = inputEvent.target inputEvent.srcElement;
inputTextField.value = oTarget.firstChild.nodeValue;
lookupUsername(inputTextField.value);
hideSuggestions( );
debugInfo("textforLookup"+oTarget.firstChild.nodeValue);
}
document.body.appendChild(suggestionDiv);
}
Most of this code sets various properties of the div, including:
For a full list of the properties that can be set, refer to Cascading Style Sheets: The Definitive Guide , by Eric A. Meyer (O'Reilly). Ajax techniques become even more portable and powerful when combined with style sheets and when using the DOM to modify the characteristics of the HTML page. 5.2.3. Handling the EventsNow we're looking at the heart of the JavaScript: the event handlers that actually make the suggestion field work. The first event we'll investigate is onkeyup . Back in init( ) , we registered the keyUpHandler( ) function as an event handler to be called whenever a key-up event (i.e., when a key is pressed and released) occurs in ajax_username . The keyUpHandler( ) function looks like this:
function keyUpHandler(inEvent) {
var potentials = new Array( );
var enteredText = inputTextField.value;
var iKeyCode = inEvent.keyCode;
debugInfo("key"+iKeyCode);
if (iKeyCode == 32 iKeyCode == 8
( 45 < iKeyCode && iKeyCode < 112)
iKeyCode > 123) /*keys to consider*/
{
if (enteredText.length > 0) {
for (var i=0; i < lookAheadArray.length; i++) {
if (lookAheadArray[i].indexOf(enteredText) == 0) {
potentials.push(lookAheadArray[i]);
}
}
showSuggestions(potentials);
}
if (potentials.length > 0) {
if (iKeyCode != 46 && iKeyCode != 8) {
typeAhead(potentials[0]);
}
showSuggestions(potentials);
}
else {
hideSuggestions( );
}
}
}
The keyUpHandler( ) function saves the current value of the input field in the enteredText variable and saves the last key pressed in iKeyCode . It then checks whether this key was valid; if so, it executes a loop that checks enteredText against the strings in the lookAheadArray . Strings matching the beginning of enteredText are saved in the array potentials . If there are potential matches, the suggestion div is displayed with the call showSuggestions(potentials) . Otherwise, the suggestion div is hidden.
Other handlers come into effect when the div is shown. The program keeps track of
mouseover
,
function keyDownHandler(inEvent) {
switch(inEvent.keyCode) {
/* up arrow */
case 38:
if (suggestionDiv.childNodes.length > 0 && cursor > 0) {
var highlightNode = suggestionDiv.childNodes[--cursor];
highlightSuggestion(highlightNode);
inputTextField.value = highlightNode.firstChild.nodeValue;
}
break;
/* down arrow */
case 40:
if (suggestionDiv.childNodes.length > 0 &&
cursor < suggestionDiv.childNodes.length-1) {
var newNode = suggestionDiv.childNodes[++cursor];
highlightSuggestion(newNode);
inputTextField.value = newNode.firstChild.nodeValue;
}
break;
/* Return key = 13 */
case 13:
var lookupName = inputTextField.value;
hideSuggestions( );
lookupUsername(lookupName);
break;
}
}
The down-arrow and up-arrow keys change the highlighted element in the suggestion div. Pressing the up-arrow key decrements the cursor index, and retrieves the indexed node and highlights it. The contents of that node (a complete username) are then
If the
function lookupUsername(foundname) {
debugInfo('looking up :'+foundname);
var username = document.getElementById("ajax_username");
var url = urlbase+"/lookup?username=" + escape(foundname)+"&type="+
escape("2");
alert('url submitting:'+url);
if (window.XMLHttpRequest) {
req = new XMLHttpRequest( );
}
else if (window.ActiveXObject) {
req = new ActiveXObject("Microsoft.XMLHTTP");
}
req.open("Get",url,true);
req.onreadystatechange = callbackLookupUser;
req.send(null);
}
5.2.3.1. Highlighting a suggestion
The
highlightSuggestion( )
function is simple. Its argument is the node that's currently selected, which we want to highlight. The method
function highlightSuggestion(suggestionNode) {
for (var i=0; i < suggestionDiv.childNodes.length; i++) {
var sNode = suggestionDiv.childNodes[i];
if (sNode == suggestionNode) {
sNode.className = "current";
}
else if (sNode.className == "current") {
sNode.className = "";
}
}
}
The
onmouseover
and
onmousedown
events were set up back in the
init( )
function. The
onmouseover
event highlights the element that
suggestionDiv.onmouseover = function(inputEvent) {
inputEvent = inputEvent window.event;
sugTarget = inputEvent.target inputEvent.srcElement;
highlightSuggestion(sugTarget);
}
The onmousedown event selects the current node. It looks up the information associated with the current username, populates the form with that information, and hides the suggestion box:
suggestionDiv.onmousedown = function(inputEvent) {
inputEvent = inputEvent window.event;
sugTarget = inputEvent.target inputEvent.srcElement;
inputTextField.value = sugTarget.firstChild.nodeValue;
lookupUsername(inputTextField.value);
hideSuggestions( );
}
5.2.4. Configuring the ServletsAll that's left is the setup for the servlet, but that is really just a repeat of what we have done in previous chapters. The web.xml file, shown in Example 5-5, sets up the servlets that this application uses, which are:
Example 5-5. The web.xml file for the Ajax customer application
We've now created a complete Ajax application with a database on the backend. The application does a lot of visual processing, using JavaScript to manipulate the DOM. Yes, the database access is old-school, but it's generic enough to allow you to plug in your own backend access: JDO, Hibernate, or whatever other technology you choose. A lot can be done to improve this application. For example, the JavaScript is very flat; it could be improved using an object-oriented approach in which all the functions are associated with a JavaScript object created by the init( ) function. Still, this application is typical of what you'll do with Ajax.
Don't be afraid to play with the JavaScript. That is where most of the visual power lies. In the past, you would have had to
|