/** 
  * DHTML Drop down menu
  *
  * This is a simple drop down menu whose layout is managed via CSS. It 
  *  is also accessible. You can tab through the menu. 
  * 
  * Menu allows limited customization in the way it shows submenus and 
  * marking steps. 
  *
  * Compatible:
  * I have tested the menu under IE6+, Firefox and Opera. Works also on 
  * IE 5.5 but there are some problems with CSS (padding and width) 
  * 
  * Please let me know if it works on other browsers. 
  *
  * @author Ivars Veksins (ivars [at] gmail [dot] com)
  *
  * IF YOU EVER USE THIS CODE PLEASE LEAVE THE HEADER AS 
  * IT IS. IT WOULD BE NICE IF YOU WOULD LET ME KNOW 
  * YOU ARE USING THIS DROP DOWN MENU BY E-MAILING ME.
  * 
  * ENJOY!
  */ 
  
  
/** 
  * Create a super class where we are going to extend 
  * our drop down menu functions. 
  */
function DHTML_Popup()
{
}

// implement garbage collector 
// garbage collector will call super functions dispose function 
// asking to release all allocated memory 
IMPLEMENT_GARBAGE(DHTML_Popup)

// settings configured by the user 
DHTML_Popup.DELAY = 200; 
DHTML_Popup.ID = "navigation"; 
DHTML_Popup.FLAGS = DHTML_Popup.NORMAL; 

// do not touch these settings
// by changing these you can break the functionality 
DHTML_Popup.TIMER = 0;

// status a menu can have
DHTML_Popup.HIDE = 1; 
DHTML_Popup.SHOW = 2; 
DHTML_Popup.HALT = 4; 
DHTML_Popup.MARK = 8; 


// variables where we save DOM references 
// in HTML page. 
DHTML_Popup.MENUS = null; 
DHTML_Popup.ITEMS = Array(); 

/** 
  * Garbage collector implementation. Release all allocated memory. 
  * As I understand global variables and DOM references have to deleted. 
  * 
  * IE leaks as hell, therefore do whatever you can. 
  */
DHTML_Popup.dispose = function() 
{ 
	// release all global properties
	DHTML_Popup.DELAY = null; 
	DHTML_Popup.ID = null;
	DHTML_Popup.HIDE = null; 
	DHTML_Popup.SHOW = null; 
	DHTML_Popup.HALT = null; 
	
	// set effect flags to null
	DHTML_Popup.NORMAL = null;
	DHTML_Popup.SCROLL_IN = null; 
	DHTML_Popup.SCROLL_OUT = null; 
	DHTML_Popup.SCROLL_INOUT = null; 
	DHTML_Popup.FADE_IN = null;
	DHTML_Popup.FADE_OUT = null; 
	DHTML_Popup.FADE_INOUT = null;

	// clear main timer 
	if (DHTML_Popup.TIMER) 
		clearTimeout(DHTML_Popup.TIMER);
		
	DHTML_Popup.MENUS = null; 
	var items = DHTML_Popup.ITEMS; 
	for (var i = 0; i < items.length; i++) 
	{ 
		var item = items[i]; 
		
		// delete all references 
		item.$holding_menus = null; 
		item.$owner = null; 
		
		item = null; 
	}
	
	DHTML_Popup.ITEMS = null; 
}

/**
  * Function called onload event to lookup for all the unordered lists 
  * and apply all the nessecery events and start the main timer which will show and hide 
  * menus. 
  */
DHTML_Popup.install = function() 
{

	// get main container which holds all menus 
	// (DIV element user specified) 
	var container = DHTML_Popup.get_container(); 
	
	if (container == null)
		return false; 
		
	// get all menus container contains 
	var menus = container.getElementsByTagName("UL"); 
	
	// go through all menus and install events 
	for (var i = 0; i < menus.length; i++) 
	{
		// current menu 
		var menu = menus[i]; 
		
		// status what is happening with the menu 
		// default state is to hide 
		menu.$status = DHTML_Popup.HIDE; 
		menu.$visible = false; 

		// events which will change menu state to show or hide
		event_install(menu,"onmouseover",DHTML_Popup.mouse_over_menu); 
		event_install(menu,"onmouseout",DHTML_Popup.mouse_out_menu); 

		// check wether the menu is root menu 
		if (i == 0) 
			menu.$root = true; 
		
		var items = menu.childNodes; 
		var last_valid_item = null; 
		
		// go through all items in the menu and set their 
		// events and any properties
		for (var n = 0; n < items.length; n++) 
		{ 
			var item = items[n]; 
			
			if (!item.innerHTML) 
				continue; 
			
			last_valid_item = item; 
			
			// set reference to owner menu 
			item.$owner = menu; 
			
			// install events to show menu only if the item has any menus 
			if (DHTML_Popup.item_has_menu(item)) {

				// install events which check if the sub menu has to be showed or not 
				event_install(item,"onmouseover",DHTML_Popup.mouse_over_item); 
				event_install(item,"onmouseout",DHTML_Popup.mouse_out_item); 
				
				// manage keyboard with the same actions 
				// only anchors receive focus and blur properly
				var anchor = item.getElementsByTagName("A"); 
				
				if (anchor.length > 0) {
					// set reference to item 
					event_install(anchor[0],"onfocus",DHTML_Popup.item_focused); 
					event_install(anchor[0],"onclick",DHTML_Popup.item_clicked); 
				}
				
				// save references to items
				DHTML_Popup.ITEMS.push(item); 
			}
			
		} 
		
		// when the last item is blured the holding menu has to be set to hidden 
		// last valid item handles the onblur event
		if (last_valid_item)
		{
			var anchor = last_valid_item.getElementsByTagName("A"); 
			
			if (anchor.length > 0) 
				event_install(anchor[0],"onblur",DHTML_Popup.item_blured); 
		}
					

	}	


	// save reference for later use
	DHTML_Popup.MENUS = menus; 
	
	// start worker "thread" 
	DHTML_Popup.start_worker(); 
	
	// delete container reference 
	container = null; 
	return true; 
}

event_install(window,"onload",DHTML_Popup.install); 



/** 
 * Keyboard accessible event which is trigered when a anchor receives 
 * a focus. On focus we are deciding which menus to show or to hide
 */
DHTML_Popup.item_focused = function() 
{ 
	var item = this.parentNode; 
	
	// fast tabbing causes to open to many menus 
	// check if is menu set to visible and not visible yet 
	var menus = DHTML_Popup.MENUS;  
	
	for (var i = 0; i < menus.length; i++) { 
		var menu = menus[i]; 
		
		if (menu.$root) continue; 
		
		if (item.$owner.$root) {	
			menu.$status = DHTML_Popup.HIDE; 
			DHTML_Popup.menu_hide(menu);
		} else { 
			// hide menu if set to hide and not hidden 
			if (menu.$status & DHTML_Popup.HIDE && menu.$visible) 
				DHTML_Popup.menu_hide(menu);
				
			// hide menus which are set to show but not showed 
			if (menu.$status & DHTML_Popup.SHOW && !menu.$visible) 
				menu.$status = DHTML_Popup.HIDE; 
		}
	}
	
	// activate new item 
	var holding = item.$holding_menus[0]; 
	
	if (holding) 
		holding.$status = DHTML_Popup.HALT | DHTML_Popup.SHOW | DHTML_Popup.MARK; 
	
}

/** 
  * Focusing and bluring breaks the functionality if using mouse 
  * and user clicks a lot. 
  * A little hack which checks whether it is a click or a tab. 
  */
DHTML_Popup.item_clicked = function() 
{
	var owner = this.parentNode.$owner; 
	if (!owner) 
		return; 
		
	owner.$clicked = true; 
}

/** 
 * Keyboard accessible event triggered when an anchor 
 * looses its focus. 
 */
DHTML_Popup.item_blured = function() 
{ 
	var owner = this.parentNode.$owner; 
	
	if (!owner) 
		return; 

	if (owner.$clicked) 
	{ 
		owner.$clicked = false; 
		return; 
	}
	
	// root menu doesn't have to be hidden 
	if (owner.$root) 
		return; 

	var holding = this.parentNode.$holding_menus; 

	owner.style.zIndex = '1'; 
	
	if (holding && holding[0].$status & DHTML_Popup.SHOW) {
		// tell to hide all
		holding[0].$hide = owner; 
		
	} else { 
		if (owner.$hide) 
			owner.$hide.$status = DHTML_Popup.HIDE; 
	
		owner.$status = DHTML_Popup.HALT | DHTML_Popup.HIDE; 
	}
	
	// delete reference
	owner = null; 
}


/** 
  * Gets a container which holds all unordered lists 
  * @return DOM reference
  */
DHTML_Popup.get_container = function()
{
	var container = document.getElementById(DHTML_Popup.ID); 
	
	if (container == null) 
		ctad_error("DHTML_Popup container can not be found."); 
		
	return container; 
}


/** 
 * function which goes through all menus in container and checks 
 * whether they have to be hidden or displayed. 
 * 
 * This function is executed in a delay specified 
 * DHTML_Popup.DELAY
 */
DHTML_Popup.start_worker = function() 
{	
	var menus = DHTML_Popup.MENUS; 
	
	if (!menus) 
		return; 
	
	// loop through all elements 
	for (var i = 0; i < menus.length; i++) 
	{ 
		
		var menu = menus[i]; 
		
		// root menu never should be hidden 
		if (menu.$root) 
			continue; 
			
		
		// check wether holding menu item has to be marked 
		// or unmarked 
		if (menu.$status & DHTML_Popup.MARK) {
			DHTML_Popup.mark(menu.parentNode); 
		} else { 
			DHTML_Popup.unmark(menu.parentNode); 
		}
		
		// check menu needs to be hidden or showed 
		if (menu.$status & DHTML_Popup.HIDE ) {
			DHTML_Popup.menu_hide(menu); 
		} else if (menu.$status & DHTML_Popup.SHOW ) { 
			DHTML_Popup.menu_show(menu); 
		}
	}
	
	DHTML_Popup.TIMER = setTimeout('DHTML_Popup.start_worker()', DHTML_Popup.DELAY ); 
}

/** 
  * Sets menu to invisible mode. 
  * @argument menu which needs to be hidden 
  */
DHTML_Popup.menu_hide = function(menu) {
	
	if (!menu.$visible) 
		return; 

	DHTML_Popup.custom_hide(menu); 
	menu.$visible = false; 
}


/** 
  * Sets menu to visible mode. 
  * @argument menu to be set to visible 
  */
DHTML_Popup.menu_show = function(menu)
{
	if (menu.$visible) 
		return; 

	// ie doesn't hide menus when not visible 
	// therefore we have to hide them ourselfs 
	// (BUG)
	var menus = menu.getElementsByTagName("UL"); 
	for (var i = 0; i < menus.length; i++) 
	{ 
		var m = menus[i]; 

		m.style.display = 'block'; 
		m.style.display = 'none'; 
	}

	DHTML_Popup.custom_show(menu); 
	menu.$visible = true; 
}; 


/** 
  * When mouse is over an item set its holding menu status 
  * to show. The main worker is going to show it. 
  */
DHTML_Popup.mouse_over_item = function() 
{
	// get menu to show 
	var menu = this.$holding_menus[0]; 

	// set menu status to show 
	if (menu.$status & DHTML_Popup.HIDE) 
		menu.$status = (menu.$status & ~DHTML_Popup.HIDE) | DHTML_Popup.SHOW | DHTML_Popup.MARK; 
	
	// change z order
	menu.style.zIndex = '2'; 
	
	DHTML_Popup.restart_worker(); 
	

}

/** 
  * When mouse is outside the item set its holding menu status 
  * to be hidden. Again, the main worker hides it. 
  */
DHTML_Popup.mouse_out_item = function() 
{
	// get menu to hide 
	var menu = this.$holding_menus[0]; 
	
	if (menu.$status & DHTML_Popup.SHOW)
		menu.$status = (menu.$status & ~(DHTML_Popup.SHOW | DHTML_Popup.MARK) ) | DHTML_Popup.HIDE; 
	
	// change z order 
	menu.style.zIndex = '1'; 

	DHTML_Popup.restart_worker();
}


/**  
  * Event handler when mouse is over menu 
  * Sets status to halt to true. Forces menu to be dislpayed (locks it)
  */
DHTML_Popup.mouse_over_menu = function() 
{
	if (this.$status & DHTML_Popup.HALT)
		return; 

	this.$status |= DHTML_Popup.HALT; 
	DHTML_Popup.restart_worker(); 
}


/**  
  * Removes the lock from menu. Menu can be hidden by setting
  * HIDE flag. 
  */
DHTML_Popup.mouse_out_menu = function() 
{
	if ( this.$status & DHTML_Popup.HALT) 
		this.$status &= ~DHTML_Popup.HALT; 
	
	DHTML_Popup.restart_worker(); 
}



/** 
  * Stops main working "thread" and 
  * starts from begining 
  */
DHTML_Popup.restart_worker = function() 
{ 
	clearTimeout(DHTML_Popup.TIMER); 
	DHTML_Popup.TIMER = setTimeout('DHTML_Popup.start_worker()',DHTML_Popup.DELAY ); 
}

/** 
  * Checks wether an item has another level 
  * menu inside. 
  * 
  * @argument LI DOM reference 
  * @return status 
  */
DHTML_Popup.item_has_menu = function (item) 
{ 
	var menus = item.getElementsByTagName("UL"); 
	
	if (menus == null || menus.length == 0) 
		return false; 
	
	// sets reference with menus inside the item 
	item.$holding_menus = menus; 
	
	return true; 
}


/** 
  * Overridable function for marking active steps
  * @argument item to mark 
  */
DHTML_Popup.mark = function (item) 
{
}

/** 
  * Overridable function which resets the item to normal state. 
  * @argument item to unmark 
  */
DHTML_Popup.unmark = function (item) 
{
}


/**
  * Function which performs custom show on menu. 
  * This function can also be overrided 
  */
DHTML_Popup.custom_show = function(menu) 
{
	menu.style.display = 'block'; 
}

/**
  * Function which performs a custom hide on menu 
  * This function can also be overrided 
  */
DHTML_Popup.custom_hide = function(menu) 
{
	menu.style.display = 'none'; 
}

// --- EFFECTS --- // 


// scrolling effect 
function Scrolling() 
{
}

IMPLEMENT_GARBAGE(Scrolling); 

// there can be multiple menus to be hidden but only one has to be shown at 
// time. Therefore show menu reference is a direct reference, but menus to hide 
// array 
Scrolling.$hidden = []; 
Scrolling.$active_out = null; 

Scrolling.$show = null; 

// scrolling step 
Scrolling.$step_size = 10; 


Scrolling.dispose = function() 
{ 
	Scrolling.$hidden = null; 
	Scrolling.$active_out = null; 
	Scrolling.$show = null; 
	Scrolling.$step_size = null; 
}

// scrolls from 1 to the original height of menu 
Scrolling.do_scrolling_in = function() 
{
	var menu = Scrolling.$show; 
	if (!menu) 
		return; 
	
	if (menu.clientHeight < menu.$original_height) 
	{
		menu.style.height = (menu.clientHeight + Scrolling.$step_size) + 'px';
		setTimeout('Scrolling.do_scrolling_in()',10); 
		
	} else { 
		// finished scrolling 
		
		menu.style.height =  'auto'; 
		menu.style.overflow = 'visible'; 
		
		Scrolling.$show = null; 	
	}
}

// scroll froms original height to 1 
Scrolling.do_scrolling_out = function() 
{
	var menu = Scrolling.$active_out; 
	
	if (!menu)
		return; 
		
	menu.$height -= Scrolling.$step_size;
	
	if (menu.$height > 0) 
	{
		menu.style.height = menu.$height + 'px'; 
		setTimeout('Scrolling.do_scrolling_out()',10); 
	} else { 
		
		menu.style.display = 'none'; 

		menu.style.overflow = 'visible'; 
		menu.style.height = menu.$original_height + 'px'; 

		// check for next element 
		Scrolling.$active_out = Scrolling.$hidden.pop(); 
		
		if (Scrolling.$active_out) 
			Scrolling.do_scrolling_out();
	}
}

// indicates that a menu has to be scrolled in 
// prepares menu and starts scrolling 
Scrolling.scroll_in = function(menu) 
{ 
	// have to set to be displayed as block 
	// otherwise the height is incorrect
	menu.style.display = 'block'; 
	
	// save the original height 
	if (!menu.$original_height) 
		menu.$original_height = menu.clientHeight; 
	
	// otherwise anchors are as they are 
	menu.style.overflow = 'hidden'; 
	menu.style.height = '1px'; 
	
	// global reference 
	Scrolling.$show = menu; 
	
	// start scrolling 
	Scrolling.do_scrolling_in(); 
}

// indicates that the menu has to be hidden 
// and asks to start scrolling out 
Scrolling.scroll_out = function(menu) 
{
	menu.style.overflow = 'hidden'; 
	
	if (!menu.$original_height) 
		menu.$original_height = menu.clientHeight;

	menu.$height = menu.$original_height; 
	
	Scrolling.$hidden.push(menu); 

	if (!Scrolling.$active_out) 
	{
		Scrolling.$active_out = Scrolling.$hidden.pop(); 
		Scrolling.do_scrolling_out(); 
	}
}
