Bookmark and Share

Revenge of the Menu Bar

See the demo page for the finished version of the code.

Positioning a Sub Menu

As with positioning a menu under a button, we first find the coordinates of the link element with respect to the page. Adding the width of the item to the horizontal coordinate will put the sub menu just to the right of the item.

  // Get position for submenu based on the menu item.

  x = getPageOffsetLeft(item) + item.offsetWidth;
  y = getPageOffsetTop(item);

Unlike the buttons and main menus however, we take the sub menu positioning a bit further. With a long chain of sub menus, each positioned downward and to the right of the previous one, it's easy to run out of room. A Sub menu may end up extending past the edge of the browser window.

To prevent this, we find the coordinates of the current, viewable area of the page using the browser's window dimensions and adjusting for any scrolling offsets.

  // Adjust position to fit in view.

  var maxX, maxY;

  if (browser.isIE) {
    maxX =
      (document.documentElement.scrollLeft   != 0 ?
       : document.body.scrollLeft)
    + (document.documentElement.clientWidth  != 0 ?
       : document.body.clientWidth);
    maxY =
      (document.documentElement.scrollTop    != 0 ?
       : document.body.scrollTop)
    + (document.documentElement.clientHeight != 0 ?
       : document.body.clientHeight);
  if (browser.isOP) {
    maxX = document.documentElement.scrollLeft + window.innerWidth;
    maxY = document.documentElement.scrollTop  + window.innerHeight;
  if (browser.isNS) {
    maxX = window.scrollX + window.innerWidth;
    maxY = window.scrollY + window.innerHeight;
  maxX -= item.subMenu.offsetWidth;
  maxY -= item.subMenu.offsetHeight;

  if (x > maxX)
    x = Math.max(0, x - item.offsetWidth - item.subMenu.offsetWidth
      + (menu.offsetWidth - item.offsetWidth));
  y = Math.max(0, Math.min(y, maxY));

Taking into account the width and height of the sub menu, we can define a range of coordinates where the sub menu can be positioned while staying fully in view.

Should our original position fall outside this range, we adjust it to place the sub menu to the left of its parent menu and/or move it upwards so that its bottom edge will remain in view.

Browser Compatibility

Methods for finding the dimensions of the client area and scroll offsets vary from browser to browser, as there is no set standard for this. It can even vary from version to version for a given browser.

In the case of IE, version 5.5 and version 6 in "quirks" mode, these values can be found in the document.body object. But IE 6 in standard mode stores these values in the document.documentElement object instead. The code above handles this by taking the values from document.documentElement if they are not set to zero. Otherwise, it takes them from document.body.

Once a suitable position is found, the sub menu is moved and made visible.

  // Position and show it.

  item.subMenu.style.left = x + "px";
  item.subMenu.style.top  = y + "px";
  item.subMenu.style.visibility = "visible";

The last step in this function cancels the event bubble.

  // Stop the event from bubbling.

  if (browser.isIE)
    window.event.cancelBubble = true;

Since the item is part of the menu DIV, and the menu DIV also has an onmousemove event handler defined for it, any mousemove event fired on the item also fires for its parent menu.

That's not good because, as we'll see next, the onmousemove handler for the menu closes any active sub menu. If the event were allowed to bubble from here, the sub menu just displayed would be immediately hidden again.

Deactivating Sub Menus

The sample menu below demonstrates the code up to this point. Mousing over an item with a sub menu activates that item and sub menu. It will stay active until you mouse over some other sub menued item.

Close Sub Menus

This presents a problem because once any sub menu is opened, you can't get rid of them. Moving the mouse to another, normal item does nothing while moving to another sub menued item just switches the active sub menu. (For this sample, you can use the link above to get rid of the sub menus).

Instead, we want the active sub menu to close when any other item in the parent menu is moused over. That's where the mouseover event handler on the menu DIV comes in.