Saturday, December 19, 2009

Auto-Completing Form Fields











Auto-Completing Form Fields


A first-rate way to help your site's visitors is to lessen the drudgery of data entry into fields. Helping them fill out forms that have a large number of choices saves them time and effort, and additionally helps provide your site with valid data.


For this example, Script 15.11 (HTML), Script 15.12 (CSS), and Script 15.13 (JavaScript) automatically show a list of U.S. states that match the letters the user types into a form field (Figure 15.6). As the user continues typing, the list shrinks until there is only one state left; this is then automatically put into the entry field, and the list goes away.


Script 15.11. This simple HTML provides the form field that will be auto-completed.





<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">
<html>
<head>
<title>My Fifth Ajax Script</title>
<link rel="stylesheet" rev="stylesheet" href="script05.css" />
<script src="script05.js" type="text/javascript" language="Javascript">
</script>
</head>
<body>
<form action="#">
Please enter your state:<br />
<input type="text" id="searchField" autocomplete="off" /><br />
<div id="popups"> </div>
</form>
</body>
</html>




Script 15.12. The CSS here styles the search field and the pop-up menu.





body, #searchfield {
font: 1.2em arial, helvetica, sans-serif;
}

.suggestions {
background-color: #FFF;
padding: 2px 6px;
border: 1px solid #000;
}

.suggestions:hover {
background-color: #69F;
}

#popups {
position: absolute;
}

#searchField.error {
background-color: #FFC;
}




Script 15.13. This JavaScript handles the server request and the pop-up display.





[View full width]
window.onload = initAll;
var xhr = false;
var statesArray = new Array();

function initAll() {
document.getElementById("searchField"). onkeyup = searchSuggest;

if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
}
else {
if (window.ActiveXObject) {
try {
xhr = new ActiveXObject ("Microsoft.XMLHTTP");
}
catch (e) { }
}
}

if (xhr) {
xhr.onreadystatechange = setStatesArray;
xhr.open("GET", "us-states.xml", true);
xhr.send(null);
}
else {
alert("Sorry, but I couldn't create an XMLHttpRequest");
}
}

function setStatesArray() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.responseXML) {
var allStates = xhr.responseXML. getElementsByTagName("item");
for (var i=0; i<allStates.length; i++) {
statesArray[i] = allStates[i]. getElementsByTagName ("label")[0].firstChild;
}
}
}
else {
alert("There was a problem with the request " + xhr.status);
}
}
}

function searchSuggest() {
var str = document.getElementById ("searchField").value;
document.getElementById("searchField"). className = "";
if (str != "") {
document.getElementById("popups"). innerHTML = "";

for (var i=0; i<statesArray.length; i++) {
var thisState = statesArray[i]. nodeValue;

if (thisState.toLowerCase().indexOf (str.toLowerCase()) == 0) {
var tempDiv = document. createElement("div");
tempDiv.innerHTML = thisState;
tempDiv.onclick = makeChoice;
tempDiv.className = "suggestions";
document.getElementById("popups"). appendChild(tempDiv);
}
}
var foundCt = document.getElementById ("popups").childNodes.length;
if (foundCt == 0) {
document.getElementById ("searchField").className = "error";
}
if (foundCt == 1) {
document.getElementById ("searchField").value = document.getElementById(
"popups"). firstChild.innerHTML;

document.getElementById("popups"). innerHTML = "";
}
}
}

function makeChoice(evt) {
var thisDiv = (evt) ? evt.target : window.event.srcElement;
document.getElementById("searchField"). value = thisDiv.innerHTML;
document.getElementById("popups"). innerHTML = "";
}






Figure 15.6. As you type, the number of possible choices narrows.

[View full size image]




To build auto-completing form fields:
















1.

Please enter your state:<br />
<input type="text" id="searchField" autocomplete="off" /><br />
<div id="popups"> </div>




Here's the bit of HTML that we care about. The one tricky (and non-standards compliant) part: that autocomplete attribute. It tells browsers not to do any auto-completion on this field, as we'll be handling it with the script. While it isn't part of any W3C recommendations, autocomplete, like XMLHttpRequest itself, has excellent cross-browser support.


2.

document.getElementById ("searchField").onkeyup = searchSuggest;




In order to grab and process each keystroke, we need an event handler, and here's ours, set in initAll().


3.

xhr.onreadystatechange = setStatesArray;
xhr.open("GET", "us-states.xml", true);
xhr.send(null);




Unlike those photographs earlier in this chapter, the names of the United States aren't likely to change. We can read the XML file (Script 15.3) in once, initialize our array, and safely assume that our list will still be valid at the end of this session.


4.

if (xhr.responseXML) {
var allStates = xhr.responseXML. getElementsByTagName("item");
for (var i=0; i<allStates.length; i++) {
statesArray[i] = allStates[i]. getElementsByTagName("label") [0].firstChild;
}
}




Here's where we read that file in, looking at each item node, finding the label node inside, and then storing label's firstChildthe name of the state itself. Each of them goes into a slot in the StatesArray array.


5.

var str = document.getElementById ("searchField").value;
document.getElementById ("searchField").className = "";




When you start typing in the field, you'll end up here, in the searchSuggest() event handler function. We start off by getting the value of searchField, which is whatever has been typed so far. Next, we clear that field's class attribute.


6.

if (str != "") {
document.getElementById("popups"). innerHTML = "";




If nothing's been entered, we don't want to do anything, so there's a check here to make sure that the user's entry has a value before we start popping up possibilities. If there's something there, we then blank out the previous list of possibilities.


7.

for (var i=0; i<statesArray.length; i++) {
var thisState = statesArray[i]. nodeValue;




Now, we loop through the list of states, storing the current state we're looking at in thisState.


8.

if (thisState.toLowerCase().indexOf (str.toLowerCase()) == 0) {




We want to see if what they've entered so far is part of a state namebut that alone isn't sufficient; we also have to make sure that what they've entered is at the beginning of the name. If you type in Kansas, you don't want to see a drop-down box asking if you want Arkansas or Kansas, after all. And so long as we're doing that check, we'll also force the comparison to be lowercase on both sides before checking indexOf().


If indexOf() returns 0that is, the entered string was found starting at position 1 of thisStatethen we know we have a hit.


9.

var tempDiv = document. createElement("div");
tempDiv.innerHTML = thisState;
tempDiv.onclick = makeChoice;
tempDiv.className = "suggestions";
document.getElementById("popups"). appendChild(tempDiv);




Because this state is a possibility, we want to add it to the list that will display. That's done by creating a temporary div, setting its innerHTML to the name of the state, adding an onclick handler and className, and then appending the whole to the popups div. Adding each state as a separate div allows us to manipulate each using JavaScript and CSS.


10.

var foundCt = document. getElementById("popups"). childNodes.length;




When we've looped through all the states, we're done setting up the popupsbut how many do we have? We calculate that, the foundCt, here.


11.

if (foundCt == 0) {
document.getElementById ("searchField").className = "error";
}




If foundCt is 0, they've entered something unexpected. We let them know that by setting the className to "error", which causes the entry field to display with a pale yellow background (based on a CSS style rule in Script 15.12).


12.

[View full width]
if (foundCt == 1) {
document.getElementById ("searchField").value = document.getElementById ("popups")
.firstChild. innerHTML;
document.getElementById ("popups").innerHTML = "";
}




If foundCt is one, however, we know that they've got a unique hit, so we can then put that state into the entry field. If they've typed in ca, they shouldn't have to type in lifornia also; we already know which state they want. We give them the full state by using the single div in popups to fill in the entry field, and then we blank out the popups div.


13.

function makeChoice(evt) {
var thisDiv = (evt) ? evt.target : window.event.srcElement;
document.getElementById ("searchField").value = thisDiv.innerHTML;
document.getElementById ("popups").innerHTML = "";
}




Another way the user can enter a state name is to click one from the pop-up list. In that case, the makeChoice() event handler function is called. First, we figure out which state the user clicked by looking at the target of the event, and that gives us a particular div. Looking at the innerHTML for that div gives us the state name, and we put that into the entry field. And finally, we clear out the pop-up list of possibilities.



Tips




  • You can see an advanced example of this technique at Google Suggest (labs.google.com/suggest/). As you type into what looks like the usual Google search field, a pop-up list appears with search results. As you type, the search results are continually filtered in the list.

  • You may have noticed that this task and the last spent a lot more time and effort making things look good than actually demonstrating Ajax, XML, and server-side technologies. That's because much of what has come to be known (or at least thought of by some) as Ajax involves not just the underlying technology but also the way the technology works. How to make that all much simpler is the goal of Chapter 16.














No comments: