DHTML Lab: Hierarchical Menus Version 3 | 16 | WebReference

DHTML Lab: Hierarchical Menus Version 3 | 16


Logo

Hierarchical Menus: Version 3
menu display

WebReference

Click the link above to reveal menu. Click anywhere on the page to hide menu.

Parameters used for the menus on this page:

menuVersion = 3;
menuWidth = 120;
childOverlap = 50;
childOffset = 5;
perCentOver = null;
secondsVisible = .5;
fntCol = "blue";
fntSiz = "10";
fntBold = false;
fntItal = false;
fntFam = "sans-serif";
backCol = "#DDDDDD";
overCol = "#FFCCCC";
overFnt = "purple";
borWid = 0;
borCol = "red";
borSty = "solid";
itemPad = 3;
imgSrc = "tri.gif";
imgSiz = 10;
separator = 3;
separatorCol = "red";
isFrames = false;
keepHilite = true; 
NSfontOver = false;
clickStart = true;
clickKill = true;
showVisited = "";

Use these buttons to view menus in frames

Background Reading:

popUp():
  column 14
  column 15
  column 18
  column 20

In script listings, cross-browser code is blue, Navigator-specific code is red, and Explorer code is green.

The [cc] symbol denotes code continuation. The code is part of the preceding line. It is placed on a new line for column formatting considerations only.

The old popUp() function is now broken into two separate functions: popUp() and popMenu(). The new popUp() performs overhead tasks before displaying the menu tree, while popMenu() does the actual menu positioning and displaying.

function popUp(menuName,e){
  if (NS4 && NSresized) startIt();
  if (!isLoaded) return;
  linkEl = (NS4) ? e.target : event.srcElement;
  if (clickStart) linkEl.onclick = popMenu;
  if (!beingCreated && !areCreated) startIt();
  linkEl.menuName = menuName;  
  if (!clickStart) popMenu(e);
}

Recall that popUp() is called when the user mouses over a page link. The first popUp() statement is NS4-specific and checks the value of NSresized. Remember that NSresized is originally false, and becomes true if the Navigator user resizes the browser window. From then on, until the end of the browser session, this variable will remain true. The true value tells the function that the frameset capture of the load event has been mangled and rendered inoperative by the resize, so we must rebuild the menus manually upon every main frame reload. So, if the browser is Navigator and if the window has been resized, we call startIt() to rebuild the menus, before continuing.

If the browser is Explorer or if the Navigator user does not have twitchy fingers, the first statement is skipped. Next, isLoaded variable is checked. If it is false, the function returns and does nothing. It will only proceed when the page/frame is completely loaded.

The third statement defines the new local linkEl variable:

linkEl = (NS4) ? e.target : event.srcElement;

linkEl represents the link element that the user moused over (<A HREF=). In Navigator, it is the target (e.target) of the mouseover event; in Explorer it is the source element (event.srcElement). We now have a JavaScript object that represents the HTML link. Next, we check the clickStart parameter variable:

  if (clickStart) linkEl.onclick = popMenu;

If the menus are set to appear on the click event (clickStart is true), we set the link's onclick handler dynamically. When the user clicks on the link, popMenu() will be called. The mouseover event always fires before the click event, for obvious reasons, so we have time to decide whether to show the menus on a mouseover or click, depending on the value of clickStart. This is a more elegant method than changing the hard-coded onMouseOver to onClick in the HTML.

The next statement should always be redundant, but is included as a safeguard. If, for some reason (you'd be surprised!) the page has loaded and the menus are not created (!areCreated) and are not in the process of being created (!beingCreated), then we create them with startIt():

  if (!beingCreated && !areCreated) startIt();

At this point in the function, the menus should be created, and we are ready to either display them (clickStart is false) or exit and wait for the click event for display. First, however, we must ensure that popMenu() will receive the menuName argument passed to popUp() from the HTML. Otherwise, it won't know which menu to display. Remember, popMenu() is called either immediately by popUp() or later with the user's click. In the first case, we could simply pass menuName to popMenu() as an argument. In the second case, we have no way of passing an argument. Therefore, we look for an object that the two functions have in common: linkEl. That is, the actual HTML link. Since both popUp() and popMenu() are called by the link, they can identify it and access its properties. Our next step is to make menuName a property of linkEl, for easy retrieval later:

  linkEl.menuName = menuName;

Finally, if we want the menus to appear on the mouseover, popMenu() is called immediately, passing the event (e) as an argument.

  if (!clickStart) popMenu(e);

Outside of a few overhead, preparatory statements, popMenu() is very much like the second part of the old popUp() function:

function popMenu(e){
  if (!isLoaded || !areCreated) return true;
  eType = (NS4) ? e.type : event.type;
  if (clickStart && eType != "click") return true;
  hideAll();
  linkEl = (NS4) ? e.target : event.srcElement;
  
  currentMenu = eval(linkEl.menuName);
  currentMenu.hasParent = false;
  currentMenu.treeParent.startChild = currentMenu;
  
  if (IE4) menuLocBod = menuLoc.document.body;
  if (!isFrames) {
    xPos = (currentMenu.menuLeft) ? currentMenu.menuLeft : (NS4) ?
[cc]  e.pageX : (event.clientX + menuLocBod.scrollLeft);
    yPos = (currentMenu.menuTop) ? currentMenu.menuTop : (NS4) ?
[cc]   e.pageY : (event.clientY + menuLocBod.scrollTop);
  }
  else {
    switch (navFrLoc) {
      case "left":
        xPos = (currentMenu.menuLeft) ? currentMenu.menuLeft : (NS4) ?
[cc]   menuLoc.pageXOffset : menuLocBod.scrollLeft;
        yPos = (currentMenu.menuTop) ? currentMenu.menuTop : (NS4) ?
[cc]   (e.pageY-pageYOffset)+menuLoc.pageYOffset :
[cc]   event.clientY + menuLocBod.scrollTop;
        break;
      case "top":
        xPos = (currentMenu.menuLeft) ? currentMenu.menuLeft : (NS4) ?
[cc]   (e.pageX-pageXOffset)+menuLoc.pageXOffset :
[cc]   event.clientX + menuLocBod.scrollLeft;
        yPos = (currentMenu.menuTop) ? currentMenu.menuTop : (NS4) ?
[cc]   menuLoc.pageYOffset : menuLocBod.scrollTop;
        break;
      case "bottom":
        xPos = (currentMenu.menuLeft) ? currentMenu.menuLeft : (NS4) ?
[cc]   (e.pageX-pageXOffset)+menuLoc.pageXOffset :
[cc]   event.clientX + menuLocBod.scrollLeft;
        yPos = (currentMenu.menuTop) ? currentMenu.menuTop : (NS4) ?
[cc]   menuLoc.pageYOffset+menuLoc.innerHeight :
[cc]   menuLocBod.scrollTop + menuLocBod.clientHeight;
        break;
      case "right":
        xPos = (currentMenu.menuLeft) ? currentMenu.menuLeft : (NS4) ?
[cc]   menuLoc.pageXOffset+menuLoc.innerWidth :
[cc]   menuLocBod.scrollLeft+menuLocBod.clientWidth;
        yPos = (currentMenu.menuTop) ? currentMenu.menuTop : (NS4) ?
[cc]   (e.pageY-pageYOffset)+menuLoc.pageYOffset :
[cc]   event.clientY + menuLocBod.scrollTop;
        break;
    }
  }
  currentMenu.moveTo(xPos,yPos);
  currentMenu.keepInWindow()
  currentMenu.isOn = true;
  currentMenu.showIt(true);
  return false;
}

The one argument of popMenu() is the event that called it (e). This argument has been passed explicitly by popUp() if clickStart is false and holds the mouseover event. If clickStart is true, then e is click, and was passed implicitly when the user clicked the link. Either way, when popMenu() begins, we know what event called it:

function popMenu(e){

A major difference in this version is that the event that displays the menus may have a different default behavior! If the menus appear on a click, then we must ensure that the HREF in the link is not followed, which would be the default behavior. The function must therefore return false, cancelling other handling of the click. There is no default mouseover behavior for a link, so the return value is harmless.

In Navigator, if any return statement includes a value, then other return statements, in the same function, must also have values. In Explorer, return without a value defaults to return true. The following statement is valid in Explorer but causes an error in Navigator:

if (variable) {
	return;
else {
	return false;
}

Back to popMenu(). We first make sure that the page is loaded and all menus have been created:

  if (!isLoaded || !areCreated) return true;

We create a variable, eType, to store the type of event that called popMenu():

  eType = (NS4) ? e.type : event.type;

Another safeguard statement. If the menu display event is click and we somehow got into popMenu() with a different event, the function returns.

  if (clickStart && eType != "click") return true;

As in older versions, we make sure any visible menus, from a previous mouseover or click, are hidden, by hiding all menus:

  hideAll();

Once again, we create a linkEl variable to store the link element that fired the event, and check its menuName property, set by popUp() and holding the string representing the menu to display. This string is evaluated and the resulting menu object is assigned to currentMenu.

  linkEl = (NS4) ? e.target : event.srcElement;
  currentMenu = eval(linkEl.menuName);

In version 3, a menu tree can be displayed starting at any child menu. This first-to-be-displayed menu must be identified as the last-one-to-be-hidden in the tree. It must, therefore, have no parent menu:

  currentMenu.hasParent = false;

The top-level menu in a menu tree (the treeParent of all menus in that tree) has, as you may recall, a startChild property that stores this first-to-be-displayed menu. We'll need this information when hiding menu trees:

  currentMenu.treeParent.startChild = currentMenu;

The remainder of the function is similar to the old popUp() in its positioning statements. The one significant difference is that we now have the ability to position menus in absolute horizontal and/or vertical coordinates. Recall that these are set in the array element tree-specific parameters and stored in every menu's menuLeft and menuTop properties. So, first we create an IE4 variable, menuLocBod, to store the verbose menuLoc.document.body object, and cut down on our typing:

  if (IE4) menuLocBod = menuLoc.document.body;

We first check for a value in the menu's menuLeft and menuTop properties, and position the menu at the pixel value(s) found. If no value is found, then the old relative-to-the-mouse-event position is calculated:

 if (!isFrames) {
    xPos = (currentMenu.menuLeft) ? currentMenu.menuLeft : (NS4) ?
[cc]   e.pageX : (event.clientX + menuLocBod.scrollLeft);
    yPos = (currentMenu.menuTop) ? currentMenu.menuTop : (NS4) ?
[cc]   e.pageY : (event.clientY + menuLocBod.scrollTop);
  }
  else {
    switch (navFrLoc) {
      case "left":
        xPos = (currentMenu.menuLeft) ? currentMenu.menuLeft : (NS4) ?
[cc]   menuLoc.pageXOffset : menuLocBod.scrollLeft;
        yPos = (currentMenu.menuTop) ? currentMenu.menuTop : (NS4) ?
[cc]   (e.pageY-pageYOffset)+menuLoc.pageYOffset :
[cc]   event.clientY + menuLocBod.scrollTop;
        break;
      case "top":
        xPos = (currentMenu.menuLeft) ? currentMenu.menuLeft : (NS4) ?
[cc]   (e.pageX-pageXOffset)+menuLoc.pageXOffset :
[cc]   event.clientX + menuLocBod.scrollLeft;
        yPos = (currentMenu.menuTop) ? currentMenu.menuTop : (NS4) ?
[cc]   menuLoc.pageYOffset : menuLocBod.scrollTop;
        break;
      case "bottom":
        xPos = (currentMenu.menuLeft) ? currentMenu.menuLeft : (NS4) ?
[cc]   (e.pageX-pageXOffset)+menuLoc.pageXOffset :
[cc]   event.clientX + menuLocBod.scrollLeft;
        yPos = (currentMenu.menuTop) ? currentMenu.menuTop : (NS4) ?
[cc]   menuLoc.pageYOffset+menuLoc.innerHeight :
[cc]   menuLocBod.scrollTop + menuLocBod.clientHeight;
        break;
      case "right":
        xPos = (currentMenu.menuLeft) ? currentMenu.menuLeft : (NS4) ?
[cc]   menuLoc.pageXOffset+menuLoc.innerWidth :
[cc]   menuLocBod.scrollLeft+menuLocBod.clientWidth;
        yPos = (currentMenu.menuTop) ? currentMenu.menuTop : (NS4) ?
[cc]   (e.pageY-pageYOffset)+menuLoc.pageYOffset :
[cc]   event.clientY + menuLocBod.scrollTop;
        break;
    }
  }

The next four statements remain unchanged from previous versions. The menu is moved into position; it is possibly repositioned to fit in the browser window; its isOn property is set; and it is displayed.

  currentMenu.moveTo(xPos,yPos);
  currentMenu.keepInWindow()
  currentMenu.isOn = true;
  currentMenu.showIt(true);

Finally, popMenu() returns false.

With our menu displayed, we can begin menu tree navigation.


Produced by Peter Belesis and

All Rights Reserved. Legal Notices.
Created: Sept. 03, 1998
Revised: Sept. 03, 1998

URL: http://www.webreference.com/dhtml/column21/hier3Popup.html