-->
Bookmark and Share

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.