DHTML Lab: Hierarchical Menus Ver. 2 (Cross-Browser/Frames); Recursive Menu Creation | WebReference

DHTML Lab: Hierarchical Menus Ver. 2 (Cross-Browser/Frames); Recursive Menu Creation


Logo

Hierarchical Menus Ver. 2 (Cross-Browser/Frames)
recursive menu creation

News

Parameters used for the menus on this page:

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 = "blue";
borWid = 1;
borCol = "#CC0000";
borSty = "solid";
itemPad = 3;
imgSrc = "tri.gif";
imgSiz = 10;
separator = 1;
separatorCol = "red";
isFrames = false;

Menu Animated GIF
Animated GIF demonstrating heirarchical menus for non-DHTML browsers.

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.

Creating the Menus

The makeMenu() function accepts up to four arguments. As we saw, makeTop() passes only the first two. Internally, makeMenu() assigns them to isChild and menuCount. When called by makeTop() these hold the values false and topCount, respectively. The other two arguments, parMenu and parItem apply when makeMenu() is called to create a second-or-more-level element, and they store the parent menu element and the parent item element.

The complete makeMenu() function is as follows:

function makeMenu(isChild,menuCount,parMenu,parItem) {
 
  menu = makeElement("elMenu" + menuCount);
  menu.array = eval("arMenu" + menuCount);
  menu.maxItems = menu.array.length / 3;
  menu.setup = menuSetup;
  menu.itemCount = 0;
        
  while (menu.itemCount < menu.maxItems) {
    menu.itemCount++;
    status = "Creating Hierarchical Menus: "
[cc]      + menuCount + " / " + menu.itemCount;
    prevItem = (menu.itemCount > 1) ? menu.item : null;
    itemName = "item"
[cc]      + menuCount + "_" + menu.itemCount;
    menu.item = makeElement(itemName,menu);
    menu.item.prevItem = prevItem;
    menu.item.setup = itemSetup;
    menu.item.setup(menu.itemCount,menu.array);
    if (menu.item.hasMore) {
      makeMenu(true,menuCount + "_" + menu.itemCount,
[cc]        menu,menu.item);
      menu = menu.parentMenu;
    }
  }
  menu.lastItem = menu.item;
  if (!isChild) parName = parItem = null;
  menu.setup(isChild,parMenu,parItem);
}

Since makeMenu() is a recursive function, that is, it calls itself, we must be meticulous in our variable assignment scheme. Instead of using variables, as in Version 1, most assignments in makeMenu() are made to custom element properties. In this way, all elements have their own properties with their own, different values assigned.

For example, the first statement in makeMenu() creates an element using "elMenu" and whatever suffix is specified by menuCount. For top-level menus, the elements created will have the identifiers: elMenu1, elMenu2, elMenu3, etc.

  menu = makeElement("elMenu" + menuCount);

This new element is assigned to the menu variable. The next statement gives menu an array property which is assigned the array that corresponds to the menu-being-created. So, if menu is elMenu1, menu.array is arMenu1.

  menu.array = eval("arMenu" + menuCount);

Recall that every menu item needs three consecutive array elements to be defined. Therefore, if we divide the total number of array elements by 3, we get the total number of items that our menu will contain. This value is assigned to the menu's maxItems property:

  menu.maxItems = menu.array.length / 3;

As in version 1, the setup function for every menu, that defines all the custom methods/properties/event handlers, is menuSetup(). We assign the menuSetup() function to the menu's setup() method:

  menu.setup = menuSetup;

Every menu has a custom counter to track the number of items created, itemCount, now initialized to 0.

  menu.itemCount = 0;

Next, we enter a while loop, and remain within the loop as long as the menu's itemCount (items created) is less than the menu's maxItems (total items to be created):

  while (menu.itemCount < menu.maxItems) {
    ...statements...
  }

Creating the Items

First, itemCount is incremented, so it always points to 1 for the first item, and we display a courtesy message in the browser's status bar.

    menu.itemCount++;
    status = "Creating Hierarchical Menus: "
[cc]      + menuCount + " / " + menu.itemCount;

Next a regular variable, prevItem, is created to store the item that was created previously. We will need this when positioning items under each other. The first time through, there is no previous item, so prevItem gets a null value.

    prevItem = (menu.itemCount > 1) ? menu.item : null;

We create a string with a unique identifier for the item, and assign it to itemName. For our first menu, the items will have identifiers in the form of: item1_1, item1_2, item1_3, etc.:

    itemName = "item"
[cc]      + menuCount + "_" + menu.itemCount;

itemName is passed to makeElement() as a first argument. Notice that when creating items (as opposed to menus) we pass a second argument: the containing menu. The item element created by makeElement() is assigned to menu.item:

    menu.item = makeElement(itemName,menu);

Now that we have an element for the item, we create a prevItem property for it and assign whatever element is stored in prevItem. Since the prevItem variable was declared just before item creation it was assigned the then-current item, which after a new item is created, becomes the previous item:

    menu.item.prevItem = prevItem;

As in version 1, every item has its own setup function, itemSetup(), which is assigned to the setup() method of the item:

    menu.item.setup = itemSetup;

The item's setup method() is called immediately, passing the current item count and menu array:

    menu.item.setup(menu.itemCount,menu.array);

Recursion

Once the menu item has been set up, we continue in makeMenu(). In the item setup, a new property is added to every item: hasMore, denoting a child menu appearing off this item.

If a child menu should appear off this item, then makeMenu() calls itself, this time with four arguments. The third and fourth arguments are the current menu and the current item. When makeMenu() receives them, these will become the child menu's parent menu and parent item.

    if (menu.item.hasMore) {
      makeMenu(true,menuCount + "_" + menu.itemCount,
[cc]        menu,menu.item);
      menu = menu.parentMenu;
    }

makeMenu() will call itself every time a child menu is to be created. If this child menu has its own child menu, makeMenu() will again call itself to create it, and so on. Only when all menu levels off an item have been created, will makeMenu() continue execution on the item. When makeMenu() returns, the current menu is always one level down in the hierarchy. To continue execution on the proper level, we must restore menu to reflect the proper position in the hierarchy. Every child menu is given a parentMenu property, when set up, storing the menu that opens it. Therefore, menu is assigned the parentMenu property of the current menu, which, as mentioned is always one level down. makeMenu() can thus continue where it left off, before recursion.

Once all items for a menu have been created, makeMenu() breaks out from the while loop to execute its final three statements:

  menu.lastItem = menu.item;
  if (!isChild) parName = parItem = null;
  menu.setup(isChild,parMenu,parItem);

When we break out of the while loop, menu.item is the final item in the menu. This element is assigned to menu.lastItem to be used in sizing the menu element later.

If the menu is a top-level menu, the parMenu and parItem variables have not been created from the third and fourth arguments of makeMenu(). We therefore create them here and assign a null value, since there is no parent menu or parent item. Lastly, we call the menu's setup() method passing three arguments culled from the initial arguments passed to makeMenu().

Although we provide a function for many menu levels, it is not a good idea to use more than two or three levels. The result is more screen clutter than if we had hard-coded the links on the page. Use only the levels absolutely necessary.

We have covered the most complex statements in the Version 2 script. It's all downhill from here.


Produced by Peter Belesis and

All Rights Reserved. Legal Notices.
Created: May. 22, 1998
Revised: May. 22, 1998

URL: http://www.webreference.com/dhtml/column20/hier2Make.html