///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                         Fading Menu Script                                //
//                       Copyright (c) 2003-2006                             //
//                            Marc Peterson                                  //
//                     marc.s.peterson at gmail.com                          //
//                                v2.3.1                                     //
//                     http://www.marcpeterson.name                          //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

// This script may be freely used as long as the comments above remain intact.

// 2006-06-30 - fixed useLink() bug when anchors were in link.  Removed code that stops script
//              since back button returns to page with javascript in same state as when user left.
// 2006-06-01 - added checkLoadState() to ensure everthing is loaded before running.
//            - Added robust checking for variable/function existence before running to prevent "is undefined" errors.
//            - Changes variable names to start with "_"
// 2006-05-10 - added setNavItemOn() to manually set a nav item's class as "on"
// 2006-05-09 - initMenuPos() created so menu position is calculated every time it is shown.  For resizing centered content. 
// 2006-05-01 - made onmouseover="initNavItem(this)" automatically initialize the nav item with needed event 
//			    handlers to simplify menu construction.
//			  - added useLink(event,oNavItem)
//
// Uses JavaScript, CSS and the DOM to create a multi-level menu system.
//
// The menu must be created in HTML with CSS.  There are two bulding blocks, menus and nav items.
// Menus consists of a number of nav items.  Any div may act as a nav item by having a "child" attribute.
// The value of "child" should be the id of the menu it is to turn on/off.  Likewise, that menu needs a
// "parent" attribute with the id of the controlling nav item.
//
// If the nav item has a class, it will be automatically given the class className+"_over" upon rollover to create
// the rollover effect.
//
// Any menu div must have the custom attribute itemtype="menu" so that it will be recognized as a menu.
// Each nav items div within the menu div should have itemtype="menu_item" so it will be recognized as a nav item.
// The very top level of nav items must have onmouseover="if (self.initNavItem) {initNavItem(this,1);};" set
// so that it can initialize that element as a nav item.  Menus will then be automatically initialized the first 
// time they are displayed.  Menus must be positioned absolutely.
//
//
// REQUIRED ATTRIBUTES/EVENT HANDLERS
//   id					- every menu needs a unique id.  every controlling nav item needs a unique id. 
//   style:position		- every menu needs position=absolute
//   [left/top]			- may be declared, or the custom attribute offset_x/y can be used to offset menu from parent item
//   onmouseover		- the topmost level of menu nav items must have these declared in the HTML:
//						  onmouseover="initNavItem(this);"
//						  all child menus/items will automatically have their onmouseover/out events defined as each 
//						  menu is first displayed.
//
// CUSTOM ATTRIBUTES
//   parent				- the id of a menu's controlling nav item.  required on any menu div.
//   child				- the id of a nav item's menu.  only required for nav items that show/hide a menu.
//   itemtype			- every menu div must have this set to "menu".  a menu nav item must have this set to "menu_item" 
//						  to have its mouseover/out handlers automatically defined.
//   [offset_x/offset_y]- x and y offset from parent item to move the menu to.  absolute positioning with left/top can also
//						  be used.
//
// OPTIONAL ATTRIBUTES
//   class				- any nav item with a class will be given that class name plus "_over" during a rollover event.
//
// EXTERNAL DEPENDENCIES
// The following external functions are needed to make the fadeMenus.js script work
//   endEventBubble()
//   findPosition() 
//   hasAttribute()
//   hasAttributeValue()
 
// Attach a listener to set a global variable to true when the page is done loading
// Note: Firefox doesn't like setting _is_win_loaded=false
if ( window.addEventListener )		window.addEventListener("load", function()	{_is_win_loaded=true;}, false);	// Moz
else if ( document.attachEvent )	window.attachEvent("onload",  function()	{_is_win_loaded=true;});		// IE

var _is_win_loaded		= false;
var _is_js_loaded		= false;
var _fade_steps			= (document.all) ? 5 : 10;			// Number of steps from no opacity to full opacity.  Give IE fewer since it renders slower.
var _max_menu_opacity	= 100;		// Maximum menu opacity [0.0-1.0, higher=more visible]
var _fade_delay			= 10;		// ms delay between each fade step
var _use_dropshadow		= (document.all) ? false : true;	// Create and use dropshadows on menus?  May make menu fading slower
var _max_shadow_opacity	= .25;		// Maximum dropshadow opacity
var _debug				= false;	// Set to true and debug messages will appear in any div with the id="debug"

// Checks that all necessary variables and functions exist.  Returns false if they don't.
// Call this at the beginning of any function that could be run before everything is loaded.
function checkLoadState()
{
	// Ensure window is loaded
	if ( _is_win_loaded === undefined ) return false;
	else if ( !_is_win_loaded ) return false;

	// If javascript has already been checked, do not check again
	else if ( _is_js_loaded ) return true;

	// Variables
	else if ( _fade_steps === undefined ) return false;
	else if ( _max_menu_opacity === undefined ) return false;
	else if ( _fade_delay === undefined ) return false;
	else if ( _use_dropshadow === undefined ) return false;
	else if ( _max_shadow_opacity === undefined ) return false;
	else if ( _debug === undefined ) return false;

	// Functions
	else if ( !self.setNavItemOn ) return false;
	else if ( !self.initNavItem ) return false;
	else if ( !self.initNavItemClosure ) return false;
	else if ( !self.navItemOver ) return false;
	else if ( !self.navItemOut ) return false;
	else if ( !self.useLink ) return false;
	else if ( !self.menuOver ) return false;
	else if ( !self.menuOut ) return false;
	else if ( !self.showMenu ) return false;
	else if ( !self.hideMenu ) return false;
	else if ( !self.initMenuPos ) return false;
	else if ( !self.initMenu ) return false;
	else if ( !self.getNavParent ) return false;
	else if ( !self.getNavChild ) return false;
	else if ( !self.getItemType ) return false;

	// Everything is loaded, continue
	else
	{
		_is_js_loaded = true;
		return true;
	}
}

// Manually set a nav item's class as being on.  [className] is optional, if not given the current class will be
// appended with "_on"
function setNavItemOn(id, className)
{
	// Don't use because IE fires this funciton before _is_win_loaded is set
//	if ( !self.checkLoadState || !checkLoadState() ) return;

	oNavItem = document.getElementById(id);
	if ( oNavItem )
	{
		// If the class name is not passed, assume to use the existing class name appended with "_on"
		if ( className === undefined )
		{
			if ( oNavItem.className && oNavItem.className.substring(oNavItem.className.length-3) != "_on" )
			{
				oNavItem.className += "_on";
			}
		}
		else oNavItem.className = className;
	}
}

// Gives a nav item the event handlers needed
//   oNavItem			is the nav element to initialize
//   [triggerMouseOver]	optional, set to 1 if the mouseover event should be triggered after initializing
function initNavItem(oNavItem, triggerMouseOver)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if ( triggerMouseOver === undefined ) triggerMouseOver = false;

	if (_debug) document.getElementById("debug").innerHTML += "<br>initNavItem("+oNavItem.id+", "+triggerMouseOver+")";

	if ( self.navItemOver )	oNavItem.onmouseover	= function() { navItemOver(this); };
	if ( self.navItemOut )	oNavItem.onmouseout		= function() { navItemOut(this); };

	// Make sure child "a" node cancels onlick event bubble so link isn't activated twice
	var a_nodes = oNavItem.getElementsByTagName("a");
	if ( a_nodes.length > 0 )
	{
		if ( self.useLink )			oNavItem.onclick	= function(e){ useLink(e,this); };
		if ( self.endEventBubble )	a_nodes[0].onclick	= function(e){ endEventBubble(e); }
	}
	if ( triggerMouseOver ) oNavItem.onmouseover(oNavItem);
}

// Called once for each nav item.  Sets up an internal function to turn an item "off" after a given amount
// of time.  A closure is used to call this with setInterval();
function initNavItemClosure(oNavItem)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>initNavItemClosure("+oNavItem.id+")";

	if ( oNavItem.checkItem === undefined )
	{
		// Give the nav item a function to help delay it's mouseout style.  This prevents flicker in IE which
		// is too aggressive in its event processing.
		oNavItem.checkItemOut = function()
		{
			// Do not use "self" or "window" to check if function exists
			if ( typeof(checkLoadState) == "undefined" || !checkLoadState() ) return;

			if (_debug) document.getElementById("debug").innerHTML += "<br>checkItemOut("+this.id+")";

			// If not enough time has passed, skip this interval
			if ( Date() - oNavItem.updated < 10 ) return;

			if ( this.state && this.state == "over" )
			{
				if ( this.className && this.className.substring( this.className.length-5) != "_over" )
				{
					 this.className += "_over";
				}
			}
			if ( this.state && this.state == "out" )
			{
				if ( this.className && this.className.substring(this.className.length-5) == "_over" )
				{
					this.className = this.className.substring(0,this.className.length-5);
				}
			}

			// Clear the interval
			clearInterval(this.interval_id);
			this.interval_id = null;
			return;
		}
	}
	return;
}

// Called when the user rolls over a nav item.  menuOver also calls this to ensure the nav item
// stays "over" while the menu is on.  If the nav item is within another menu, then menuOver() is
// called for that menu.
function navItemOver(oNavItem)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>navItemOver("+oNavItem.id+")";

	oNavItem.state = "over";
	oNavItem.updated = new Date();
	if ( oNavItem.interval_id )
	{
		clearInterval(oNavItem.interval_id);	// Clear any existing nav out interval
		oNavItem.interval_id = null;
	}

	if ( oNavItem.className && oNavItem.className.substring(oNavItem.className.length-5) != "_over" )
	{
		oNavItem.className += "_over";
	}

	// Recursively check that all previous menus/navs are on
	var parentNode = oNavItem.parentNode;
	if ( self.getItemType && self.menuOver && self.getItemType(parentNode) == "menu" ) menuOver(parentNode);

	if ( self.getNavChild )
	{
		var child = self.getNavChild(oNavItem);
		if ( child != null && self.getItemType && self.showMenu && ( getItemType(child) == "menu") ) showMenu(child);

		// This degub clause causes errors if the nav is hammered.	
//		if (_debug) document.getElementById("debug").innerHTML += "<br>&nbsp;&nbsp;&nbsp; className="+oNavItem.className;
	}
}


// Called when the user rolls out of a nav item.  menuOut also calls this to allow the nav item
// to turn "off" if the user is no longer over it.  If the nav item is within another menu, then 
// menuOut() is called for that menu.
// A closure is used on the nav item to allow for a little delay before the style is reverted to
// the non-over style.  This prevents flicker in IE as it fires unholy amounts of over/out events
// from every item in a div (namely <a> tags).
function navItemOut(oNavItem)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>navItemOut("+oNavItem.id+")";

	oNavItem.state = "out";

	if ( oNavItem.interval_id === null || oNavItem.interval_id == null)
	{
		initNavItemClosure(oNavItem);
		oNavItem.interval_id = setInterval(function() {oNavItem.checkItemOut();}, 10);
	}

	var parentNode = oNavItem.parentNode;
	if ( getItemType(parentNode) == "menu" ) menuOut(parentNode);

	var child = getNavChild(oNavItem)
	if (child != null && (getItemType(child) == "menu") ) hideMenu(child);

	if (_debug) document.getElementById("debug").innerHTML += "<br>&nbsp;&nbsp;&nbsp; className="+oNavItem.className;
}

// Called when the user clicks a nav item with this handler on the onclick event.
// Finds any link within the nav item and uses it.
function useLink(e, oNavItem)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>useLink("+e+", "+oNavItem.id+")";

	endEventBubble(e);
	var a_nodes = oNavItem.getElementsByTagName("a",1);
	if ( a_nodes.length > 0 )
	{
		// Commented out since back button returns to page in same javascript state, and
		// this line causes menus to stop working
//		if ( a_nodes[0].href.indexOf("#") == -1 ) _is_win_loaded = false;	// Disable function calls if link is not an anchor
		window.location.href = a_nodes[0].href;		// Open this link
	}
}

// Called when the user rolls over a menu.  If there is a parent nav item, then navItemOver() is called
// with that nav item so ensure it and all parent items stays "on".
function menuOver(oMenu)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>menuOver("+oMenu.id+")";

	var parent = getNavParent(oMenu);
	if ( parent != null ) navItemOver(parent);

	showMenu(oMenu);
}

// Called when the user rolls out of a menu.  If there is a parent nav item, then navItemOut() is called
// with that nav item to allow it and all parent items to turn "off".
function menuOut(oMenu)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>menuOut("+oMenu.id+")";

	// moved to fadeMenu() and run when menu is completely hidden to reduce IE flicker
	//	var parent = getNavParent(oMenu);
	//	if ( parent != null ) navItemOut(parent);

	hideMenu(oMenu);
}

// Called whenever a menu needs to be shown.  May be called from many sources.  Initializes a menu or changes
// its state to ensure it will fade "on" during a setInterval() session.
function showMenu(oMenu)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>showMenu("+oMenu.id+")";


	// If the menu has not been initialized, initialize it
	if ( oMenu.initialized === undefined ) initMenu(oMenu);

	// Initialize menu state if the menu hasn't been used before
	if ( oMenu.state == null || oMenu.state === undefined ) oMenu.state = "off";

	if ( oMenu.state == "show" || oMenu.state == "on" ) return;		// Menu is already being faded-in
	else if ( oMenu.state == "hide" ) oMenu.state = "show";			// Menu is being faded-out.  Switch to fade-in
	else if ( oMenu.state == "off" )								// Menu is completely hidden.  Begin fade animation
	{
		oMenu.step				= 0;
		oMenu.state				= "show";
		initMenuPos(oMenu);
		if ( oMenu.filters ) oMenu.style.filter = "Alpha(Opacity=0)";
		else oMenu.style.MozOpacity = 0;
		oMenu.style.visibility	= "visible";

		// Since IE cannot pass objects to setInterval, use closure so it has access to oMenu's info
		oMenu.interval_id	= setInterval(function() {oMenu.fadeMenu();}, _fade_delay);
	}
}

// Called whenever a menu needs to be hidden.  May be called from many sources.  Initializes a menu or changes
// its state to ensure it will fade "off" during a setInterval() session.
function hideMenu(oMenu)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>hideMenu("+oMenu.id+")";

	// If the menu has not been initialized, initialize it
	if ( oMenu.initialized === undefined ) initMenu(oMenu);

	// Initialize menu state if the menu hasn't been used before
	if ( oMenu.state == null || oMenu.state === undefined ) oMenu.state = "off";

	if ( oMenu.state == "hide" || oMenu.state == "off" ) return;
	else if ( oMenu.state == "show" ) oMenu.state = "hide";
	else if ( oMenu.state == "on" )
	{
		oMenu.step				= _fade_steps;
		oMenu.state				= "hide";
		oMenu.style.visibility	= "visible";
		if ( oMenu.filters ) oMenu.style.filter = "Alpha(Opacity="+100+")";
		else oMenu.style.MozOpacity = 1;

		// Since IE cannot pass objects to setInterval, use closure so it has access to oMenu's info
		oMenu.interval_id	= setInterval(function() {oMenu.fadeMenu();}, _fade_delay);
	}
}

// Moves a menu to the correct offset of its parent
function initMenuPos(oMenu)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	var parent_pos = findPosition(document.getElementById(oMenu.getAttribute("parent")));

	var oShadow = (document.all && _use_dropshadow) ? document.getElementById(oMenu.id+"_shadow") : null;

	// Move position position if offsets are defined.  Offset is based on parent's position.
	if ( hasAttribute(oMenu, "offset_x") )
	{
		var offset_x = parseInt(oMenu.getAttribute("offset_x"),10);
		oMenu.style.position = "absolute";
		oMenu.style.left = (offset_x+parent_pos.x)+"px";
		if ( oShadow ) oShadow.style.left = (offset_x+parent_pos.x)+"px";
	}
	if ( hasAttribute(oMenu, "offset_y") )
	{
		var offset_y = parseInt(oMenu.getAttribute("offset_y"),10);
		oMenu.style.position = "absolute";
		oMenu.style.top = (offset_y+parent_pos.y)+"px";
		if ( oShadow ) oShadow.style.top = (offset_y+parent_pos.y)+"px";
	}
}

// Called when a menu is shown/hidden for the very first time.  Sets up the dropshadow (IE only) and
// creates the fadeMenu() closure function within the menu object.  This allows setInterval()
// and fadeMenu() to access the menu's variables as it shows/fades that menu.  Each menu will have its 
// own instance of fadeMenu().
function initMenu(oMenu)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>initMenu("+oMenu.id+")";

	// If already initialized, return
	if ( oMenu.initialized !== undefined ) return;

	oMenu.initialized	= true;		// Ensure initialization does not happen again
	oMenu.style.zIndex	= 20;		// Ensure menu displays over dropshadow

	initMenuPos(oMenu);

	// Create the dropshadow layer before any other event handlers are created (IE only)
	if ( document.all && _use_dropshadow )
	{
		// Clone the menu to make it into a shadow
		var oDiv				= oMenu.cloneNode(true);
		oDiv.id					= oMenu.id+'_shadow';
		oDiv.style.position		= "absolute";
		oDiv.style.zIndex		= 10;
		document.body.appendChild(oDiv);
	}


	// Give the menu the needed onmouseover/out handlers
	oMenu.onmouseover = function() { menuOver(this); };
	oMenu.onmouseout  = function() { menuOut(this); };

	// Give all nav items the needed onmouseover/out handlers
	for (var i=0; i<oMenu.childNodes.length; i++)
	{
		var cur_node = oMenu.childNodes[i];
		if ( hasAttributeValue(cur_node, "itemtype", "menu_item") )
		{
			initNavItem(cur_node,0);
		}
	}


	// Calling this menu's fadeMenu() function with setInterval() allows an independent instance of
	// fadeMenu() to gradually fade/show the menu.  Uses closure.
	oMenu.fadeMenu = function()
	{
		// Do not use "self" or "window" to check if function exists
		if ( typeof(checkLoadState) == "undefined" || !checkLoadState() ) return;

		if ( this.state == null || this.state === undefined ) return;

		var oShadow = (document.all && _use_dropshadow) ? document.getElementById(this.id+"_shadow") : null;

		if ( this.state == "hide" )
		{
			this.step--;
			if ( this.step <= 0 )						// If finished hiding this menu
			{
				clearInterval(this.interval_id);		// Stop setInterval() from continuing
				this.interval_id		= null;
				this.state				= "off";
				this.style.visibility	= "hidden";
				var opacity				= 0; 
				if ( oShadow ) oShadow.style.visibility = "hidden";

				var parent = getNavParent(this);
				if ( parent != null ) navItemOut(parent);

			}
			else
			{
				this.style.visibility = "visible";
				var opacity = this.step*(_max_menu_opacity/_fade_steps);
				if ( oShadow ) oShadow.style.visibility = "visible";
				if ( oShadow ) oShadow.style.display = "block";
			}
		}

		else if ( this.state == "show" )
		{
			this.step++;
			if ( this.step >= _fade_steps )				// If finished showing this menu
			{
				clearInterval(this.interval_id);		// Stop setInterval() from continuing
				this.interval_id		= null;
				this.state				= "on";
				this.style.visibility	= "visible";
				var opacity				= 100;
				if ( oShadow ) oShadow.style.visibility = "visible";
				if ( oShadow ) oShadow.style.display = "block";
			}
			else
			{
				this.style.display		= "block";		// Must set display here or else IE flickers
				this.style.visibility	= "visible";
				var opacity = this.step*(_max_menu_opacity/_fade_steps);
				if ( oShadow ) oShadow.style.visibility = "visible";
				if ( oShadow ) oShadow.style.display = "block";
			}
		}

//		Old method of changing IE opacity	
//		if ( this.filters ) this.filters.item("DXImageTransform.Microsoft.Alpha").opacity = opacity;
//		if ( this.filters ) this.style.filter = "progid:DXImageTransform.Microsoft.Alpha('opacity'="+opacity+")";
		if ( this.filters ) this.style.filter = "Alpha(Opacity="+opacity+")";
		else this.style.MozOpacity = opacity/100;


		// Update dropshadow opacity in IE only
		if ( document.all && _use_dropshadow && oShadow.filters )
		{
			oShadow.style.filter = "progid:DXImageTransform.Microsoft.Blur(PixelRadius=3, MakeShadow=1, ShadowOpacity="+(opacity/100)*_max_shadow_opacity+")";
		} 
	}
}

// Returns the custom attribute "parent" if the object has it.  null otherwise.  
function getNavParent(obj)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>getNavParent("+obj.id+")";

	var parent = null;
	if ( obj.getAttribute )
	{
		var parent_id = obj.getAttribute("parent");
		if ( parent_id != null ) parent = document.getElementById(parent_id);
	}
	return parent;
}

// Returns the custom attribute "child" if the object has it.  null otherwise.  
function getNavChild(obj)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>getNavChild("+obj.id+")";

	var child = null;
	if ( obj.getAttribute )
	{
		var child_id = obj.getAttribute("child");
		if ( child_id != null ) child = document.getElementById(child_id);
	}
	return child;
}

// Returns the custom attribute "itemtype" if the object has it.  null otherwise.
// Currently, only the type "menu" is used.  
function getItemType(obj)
{
	if ( !self.checkLoadState || !checkLoadState() ) return;

	if (_debug) document.getElementById("debug").innerHTML += "<br>getItemType("+obj.id+")";

	if ( obj.getAttribute ) return obj.getAttribute("itemtype");
	else return null;
}

