While typing “k,” “i,” “n” in a standard drop-down will result in a jump to the first list entry beginning with “k,” then the first beginning with “i,” then the first beginning with “n,”
Trang 1ing country names Some have worked around the problem of locating particular countries in these long lists by putting the more frequently-selected countries at the top,13 but this is hardly an ideal solution
It is possible to press the key that corresponds with the initial letter of an entry
in the list in order to jump to that entry; repeatedly hitting that key will move between list entries that begin with that letter This suggests an improvement: perhaps instead of keypresses triggering initial-letter searches only, they should accumulate into a string, which is matched as a whole While typing “k,” “i,” “n”
in a standard drop-down will result in a jump to the first list entry beginning with “k,” then the first beginning with “i,” then the first beginning with “n,” this could be changed so that those keypresses jump the selection to the first entry containing the string “kin.” That would probably be the United Kingdom (or the Kingdom of Tonga!), in the countries example
Functionality very similar to this is actually already present in both Safari and
Firefox Both of those browsers let you type a series of letters to match the start
of an entry in a drop-down list This example takes this feature a step further by searching for the string anywhere in the list item And it works in Internet Explorer
to boot! Unfortunately, Safari does not support handling keyboard events on drop-down lists with JavaScript As a result, the enhancement we will undertake
in this section will not apply to that browser
A number of further enhancements also suggest themselves: the current accumu-lated string should be displayed somewhere so that the user can see what they’ve entered, similar to Firefox’s “type-ahead find” feature It should also be possible,
as with type-ahead find, to press Backspace to remove the most recently-added
letter from the accumulated string Finally, after a period without typing, the accumulated string should be reset to blank to allow typing from scratch Here’s an example HTML file containing the countries list:
File: typeahead.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Type-ahead drop-down lists</title>
<script type="text/javascript" src="typeahead.js"></script>
13
Sometimes, developers place just one country at the top—the United States—leaving UK residents such as myself, and other non-Americans, to scroll through the ridiculously long list Hmph (Australi-ans don’t mind—Ed.)
Chapter 6: Forms and Validation
Trang 2</head>
<body>
<h1>Type-ahead drop-down lists</h1>
<form action="">
<p>
<select name="country">
<option value="AFG">Afghanistan</option>
<option value="ALB">Albania</option>
<option value="DZA">Algeria</option>
… <option value="ZAR">Zaire</option>
<option value="ZMB">Zambia</option>
<option value="ZWE">Zimbabwe</option>
</select>
</p>
</form>
</body>
</html>
The associated JavaScript should attach an event listener to each select element
in the document Browsers offer three events for handling pressed keys: keyup,
keydown, and keypress As we saw in Chapter 3, despite being the best-supported
of these properties, keypress is nonstandard, and a little limited In particular,
in some browsers it does not fire for “control” keys such as Backspace, which is
required by this script We’ll therefore use keydown for this script
In summary, we’ll create a library object as follows:
File: typeahead.js
var tADD = { addEvent: function(elm, evType, fn, useCapture) { … }, init: function() { … },
addKey: function(e) { … } }
tADD.addEvent(window, 'load', tADD.init, false);
This is mostly standard setup As only a single listener is required, we’ll put it all
in typeahead.js There’s nothing else in that file Here’s the init method:
File: typeahead.js (excerpt)
init: function() {
if (!document.getElementsByTagName) return;
var selects = document.getElementsByTagName('select');
for (var i = 0; i < selects.length; i++) { tADD.addEvent(selects[i], 'keydown', tADD.addKey, false);
Type-Ahead Drop-Down Lists
Trang 3tADD.addEvent(selects[i], 'keypress',
function(e) { if (e) e.preventDefault(); }, false); }
},
This decorates all select elements with a keydown event listener and a keypress
event listener The keydown listener, addKey, will implement the type-ahead be-havior The keypress listener is in place for one reason only: the Firefox browser
will navigate to the previous page when the user types Backspace, even if the
keydown event listener calls preventDefault to cancel the event To prevent this, the keypress event must be cancelled by its own listener
Here’s the keydown event listener:
File: typeahead.js (excerpt)
addKey: function(e) {
var t = window.event ? window.event.srcElement : e ?
e.target : null;
if (!t) return;
if (e && e.which) {
var code = e.which;
} else if (e && e.keyCode) {
var code = e.keyCode;
} else if (window.event && window.event.keyCode) {
var code = window.event.keyCode;
} else {
return;
}
var character = String.fromCharCode(code).toLowerCase();
if (t.timeout_key)
clearTimeout(t.timeout_key);
if (!t.keyword)
t.keyword = '';
if (code == 8) {
if (t.keyword != '')
t.keyword = t.keyword.substr(0, t.keyword.length - 1); } else if (code >= 32) {
t.keyword += character;
}
if (t.keyword == '') {
Chapter 6: Forms and Validation
Trang 4window.status = t.keyword = '';
} else { window.status = 'Searching: ' + t.keyword;
t.timeout_key = setTimeout(
function() { window.status = t.keyword = ''; }, 5000);
var gotoIndex = t.selectedIndex;
for (var i = 0; i < t.options.length; i++) {
if (t.options[i].text.toLowerCase().indexOf(t.keyword) != -1) {
gotoIndex = i;
break;
} } setTimeout(function() { t.selectedIndex = gotoIndex; }, 1); }
if (window.event) { window.event.cancelBubble = true;
window.event.returnValue = false;
} else if (e) { e.stopPropagation();
e.preventDefault();
} }
As described in Peter Paul Koch’s Event Properties summary14, the code of the pressed key is available from the keyCode or which properties of the event object (we get that object in the normal cross-browser way) Here’s the code that ensures that we have both an event object and a key at the end:
File: typeahead.js (excerpt)
var t = window.event ? window.event.srcElement : e ? e.target : null;
if (!t) return;
if (e && e.which) { var code = e.which } else if (e && e.keyCode) { var code = e.keyCode;
} else if (window.event && window.event.keyCode) { var code = window.event.keyCode;
14 http://www.quirksmode.org/
Type-Ahead Drop-Down Lists
Trang 5} else {
return;
}
Next, we convert the supplied code into a lowercase character: the character is converted to lowercase because the search through the drop-down will be case-insensitive There are also serious browser issues with case-sensitive keystroke detection
File: typeahead.js (excerpt)
var character = String.fromCharCode(code).toLowerCase();
Below, setTimeout is used to implement the five-second string reset timer men-tioned; if a timer is currently running, we cancel it, because a key has just been pressed We don’t want the typed-in string cleared halfway through the user typing it, even if they are a bit slow
File: typeahead.js (excerpt)
if (t.timeout_key)
clearTimeout(t.timeout_key);
The accumulated string of characters will be stored in a property of the select
element named keyword This property is created by the code, using (again) JavaScript’s handy ability to attach arbitrary properties to objects If the property does not exist, it is created as an empty string:
File: typeahead.js (excerpt)
if (!t.keyword)
t.keyword = '';
The Backspace key has a keyCode of 8 If Backspace has been pressed, and some
letters have accumulated, we remove the last accumulated letter:
File: typeahead.js (excerpt)
if (code == 8) {
if (t.keyword != '')
t.keyword = t.keyword.substr(0, t.keyword.length - 1);
If a key other than Backspace was pressed, then we add the corresponding
character to the accumulated string (as long as the key isn’t a control character;
we don’t want to add a line feed if Enter is pressed).15
15 http://www.js-x.com/syntax/key_codes.php provides a table of keycodes, including those generated
by control characters.
Chapter 6: Forms and Validation
Trang 6File: typeahead.js (excerpt)
} else if (code >= 32) { t.keyword += character;
}
Next, we set the message in the browser’s status bar to display the accumulated string, providing visual feedback to the user.16 If the accumulated string is empty (i.e if we’ve just backspaced away the last character), we empty the status bar
to match
File: typeahead.js (excerpt)
if (t.keyword == '') { window.status = '';
} else { window.status = 'Searching: ' + t.keyword;
Set a timeout to blank the accumulated string in five seconds’ time Note the use
of an anonymous function for simplicity
File: typeahead.js (excerpt)
t.timeout_key = setTimeout(
function() { window.status = t.keyword = ''; }, 5000);
Finally, we’ll iterate through the list entries in the drop-down until one that contains the accumulated string is found If one is found, we set it as the selected entry If not, we set the selected entry to remain as the currently selected entry
In either case, we set the selected entry after a tiny delay, because Mozilla browsers will do their own type ahead navigation immediately after this event listener runs (there is currently no way to prevent it), so our selection assignment must come
in after that
File: typeahead.js (excerpt)
var gotoIndex = t.selectedIndex;
for (var i = 0; i < t.options.length; i++) {
if (t.options[i].text.toLowerCase().indexOf(t.keyword) != -1) {
gotoIndex = i;
break;
}
16 This may not work in all browsers: the browser status bar has been so misused for hiding URLs or for scrolling messages that manipulation of its contents from JavaScript is now sometimes disabled
by default.
Type-Ahead Drop-Down Lists
Trang 7}
setTimeout(function() { t.selectedIndex = gotoIndex; }, 1);
Like many DHTML enhancements, this is a simple improvement over the existing in-browser functionality, and degrades neatly to doing nothing in browsers that
do not support it
The script does have the disadvantage that it’s not necessarily very discoverable; the only hint that a given drop-down list is using this new, more-usable method
of finding items is that the status bar changes to display the accumulated string, and, as noted, this may not take effect in some browsers On public Websites, therefore, this script won’t cause a problem, but it may not enhance usability as much as you might have expected On an intranet, or some other environment
in which users can undergo training that includes a description of how the en-hanced drop-down works, this feature can seriously improve the usability of long drop-down lists It may also be possible to display a tooltip, rather than a status bar message, when the user scrolls through the list with the keys, which would make the new behavior more apparent That’s an exercise for you to try for yourself!
Summary
In this chapter, we’ve seen the ways that DHTML can enhance form-filling, one
of the most common activities in any Web application We’ve seen how to im-plement the regular expression-based validation of form fields through DOM techniques We’ve also learned how to make life easier on developers by integrat-ing that validation with the equivalent validation that must be completed on the server There’s no need to write the same code twice in two languages
We then looked at enhancing individual form widgets to work in more complex ways, or to emulate more useful widgets from client-side applications Those en-hancements help overcome some limitations of Web browsers’ rather basic form implementations We also highlighted work that others have already done in this area Finally, a new technique was presented for enhancing the use of large drop-down lists
Chapter 6: Forms and Validation
Trang 8Advanced Concepts and Menus
7
Why didn’t you bring something more advanced? Show me a piece of future technology.
—Dr Silberman, The Terminator
In this chapter, we’ll explore a DHTML idea that seems quite complex: a multi-level animated menu These menus abound on the Web—in fact, some firms do
a roaring trade selling code to generate DHTML menus for use in Website nav-igation But, as it turns out, such menus aren’t complex at all
The principles of unobtrusive DHTML and the power of the DOM mean that the actual code required to create a multi-level animated navigation system can
be quite short; nevertheless, advanced concepts are at work in such systems Understanding these concepts is a key aspect of large-scale DOM programming
A multi-level animated menu is a big project, so let’s see what we’re aiming for Figure 7.1 shows the menu we’re about to develop
Normally, the menu shows only the two leftmost menu items visible in the figure
In the figure, the user has moused over the second of those two items (DHTML Tutorials), causing a submenu to show The user then moused over the first item
in the submenu (By Simon Willison) to reveal the rightmost submenu The top of this final menu is level with that of the middle one because the first item of the middle menu was chosen
Trang 9Without further ado, let’s start development! There’s quite a lot of code involved, but we’ll step through it one small piece at a time
Figure 7.1 The goal: a multi-level menu.
Creating Menu Content
The first step is to create the raw HTML content; then, we’ll bash it into shape with some CSS styling
Create Semantic Menu Content
As we’ve seen through earlier chapters, laying out HTML so that it’s semantically correct makes dealing with the code much simpler We want the menus to appear
as shown in Figure 7.1, which means that, like most other navigation systems, each level of this menu must contain either links or submenus The ideal way to lay out this kind of multi-level menu is to use the unordered list tag, <ul> So, first, let’s lay out the menu
File: menu-stage-1.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Client-side form validation</title>
<base href="http://www.sitepoint.com/">
</head>
<body>
Chapter 7: Advanced Concepts and Menus
Trang 10<ul class="slidingmenu">
<li>
<a href="http://www.sitepoint.com/">SitePoint articles</a> <ul>
<li><a href="article/search-engine-spam-techniques"
>Latest Search Engine Spam Techniques</a></li>
<li><a href="article/free-web-design-apps"
>Free Web Design Apps You Can't Live Without!</a> </li>
<li><a href="article/securing-apache-2-server-ssl"
>Securing Your Apache 2 Server with SSL</a></li>
</ul>
</li>
<li>
<a href="subcat/javascript">DHTML Tutorials</a>
<ul>
<li>
<a href="articlelist/345">By Simon Willison</a>
<ul>
<li><a href="article/rounded-corners-css-javascript" >Rounded Corners with CSS and JavaScript</a>
</li>
<li><a href="article/bookmarklets"
>Better Living Through Bookmarklets</a></li>
<li><a href="article/simple-tricks-usable-forms"
>Simple Tricks for More Usable Forms</a></li> </ul>
</li>
<li><a href="article/smooth-scrolling-javascript"
>My tutorial</a></li>
<li><a href="article/behaved-dhtml-case-study"
>By Aaron Boodman</a>
<ul>
<li><a href="article/behaved-dhtml-case-study"
>Well-Behaved DHTML: A Case Study</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</body>
</html>
Create Semantic Menu Content