[ Microsoft WebBuilder logo ]

February 1999

Capturing User Input from the Keyboard

When a user types into a text box (for example, a TEXT or TEXTAREA element) the most common way to register the event is with the onChange event handler. But the Change event is only triggered when the user leaves the text box by either tabbing or clicking out of the field. What if you want to know what the user's doing while he's in there typing? Both IE 4 and Navigator 4 have come up with a way to do that by providing several new functions for capturing user input from the keyboard, key by key.

These new event handlers, onKeyDown, onKeyPress, and onKeyUp, can be used to trap keyboard events whenever a text box has focus. In this article, we'll look at how you can use one of these new event handlers, onKeyUp, to capture individual characters that the user types and then use those characters to find text strings on a Web page.

The new keyboard event handlers

The onKeyDown event handler fires when the user presses the key far enough to make contact, while the onKeyUp handler fires when electrical contact with the key breaks. The onKeyPress event handler fires when there has been one complete combination of KeyDown and KeyUp events.

Once you trap a keyboard event, you also need to get the character value of the key that was pressed. Fortunately, the character value of a key is a property of the keyboard event object that's created every time an event occurs. Once the key value is captured, however, it also needs to be converted from the way it's represented in the system (its character code) to an alphanumeric character that we recognize. We'll show you how to do all that in this article.

Welcome to the Monroe County Mall

The example we've worked up to illustrate the onKeyup event handler is a store directory for the fictitious Monroe County Mall. Monroe County Mall's directory page is displayed in Figure A.

Figure A: You can find a store on the Monroe County Mall's directory page by typing just a partial name in the text box.


The purpose of the page is to allow a user to type even the partial name of a store, and the page will find a match among the list of stores displayed. The name of the store that was found is highlighted by having an arrow appear next to the store name.

In Figure B, the user has typed in one character, "C", and the arrow points to the store Chairs for Everyone.

Figure B: Typing just one character finds the first store with that character in its name.


Then, in the Figure C, the user has typed in a second character, "a", and now the arrow points to the store named Captain Ahab's Pizza. As the user continues to type, the matching is refined until the store the user wants is highlighted.

Figure C: Typing a second character refines the search until the store the user is looking for is found.


Monroe County Mall's directory page uses frames, and Listing A contains the frame definition for the page.

Listing B shows the code for the list of stores that appears in the right-hand frame of our page, which we've named stores. Notice that each of the items on the list has been given a name. We'll use that name later to insert the arrow that highlights the store name.

Listing A: Frame definition file for the Monroe County Mall directory page

<HTML>
<FRAMESET cols = "250, *" >
	<FRAME name = "message" src = "message2.htm">
	<FRAME name = "stores" src = "stores2.htm">
</FRAMESET>
</HTML>
Listing B: Code for the stores frame
<HTML>
<HEAD>
<TITLE> Monroe County Mall store listing</TITLE>
<STYLE>
LI {list-style:none}
</STYLE>
</HEAD>
<BODY >
<UL>  
	<LI name = "Bonmot" > Bonmot's </LI>
	<LI name = "Chairs" > Chairs for Everyone </LI> 
	<LI name = "Captain" > Captain Ahab's Pizza </LI>
	<LI name = "Donuts" > Donuts by Dolores </LI> 
	<LI name = "Everything"> Everything's A Dollar 
		Ninety-Nine  </LI>  
	<LI name = "Gilda's" > Gilda's</LI>
	<LI name =  "Greasy" >The Greasy Spoon  </LI>
	<LI name = "Harry"> Harry's House of 
		Haberdashery  </LI>
	<LI name = "KiddleDoodle" >  KiddleDoodle</LI> 
	<LI name =  "Sixties" > The Sixties Store </LI>
	<LI name = "Tilda" > Tables Settings by Tilda </LI>
	<LI name = "Victoria" > Victoria's Broom 
		Closet </LI>
</UL>
</BODY>
</HTML>

Finding a store at the Mall

The file that appears in the left-hand frame on our page, which we've named message, contains the code that actually does all the work. The code for this file appears in Listing C. If you start by looking at the BODY code, you'll see that it contains a text box (called inbox) and two buttons. When the user releases a key after typing a character into the text box, the event is captured by the onKeyUp handler, which calls the findstore function, passing it the event object that was trapped.

Listing C: Code for the message frame

<HTML>
<HEAD>
<TITLE> index page</TITLE>
<STYLE>
IMG {float:left;height:10;width:10}
P {font-size: 14; font-family: palatino; 
	font-weight: 400; text-align: center}
P.sell {font-size: 20; font-family: arial; 
	font-weight: bold}
</STYLE>
<SCRIPT language = "JScript">
var storename = new String;
var newstore = new String;
var first = true;
var myelement;
var mystring = new String;
var mytext = new String;
  
function findstore(e) {
	var bodyText = parent.stores.document.
		body.createTextRange();

	var charCode = String.fromCharCode(e.keyCode);  

	if ((charCode >= "a" && charCode <= "z") || 
		(charCode >= "A" && charCode <= "Z" )) {
		if (bodyText.findText(inbox.value)) {
			
			//Remove arrow pointing to store previously 
			//found by replacing its original contents
			if ( !first) 
				myelement.innerText = mytext;
			myelement = bodyText.parentElement();
			mytext = myelement.innerText;
			myelement.innerHTML = "<img src = 
				'R_arrow.gif' align = left>" + mytext;
			first = false;
		}
	}
}

function showstores() {
   parent.stores.document.location = "stores2.htm";
}

function showmap() {
	if (inbox.value != "") 
		parent.stores.document.location = myelement.name 
		+ ".htm";
	//Empty the text box
	inbox.value = "";
}
</SCRIPT>
</HEAD>
<BODY onload = "inbox.value = "">
<P class = sell>
	Welcome to the Monroe County Mall.
<BR>
	We sell EVERYTHING! 
<P>
To find the store from our listing, begin 
	entering the name of the store
		 you are looking for in the 
text box. Don't worry if you don't know the full 
name - even a partial name will do! <BR>
<BR>
<INPUT name = "inbox" type = "text" 
	onkeyup="findstore(event)" >
<BR>
<BR>
<INPUT name = "map" type = "button" value = 
	"Mall Map" onclick ="showmap()" >
<BR>
<INPUT name = "map" type = "button" value = 
	"Store Listing" onclick ="showstores()" >
</BODY>
</HTML>
The first thing findstore does is create a textRange object, named bodyText, over the BODY of the document in the stores frame; that is, the document displaying our store listing. Then the statement


var charCode =
	String.fromCharCode(e.keyCode);
accomplishes several things. The keyCode property of e, the event that was passed to the function, is sent to the String method fromCharCode. This converts the character code representation of our character to an alphanumeric character, which is then assigned to the variable charCode.

The if statement


if ((charCode >= "a" && charCode <= "z") || 
	(charCode >= "A" && charCode <= "Z" ))
checks to make sure that the character is a letter, and if it is, we process the rest of the statements in the if block. The statement

if (bodyText.findText(inbox.value)) {
is responsible for finding the store name that most closely matches the partial string currently in the text box. Since bodyText points to all of the text in the document containing our store listing, it can use the textRange method findtext to find matching text in that document. If matching text is found, bodyText now points to the text (that is, some part of a store name within an LI element) that it found.

Your store here

The last tricky little piece of code in this function is responsible for placing the arrow next to the store name that was found. All we currently have is the partial store name within an LI element that matches the string in the text box. What we want is the entire text string so we can then add a graphic to it. Here are the statements that accomplish that:

myelement = bodyText.parentElement();
mytext = myelement.innerText;
myelement.innerHTML = "<img src  = 
	'R_arrow.gif' align = left>" + mytext;
First, myelement is assigned the parent element of the text we've found, which its LI element. Then, we get the entire text in that LI element using the innerText property (remember, it's read/write so we can get its value as well as change its value) and assign it to mytext. Finally, we assign a new value for the LI element using the innerHTML property, adding the IMG tag with our arrow image to the original text we copied into mytext.

Who's on first?

The only thing we haven't explained in this function is the Boolean variable first, which we declared as a global in the beginning of our script. We need that Boolean variable because as the user moves from store name to store name, we want to display only one arrow. The first time we assign an arrow to an element, we don't need to worry, but after the first time, we need to strip the arrow off of the element we previously found by replacing its original text. Since the original text has been saved in the mytext variable, we can put it back with the statement:


if ( !first) 
	myelement.innerText  = mytext;
Using the variable first in this way is an excellent illustration of the power of Boolean variables in your scripts. We'll talk more about Boolean variables in a future issue of Microsoft Web Builder.

If you need a map...

There are two other functions in Listing C: showstores and showmap. They're simple enough--when the user clicks on the Mall Map button, showmap is called and a map showing the location of the selected store is loaded into the stores frame with the statement

parent.stores.document.location = 
	myelement.name + ".htm";
This is shown in Figure D. The function showstores simply replaces the map with the original store listing.

Figure D: Clicking on the Mall Map button brings up a map of the Mall, showing the location of the selected store.


Notice that we used the strategy of concatenating the name of the element with ".htm" to get that element's file name.

Conclusion

We touched on quite a few DHTML features as we developed our simple store directory example: the onkeyUp event handler; the textRange's findtext method and parentElement property; the keycode property of the event object; the String method changeCode; and the element properties innerText and innerHTML. We also did a little more dynamic frame loading. If you've developed a project using some of these same features, we'd love to hear about it, and maybe even share it with our readers. Send email to webbuilder@zdjournals.com.

Copyright © 1999, ZD Inc. All rights reserved. ZD Journals and the ZD Journals logo are trademarks of ZD Inc. Reproduction in whole or in part in any form or medium without express written permission of ZD Inc. is prohibited. All other product names and logos are trademarks or registered trademarks of their respective owners.