/*------------------------------------------------------------------------------
Cross-Browser Menu Control Script
  Tested on: IE 5.0, IE 6.0, Netscape 7.1, Opera 7.10, Mac IE 5.1 (w/ positioning bug)

Created by Jeff Lanning - Sept 25, 2003
Copyright (C) 2003 QCI.  All rights reserved.
--------------------------------------------------------------------------------*/

// member variables
var _menusReady = false;
var _menus = new Array();
var _subMenus = new Array();
var _menuTimer = null;
var _subMenuTimer = null;

// attach to the page load event to initalize
var _bodyOnLoad = window.onload;
window.onload = _initMenus;

// initalizes the menus
function _initMenus()
{	
	// initalize menus only if key features are supported
	if( !document.getElementById ) return;
			
	// initalize the menus in the external array
	if( typeof(Menus) != 'undefined' )
	{
		var defaults = (typeof(MenuDefaults) != 'undefined' ? MenuDefaults : new Object());
		_menus = _processMenusArray(Menus, defaults, false, _cancelHideMenus, _requestHideMenus)
	}
		
	// initalize the sub menus in the external array
	if( typeof(SubMenus) != 'undefined' )
	{
		var defaults = (typeof(SubMenuDefaults) != 'undefined' ? SubMenuDefaults : new Object());
		_subMenus = _processMenusArray(SubMenus, defaults, true, _subMenuOver, _subMenuOut)
	}

	// run the hide code to reset to a know state
	_hideMenus();
		
	// menu system is ready
	_menusReady = true;

	// attach to the page unload event
	window.onunload = _stopMenus;

	// execute the original body.onload event
	if( _bodyOnLoad ) _bodyOnLoad();
}

// processes and initalizes all items in an array of menus or sub menus and returns an array just valid menus
function _processMenusArray(menus, defaults, isSubMenus, onMenuOver, onMenuOut)
{
	var items = new Array();
	
	// loop through all menus in the passed array, add valid ones to our new array
	var count = menus.length;
	for( var i = 0; i < count; i++ )
	{
		var item = menus[i];
		
		// get the group element, if an id as provided
		var groupID = item.groupID;
		var group = (groupID ? document.getElementById(groupID) : null);
		if( !group ) continue;
				
		// set group properties and events
		group.isGroup = true;
		group.isSub = isSubMenus;
		group.cssClass = (typeof(item.groupCssClass) != 'undefined' ? item.groupCssClass : 
			(typeof(defaults.groupCssClass) != 'undefined' ? defaults.groupCssClass : null));
		group.cssClassOn = (typeof(item.groupCssClassOn) != 'undefined' ? item.groupCssClassOn : 
			(typeof(defaults.groupCssClassOn) != 'undefined' ? defaults.groupCssClassOn : null));
		group.onFunc = (typeof(item.onGroupOn) != 'undefined' ? item.onGroupOn : 
			(typeof(defaults.onGroupOn) != 'undefined' ? defaults.onGroupOn : null));
		group.offFunc = (typeof(item.onGroupOff) != 'undefined' ? item.onGroupOff : 
			(typeof(defaults.onGroupOff) != 'undefined' ? defaults.onGroupOff : null));
		group._onmouseover = group.onmouseover;
		group._onmouseout = group.onmouseout;
		group.onmouseover = _groupOver;
		group.onmouseout = _groupOut;
		
		// get the menu ID; or build the menu with a generated ID
		var menuID = (item.menuID ? item.menuID : null);
		if( typeof(item.menuItems) != 'undefined' )
		{
			menuID = (!isSubMenus ? "mnuDyn" + i : "mnuDynSub" + i);
			_buildMenu(menuID, item.menuItems);
			item.menuID = menuID;
		}
		
		// get the menu element
		var menu = (menuID ? document.getElementById(menuID) : null);
		if( menu )
		{		
			// set menu properties and events
			menu.isSub = isSubMenus;
			menu.showOnRight = (typeof(item.showOnRight) != 'undefined' ? item.showOnRight : 
				(typeof(defaults.showOnRight) != 'undefined' ? defaults.showOnRight : isSubMenus));
			menu.onmouseover = onMenuOver;
			menu.onmouseout = onMenuOut;
			menu.style.zIndex = (isSubMenus ? 32767 : 32766);
			if( menu.className.length <= 0 ) // no class defined
			{
				menu.className = (typeof(item.menuCssClass) != 'undefined' ? item.menuCssClass : 
					(typeof(defaults.menuCssClass) != 'undefined' ? defaults.menuCssClass : ""));
			}

			// create cross-refrences between menu and group
			group.menu = menu;
			menu.group = group;
		}
		else // menu element not found
		{
			item.menuID = null;
		}
		
		// add this item to our array
		item.group = group;
		items[items.length] = item;
	}
	
	// return array of valid menus
	return items;
}


// puts the menu control in a quiet state
function _stopMenus()
{
	_cancelHideMenus();
	_menusReady = false;
}

// creates a menu with the specified ID, containing items defined in the passed array
function _buildMenu(menuID, menuItems)
{
	// get the user-defined default menu item defaults, if available
	var defaults = (typeof(MenuItemDefaults) != 'undefined' ? MenuItemDefaults : new Object());
	
	// create the menu container
	var div = document.createElement("div");
	div.id = menuID;
	div.style.position = "absolute";
	div.style.visibility = "hidden";
	div.style.top = "0px";
	div.style.left = "0px";
	
	// create the table elements
	var table = document.createElement("table");
	table.cellSpacing = 0;
	table.cellPadding = 0;
	table.border = 0;
	
	// create a table cell for each menu item in the array
	var tbody = document.createElement("tbody");
	var count = menuItems.length;
	for( var i = 0; i < count; i++ )
	{
		var menuItem = menuItems[i];

		// ignore this item if no text property is specified
		if( !menuItem.text ) continue;

		// normalize the menu item properties and events (ensure each is defined)
		menuItem.cssClass = (typeof(menuItem.cssClass) != 'undefined' ? menuItem.cssClass : 
			(typeof(defaults.cssClass) != 'undefined' ? defaults.cssClass : ""));
		menuItem.cssClassOver = (typeof(menuItem.cssClassOver) != 'undefined' ? menuItem.cssClassOver : 
			(typeof(defaults.cssClassOver) != 'undefined' ? defaults.cssClassOver : ""));
		menuItem.onOver = (typeof(menuItem.onOver) != 'undefined' ? menuItem.onOver : 
			(typeof(defaults.onOver) != 'undefined' ? defaults.onOver : null));
		menuItem.onOut = (typeof(menuItem.onOut) != 'undefined' ? menuItem.onOut : 
			(typeof(defaults.onOut) != 'undefined' ? defaults.onOut : null));
		menuItem.onClick = (typeof(menuItem.onClick) != 'undefined' ? menuItem.onClick : 
			(typeof(defaults.onClick) != 'undefined' ? defaults.onClick : null));

		// create and define the table row and cell
		var tr = document.createElement("tr");
		var td = document.createElement("td");
		td.menuItem = menuItem;
		if( menuItem.id ) td.id = menuItem.id;
		td.className = menuItem.cssClass;
		td.noWrap = true;

		// add menu item text
		var txt = document.createTextNode(menuItem.text);
		td.appendChild(txt);

		// attach event handlers
		td.onmouseover = _menuItemOver;
		td.onmouseout = _menuItemOut;
		td.onclick = _menuItemClick;
		
		// add the td to the tr; tr to the tbody
		tr.appendChild(td);
		tbody.appendChild(tr);
	}
	
	// add the tbody to the table; table to the div
	table.appendChild(tbody);
	div.appendChild(table);
				
	// add this menu container to the page body
	document.body.appendChild(div);
}


// handles the mouse moving over a menu group or sub group
function _groupOver(evt)
{
	if( !evt ) evt = window.event;
	if( !evt ) return;

	var elem = (evt.target ? evt.target : evt.srcElement);
	while( elem && !elem.isGroup ) { elem = elem.parentNode; }
	if( elem == null ) return;

	// fire any previous event handler and show the menu for this group
	if( elem._onmouseover ) elem._onmouseover(evt);	
	_showMenu(elem);
}

// handles the mouse moving over a menu group or sub group
function _groupOut(evt)
{
	if( !evt ) evt = window.event;
	if( !evt ) return;

	var elem = (evt.target ? evt.target : evt.srcElement);
	while( elem && !elem.isGroup ) { elem = elem.parentNode; }
	if( elem == null ) return;
		
	// fire any previous event handler and start to hide menu
	if( elem._onmouseover ) elem._onmouseover(evt);
	if( !elem.isSub ) { _requestHideMenus(); }
	else { _requestHideSubMenus(); }
}


// handles the mouse moving over a sub menu box
function _subMenuOver()
{
	_cancelHideSubMenus();
	_cancelHideMenus();
}
// handles the mouse moving out of a sub menu box
function _subMenuOut()
{
	_requestHideSubMenus();
	_requestHideMenus();
}


// handles mouse over events on dynamically created menu items
function _menuItemOver(evt)
{
	if( !evt ) evt = window.event;
	if( !evt ) return;

	var target = (evt.target ? evt.target : evt.srcElement);
	while( target && !target.menuItem ) { target = target.parentNode; }
	if( target == null ) return;
	
	// switch the css class and fire any external handler
	target.className = target.menuItem.cssClassOver;
	if( target.menuItem.onOver ) target.menuItem.onOver(target);
}

// handles mouse out events on dynamically created menu items
function _menuItemOut(evt)
{
	if( !evt ) evt = window.event;
	if( !evt ) return;

	var target = (evt.target ? evt.target : evt.srcElement);
	while( target && !target.menuItem ) { target = target.parentNode; }
	if( target == null ) return;
	
	// reset the css class and fire any external handler
	target.className = target.menuItem.cssClass;
	if( target.menuItem.onOut ) target.menuItem.onOut(target);
}

// handles click events on dynamically created menu items
function _menuItemClick(evt)
{
	if( !evt ) evt = window.event;
	if( !evt ) return;

	var target = (evt.target ? evt.target : evt.srcElement);
	while( target && !target.menuItem ) { target = target.parentNode; }
	if( target == null ) return;
	
	_hideMenus();
	
	// fire the external handler or browser to the new page
	if( target.menuItem.onClick )
	{
		target.menuItem.onClick(target);
	}
	else
	{
		if( target.menuItem.url ) window.location.href = target.menuItem.url;
	}
}


// starts a timer that will hide all menus, unless cancelled
function _requestHideMenus()
{
	clearTimeout(_menuTimer);
	_menuTimer = setTimeout("_hideMenus()", 250);
}
// cancels the timer that hides all menus
function _cancelHideMenus()
{
	clearTimeout(_menuTimer);
}


// starts a timer that will hide all sub menus, unless cancelled
function _requestHideSubMenus()
{
	clearTimeout(_subMenuTimer);
	_subMenuTimer = setTimeout("_hideSubMenus()", 125);
}
// cancels the timer that hides all sub menus
function _cancelHideSubMenus()
{
	clearTimeout(_subMenuTimer);
}


// shows a menu, positioned directly under its associated group element
function _showMenu(group)
{
	// bail is memu system is not ready
	if( !_menusReady ) return;
		
	// cancel any pending hide and do it manually
	_cancelHideMenus();
	_cancelHideSubMenus();
	if( !group.isSub ) { _hideMenus(); }
	else { _hideSubMenus(); }
	
	// show the menu for this group, if there is one
	var menu = group.menu;
	if( menu )
	{
		// determine the box coords for the menu group
		var box = _getElementBoxPosition(group);

		// move the element into position under or beside the group menu
		if( menu.showOnRight ) // show on right
		{
			menu.style.top = box.top + 'px';
			menu.style.left = box.right + 'px';
		}
		else // show below
		{
			menu.style.top = box.bottom + 'px';
			menu.style.left = box.left + 'px';
		}
		
		// hind any combos that will overlap this menu
		_hideOverlappingCombos(menu);
			
		// make the menu visible
		menu.style.visibility = 'visible';
	}
		
	// change group css class and fire the group on function
	if( group.cssClassOn ) group.className = group.cssClassOn;
	if( group.onFunc ) group.onFunc(group);
}

// brute force hiding of all menus
function _hideMenus()
{
	// hide all sub menus
	_hideSubMenus();
	
	// hide each menu
	var count = _menus.length;
	for( var i = 0; i < count; i++ )
	{
		// get the group element
		var group = _menus[i].group;

		// hide the menu, change group css, and fire the group off function
		if( group.menu ) group.menu.style.visibility = 'hidden';
		if( group.cssClass ) group.className = group.cssClass;
		if( group.offFunc ) group.offFunc(group);		
	}
	
	// retore the visibility of hidden combos
	_showHiddenCombos();
}

// brute force hiding of all sub menus
function _hideSubMenus()
{
	var count = _subMenus.length;
	for( var i = 0; i < count; i++ )
	{
		// get the group element
		var group = _subMenus[i].group;

		// hide the menu, change group css, and fire the group off function
		if( group.menu ) group.menu.style.visibility = 'hidden';
		if( group.cssClass ) group.className = group.cssClass;
		if( group.offFunc ) group.offFunc(group);
	}
	
	// retore the visibility of hidden combos
	//TODO: _showHiddenCombos();
}


// hides every combo box (select tags) that overlaps the passed box
function _hideOverlappingCombos(elem)
{	
	// determine the passed elements bounding box
	var box = _getElementBoxPosition(elem);

	// examine every combo on the page, hiding ones that overlap
	var combos = document.getElementsByTagName("select");
	var count = combos.length;
	for( var i = 0; i < count; i++ )
	{
		var combo = combos[i];
		if( !combo ) continue;

		// skip this combo if it is already hidden
		if( combo.style.visibility == 'hidden' ) continue;
			
		// hind the combo if it overlaps the menu
		// note: adding a flag to the combo so we know it should be restored
		var comboBox = _getElementBoxPosition(combo);
		if( _doBoxesOverlap(comboBox, box) ) // overlap
		{
			combo.style.visibility = 'hidden';
			combo.hiddenByMenu = true;
		}
	}
}


// makes all hidden combos (select tags) visible again
function _showHiddenCombos()
{
	// show all the combo boxes that where hidden by a menu
	var combos = document.getElementsByTagName("select");
	for( var i = 0; i < combos.length; i++ )
	{
		var combo = combos[i];
		if( !combo ) continue;

		if( combo.hiddenByMenu )
		{
			combo.style.visibility = 'visible';
			combo.hiddenByMenu = null;
		}
	}
}


// determines the coordinates of an element's corners, relative to the page
// return an object with 'top', 'left', 'bottom', and 'right properties
function _getElementBoxPosition(elem)
{
	// determine the top and left values of the element
	var top = 0;
	var left = 0;
	var currentElem = elem;
	while( currentElem != null )
	{
		top += currentElem.offsetTop;
		left += currentElem.offsetLeft;
		currentElem = currentElem.offsetParent;
	}

	// determine bottom and right values of the element
	var bottom = top + elem.offsetHeight;
	var right = left + elem.offsetWidth;

	// return the box corners
	return {left:left, top:top, right:right, bottom:bottom};
}


// returns true if the two boxes specified overlap
function _doBoxesOverlap(box1, box2)
{
	if( (box1.left <= box2.right) && (box1.right >= box2.left) )
	{
		if( (box1.top <= box2.bottom) && (box1.bottom >= box2.top) )
		{
			return true;
		}
	}
	
	return false;
}


