DHTML Lab: Hierarchical Menus, I; User Interface | WebReference

DHTML Lab: Hierarchical Menus, I; User Interface


DHTML Hierarchical Menus, Part I
getting the menus on the page




Our menus can appear anywhere on your page, even in several places! To associate a link with a menu, we simply use the onMouseOver and onMouseOut event handlers of the link. These event handlers call popUp() and popDown(), respectively. For example, the left column links use this HTML:

<P><A HREF="/"
      onMouseOver = "popUp('elMenu1',event)"
      onMouseOut = "popDown('elMenu1')">
<P><A HREF="/index2.html"

The onMouseOver calls popUp(), providing two arguments, a string with the menu identifier, and the event object. We need the event object because we'll need to figure out the mouse position for menu positioning. When we use an event handler within a tag, we must explicitly send the event object as an argument to the handling function, using the event keyword. As we know from many other columns, the event object is sent to a handler function implicitly when the handler is defined in a script. This is the first time we have used it in this way.

When the user takes the mouse off the link popDown() is called, with a single argument, the menu identifier string.

Displaying the Top Level Menu

When popUp() is called, it first assigns menuName to the string argument, and the standard e to the event object argument. Its first statement checks to see if the menus have been created. If they haven't, the function returns. This will avoid any errors that may occur if a user mouses over the link before the menu-creating functions have completed their work.

Next, we call the hideAll() function, which makes sure that all menus are closed. Since as we shall see further down, top level menus are closed with a timer, it is possible that a different un-related menu will be visible when we mouseover the link. We need to ensure that no menu is showing before we display the menu associated with the link.

function popUp(menuName,e) {
   if (!areCreated) return;
   currentMenu = eval(menuName);
   xPos = e.pageX;
   yPos = e.pageY;
   currentMenu.isOn = true;

We assign the evaluated menuName string to the global variable currentMenu, which tracks the menu in use. For example, if the menu-in-question is elMenu1, menuName is "elMenu1", making the statement:

   currentMenu = eval(menuName);

the same as:

   currentMenu = eval("elMenu1");

which, in turn, is the same as:

   currentMenu = elMenu1;

We use e.pageX and e.pageY to calculate the position of the mouse at the time of the mouseover and we move the menu to that position using moveTo().

Now, we begin using all those methods we created for the menus when we set them up. Notice the English-like statement flow when using custom methods. We call the keepInWindow method, to ensure that the menu is kept within the user's browser window. Then we set the menu's isOn property to true, since the menu is now ON, and we make it visible using the showIt method.

Keeping the Menus on-Screen

Once a menu is positioned, and before it is made visible, the keepInWindow method is called. If the menu is in danger of being wholly or partially off-screen, it is repositioned to appear within the client's window.

Check out the behaviour of these right-aligned menus:



Navigator 4 for Windows95/NT does not take into account visible scrollbars when calculating the inner width of a client window. The Macintosh version does. A variable, scrBars, is initialized with a value of 20 to account for scrollbars. Mac users will get an extra 20 pixels of space. It is not worth a platform check for such a small, inconsequential discrepancy.

function keepInWindow() {
  scrBars = 20;
  winRight = (keep with next line)
   (window.pageXOffset + window.innerWidth) - scrBars;
  rightPos = this.left + menuWidth;
  if (rightPos > winRight) {
    if (this.hasParent) {
      parentLeft = this.parentMenu.left;
      newLeft = ((parentLeft-menuWidth) + childOverlap);
      this.left = newLeft;
    else {
      dif = rightPos - winRight;
      this.left -= dif;
  winBot = (keep with next line)
   (window.pageYOffset + window.innerHeight) - scrBars;
  botPos = this.top + this.fullHeight;
  if (botPos > winBot) {
    dif = botPos - winBot;
    this.top -= dif;

Two new properties of the window object are used to calculate the horizontal pixel position of the rightmost part of the client window:

pageXOffset returns the horizontal position of the visible page as compared to the full page. In effect, it tells us how much of the page is off the left of the browser window when horizontal scrollbars are present. innerWidth returns the width of the client area of the browser. Adding the two values together gives us the page position of the right window boundary. Subtracting the scrollbar allowance gives us a safer calculation of the rightmost part of the visible page. This value, we store in winRight.

The rightmost extent of the menu itself is calculated by adding its left property to menuWidth, and stored in rightPos.

If rightPos is greater than winRight, then the menu is off the right of the window and must be repositioned horizontally.

If the menu is a top level one, with no parent, then the menu is simply moved to the left by the amount that is off-window. If it does have a parent then it must appear to the left of the parent overlapping it by the same amount as in-window child menus.

Using window.pageYOffset and window.innerHeight, the same calculations are performed for the vertical position of the menu. Since overlapping is not an issue, the menu is simply moved up by the amount that was previously hidden off the bottom.

Menu Visibility Toggle

The showIt method calls the showIt() function used earlier in the column as an example:

function showIt(on) {
   this.visibility = (on) ? "show" : "hide";

Hiding All Menus

The hideAll() function is called only by popUp() since it is the only time when we want all visible menus to be wiped out instantly. It simply uses topCount, the global variable which now stores the total number of top level menus created, to move through all top level menus. It sets their isOn property to false, hides all their child menus if any are open, then hides the top menu itself.

function hideAll(){
   for(i=1; i<topCount; i++) {
      temp = eval("elMenu" + i);
      temp.isOn = false;
      if (temp.hasChildVisible) temp.hideChildren();

The child menu check uses the hasChildVisible property. As we shall see, this property is created and set when a child menu is made visible. hasChildVisible will evaluate to true if it stores a value of true or 1. It will evaluate to false if it stores a value of false, of 0, or if it does not yet exist as a property. The first time popUp() is called, no menus are visible and certainly no child menus are visible. Therefore the non-existent hasChildVisible evaluates to false. If, however, it was a repeat call to popUp() and there were menus and child menus visible, the hideChildren method of the top level menus would be called to hide their child menus.

Mousing off the Link

The popDown() function has the same check for menu creation as popUp(). Then it assigns the menu-in-question to the whichEl local variable. The menu's isOn property is made false and the hideTop() method is invoked.
function popDown(menuName){
   if (!areCreated) return;
   whichEl = eval(menuName);
   whichEl.isOn = false;

Why did we not just hide the menu when the mouse leaves the link? Because the mouse may be on its way over to the menu! If we simply hid it, we would never get the chance to enter the menu and navigate. Therefore, we give control to hideTop(), which will give us time to get to the menu. To properly understand hideTop(), and all the hiding methods, however, we must first look at what happens when we enter a menu.

Produced by Peter Belesis and

All Rights Reserved. Legal Notices.
Created: Feb. 19, 1998
Revised: Feb. 19, 1998

URL: http://www.webreference.com/dhtml/column14/menuStart.html