Back to the Web Developer's Journal Main Page
internet.com
side nav bar

Ever wanted to see a detailed walk-through of a sophisticated JavaScript program? Well get your hiking boots on and join us for a stroll through the entire length of Aaron Prenot's cascading menu script.
Javascript Articles:

HOW DID THEY DO THAT???

Find out in:
Amazing HTML



Site Map

Jobs at webdeveloper.com


Check out our Web-based
Discussion Groups:

Check out and join our email-based Mailing Lists for Web developers.


Developer Channel
FlashKit
Jobs.webdeveloper
JavaScript.com
JavaScriptSource
JustSMIL
ScriptSearch
Streaming Media World
WebDeveloper.com
WebReference
XMLFiles
WDVL
Discussion Groups Book Reviews Software Reviews Download Web Tools

You'll need a big pot of coffee for this one.

Cascading Menu Script Explained

by Aaron Prenot

This article follows on from Cascading Menus and fully explains how the JavaScript in that menu program works. It's also a crash course in Dynamic HTML (DHTML), and to understand it you will need to know basic JavaScript.
August 16, 2000

You can refer to the full JavaScript code for HMenu.js at any time through this link.

The Script

Let's recap on the elements dealt with in Cascading Menus. A menu is made up of four basic items - the menu itself, menu items, submenu items, and submenus. A menu item is a single entry in a menu. A submenu item opens a submenu when the mouse is moved over it. The menu is the container for all the menu items and submenu items. All menus that are not submenus are referred to as top level menus.

The menus themselves are defined using HTML and a lot of DIV elements, one placed inside the other, and all placed inside the grandaddy DIV element, menuContainer.

initMenu()

To make the menus come to life, the webmaster has to add onload="initMenu()" to their document's BODY tag. initMenu() is the function that gets the ball rolling.

Which brings us on to the file where all the real code is contained, HMenu.js. The rest of the article will walk you through it, section by section.


function initMenu(){
	// test for IE4+, it won't work otherwise
	if(!document.all) return false;
	
	menuContainer.activeMenu = null;
	menuContainer.closeAll = closeAll;
	findMenus();
	attachMenus();
}

initMenu() looks to be a very plain function, and it is. First, it makes sure that the browser is Internet Explorer 4 or above. This is done with if(!document.all) return false;. What this does is check to see if the object document.all exists. document.all is the object that Internet Explorer uses to keep track of all the information in a web page. Netscape Navigator and others don't use this object, so if it doesn't exist then we aren't using Internet Explorer and we exit the function.

Next we initialize the variable menuContainer.activeMenu. menuContainer is the DIV element that we use to hold all the menu definitions. We just give it a variable activeMenu so that we can always tell which, if any, menu is open. Then we add a function to the menuContainer element, closeAll. This function will close the active menu, and all its submenus.

After we attach the variable and the function to menuContainer, we then run the function findMenus(). This function sorts though all the HTML that we created to find all the real menu definitions, including the submenus.

findMenus()


function findMenus(){
	var cTag = menuContainer.children;
	for(var i=0; i < cTag.length; i++){
		tcTag = cTag[i];
		if(tcTag.className == "menu"){
			var tMenu = findSubMenus(tcTag);
			menus[menus.length] = tMenu;
		}
	}
	for(var i=0; i < menus.length; i++){
		var tcTag = menus[i]
		moveHTML(tcTag);
	}
	for(var i=0; i < menus.length; i++){
		var tcTag = menus[i];
		setupMenu(tcTag);
	}
}

It does this by using the special collection (a collection is an array of objects or elements) children. This is all of the elements immediately inside of any given HTML element. I say immediate because, like real children, you don't count the ones that belong to one of your children (grandchildren). It stores all of the children of the menuContainer element to the variable cTag. Then, we loop through each of the children, storing the actual child element in the variable tcTag. You don't have to do this, but it makes it easier to type later.

Then we check to see if it is a menu. Since all menus, including submenus, have the class="menu" inside their DIV element, we can check for that by examining the child's className property. If the className property is set to menu, then bingo, we have a menu! We must also check to see if there are any submenus. That is done with the function findSubMenus(). We pass findSubMenus() the menu element, and then it gives us back a genuine menu object. Now, before we delve into this new function, let's explain what a menu object is.

The Menu Object


function Menu(){
	this.id = "";
	this.subMenus = new Array();
	this.items = new Array();
	this.hasChildren = false;
	this.isChild = false;
	this.parentMenu = null;
	this.parentItem = null;
}

A menu object is a special type of function that doesn't really do anything. Instead, it just stores information into whatever variable is used to create the object using this.

To create a menu object, you use the new operator:


var menuObject = new Menu();

So in our case, anything stored using this, would actually be stored in menuObject. In other programming languages, it would be called a class. A menu object contains its id (set later), a collection of all its subMenus (if it has any), a collection of the items in the menu, and information regarding its location in the menu hierarchy.

hasChildren tells us whether or not this menu has any submenus and isChild tells us if this is a submenu. If this is a submenu, parentMenu will point to the menu that this sprouts from, and parentItem will point to the item that this sprouts from.

findSubMenus()


function findSubMenus(menu){
	var cMenu = menu.children;
	var tMenu = new Menu();
	tMenu.id = menu.id;
	
	for(var i=0; i < cMenu.length; i++){
		var tcMenu = new Item();
		tcMenu.id = cMenu[i].id;
		if(tcMenu.id.indexOf("subMenu") != -1){
			++i;
			var subMenu = cMenu[i];
			tMenu.subMenus[tMenu.subMenus.length] = findSubMenus(subMenu);
			tMenu.subMenus[(tMenu.subMenus.length - 1)].isChild = true;
			tMenu.subMenus[(tMenu.subMenus.length - 1)].parentMenu = tMenu;
			tMenu.subMenus[(tMenu.subMenus.length - 1)].parentItem = tcMenu;
			tMenu.hasChildren = true;
			tcMenu.hasMenu = true;
			tcMenu.menu = tMenu.subMenus[(tMenu.subMenus.length - 1)];
		}
		tcMenu.parentMenu = tMenu;
		tMenu.items[tMenu.items.length] = tcMenu;
	}
	return tMenu;
}

findSubMenus() starts like findMenus() by storing the children for the element in the variable cMenu. Then we create a menu object, which we discussed a moment ago, in tMenu. Now we set the menu object's id to be the same as the element's id. Once again we loop through all the of element's children, only this time we create a new item object in tcMenu. An item object stores the item element's id, a variable that points to the menu it is in (parentMenu), a variable to tell us if the item is a submenu item (hasMenu), and if it is a submenu item the variable menu will point to the submenu.

We are trying to see if the item's class is set to subMenu. As we already know, a submenu item comes just before the definition for the submenu, so when we find a submenu item we automatically jump to the next child (++i). Then we search this new submenu for more more submenus by calling findSubMenus() again, passing the newly found submenu. The menu object returned by findSubMenus() is then added to the current menu's subMenus collection, and we set the properties in the menu object that pertain only to a submenu.

Confusing? Functions like this (called recursive functions, because they call themselves) usually are. Let's try a practical example from the first article, with the editors. First findMenus() finds menu1, the only menu we have. It calls findSubMenus() and passes menu1. findSubMenus() then starts looping through menu1 and immediately finds the first submenu item, the item for Bruce Morris (submenu1_1). It then jumps to the next child in menu1, which is menu1_1, the submenu for Bruce Morris. We call findSubMenus() again, this passing menu1_1. When we loop though menu1_1's children, we never find any items with subMenu in className, so instead of calling findSubMenus() again, we create all the menu items, and then pass back the new menu object. Now we are back in the original findSubMenus() call, and in the next loop we find the submenu item for Charlie Morris, and the process starts again. After we loop through all the submenu items and menus, we pass back the menu object for menu1 to findMenus() and we try to find another menu, but we don't, so findMenus() continues in a different way.

Ok, back to findSubMenus(). As we are looping though the menu's items, if the item is not a submenu item then we don't need to call findSubMenus() again. After we check to see if an item is a submenu item, whether it is or not, we add the item to the menus items collection. Once we exhaust all the items in the menu, we will pass the menu object back to findMenus()...

findMenus() - Continued...

As we search menuContainer for menus, the menu objects that are returned from findSubMenus() are stored in a global array called simply enough menus. This array will contain all of the top level menus for the page.

After we find all the menus, it's time to do the complicated stuff.

For convenience, content for the menus is written in HTML, but to make all the menus separate boxes, we have first to move the HTML around. This is accomplished in the second loop in findMenus().


function findMenus(){
	var cTag = menuContainer.children;
	for(var i=0; i < cTag.length; i++){
		tcTag = cTag[i];
		if(tcTag.className == "menu"){
			var tMenu = findSubMenus(tcTag);
			menus[menus.length] = tMenu;
		}
	}
	for(var i=0; i < menus.length; i++){
		var tcTag = menus[i];
		moveHTML(tcTag);
	}
	for(var i=0; i < menus.length; i++){
		var tcTag = menus[i];
		setupMenu(tcTag);
	}
}

With this loop, instead of cycling through all the children of the menu elements, we use the menu objects created by findSubMenus(). The loop goes though menus, the array containing all of the top level menu objects. We store the menu object in the variable tcTag, and then we call a new function, moveHTML(). We pass the menu object to moveHTML(), and now we brace ourselves, because this is another one of those recursive functions.

moveHTML()


function moveHTML(menu){
	if(menu.hasChildren == true){
		for(var i=0; i < menu.subMenus.length; i++){
			moveHTML(menu.subMenus[i]);
		}
	}
	var tMenu = eval(menu.id);
	var tMenuHTML = tMenu.outerHTML;
	
	tMenu.outerHTML = "";
	menuContainer.innerHTML += tMenuHTML;
}

As simple as this code looks, it does a bundle. First we start by checking to see if this menu has any submenus, because if it does, we will need to move that HTML first. This is where the recursiveness comes into play. If the current menu (right now the top menu) has any submenus, we will loop through all of the submenus. For every submenu, we will call moveHTML() again, and again check for submenus, until we get to the very last (bottom) submenu. For every menu that we find, the code after the loop will execute.

First we reference the actual HTML element. Since an element is referenced by its id, we use the eval() function. This will turn the string contained in the menu object's id variable (say "menu1") into the the code for referencing the element (menu1). Next we temporarily store the element's HTML, contained in its outerHTML variable. Then we erase the element from the document by setting its HTML (in outerHTML) to nothing. Then we recreate it by adding the HTML inside the menuContainer element. The HTML inside an element is stored in innerHTML, hence the inner. And now we have the menu element restored, good as new, only now, it isn't nested inside of another menu any more.

In case you're wondering why we can't move the HTML as soon the menu is found, it's because we can't move the child of an element that we've already referenced, without creating errors.

findMenus() - Continued...Again

Now that we have moved all the HTML around so that the menus will look right, we have to make the menus actually do something. Right now, they are just a bunch or boxes (at least now they're pretty) that do nothing. They don't even appear yet. The last loop in findMenus() takes care of that (but not all by itself) and is the most complicated part of the script.


function findMenus(){
	var cTag = menuContainer.children;
	for(var i=0; i < cTag.length; i++){
		tcTag = cTag[i];
		if(tcTag.className == "menu"){
			var tMenu = findSubMenus(tcTag);
			menus[menus.length] = tMenu;
		}
	}
	for(var i=0; i < menus.length; i++){
		var tcTag = menus[i]
		moveHTML(tcTag);
	}
	for(var i=0; i < menus.length; i++){
		var tcTag = menus[i];
		setupMenu(tcTag);
	}
}

This last loop, once again, goes through the menus array, only this time it calls the latest greatest function setupMenu(). Once again we pass the menu object to the function, and once again we will brace ourselves for a recursive ride.

Final Part >>>
Back to the Web Developer's Journal
Contact WDJ   •    Suits!   •    Propheads!   •    Ponytails!
Discuss   •    Subscribe   •    Search


internet.com

IT | Developer | Internet News | Small Business | Personal Technology | International | Search internet.com | Advertise | Corporate Info
Newsletters | Tech Jobs | E-mail Offers

internet.commerce
Be a Commerce Partner                                
  

internet.commediabistro.comJusttechjobs.comGraphics.com

Search:

WebMediaBrands Corporate Info

Legal Notices, Licensing, Reprints, Permissions, Privacy Policy.
Advertise | Newsletters | Shopping | E-mail Offers | Freelance Jobs