/**
 * The TreeMenu class creates an explorer-like interface from nested lists.
 * The menu can use either server-side facilities to create classes for
 * the styling with CSS to hook on to.
 *
 * Only set inAllowWhitespace to true when necessary, as it slows down the script.
 *
 * Usage instructions at http://treemenu.nornix.com/info/usage
 *
 * @author      Anders Nawroth <http://www.anders.nawroth.com/>
 * @author      Eric Jexén <http://eric.jexen.se/>
 * @license     LGPL http://treemenu.nornix.com/license
 * @copyright   2006
 * @version     1.78 (2006-06-05)
 * @constructor
 * @param       {String} inMenuId id of the element surrounding the menu
 * @param       {boolean} inAllowWhitespace if true, whitespace is allowed between menu elements in the HTML code
 */
function TreeMenu (inMenuId, inAllowWhitespace)
{
	/**
	 * Variable to work around ECMAScript problems with the "this" keyword inside functions.
	 * @type Object
	 * @private
	 */
	var self = this;
	/**
	 * Id of current menu wrapper.
	 * @type String
	 * @private
	 */
	var menuId = inMenuId ? inMenuId : "menu";
	/**
	 * Name of cookie, to avoid namespace-clashes between multiple menus.
	 * @type String
	 * @private
	 */
	var cookieName = "tree" + menuId;
	/**
	 * Allow whitespace in menu (true) or not (false).
	 * @type boolean
	 * @private
	 */
	var allowWhitespace = (inAllowWhitespace === false) ? false : true;
	/**
	 * RegExp to find the class "open" in strings.
	 * @type RegExp
	 * @private
	 * @final
	 */
	self.openPattern = /(^| )open( |$)/;

	/**
	 * Method that starts the {@link TreeMenu#init}.
	 * Init will be called as soon as the menu object is available in the DOM.
	 * @member TreeMenu
	 */
	this.start = function()
	{
		if (document.getElementById(menuId))
		{
			init();
		}
		else
		{
			setTimeout(self.start, 10);
		}
	}

	/**
	 * Event handler for click events on the inserted span elements.
	 */
	this.toggleClick = function()
	{
		toggle(this.parentNode);
	}

	/**
	 * Event handler for link click events, used on empty links.
	 * @param e event object
	 */
	this.toggleClickLink = function(e)
	{
		toggle(this.parentNode);
		cancelEvent(e);
		return false;
	}

	/**
	 * Event handler for key press events.
	 * Looks for [spacebar] and [enter] key strokes.
	 * @param e event object
	 */
	this.checkKeypress = function(e)
	{
		var keyCode;
		if (e.keyCode)
		{
			keyCode = e.keyCode;
		}
		else
		{
			keyCode = e.which;
		}
		if (keyCode == 32 || (keyCode == 13 && isHrefEmpty(this)))
		{
			toggle(this.parentNode);
			cancelEvent(e);
			return false;
		}
		return true;
	}

	/**
	 * Event handler for the close tree button.
	 */
	this.closeTree = function()
	{
		for (var i = 0, li; li = self.menuFolders[i]; i++)
		{
			makeClosed(li);
		}
		ieFix();
	}

	/**
	 * Event handler for the open tree button.
	 */
	this.openTree = function()
	{
		for (var i = 0, li; li = self.menuFolders[i]; i++)
		{
			makeOpen(li);
		}
		ieFix();
	}

	/**
	 * Initialize the menu.
	 * @private
	 */
	function init()
	{
		if (!document.getElementById || !document.createElement) return;
		self.menu = document.getElementById(menuId);
		self.menuElements = self.menu.getElementsByTagName("li");
		// set classes, if not set in the HTML
		if (self.dynamicClasses)
		{
			setClasses();
		}
		if (self.openCloseAll)
		{
			createOpenCloseAllIcons();
		}
		// add open/close nodes for every folder, using an empty <span> element
		prepareFolderTree();
		// re-render menu now if IE
		ieFix();
		// add unload handler to save cookie
		addEvent(window, "unload", save);
		// preload images for faster rendering
		for (var imgNo in self.preloadImages)
		{
			(new Image()).src = self.preloadImages[imgNo]; // no need to keep the images!
		}
	}

	/**
	 * Dynamically set classes on menu items.
	 * Uses classes folder/document/open/closed/last.
	 * Make folders from the current element/page and "upwards" open.
	 * @private
	 */
	function setClasses()
	{
		var page = window.location;
		// set up root node
		// - find an <a> element that is a child of the menu element
		for (var i = 0, a; a = self.menu.childNodes[i]; i++)
		{
			if (a.nodeName && a.nodeName.toLowerCase() == "a")
			{
				a.className += a.className ? " root" : "root";
				if (a.href == page)
				{
					a.removeAttribute("href");
				}
				break;
			}
		}
		// loop list items
		for (var i = 0, li; li = self.menuElements[i]; i++)
		{
			if (isFolder(li))
			{
				li.className = "folder closed"; // default is closed
			}
			else
			{
				li.className = "document"; // only one childNode (a link)
			}
			if (li == li.parentNode.lastChild)
			{
				li.className += " last";
			}
			if (li.firstChild.href == page)
			{
				// current page
				li.firstChild.removeAttribute("href");
				if (isFolder(li)) // only open if li is folder
				{
					li.className = li.className.replace("closed", "open");
				}
				// trace upwards in the tree to open the "path" to the current page
				var node = li.parentNode.parentNode;
				while (node && node != self.menu)
				{
					makeOpen(node);
					node = node.parentNode.parentNode;
				}
			}
		}
	}

	/**
	 * Prepare tree from stored state in cookie.
	 * Adds span elements inside all li elements.
	 * Adds event handlers on menu items.
	 * Creates the self.menuFolders array of all menu folders.
	 * @private
	 * @requires common Uses the readCookie() function.
	 */
	function prepareFolderTree()
	{
		var oldTree = readCookie(cookieName);
		var orgSpan = document.createElement("span");
		orgSpan.title = self.openFolderTitle; // default value is closed folder
		// setup list of folder elements
		self.menuFolders = new Array();
		var iFolder = 0;
		for (var i = 0, li; li = self.menuElements[i]; i++)
		{
			if (isFolder(li))
			{
				self.menuFolders.push(li);
				// open/close folders with the space bar or enter key or mouse click
				if (isHrefEmpty(li.firstChild))
				{
					addEvent(li.firstChild, "click", self.toggleClickLink);
				}
				addEvent(li.firstChild, "keypress", self.checkKeypress);
				var span = orgSpan.cloneNode(false);
				if (self.dynamicClasses && li.className.search(self.openPattern) != -1)
				{
					// folder opened in setClasses() !
					span.title = self.closeFolderTitle;
				}
				li.insertBefore(span, li.firstChild);
				addEvent(span, "click", self.toggleClick);
				var chr = oldTree.charAt(iFolder++);
				if (chr && chr == "-")
				{
					makeOpen(li);
				}
			}
		}
	}
	/**
	 * Method that saves the current tree state in a cookie.
	 * @requires common Uses the createCookie() function.
	 * @private
	 */
	function save()
	{
		var s = "";
		for (var i = 0, li; li = self.menuFolders[i]; i++)
		{
			if (li.className.search(self.openPattern) != -1)
			{
				s += "-";
			}
			else
			{
				s += "+";
			}
		}
		createCookie(cookieName, s);
	}
	/**
	 * Check if a link is emtpy or "fake-empty".
	 * "#" or "javascript:;" are regardes as "fake-empty".
	 * @param {HTMLAnchorElement} node HTML a element
	 * @return {boolean} true if the link is empty or "fake-empty"
	 * @private
	 */
	function isHrefEmpty(node)
	{
		if (node.href && (node.href == window.location+"#" || node.href == "javascript:;"))
		{
			return true;
		}
		return false;
	}

	if (document.all && document.opera === undefined)
	{
		/**
		 * IE bugfix, forces IE to re-render the menu
		 * and sets focus on elements that have fired an event
		 * @private
		 */
		var ieFix = function()
		{
			self.menu.style.display = "none";
			self.menu.style.display = "block";
			if (window.event && window.event.srcElement) window.event.srcElement.focus();
		}
	}
	else
	{
		/**
		 * @ignore
		 */
		var ieFix = function(){}
	}

	/**
	 * Check if a li element is representng a folder in the menu.
	 * Works different when  allowing or not allowing whitespace in the menu.
	 * @param {HTMLLIElement} node HTML li element
	 * @return {boolean} true if the li element is a folder in the menu
	 * @private
	 */
	function isFolder(li)
	{
		if (allowWhitespace)
		{
			for (var i=0, child; child = li.childNodes[i]; i++)
			{
				if (child.nodeName && child.nodeName.toLowerCase() == "ul")
				{
					return true;
				}
			}
			return false;
		}
		else
		{
			// this is enough when there are no comments, whitespace or extra text nodes
			return li.childNodes.length > 1;
		}
	}

	/**
	 * Toggle a folder in the menu.
	 * @param {HTMLLIElement} node HTML li element
	 * @private
	 */
	function toggle(node)
	{
		if (node.className.search(self.openPattern) === -1)
		{
			makeOpen(node);
		}
		else
		{
			makeClosed(node);
		}
		ieFix();
	}

	/**
	 * Make a folder in the menu open.
	 * @param {HTMLLIElement} li HTML li element
	 * @private
	 */
	function makeOpen(li)
	{
		swapClasses(li, "closed", "open");
		li.firstChild.title = self.closeFolderTitle;
	}

	/**
	 * Make a folder in the menu closed.
	 * @param {HTMLLIElement} li HTML li element
	 * @private
	 */
	function makeClosed(li)
	{
		swapClasses(li, "open", "closed");
		li.firstChild.title = self.openFolderTitle;
	}

	/**
	 * Add icons as HTML a elements for opening and closing all icons.
	 * @private
	 */
	function createOpenCloseAllIcons()
	{
		var orgA = document.createElement("a");
		orgA.href = "javascript:;";
		var a = orgA.cloneNode(false); // new <a> tag
		a.className = "closeTree";
		a.title = self.closeTreeTitle;
		addEvent(a, "click", self.closeTree);
		self.menu.insertBefore(a, self.menu.firstChild.nextSibling);
		a = orgA.cloneNode(false); // new <a> tag
		a.className = "openTree";
		a.title = self.openTreeTitle;
		addEvent(a, "click", self.openTree);
		self.menu.insertBefore(a, self.menu.firstChild.nextSibling.nextSibling);
	}
}

// CONFIG: set public members to default values
/**
 * Setting to turn dynamic class population on/off.
 * Set to true to generate the menu classes dynamically.
 * @type boolean
 */
TreeMenu.prototype.dynamicClasses = true;
/**
 * Setting to add icons for open/close all icons.
 * Set to true to generate open/close icons.
 * @type boolean
 */
TreeMenu.prototype.openCloseAll = true;
/**
 * List of images to preload.
 * Be careful with the order of the images!
 * @type Array
 */
TreeMenu.prototype.preloadImages =
	[
		"/style/nornix-home-icon.png",
		"/style/nornix-close-icon.png",
		"/style/nornix-open-icon.png",
		"/style/nornix-plus-node.png",
		"/style/nornix-minus-node.png",
		"/style/nornix-folder-closed-icon.png",
		"/style/nornix-doc-node-icon.png",
		"/style/nornix-folder-open-icon.png",
		"/style/nornix-treemenu-line.png"
	];
// text to use: change according to your needs, here or
// from an other JS-file or from the HTML
/**
 * Text for "close all" title attribute.
 * @type String
 */
TreeMenu.prototype.closeTreeTitle = "close all folders";
/**
 * Text for "open all" title attribute.
 * @type String
 */
TreeMenu.prototype.openTreeTitle = "open all folders";
/**
 * Text for "close folder" title attribute.
 * @type String
 */
TreeMenu.prototype.closeFolderTitle = "close folder";
/**
 * Text for "open folder" title attribute.
 * @type String
 */
TreeMenu.prototype.openFolderTitle = "open folder";

// start the script
var treemenu = new TreeMenu("menu");
treemenu.start();
