I have prepared a drop in replacement for the
drop_down_menus.js script used in the
Implementing JavaScript-enabled Drop-Down Menus project in Chapter 3.
The code that I have prepared as a replacement ought to be able to survive with the same structural markup as presented in the book.
The script that appears here has a few fundamental differences with the code supplied with the book.
* It uses a mouseover to open and close menus
* It does not have any keyboard-based navigation
mouseover vs. click
In the book I discussed the pitfalls of a mouseover based menu system. After some further consideration I came to the conclusion that mouseover is the best way to go, but only if you also implement a "sticky" menu system. That is to say, menus that don't immediately close when the mouse leaves the menu. This script includes this "sticky" menu feature, a brief timeout is set (almost a second) so that menus don't close immediately, making this menu more accessible.
Keyboard Navigation
I'm still searching for the best way to implement keyboard-based navigation. The approach I used in the book fails when a page is large enough to scroll and the user wants to use the arrow keys to scroll the page. I believe the best approach is to use tabs for keyboard-based navigation. I will try to revisit this functionality soon.
The Code
Just replace the contents of
drop_down_menus.js with the following:
Code:
/*
//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
//\\\ \\\\\\\\|
//\\\ @@ @@\\\\\\| Menu Script
//\\ @@@@ @@@@\\\\\| (c) Copyright 2006 Richard York, All rights Reserved
//\\\@@@@| @@@@\\\\\|
//\\\ @@ |\\@@\\\\\\|
//\\\\ || \\\\\\\|
//\\\\ \\_ \\\\\\|
//\\\\\ \\\\\|
//\\\\\ ---- \@@@@|
//@@@@@\ \@@@@|
//@@@@@@\ \@@@@@|
//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
*/
// addEvent functions
//
// written by Dean Edwards, 2005
// with input from Tino Zijdel
//
// http://dean.edwards.name/weblog/2005/10/add-event/
function addEvent(element, type, handler)
{
if (typeof(element) == 'string')
{
element = document.getElementById(element);
}
if (typeof(element) == 'object' && element && handler)
{
// assign each event handler a unique ID
if (!handler.$$guid)
{
handler.$$guid = addEvent.guid++;
}
// create a hash table of event types for the element
if (!element.events)
{
element.events = {};
}
// create a hash table of event handlers for each element/event pair
var handlers = element.events[type];
if (!handlers)
{
handlers = element.events[type] = {};
// store the existing event handler (if there is one)
if (element['on' + type])
{
handlers[0] = element['on' + type];
}
}
// store the event handler in the hash table
handlers[handler.$$guid] = handler;
// assign a global event handler to do all the work
element['on' + type] = handleEvent;
}
};
// a counter used to create unique IDs
addEvent.guid = 1;
function removeEvent(element, type, handler)
{
// delete the event handler from the hash table
if (element.events && element.events[type] && handler)
{
delete element.events[type][handler.$$guid];
}
};
function handleEvent(event)
{
var returnValue = true;
// grab the event object (IE uses a global event object)
event = event || fixEvent(window.event);
// get a reference to the hash table of event handlers
var handlers = this.events[event.type];
// execute each event handler
for (var i in handlers)
{
this.$$handleEvent = handlers[i];
if (this.$$handleEvent(event) === false)
{
returnValue = false;
}
}
return returnValue;
};
function fixEvent(event)
{
// add W3C standard event methods
event.preventDefault = fixEvent.preventDefault;
event.stopPropagation = fixEvent.stopPropagation;
return event;
};
fixEvent.preventDefault = function()
{
this.returnValue = false;
};
fixEvent.stopPropagation = function()
{
this.cancelBubble = true;
};
var hMenu = {
active : false,
timer : false,
last : '',
attachEvents : function()
{
// Grab all the list elements with a menu class name
// and apply a mouseover event.
var $menus = cssQuery('ul#menu li.menu');
for (var $i = 0, $l = $menus.length; $i < $l; $i++)
{
addEvent(
$menus[$i],
'mouseover',
function($e)
{
$e.stopPropagation();
hMenu.stopTimer();
var $childMenu = cssQuery('#' + this.id + ' > ul');
if ($childMenu && $childMenu.length)
{
// This bit hides sibling drop down menus, where the
// menu is nested more than one deep.
$nodes = (this.parentNode.id == 'menu')?
cssQuery('ul#menu ul')
:
cssQuery('li#' + this.parentNode.parentNode.id + ' ul ul');
if ($nodes && $nodes.length)
{
for (var $i = 0, $l = $nodes.length; $i < $l; $i++)
{
if ($nodes[$i] && $nodes[$i].style)
{
$nodes[$i].style.visibility = 'hidden';
}
}
}
$childMenu[0].style.visibility = 'visible';
}
}
);
}
addEvent(
'menu',
'mouseover',
function()
{
hMenu.active = true;
}
);
addEvent(
'menu',
'mouseout',
function()
{
hMenu.active = false;
if (!hMenu.timer)
{
hMenu.timer = setInterval('hMenu.isActive();', 800);
}
}
);
},
isActive : function()
{
if (!hMenu.active)
{
hMenu.close();
}
},
stopTimer : function()
{
if (hMenu.timer)
{
clearInterval(hMenu.timer);
hMenu.timer = '';
}
},
close : function()
{
var $nodes = cssQuery('ul#menu ul');
if ($nodes.length)
{
for (var $i = 0, $l = $nodes.length; $i < $l; $i++)
{
if ($nodes[$i] && $nodes[$i].style)
{
$nodes[$i].style.visibility = 'hidden';
}
}
}
clearInterval(hMenu.timer);
hMenu.timer = '';
}
};
addEvent(window, 'load', hMenu.attachEvents);
Regards,
Rich
--
Author,
Beginning CSS: Cascading Style Sheets For Web Design
CSS Instant Results
http://www.catb.org/~esr/faqs/smart-questions.html