It does have to do with the specific requirements. Not all web pages
are for general web consumption. Some are for back-end pages where the
user logs in and is known to have a certain set of prerequisite
capabilities. In these cases, spending the extra time to make the page
degrade gracefully or otherwise may not be considered a wise use of
development dollars.
Correct, but popup menus are simple enough that you would just do them
right in the first place.
Another option is a gateway page that branches to two versions of the
application. This is like GMail. One branch is for JavaScript enabled
Please don't use Google as an example. Google throws JS errors at me
constantly. But I get the idea. I don't like it, but I get it.
(and sufficiently capable JavaScript) and the other branch is for
anyone not capable of the "full" version. So in this case the fancy
drop down menus would not have to degrade either.
Fancy dropdown menus? Granted they aren't really necessary for most
Web pages/applications, but they are fairly trivial widgets, which is
one reason they are so prevalent.
Hierarchies of menus would seem an unwieldy choice for site
navigation, but it is apparently the preferred UI for most Web
developers. Unfortunately, most implementations have lousy usability
and/or accessibility. It's as if the developers had never used menus
in software applications. Of course, if they were paying attention to
their desktops, they would have noticed that navigation is done with a
tree, not a series of cascading menus.
Here is an example of how I think menus should work. The code is a
little slapdash, but in my brief testing it functioned flawlessly.
The default behavior does not popup top-level menus on rollovers (I
hate that), but there is a global option to change this. And they
don't vanish just because you move the mouse away (click to hide all
active menus.) And yes, there is a configurable delay for hiding sub-
menus when it makes sense to hide them. As for accessibility,
keyboard users will have no problem and I think most screen readers/
aural browsers will work as well. I tested in IE7, the latest Mozilla
and Netscape, Opera 9 and Windows Safari beta. IE7 in quirks mode had
a weird issue with adding space between listitems when sub-menus open,
but I haven't investigated that (you shouldn't use quirks mode
anyway.) It should be okay in IE6 in standards mode.
The example has three menus with identical content: one styled as a
horizontal menubar, one vertical and the other as a popup menu. The
orientations are determined solely by CSS. The instantiation of the
popup menu and positioning of child popups are functions of the
script.
Use something like this and you won't need a branch page as the menus
degrade gracefully without script as well as without script and
style. As usual, the hardest case to get perfect is where script is
enabled but style is not. The specific examples I used are admittedly
lousy in this case as only the leaf nodes navigate, despite the fact
that all nodes have perfectly good href's. So if you must use
cascading menus for navigation (as opposed to commands), it is a good
idea to relegate links to leaf nodes.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "
http://www.w3.org/
TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Menus</title>
<style type="text/css" media="all">
ul.menubar { list-style-type:none;padding:0;margin:0 }
ul.menubar a, ul.menuPopup a { padding: 0 .25em 0 .25em }
ul.menubar li { margin:0;padding:0;display:inline}
ul.menubar li a:link, ul.menubar li a:visited { text-decoration:none }
ul.menubar li a:hover { background-color:#0000DD;color:white }
ul.menuPopup { list-style-type:none;padding:0;margin:0;border
utset
2px;background-color:threedface;color:windowtext;z-index:1 }
ul.menuPopup li { white-space:nowrap;display:block }
ul.menuPopup li a:link, ul.menuPopup li a:visited { text-
decoration:none;display:block }
ul.menuPopup li a:hover { background-
color:#0000DD;color:white;display:block }
ul.menuPopup li a.popup:after { content: "\0020\00BB"; }
#myMenubarVertical.menubar { width:5em;margin-bottom:1em }
#myMenubarVertical.menubar li, #myMenubarVertical.menubar li a
{ display:block }
#testPopupMenu { display:block; margin-top:1em }
body {font-family:sans-serif;background-color:threedface}
</style>
<!--[if IE]>
<style type="text/css" media="all">
ul.menubar { display:inline-block } /* Adds layout to fix IE relative
positioning bug
</style>
<![endif]-->
<style type="text/css" media="handheld">
body { margin:4px }
</style>
<style type="text/css" media="print">
body {font-family:serif}
ul.menubar, ul.menuPopup { list-style-type:disc;padding:0 0 0 1em }
ul.menuPopup { border:none;background-
color:white;color:black;position:static !important;display:block !
important }
ul.menuPopup li a {color:black;background-color:inherit}
ul.menuPopup li a.popup:after { content: ""; }
#testPopupMenu { display:none }
</style>
<script type="text/javascript">
var global = this;
if (this.document && this.document.getElementsByTagName &&
this.document.getElementById) {
(function() {
var html = global.document.getElementsByTagName('html');
if (html && html[0] && html[0].style &&
typeof(html[0].style.visibility) == 'string')
{ global.document.write('<style type="text/css"
media="all">#myMenubar, #myMenubarVertical, #myPopupMenu,
#testPopupMenu { visibility:hidden }<\/style>'); }
})();
this.onload = function() {
// Global options
var menuPause = 500; // Delay before hiding/showing sub-menu
var startWithRollover = false; // Default requires click to show
initial top-level item (second click hides)
// Set to true to allow rollover to show top-level menus (click
navigates)
var doc = global.document;
var el, elButton;
var activeMenu, timer, hrefTypeUnknown;
function nodeName(el) {
var n = (el.tagName || el.nodeName);
return n && n.toLowerCase();
}
function cancelPropagation(e) {
e = e || global.event;
if (e.stopPropagation) { e.stopPropagation(); }
e.cancelBubble = true;
}
function setStatus(s) {
// Firefox unload/mouseover bug requires typeof check here
if (typeof(global) != 'undefined') { global.setTimeout(function()
{ if (typeof(global) != 'undefined') { global.status = s; } }, 0); }
return true;
}
function menuRoot(list) {
var root;
if (!list.parentNode || nodeName(list.parentNode) != 'li') { return
null; }
while (list.parentNode) {
list = list.parentNode;
if (list && nodeName(list) == 'ul') { root = list; }
}
return (root._isRootless)?null:root;
}
function isAncestor(listChild, listParent) {
while (listChild.parentNode && listChild.parentNode != document) {
if (listChild.parentNode == listParent) { return true; }
listChild = listChild.parentNode;
}
}
function hideActiveMenu(branch, root) {
if (timer) { global.clearTimeout(timer); timer = null; }
activeMenu.style.display = 'none';
var parentMenuItem = activeMenu.parentNode;
if (parentMenuItem && nodeName(parentMenuItem) == 'li' &&
parentMenuItem.parentNode && parentMenuItem.parentNode != branch &&
parentMenuItem.parentNode != root) {
activeMenu = parentMenuItem.parentNode;
hideActiveMenu(branch, root);
}
activeMenu = branch || null;
}
function showMenu(el, branch, side, pos) {
if (timer) { global.clearTimeout(timer); timer = null; }
var elOffset, elParent, tag, a;
side = side || 'bottom';
elParent = el.parentNode;
el.style.visibility = 'hidden';
el.style.display = 'block';
if (!pos && elParent && nodeName(elParent) == 'li') {
pos = [0, 0];
if (elParent != branch) {
elOffset = elParent;
do {
pos[0] += elOffset.offsetTop || 0;
pos[1] += elOffset.offsetLeft || 0;
if (elParent != elOffset) {
pos[0] += elOffset.clientTop || 0;
pos[1] += elOffset.clientLeft || 0;
}
elOffset = elOffset.offsetParent;
if (elOffset) {
tag = nodeName(elOffset);
if (tag != 'li' && tag != 'ul') { break; }
}
}
while (elOffset && elOffset != branch);
}
if (side == 'right') { pos[1] += elParent.offsetWidth || 0; } else
{ pos[0] += elParent.offsetHeight || 0; }
}
if (pos) {
el.style.top = pos[0] + 'px';
el.style.left = pos[1] + 'px';
}
el.style.visibility = 'visible';
if (activeMenu && !isAncestor(el, activeMenu) && activeMenu != el)
{
var root = menuRoot(activeMenu);
hideActiveMenu((branch == root)?null:branch, root);
}
activeMenu = el;
a = el.getElementsByTagName('a');
if (a[0] && a[0].focus) { a[0].focus(); }
}
function showMenuPopup(el) {
showMenu(el, null);
}
function attachPopupActivator(el, list) {
el.onclick = function(e) { if (list.style.display == 'none')
{ showMenu(list, null); } else { hideActiveMenu(null, null); }
cancelPropagation(e); };
}
function initializeChildMenus(list, root, initialSide) {
initialSide = initialSide || 'bottom';
if (typeof(root) == 'undefined') { root = list; }
var lists, itemFocused;
var anchors = list.getElementsByTagName('a');
var i = 0;
var l = anchors.length;
while (i < l) {
if (anchors
.parentNode) {
lists = anchors.parentNode.getElementsByTagName('ul');
if (lists && lists[0]) {
if (lists[0].style.display != 'none') { // already did this one?
hrefTypeUnknown = typeof(anchors.href) == 'unknown';
if (root != list || !startWithRollover || (!hrefTypeUnknown && !
anchors.href)) {
if (!hrefTypeUnknown) { anchors.href = anchors.href ||
'#'; }
anchors.onclick = (function(el) { return function(e) { if
(el.style.display == 'none') { showMenu(el, list, (root == list)?
initialSide:'right'); } else { if (root == list)
{ hideActiveMenu((root == list)?null:list, root); } }
cancelPropagation(e); return false; }; })(lists[0]);
}
anchors.tabIndex = 0;
initializeChildMenus(lists[0], root);
anchors.onmouseover = (function(el) { return function(e)
{ if (this.title) { setStatus(this.title); } if ((!activeMenu && !
startWithRollover) || (activeMenu && isAncestor(activeMenu, el)))
{ return; } if (timer) { global.clearTimeout(timer); } timer =
global.setTimeout(function() { showMenu(el, list, (root == list)?
initialSide:'right'); }, (list == root)?0:menuPause); return
true; }; })(lists[0]);
anchors.onmouseout = function() { if (timer)
{ clearTimeout(timer); timer = null; } if (this.title) { return
setStatus(''); } };
anchors.className = 'popup';
lists[0].className = 'menuPopup';
lists[0].style.position = 'absolute';
lists[0].style.display = 'none';
}
}
else {
if (anchors.parentNode.parentNode &&
anchors.parentNode.parentNode.style.display != 'none') { // already
did this one?
anchors.onmouseover = function() { if (this.title)
{ setStatus(this.title); } if (timer) { global.clearTimeout(timer);
timer = 0; } if (activeMenu && !isAncestor(this, activeMenu)) { timer
= global.setTimeout(function() { hideActiveMenu((list == root)?
null:list, menuRoot(activeMenu)); }, (list == root)?0:menuPause); }
return true; };
anchors.onmouseout = function() { if (this.title) { return
setStatus(''); } };
}
}
i++;
}
}
if (l) {
global.onunload = function() {
i = 0;
l = anchors.length;
while(i < l) {
anchors.onclick = anchors.onmouseover =
anchors.onmouseout = null;
i++;
}
};
}
}
function documentClickHandler(list, root) {
if (typeof(root) == 'undefined') { root = list; }
return function() {
if (activeMenu && (isAncestor(activeMenu, list) || activeMenu ==
list)) { hideActiveMenu(null, root); }
};
}
function attachDocumentClickHandler(list, root) {
var onclickOld = document.onclick;
var onclickNew = documentClickHandler(list, root);
document.onclick = (onclickOld)?function(e) { onclickOld(e);
onclickNew(e); }nclickNew;
}
function initializeMenu(list, className, initialSide) {
list.className = className || 'menubar';
list.style.position = 'relative';
initializeChildMenus(list, list, initialSide);
attachDocumentClickHandler(list);
}
function initializeMenuPopup(list, className) {
list.className = className || 'menuPopup';
list._isRootless = true;
initializeChildMenus(list, null);
list.style.position = 'absolute';
list.style.display = 'none';
attachDocumentClickHandler(list, null);
}
el = doc.getElementById('myMenubar');
if (el && el.parentNode && el.style && typeof(el.style.display) ==
'string' && typeof(el.style.position) == 'string') {
initializeMenu(el);
el.style.visibility = 'visible';
el = doc.getElementById('myMenubarVertical');
if (el) {
initializeMenu(el, null, 'right');
el.style.visibility = 'visible';
}
el = doc.getElementById('myPopupMenu');
if (el) {
initializeMenuPopup(el);
el.style.visibility = 'visible';
elButton = document.getElementById('testPopupMenu');
if (elButton) {
elButton.disabled = false;
attachPopupActivator(elButton, el);
elButton.style.visibility = 'visible';
}
}
}
};
}
</script>
</head>
<body>
<ul id="myMenubarVertical"><li><a title="Google sites" href="http://
www.google.com">Google</a><ul><li><a href="http://
www.gmail.com">GMail</a><ul><li><a href="http://www.gmail.com">Inbox</
a></li><li><a href="http://www.gmail.com">Outbox</a></li></ul></
li><li><a href="http://www.googlecode.com">Google Code</a><ul><li><a
href="http://code.google.com/apis/ajaxsearch/">Google Ajax Search</a></
li><li><a href="http://www.google.com/apis/maps/">Google Maps</a></
li><li><a href="http://code.google.com/webtoolkit/">Google Web
Toolkit</a></li></ul></li><li><a href="http://www.google.com/
talk">GTalk</a></li></ul></li><li><a href="http://www.msn.com"
title="MSN sites">MSN</a><ul><li><a href="http://www.msnbc.com">News</
a></li><li><a href="http://msn.foxsports.com">Sports</a></li><li><a
href="http://weather.msn.com">Weather</a></li></ul></li><li><a
href="http://www.yahoo.com" title="Yahoo! site">Yahoo!</a><ul><li><a
href="http://www.yahoo.com/r/26">Sports</a></li><li><a href="http://
www.yahoo.com/r/4c">Tech</a></li></ul></li></ul>
<ul id="myMenubar"><li><a title="Google sites" href="http://
www.google.com">Google</a><ul><li><a href="http://
www.gmail.com">GMail</a><ul><li><a href="http://www.gmail.com">Inbox</
a></li><li><a href="http://www.gmail.com">Outbox</a></li></ul></
li><li><a href="http://www.googlecode.com">Google Code</a><ul><li><a
href="http://code.google.com/apis/ajaxsearch/">Google Ajax Search</a></
li><li><a href="http://www.google.com/apis/maps/">Google Maps</a></
li><li><a href="http://code.google.com/webtoolkit/">Google Web
Toolkit</a></li></ul></li><li><a href="http://www.google.com/
talk">GTalk</a></li></ul></li><li><a href="http://www.msn.com"
title="MSN sites">MSN</a><ul><li><a href="http://www.msnbc.com">News</
a></li><li><a href="http://msn.foxsports.com">Sports</a></li><li><a
href="http://weather.msn.com">Weather</a></li></ul></li><li><a
href="http://www.yahoo.com" title="Yahoo! site">Yahoo!</a><ul><li><a
href="http://www.yahoo.com/r/26">Sports</a></li><li><a href="http://
www.yahoo.com/r/4c">Tech</a></li></ul></li></ul>
<input type="button" id="testPopupMenu" value="Popup menu" disabled>
<ul id="myPopupMenu"><li><a title="Google sites" href="http://
www.google.com">Google</a><ul><li><a href="http://
www.gmail.com">GMail</a><ul><li><a href="http://www.gmail.com">Inbox</
a></li><li><a href="http://www.gmail.com">Outbox</a></li></ul></
li><li><a href="http://www.googlecode.com">Google Code</a><ul><li><a
href="http://code.google.com/apis/ajaxsearch/">Google Ajax Search</a></
li><li><a href="http://www.google.com/apis/maps/">Google Maps</a></
li><li><a href="http://code.google.com/webtoolkit/">Google Web
Toolkit</a></li></ul></li><li><a href="http://www.google.com/
talk">GTalk</a></li></ul></li><li><a href="http://www.msn.com"
title="MSN sites">MSN</a><ul><li><a href="http://www.msnbc.com">News</
a></li><li><a href="http://msn.foxsports.com">Sports</a></li><li><a
href="http://weather.msn.com">Weather</a></li></ul></li><li><a
href="http://www.yahoo.com" title="Yahoo! site">Yahoo!</a><ul><li><a
href="http://www.yahoo.com/r/26">Sports</a></li><li><a href="http://
www.yahoo.com/r/4c">Tech</a></li></ul></li></ul>
</body>
</html>