/**
 *    GD-Menu - object oriented customizable multi-level menu system
 *    ==============================================================
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *    The GPL can be found - if provided - in the file copyleft.txt.
 *
 *    @author Ruediger Reinhardt <roidsear@gmail.com>
 *    @version 0.34.15
 *    $Id   $
 *
 */


/**
 *	global array menuCollection - contains the complete menu structure
 *
 *	@see menu()
 */
var menuCollection = [];


/**
 *	Class menu - Menu class containing the structure, items and the templates
 *
 *	This is the class constructor, which instanciates the menu object and handles all
 *	events in the menu system.
 *
 *	@param array	menuItems	Array with collection of menu items, declared externally
 *	@param array	menuTemplate	Array with default settings (position, width, etc.)
 *	@return object	instance of menu class or false, if browser is not compatible
 *	@access public
 */
function menu(menuItems, menuTemplate)
{

	/* check for DOM compatible browser and return false if not present */
	if (!document.body || !document.body.style) return false;

	/* else, we have a compatible browser and can continue... */

	/* store the configuration structure */
	this.menuConfig		= menuItems;

	/* store the template structure */
	this.menuTemplate 	= menuTemplate;

	/* get the menu id (length of menu array) */
	this.menuID 		= menuCollection.length;

	/* declare the menu tree objects */
	this.menuIndex 		= [];
	this.menuChildren 	= [];

	/* assign methods and event handlers to the events */
	this.expand      = menuExpand;
	this.collapse    = menuCollapse;
	this.onclick     = menuOnClick;
	this.onmouseout  = menuOnMouseOut;
	this.onmouseover = menuOnMouseOver;
	this.onmousedown = menuOnMouseDown;

	/* assign methods and properties which are used to access parent items and corresponding settings */
	this.getProperty = function (subKey) {
							return this.menuTemplate_def[subKey];
						};

	this.Root = this;
	this.menuDepth = -1;
	this.menuPosX = 0;
	this.menuPosY = 0;

	/* initalize menu items recursively */
	for (menuOrder = 0; menuOrder < menuItems.length; menuOrder++)
	{
		new menuItem(this, menuOrder);
	}

	/* register current object itself in global menu collection */
	menuCollection[this.menuID] = this;

	/* set the root menu level to visible */
	for (var menuOrder = 0; menuOrder < this.menuChildren.length; menuOrder++)
	{
		this.menuChildren[menuOrder].menuOuterElement.style.visibility = 'visible';
	}
	
	/* return the menu itself as a reference handler */
	return this;
}


/**
 *	Event handler method for onClick event
 *
 *	@param int	menuID	menu index
 *	@return void
 *	@access private
 */
function menuOnClick(menuID)
{
	/* don't link if the item has no defined link target */
	//alert("Current ID: " + menuID);
	var theLink = this.menuIndex[menuID].menuConfig[1];
	return Boolean( theLink );
}


/**
 *	Event handler method for onMouseOut event
 *
 *	@param int	menuID menu index
 *	@return void
 *	@access private
 */
function menuOnMouseOut(menuID)
{
	/* lookup the object of the new item */
	var objectItem = this.menuIndex[menuID];

	/* perform rollout */
	objectItem.menuOuterElement.className = objectItem.getStyle(0, 0);
	objectItem.innerElement.className = objectItem.getStyle(1, 0);

	/* start the onMouseOver timer */
	this.objectHidetimer = setTimeout('menuCollection['+ this.menuID +'].collapse();', 600);
}


/**
 *	Event handler method for OnMouseOver event
 *
 *	@param int	menuID menu index
 *	@return void
 *	@access private
 */
function menuOnMouseOver(menuID)
{
	/* cancel the onMouseOut menu close and itemOpen delay timers */
	clearTimeout(this.objectHidetimer);
	this.objectHidetimer = null;
	clearTimeout(this.objectShowtimer);

	/* lookup the object of the new item */
	var objectItem = this.menuIndex[menuID];

	/* update the status bar */
	objectItem.updateStatus();

	/* perform rollout */
	objectItem.menuOuterElement.className = objectItem.getStyle(0, 1);
	objectItem.innerElement.className = objectItem.getStyle(1, 1);

	/* start the expand timer */
	this.objectShowtimer = setTimeout('menuCollection['+ this.menuID +'].expand(' + menuID + ');', expandDelay);
}


/**
 *	Event handler method for OnMouseDown event
 *
 *	@param int	menuID menu index
 *	@return void
 *	@access private
 */
function menuOnMouseDown(menuID)
{

	/* lookup the object of the new item */
	var objectItem = this.menuIndex[menuID];

	/* apply mousedown style */
	objectItem.menuOuterElement.className = objectItem.getStyle(0, 2);
	objectItem.innerElement.className = objectItem.getStyle(1, 2);

	/* expand current item */
	this.expand(menuID);
}


/**
 *	Collapses current menu item
 *
 *	@param int	menuID	menu index
 *	@return void
 *	@access private
 */
function menuCollapse(menuID)
{
	/* clear the timer for the opening delay */
	clearTimeout(this.objectShowtimer);

	/* collapse to root menu level by default */
	var menuToLevel = (menuID
		? this.menuIndex[menuID].menuDepth
		: 0);

	/* hide all items above the specified level */
	for (menuID = 0; menuID < this.menuIndex.length; menuID++)
	{
		var objectCurrentItem = this.menuIndex[menuID];
		if (objectCurrentItem.menuDepth > menuToLevel && objectCurrentItem.propertyVisible)
		{
			objectCurrentItem.menuOuterElement.style.visibility = 'hidden';
			objectCurrentItem.propertyVisible = false;
		}
	}

	/* reset current item if the mouse has left the item area */
	if (!menuID) this.objectCurrent = null;
}


/**
 *	Expands current menu item
 *
 *	@param int	menuID	menu index
 *	@return void
 *	@access private
 */
function menuExpand(menuID)
{
	/* expand only when mouse is over any of the  menu items */
	if (this.objectHidetimer) return;

	/* lookup current item */
	var objectItem = this.menuIndex[menuID];

	/* close previously opened items, if any */
	if (this.objectCurrent && this.objectCurrent.menuDepth >= objectItem.menuDepth)
	{
		this.collapse(objectItem.menuID);
	}

	/* and assign the hovered over item as current item */
	this.objectCurrent = objectItem;

	/* if there are no children to open, exit */
	if (!objectItem.menuChildren) return;

	/* show direct child items of current item */
	for (var menuOrder = 0; menuOrder < objectItem.menuChildren.length; menuOrder++)
	{
		var objectCurrentItem = objectItem.menuChildren[menuOrder];
		objectCurrentItem.menuOuterElement.style.visibility = 'visible';
		objectCurrentItem.propertyVisible = true;
	}
}


/**
 *	Instanciates the inidividual menu objects
 *
 *	Instanciation method for item object.
 *	Creates the object specs and registers them in the global collection.
 *	Generates the corresponding HTML code for the current menuitem
 *	Uses <a href=".."><div>...</div></a> structure approach, refering to the
 *	externally declared CSS.
 *
 *	@param obj	objectParent parent object of current item
 *	@param int	menuOrder	menu item index
 *	@return instance of menuItem or false, if no config present
 *	@access private
 */
function menuItem(objectParent, menuOrder)
{

	/* store parameters passed to the class constructor */
	this.menuDepth  = objectParent.menuDepth + 1;
	this.menuConfig = objectParent.menuConfig[menuOrder + (this.menuDepth ? 3 : 0)];

	/* if required parameters are missing, return false */
	if (!this.menuConfig) return false;

	/* store info inherited from the parent item */
	this.Root							= objectParent.Root;
	this.objectParent						= objectParent;
	this.menuOrder							= menuOrder;

	/* register item in global and parent collections */
	this.menuID 							= this.Root.menuIndex.length;
	this.Root.menuIndex[this.menuID]	= this;
	objectParent.menuChildren[menuOrder] 	= this;
	var Root 							= this.Root,
		menuTemplate						= this.Root.menuTemplate;

	/* assign methods */
	this.getProperty 						= menuItemGetProperty;
	this.getStyle							= menuItemGetStyle;
	this.updateStatus						= menuItemtUpdateStatus;

	this.menuPosX = menuOrder
		? objectParent.menuChildren[menuOrder - 1].menuPosX + this.getProperty('left')
		: objectParent.menuPosX + this.getProperty('leftBlock');

	this.menuPosY = menuOrder
		? objectParent.menuChildren[menuOrder - 1].menuPosY + this.getProperty('top')
		: objectParent.menuPosY + this.getProperty('topBlock');

	/* generate HTML code for the current item */
	document.write (
		'<a id="element' + Root.menuID + '_'
			+ this.menuID +'outer" class="' + this.getStyle(0, 0)
			+ '" href="' + this.menuConfig[1] + '&clickedID=' + this.menuID + '"'
			+ (this.menuConfig[2] && this.menuConfig[2]['tw'] ? ' target="'
			+ this.menuConfig[2]['tw']
			+ '"' : '')
			+ ' style="position: absolute; top: '
			+ this.menuPosY + 'px; left: '
			+ this.menuPosX + 'px; width: '
			+ this.getProperty('width') + 'px; height: '
			+ this.getProperty('height') + 'px; visibility: hidden;'
			+' z-index: ' + this.menuDepth + ';" '
			+ 'onclick="return menuCollection['
			+ Root.menuID + '].onclick('
			+ this.menuID + ');" onmouseout="menuCollection['
			+ Root.menuID + '].onmouseout('
			+ this.menuID + ');" onmouseover="menuCollection['
			+ Root.menuID + '].onmouseover('
			+ this.menuID + ');" onmousedown="menuCollection['
			+ Root.menuID + '].onmousedown('
			+ this.menuID + ');"><div id="element' + Root.menuID
			+ '_'
			+ this.menuID +'inner" class="' + this.getStyle(1, 0) + '">'
			+ this.menuConfig[0] + "</div></a>\n"
		);

	this.innerElement		= document.getElementById('element' + Root.menuID + '_' + this.menuID + 'inner');
	this.menuOuterElement	= document.getElementById('element' + Root.menuID + '_' + this.menuID + 'outer');
	this.propertyVisible	= !this.menuDepth;

	/* if the current item is a tree leaf, then perform no more initializations */
	if (this.menuConfig.length < 4)	return;

	/* node specific methods and properties */
	this.menuChildren = [];		// children of current item (left empty if no further nodes are defined)

	/* recursive initialization of menu chainline */
	for (var menuOrder = 0; menuOrder < this.menuConfig.length - 3; menuOrder++)
	{
		new menuItem(this, menuOrder);
	}
}


/**
 *	Gets properties of corresponding menu item
 *
 *	@param	int	subKey key for menu item
 *	@return void
 *	@access private
 */
function menuItemGetProperty(subKey)
{

	/* check if value is defined for current level */
	var subKeyValue			= null,
		currentItemLevel	= this.Root.menuTemplate[this.menuDepth];

	/* return value if explicitly defined */
	if (currentItemLevel)
		subKeyValue			= currentItemLevel[subKey];

	/*  if no value is defined, request it recursively from the parent level */
	return (subKeyValue == null ? this.objectParent.getProperty(subKey) : subKeyValue);
}


/**
 *	Gets style of corresponding menu item
 *
 *	@param	int	subKey key for menu item
 *	@return void
 *	@access private
 */
function menuItemGetStyle(itemPos, itemState)
{

	var itemCSS					= this.getProperty('css');
	var currentItemOuterClass	= itemCSS[itemPos ? 'inner' : 'outer'];

	/* assign the  same class for all available states */
	if (typeof(currentItemOuterClass) == 'string')
		return currentItemOuterClass;

	/* if not explicitly defined, inherit the class from previous state */
	for (var currentItemState = itemState; currentItemState >= 0; currentItemState--)
	{
		if (currentItemOuterClass[currentItemState])
		{
			return currentItemOuterClass[currentItemState];
		}
	}
}


/**
 *	Sets status bar display text
 *
 *	@param	string	theMessage Message to be displayed
 *	@return void
 *	@access private
 */
function menuItemtUpdateStatus(theMessage)
{
	window.setTimeout("window.status=unescape('" + (theMessage
		? ''
		: (this.menuConfig[2] && this.menuConfig[2]['sb']
			? escape(this.menuConfig[2]['sb'])
			: escape(this.menuConfig[0]) + (this.menuConfig[1]
				? ' ('+ escape(this.menuConfig[1]) + ')'
				: ''))) + "')", 10);
}

/* EOF ;) */

