Revenge of the Menu Bar
See the demo page for the finished version of the code.Utility Functions
To make things easier, some utility functions are defined. These are used in several places throughout the menu bar script to handle simple, generic tasks. Like the code for browser detection, they could useful in other scripts as well.
The first is called getContainerWith(). The code for the menu
bar includes a number of event handlers which may be fired from many of the
different elements making up the components we've defined (the bar, buttons,
menus, menu items, etc.).
Using the Event object associated with an event, you can
determine which element fired it, such as a link. But for the menu bar, we'll
often need to locate an outer or containing element, such as the menu DIV
that holds a given item link.
function getContainerWith(node, tagName, className) {
// Starting with the given node, find the nearest containing element
// with the specified tag name and style class.
while (node != null) {
if (node.tagName != null && node.tagName == tagName &&
hasClassName(node, className))
return node;
node = node.parentNode;
}
return node;
}
This function does just that. Specifying the type of tag to look for and
a style class name, you can give it a starting element. It will move up the
document node tree looking for a match. The result is either the matching
element or a null value (meaning no match). Note that if the
starting node itself matches the criteria, it will be returned.
To match the class name it calls another utility function,
hasClassName(). Remember that you can specify several style class
names in a tag's CLASS attribute (separated by spaces) so it helps to be able
to identify individual names when accessing this value programmatically.
function hasClassName(el, name) {
var i, list;
// Return true if the given element currently has the given class
// name.
list = el.className.split(" ");
for (i = 0; i < list.length; i++)
if (list[i] == name)
return true;
return false;
}
This function takes a given name and checks it against each name in the
element's className property for a match, returning either
true or false.
Also dealing with multiple class names on an element is the
removeClassName() function. Adding a class name to an element
object is easy, you just append the name to its className
property, preceeded by a space.
elObj.className += " myClassName";
Removing a class name can be little more difficult since there may be more
than one in the element's className property. This function splits
up that string of space-separated class names and rebuilds them, minus the
specified name.
function removeClassName(el, name) {
var i, curList, newList;
if (el.className == null)
return;
// Remove the given class name from the element's className property.
newList = new Array();
curList = el.className.split(" ");
for (i = 0; i < curList.length; i++)
if (curList[i] != name)
newList.push(curList[i]);
el.className = newList.join(" ");
}
The last pair of utility functions are used for positioning. The drop down menus and sub menus are absolutely positioned elements which will need to be placed near a corresponding button or menu item element. These two functions are used to calculate the pixel coordinates of those elements on the page.
function getPageOffsetLeft(el) {
var x;
// Return the x coordinate of an element relative to the page.
x = el.offsetLeft;
if (el.offsetParent != null)
x += getPageOffsetLeft(el.offsetParent);
return x;
}
function getPageOffsetTop(el) {
var y;
// Return the x coordinate of an element relative to the page.
y = el.offsetTop;
if (el.offsetParent != null)
y += getPageOffsetTop(el.offsetParent);
return y;
}
Now for the code specific to the the menu bar.