/*
 *  File:     pexodmenu.js
 *
 *  Usage:    Initialization:
 *            Note that the name of the global variable, MainMenu is passed as a parameter.
 *            The first parameter is the id of the element containing the menu bar.
 *
 *            var MainMenu = new PexodMenu( "menuholder", "MainMenu" );
 *
 *            Set up each menu:
 *            The return value, nMenuNumber, is used to set up the drop down menu.
 *            The parameter is the text displayed in the home cell in the menu bar.
 *
 *            nMenuNumber = MainMenu.CreateMenu( "go to google" );
 *
 *            Optional, make home cell clickable:
 *
 *            MainMenu.AddMenuClick( nMenuNumber, "location=\"http://www.google.com\";" );
 *
 *            Add items to drop down menu:
 *
 *            MainMenu.AddDropDownItem( nMenuNumber, "Google", "location=\"http://www.google.com\";" );
 *            MainMenu.AddDropDownItem( nMenuNumber, "PExOD", "location=\"http://www.pexod.com\";" );
 *
 *            Customization:
 *            Note that there are several member variables that are intended to be
 *            modified to suit. These include the colour of the rollovers and tweaks
 *            to the postion of the drop down menu relative to the home cell.
 *
 *  Project:  PExOD public side, "p3"
 *
 *  Author:   Doug Plant
 *
 *  Copy:     Please note that this file is copyright PExOD.com. See file copyright.txt for details.
 */

//-----------------------------------------------------------------------------
// sGlobalVariableName is the name of the variable, in the global context that
// contains the instance of the PexodMenu
// sRootContainerId is the root container, important for calculating the
// location of the drop down, probably not the body, but the last container
// whose offsets are not included in the tally
function PexodMenu( sHostId, sRootContainerId, sGlobalVariableName )
{
	var m_HostId = sHostId;							// id of user-defined container
	var m_sGlobalInstanceName = sGlobalVariableName;// name of the global variable name
	var m_nMenuCount = 0;							// count of home cells
	var m_CurrentMenuId = "";						// the currently showing drop down menu
	var m_hHideTimerHandle = 0;						// timer that hides the drop down menu
	var m_bUsingIE = true;							// set as a side effect in pInitMenuBar(), used for the position corrections
	var m_sRootContainerId = sRootContainerId;

	// settables
	var m_DropDownMenuZIndex = 10;					// keep drop down menu above everything
	var m_nMenuTimeout = 250;						// timeout, upon which the menu hides
	var m_nVerticalCorrn_IE = 0;					// correction for placement of dd menu
	var m_nVerticalCorrn = 0;
	var m_nHorizontalCorrn_IE = 0;					// correction for placement of dd menu
	var m_nHorizontalCorrn = 0;
	var m_sSepratorString = " |";					// optional seperator string that appears between main menu items

	// externally defined style info
	var m_StyleClass_MenuBar = "PM_mainmenu";
	var m_StyleClass_HomeCell = "PM_homecell";
	var m_StyleClass_HomeCell_RO = "PM_homecell_ro";
	var m_StyleClass_HomeCellSeprator = "PM_homecell_seprator";
	var m_StyleClass_DDTable = "PM_dropdownmenuholder";
	var m_StyleClass_DDCell = "PM_dropdownmenuitem";
	var m_StyleClass_DDCell_RO = "PM_dropdownmenuitem_ro";

	// key strings
	var m_KeyPexodMenu = "_PM_";
	var m_KeyHomeCell = "_HC_";
	var m_KeyDropDownTable = "_DDT_";
	var m_KeyDropDownCell = "_DDC_";
	var m_KeyTagMenuLength = "DropdownMenuLength";

	// set up div to contain the menu home cells
	pInitMenuBar();

	//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
	//
	// UTILITY METHODS:
	//
	//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

	//----------------------------------------------------
	// note that event names are lowercase, and that
	// you can use a parameterized call, like MyFunc( N )
	pAddEventHandler = function( oElement, sEventName, sHandlerCall )
	{
		var localF;
		try
		{
			// wrap the handler in a new function in case there are parameters
			localF = new Function( sHandlerCall );
			if( oElement.addEventListener )
			{
				oElement.addEventListener( sEventName, localF, false );	// FF
			}
			else if( oElement.attachEvent )
			{
				oElement.attachEvent( "on"+sEventName, localF );		// IE
			}
		}
		catch( e )
		{
			var sMsg = "";
			sMsg = sMsg + "EXCEPTION in dputil_AddEventHandler():\n"
			sMsg = sMsg + "Exception description: " + e.description + "\n";
			sMsg = sMsg + "Containing element node name: " + oElement.nodeName + "\n";
			sMsg = sMsg + "Containing element id: " + oElement.id + "\n";
			sMsg = sMsg + "Containing element name: " + oElement.name + "\n";
			sMsg = sMsg + "sEventName: " + sEventName + "\n";
			//sMsg = sMsg + "NOTE 1: I think arguments passed are evaluated even before this step, so an error in argument will cause a mysterious exception.\n";
			sMsg = sMsg + "sHandlerCall: " + sHandlerCall + "\n";
			alert( sMsg );
		}
	}

	//----------------------------------------------------
	// utility function to get parent object
	// bridge IE FF divide
	function pGetParentElement( oElement )
	{
		if( oElement.parentElement )
		{
			return oElement.parentElement;
		}
		else if( oElement.parentNode ) // FF
		{
			return oElement.parentNode;
		}
	}

	//----------------------------------------------------
	// utility to extract integer from style property
	// NOTE - this is a bit of a hack - like, returning
	// 0 for instead of whatever auto stands for, is
	// just a wishfull guess on my part
	function pExtractInteger( $sString )
	{
		$sNumber = "";
		if( "" == $sString )
		{
			return 0;
		}
		else
		{
			if( "auto" == $sString )
			{
				// for IE
				return 0;
			}
			return parseInt( $sString );
		}
	}

	//----------------------------------------------------
	// this is a utility function to query existing style
	// information ( that is, style info set by an external
	// style sheet )
	// The advantage of this is that is bridges the IE FF gap.
	// Note that padding info is the same for both:
	// paddingTop, PaddingLeft, etc.
	//
	function pGetElementStyle( elemID, IEStyleProp, CSSStyleProp )
	{
		var elem = document.getElementById( elemID );
		var sResult = "";

		if( elem.currentStyle )
		{
			sResult = elem.currentStyle[ IEStyleProp ];
		}
		else if( window.getComputedStyle )
		{
			var compStyle = window.getComputedStyle( elem, "" );
			sResult = compStyle[ CSSStyleProp ];
		}
		return sResult;
	}

	//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
	//
	// PRIVATE METHODS:
	//
	//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

	//----------------------------------------------------
	// return an array with left/top coords
	// the drop down will be placed underneath the
	// sTargetId element
	function pLocateDropDownMenu( sTargetId )
	{
		// use this msg to locate the dropdown menu and figure the offset
		// correction needed
		var sMsg = "";
		var oE = document.getElementById( sTargetId );
		var SumTop = 0;
		var SumLeft = 0;
		if( m_bUsingIE )
		{
			SumTop = m_nVerticalCorrn_IE;
			SumLeft = m_nHorizontalCorrn_IE;
		}
		else
		{
			SumTop = m_nVerticalCorrn;
			SumLeft = m_nHorizontalCorrn;
		}
		sMsg += "SumTop=" + SumTop + "\n";
		sMsg += "SumLeft=" + SumLeft + "\n";
		// these place the point underneath and to the left of the target
		SumTop += oE.offsetHeight;
		SumLeft += oE.offsetLeft;
		sMsg += "SumTop=" + SumTop + "\n";
		sMsg += "SumLeft=" + SumLeft + "\n";
		// sum up offsets, padding and margins of parent elements
		oE = pGetParentElement( oE );
		do
		{
			SumTop += oE.offsetTop;
			SumTop += pExtractInteger( pGetElementStyle( oE.id, "marginTop", "marginTop" ) );
			SumTop += pExtractInteger( pGetElementStyle( oE.id, "paddingTop", "paddingTop" ) );
			SumLeft += oE.offsetLeft;
			SumLeft += pExtractInteger( pGetElementStyle( oE.id, "marginLeft", "marginLeft" ) );
			SumLeft += pExtractInteger( pGetElementStyle( oE.id, "paddingLeft", "paddingLeft" ) );
			sMsg += "i SumTop=" + SumTop + "\n";
			sMsg += "i SumLeft=" + SumLeft + "\n";
			// locate the parent of the next container
			oE = pGetParentElement( oE );
		}
		while( m_sRootContainerId != oE.id ) // repeat until hit user-indicated stop point
		// the debug message:
		//alert( sMsg );
		return new Array( SumLeft, SumTop );
	}

	//----------------------------------------------------
	// attach rowless table to body, make it invisible
	function pInitEmptyDDMenu( nMenuIndex )
	{
		var oBodys = document.getElementsByTagName( "body" );
		var newTable = document.createElement( "table" );
		var newTBody = document.createElement( "tbody" );
		var sTableId = m_HostId + m_KeyPexodMenu + m_KeyDropDownTable + nMenuIndex;
		var sHomeCellId = m_HostId + m_KeyPexodMenu + m_KeyHomeCell + nMenuIndex;
		var aCoords = pLocateDropDownMenu( sHomeCellId );
		newTable.setAttribute( "id", sTableId );
		newTable.setAttribute( m_KeyTagMenuLength, 0 );
		newTable.setAttribute( "cellSpacing", "0" );
		newTable.setAttribute( "cellPadding", "0" );
		newTable.setAttribute( "className", m_StyleClass_DDTable );	// for IE
		newTable.setAttribute( "class", m_StyleClass_DDTable );		// for FF
		newTable.style.zIndex = m_DropDownMenuZIndex;
		newTable.style.visibility = "hidden";
		newTable.style.position = "absolute";
		newTable.style.left = aCoords[0] + "px";
		newTable.style.top = aCoords[1] + "px";
		newTable.style.cursor = "pointer";

		oBodys[0].appendChild( newTable );
		newTable.appendChild( newTBody );
	}

	//----------------------------------------------------
	function pAppendNewItem( nMenuIndex, sDisplay, sAction )
	{
		// get the table, then get the tbody
		var sTableId = m_HostId + m_KeyPexodMenu + m_KeyDropDownTable + nMenuIndex;
		var oTable = document.getElementById( sTableId );
		var oTBody = oTable.getElementsByTagName( "tbody" )[0];
		// extract number of menu items, stored as a property in the table
		var nNumberMenuItems = parseInt( oTable.getAttribute( m_KeyTagMenuLength ) );
		// generate the id of the cell we're about to create
		var sCellId = m_HostId + m_KeyPexodMenu + m_KeyDropDownTable + nMenuIndex + m_KeyDropDownCell + nNumberMenuItems;
		// build up components
		var newRow = document.createElement( "tr" );
		var newCell = document.createElement( "td" );
		var newText = document.createTextNode( sDisplay );
		newCell.setAttribute( "id", sCellId );
		newCell.setAttribute( "className", m_StyleClass_DDCell );	// for IE
		newCell.setAttribute( "class", m_StyleClass_DDCell );		// for FF
		// add event handlers for cell rollover
		pAddEventHandler( newCell, "mouseover", m_sGlobalInstanceName+".RolloverDropdownItem( '" + sCellId + "' )" );
		pAddEventHandler( newCell, "mouseout", m_sGlobalInstanceName+".RolloutDropdownItem( '" + sCellId + "', '" + nMenuIndex + "' )" );
		// in addition to the user-defined onclick handler to do whatever the user
		// wants, we're also sticking in a call to cause the menu to disappear when
		// you click on it
		pAddEventHandler( newCell, "click", m_sGlobalInstanceName+".HideDropdownMenu( '" + sTableId + "' ); " + sAction );
		// and assemble the parts as a new row
		newCell.appendChild( newText );
		newRow.appendChild( newCell );
		oTBody.appendChild( newRow );
		// increment the number of drop down menu items, and save back to table
		oTable.setAttribute( m_KeyTagMenuLength, ++nNumberMenuItems )
	}

	//----------------------------------------------------
	// the menu bar has an id that includes the id of the
	// container - the use of the container id is a way to
	// guarantee the uniqueness of this menu-system
	function pInitMenuBar()
	{
		var m_oHostElement = document.getElementById( m_HostId );
		var m_oMainMenuDiv = document.createElement( "div" );
		// determine which layout hacks to use
		if( m_oMainMenuDiv.addEventListener )
		{
			m_bUsingIE = false;
		}
		else if( m_oMainMenuDiv.attachEvent )
		{
			m_bUsingIE = true;
		}
		m_oMainMenuDiv.setAttribute( "id", m_HostId + m_KeyPexodMenu );
		m_oMainMenuDiv.setAttribute( "className", m_StyleClass_MenuBar );	// for IE
		m_oMainMenuDiv.setAttribute( "class", m_StyleClass_MenuBar );		// for FF
		m_oHostElement.appendChild( m_oMainMenuDiv );
	}

	//----------------------------------------------------
	// kill the hide timeout
	function pKillHideTimer()
	{
		window.clearTimeout( m_hHideTimerHandle );
		m_hHideTimerHandle = 0;
	}

	//----------------------------------------------------
	// start timeout to hide current menu
	function pStartHideTimer( sTableId )
	{
		m_hHideTimerHandle = window.setTimeout( m_sGlobalInstanceName+".HideDropdownMenu( '" + sTableId + "' )", m_nMenuTimeout );
	}

	//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
	//
	// PRIVILEGED METHODS:
	//
	//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

	//----------------------------------------------------
	// do rollover effect on menu home cell,
	// called from event handler
	this.RolloverMenuHome = function( nHomeCellNumber )
	{
		// handle rollover stuff
		var sHomeCellId = m_HostId + m_KeyPexodMenu + m_KeyHomeCell + nHomeCellNumber;
		var oHomeCell = document.getElementById( sHomeCellId );
		oHomeCell.setAttribute( "className", m_StyleClass_HomeCell_RO );	// for IE
		oHomeCell.setAttribute( "class", m_StyleClass_HomeCell_RO );		// for FF
		// hide any visible menu, and so long as cursor is on home cell, keep menu up
		this.HideDropdownMenu( m_CurrentMenuId );
		pKillHideTimer();
		// if this menu has anything in it, show it
		var sTableId = m_HostId + m_KeyPexodMenu + m_KeyDropDownTable + nHomeCellNumber;
		m_CurrentMenuId = sTableId;
		var oDDMenu = document.getElementById( sTableId );
		if( oDDMenu )
		{
			var nNumberMenuItems = parseInt( oDDMenu.getAttribute( m_KeyTagMenuLength ) );
			if( 0 < nNumberMenuItems )
			{
				// repostion the drop down, every time you show it, in case the browser has resized
				var aCoords = pLocateDropDownMenu( sHomeCellId );
				oDDMenu.style.left = aCoords[0] + "px";
				oDDMenu.style.top = aCoords[1] + "px";
				oDDMenu.style.visibility = "visible";
			}
		}
	}

	//----------------------------------------------------
	// do rollout effect on menu home cell
	// called from event handler
	this.RolloutMenuHome = function( nHomeCellNumber )
	{
		var sHomeCellId = m_HostId + m_KeyPexodMenu + m_KeyHomeCell + nHomeCellNumber;
		var oHomeCell = document.getElementById( sHomeCellId );
		oHomeCell.setAttribute( "className", m_StyleClass_HomeCell );	// for IE
		oHomeCell.setAttribute( "class", m_StyleClass_HomeCell );		// for FF
		// start hide timer
		var sTableId = m_HostId + m_KeyPexodMenu + m_KeyDropDownTable + nHomeCellNumber;
		pStartHideTimer( sTableId );
	}

	//----------------------------------------------------
	// do rollover effect on drop down menu cell,
	// called from event handler
	this.RolloverDropdownItem = function( sItemId )
	{
		pKillHideTimer();
		var oItem = document.getElementById( sItemId );
		oItem.setAttribute( "className", m_StyleClass_DDCell_RO );	// for IE
		oItem.setAttribute( "class", m_StyleClass_DDCell_RO );		// for FF
	}

	//----------------------------------------------------
	// do rollout effect on drop down menu cell,
	// called from event handler
	this.RolloutDropdownItem = function( sItemId, nMenuIndex )
	{
		var oItem = document.getElementById( sItemId );
		oItem.setAttribute( "className", m_StyleClass_DDCell );	// for IE
		oItem.setAttribute( "class", m_StyleClass_DDCell );		// for FF
		// start hide timer
		var sTableId = m_HostId + m_KeyPexodMenu + m_KeyDropDownTable + nMenuIndex;
		pStartHideTimer( sTableId );
	}

	//----------------------------------------------------
	// hide the drop down menu
	// called from event handler
	this.HideDropdownMenu = function( sMenuId )
	{
		var oMenu = document.getElementById( sMenuId );
		if( oMenu )
		{
			oMenu.style.visibility = "hidden";
		}
		m_CurrentMenuId = "";
	}

	//----------------------------------------------------
	// call this from page for each home cell you want
	// create home cell, insert into menu bar, init empty drop down menu
	this.CreateMenu = function( sHomeText )
	{
		var m_oMainMenuDiv = document.getElementById( m_HostId + m_KeyPexodMenu );
		var oNewSpan = document.createElement( "span" );
		var sHomeCellId = m_HostId + m_KeyPexodMenu + m_KeyHomeCell + m_nMenuCount;
		oNewSpan.setAttribute( "id", sHomeCellId );
		oNewSpan.setAttribute( "className", m_StyleClass_HomeCell );	// for IE
		oNewSpan.setAttribute( "class", m_StyleClass_HomeCell );		// for FF
		// add event handlers for rolling over
		pAddEventHandler( oNewSpan, "mouseover", m_sGlobalInstanceName+".RolloverMenuHome('" +m_nMenuCount+ "')" );
		pAddEventHandler( oNewSpan, "mouseout", m_sGlobalInstanceName+".RolloutMenuHome('" + m_nMenuCount + "');" );
		var newText = document.createTextNode( sHomeText );
		oNewSpan.appendChild( newText );
		// deal with possible separator string
		if( m_nMenuCount > 0 && "" != m_sSepratorString )
		{
			var oSepSpan = document.createElement( "span" );
			var oSepText = document.createTextNode( m_sSepratorString );
			oSepSpan.setAttribute( "className", m_StyleClass_HomeCellSeprator );
			oSepSpan.setAttribute( "class", m_StyleClass_HomeCellSeprator );
			oSepSpan.appendChild( oSepText );
			m_oMainMenuDiv.appendChild( oSepSpan );
		}
		m_oMainMenuDiv.appendChild( oNewSpan );
		// set up structure to hold any dropdown menu info
		pInitEmptyDDMenu( m_nMenuCount );
		// increment count of supported menus
		m_nMenuCount += 1;
		return m_nMenuCount-1;
	}

	//----------------------------------------------------
	// make a home cell clickable, the sAction might look
	// like: "location=\"http://www.google.com\";"
	this.AddMenuClick = function( nMenuIndex, sAction )
	{
		var sHomeCellId = m_HostId + m_KeyPexodMenu + m_KeyHomeCell + nMenuIndex;
		var oHomeCell = document.getElementById( sHomeCellId );
		pAddEventHandler( oHomeCell, "click", sAction );
		oHomeCell.style.cursor = "pointer";
	}

	//----------------------------------------------------
	// add an item to drop down menu
	this.AddDropDownItem = function( nMenuIndex, sDisplay, sAction )
	{
		pAppendNewItem( nMenuIndex, sDisplay, sAction );
	}
}

