/*************************************************************************

  Implements popup/rollover menu functionality using a set
  of nested HTML lists to specify the menu structure.  Utilizes the
  MenuManager object to handle the menu interface functionality.

  Original Author:  Brian Stucky, February 22, 2006
  Added menu animation code:  Brian Stucky, February 24, 2007
  Rewritten to improve the "feel" of the menu interface and clean up the code:  Brian Stucky, July 22, 2009

  Tested and works with:
	Firefox 3.0.11, WinXP
	Firefox 3.0.10, XUbuntu
	SeaMonkey 1.0.7, WinXP
	Netscape 8.1 and 7.2, WinXP
	MS IE 8.0, WinXP
	MS IE 6.0, Win2000
	Opera 9.10, WinXP
	Konquerer 4.2.2, XUbuntu

  Tested and does not work with:

  Needs to be tested:
	MS IE 5.5, Win98
	MS IE 5.0/Win98
	Firefox 1.0.7/MacOS 10.3
	Safari 1.3.1

  Copyright(c) Bethel College, 2009

**************************************************************************/

function MenuManager()
{
	var self = this;
	this.notIE = navigator.appName.indexOf("Microsoft") == -1;
	this.frames = 6;
	this.isVisible = false;
	this.revealTimer = null;
	this.hoverTimer = null;
	this.hideAllTimer = null;
	this.revealElement = null;
	this.menuLevel = 0;
	this.initialDelay = 400;
	this.secondaryDelay = 200;
	this.popups = new Array();

	if (this.notIE)
		this.animateReveal = function() { self.revealScroll(); };
	else
		this.animateReveal = function() { self.revealBasic(); };
	this.cancelAnimate = this.cancelRevealScroll;
}

MenuManager.prototype.handleMouseOver = function(element)
{
	var self = this;

	// cancel the timer for hideAll()
	if (this.hideAllTimer != null)
	{
		clearTimeout(this.hideAllTimer);
		this.hideAllTimer = null;
	}

	// choose the hover delay time based upon whether or not the menus have been initially opened
	var delay = this.secondaryDelay;
	if (!this.isVisible)
		delay = this.initialDelay;

	this.menuLevel = element.parentNode.parentNode.getAttribute('menulevel');

	// top-level menus will display immediately; others will require the mouse to hover over them for "delay" milliseconds
	if ((this.menuLevel == 0) && (this.isVisible))
		this.reveal(element);
	else
		this.hoverTimer = setTimeout(function() { self.reveal(element); }, delay);
}

MenuManager.prototype.handleMouseOut = function()
{
	var self = this;

	// if an item is in the process of displaying, cancel it
	if (this.revealTimer != null)
		this.cancelAnimate();

	// if an item is waiting for a mouse hover time, cancel it
	if (this.hoverTimer != null)
	{
		clearTimeout(this.hoverTimer);
		this.hoverTimer = null;
	}

	// set a timer to hide all visible popups
	this.hideAllTimer = setTimeout(function() { self.hideAll(); }, this.initialDelay);
}

MenuManager.prototype.hideAll = function()
{
	var item;

	// hide all visible popups
	while (this.popups.length > 0)
	{
		item = this.popups.shift();
		item.style.visibility = 'hidden';
		item.style.zIndex = '0';
	}

	this.isVisible = false;
	this.hideAllTimer = null;
}

MenuManager.prototype.hideOtherPopups = function()
{
	// hide the visible popups that are at a deeper menulevel
	for (var i = this.popups.length - 1; i >= 0; i--)
	{
		if (this.popups[i].getAttribute('menulevel') > this.menuLevel)
		{
			this.popups[i].style.visibility = 'hidden';
			this.popups[i].style.zIndex = '-1';
			this.popups.splice(i, 1);
		}
	}
}

MenuManager.prototype.reveal = function(element)
{
	var parentElement = element.parentNode;

	this.hoverTimer = null;

	// hide any open popups
	this.hideOtherPopups();

	// see if this item has a submenu
	var revealElement;
	for (i = 0; i < parentElement.childNodes.length; i++)
	{
		if (parentElement.childNodes[i].nodeName == 'UL')
		{
			// there is a sumbmenu, so set a timer to display it
			this.revealElement = parentElement.childNodes[i];
			this.currFrame = 0;

			this.popups.push(this.revealElement);

			// if no menu items are visible, then animate the menu reveal
			if (!this.isVisible)
				this.animateReveal();
			else
				this.revealBasic();

		}
	}
}

MenuManager.prototype.revealBasic = function()
{
	this.revealElement.style.zIndex = '500';

	this.revealElement.style.visibility = 'visible';
	this.isVisible = true;
}

MenuManager.prototype.revealScroll = function()
{
	if (this.currFrame == 0)
	{
		this.revealSize = 0;
		this.revealElement.style.clip = 'rect(auto auto 0px auto)';
		this.revealElement.style.visibility = 'visible';
	}

	this.revealSize += (this.revealElement.offsetHeight / this.frames);
	this.revealElement.style.clip = 'rect(auto auto ' + this.revealSize + 'px auto)';
	this.currFrame++;

	if (this.currFrame == this.frames)
	{
		if (this.notIE)
			this.revealElement.style.clip = 'auto';
		else
			this.revealElement.style.clip = 'rect(auto auto auto auto)';

		this.isVisible = true;
		this.revealTimer = null;
	}
	else
		this.revealTimer = setTimeout(this.animateReveal, 10);
}

MenuManager.prototype.cancelRevealScroll = function()
{
	if (this.revealTimer != null)
		clearTimeout(this.revealTimer);

	this.revealTimer = null;

	this.revealElement.style.visibility = 'hidden';
		
	if (this.notIE)
		this.revealElement.style.clip = 'auto';
	else
		this.revealElement.style.clip = 'rect(auto auto auto auto)';

	//this.isVisible = false;
}


var menuManager = new MenuManager();

function parseMenu(rootNode, depth)
{
	var node;

	for (var i = 0; i < rootNode.childNodes.length; i++)
	{
		node = rootNode.childNodes[i];
		if (node.nodeName == 'A')
		{
			node.onmouseover = function() { menuManager.handleMouseOver(this); };
			node.onmouseout = function() { menuManager.handleMouseOut(); };
		}
		else if (node.nodeName == 'UL')
		{
			node.setAttribute('menulevel', depth);
			parseMenu(node, depth + 1);
		}
		else if (node.nodeName == 'LI')
		{
			parseMenu(node, depth);
		}
	}
}

function initMenu(id)
{
	var menuElement = document.getElementById(id);

	if (menuElement)
	{
		parseMenu(document.getElementById(id), 1);
		document.getElementById(id).setAttribute('menulevel', 0);
	}
}


/********
  Fix IE 5.0's broken Javascript support by implementing the array functions we need.
  Note that these array functions are not full implementations of the standard Javascript
  array methods; they simply provide the functionality we need for the menus.
********/

// this simplified implementation of push adds a single element at the end of the array
if (!Array.prototype.push)
{
	Array.prototype.push = function(data)
	{
		this[this.length] = data;
	}
	// alert("IE 5 sucks!");
}

if (!Array.prototype.shift)
{
	Array.prototype.shift = function()
	{
		var data = this[0];

		if (this.length > 0)
		{
			for (var i = 0; i < this.length - 1; i++)
				this[i] = this[i+1];

			this.length--;
		}

		return data;
	}
}

// this simplified implementation of splice ignores the len argument and only removes the array entry at
// the location specified by index and returns it
if (!Array.prototype.splice)
{
	Array.prototype.splice = function(index, len)
	{
		var data = this[index];

		for (var i = index; i < this.length - 1; i++)
			this[i] = this[i+1];

		this.length--;

		return data;
	}
}

