/*-------------------------------------------------------------------
 *	FILENAME	menu.js
 *
 *	PROJECT		Knight Area website
 *	DATE		Thursday, September 17, 2009
 *	VERSION		1.2
 *
 *	Defines classes for multi-level, dynamic menus, supporting
 *	images for representation and highlighting. Works in all modern
 *	browsers.
 *
 *	(C) 2009 Mark Smit
 *-------------------------------------------------------------------*/

/*-------------------------------------------------------------------
 * GLOBAL DEFINITIONS
 *-------------------------------------------------------------------*/

MNU_UP		= 1;
MNU_DOWN	= 2;
MNU_MAX_Y   = 537;

/*-------------------------------------------------------------------
 *	MENU CLASS
 *
 *	This is the main class for a Menu. It can have a menuItem (when it is
 *	a submenu) or a document node as parent. MenuItems are attached
 *	to the Menu by passing the Menu object as parent to the constructor
 *	of the menuItem.
 *
 *	Properties:
 *	- parent		MenuItem or document node to which this Menu belongs;
 *	- expanded		TRUE if expanded, FALSE if collapsed
 *
 *	Methods:
 *	- attachItem	Attaches/adds an MenuItem to the Menu;
 *  - getContainer	Returns the document node in which the menu is contained;
 *	- expand		No argument: Expands the menu;
 *					MNU_UP: expands this & propagates to the parent;
 *					MNU_DOWN: expands this & propagates to all items;
 *	- collapse		Collapses the menu and all its children;
 *	- render		Draws the menu if it is expanded, hides it when it
 *					is collpased;
 *	- toggle		Collapses the menu when it is expanded, and vv;
 *	- redraw		Propagates the method to the parent until the parent
 *					no longer supports this method, then it renders
 *					the menu.
 *	- locate		Locates the item with the specified link and opens
 *					the path to this item.
 *  - load			Loads a menu from an XML file.
 *	- buildFromXML	Builds the menu from the specified XML object.
 *-------------------------------------------------------------------*/

function Menu (
	parent)			// MenuItem or document node to which this Menu belongs
{
	// METHODS

	// attachItem method
	this.attachItem = function (item) {
		this.items [this.items.length] = item;
	}

	// getContainer method
	this.getContainer = function () {
		if (!this.parent)
			return null;

		if (this.parent.getContainer)
			return this.parent.getContainer();
		else
			return this.parent;
	}

	// expand method
	this.expand = function (dir) {

		var i = 0;

		this.expanded = true;
		if (dir == MNU_UP) {
			if (this.parent.expand)
				this.parent.expand (MNU_UP);
		}
		else if (dir == MNU_DOWN) {
			for (i=0; i<this.items.length; i++) {
				this.items[i].expand (MNU_DOWN);
			}
		}
	}

	// Locate function
	this.locate = function (link) {

		var i = 0;

		for (i=0; i<this.items.length; i++) {
			this.items[i].locate (link);
		}
	}

	// collapse method
	this.collapse = function () {

		var i = 0;

		this.expanded = false;

		for (i=0; i<this.items.length; i++) {
			this.items[i].collapse ();
		}
	}

	// render mthod
	this.render = function (x, y) {

		var i = 0;

		for (i=0; i<this.items.length; i++) {
			y = this.items[i].render (x, y);
		}

		return y;
	}

	// toggle method
	this.toggle = function () {
		if (this.expanded == true)
			this.collapse();
		else
			this.expand();
	}

	// redraw method
	this.redraw = function () {

		var style;

		if (this.parent.redraw)
			this.parent.redraw();
		else {
			if (this.items.length  > 0)	 {
				style = this.items[0].image.style;
				this.render(to_numeric(style.left), to_numeric(style.top));
			}
			else
				this.render(0,0);
		}
	}

	// Loads the menu from an XML file
	this.load = function (menufile, pic_template, pic_template_h) {

		// Get the XML document as an XMLHttpRequest
		var xmlhttp = null;

		if (window.XMLHttpRequest) {
			// code for Mozilla, etc.
			xmlhttp = new XMLHttpRequest();
		}
		else if (window.ActiveXObject) {
			// code for IE
			xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
		}

		// Send HTTP request
		if (xmlhttp != null) {
			xmlhttp.open ("GET", menufile, false);
			xmlhttp.send (null);
		}
		else {
			alert ("Your browser does not support XMLHTTP.");
			return;
		}

		// Process the XML menu file
		this.buildFromXML (getFirstElementByTagName(xmlhttp.responseXML, 'menu'),
			pic_template, pic_template_h);
	}

	// Builds the menu from the specified menu node
	this.buildFromXML = function (menuNode, pic_template, pic_template_h) {

		var i=0;
		var itemNode = null;
		var itemName = null;
		var itemLink = null;
		var itemLinkNode = null;
		var itemTooltip = null;
		var itemHeight = null;
		var itemWidth = null;
		var menuItem = null;

		for (i=0; i<menuNode.childNodes.length; i++) {

			itemNode = menuNode.childNodes[i];

			if (itemNode.nodeType == 1) {

				// Get attribute values
				itemName = itemNode.getAttribute('name');
				itemHeight = itemNode.getAttribute('height');
				itemWidth = itemNode.getAttribute('width');
				itemLinkNode = getFirstElementByTagName(itemNode, 'link');
				if (itemLinkNode == null) {
					itemLink = null;
				}
				else {
					if (itemLinkNode.text == null)
						itemLink = itemLinkNode.textContent;
					else
						itemLink = itemLinkNode.text;
				}
				itemTooltip = itemNode.getAttribute('tooltip');

				// Create the menuItem
				menuItem = new MenuItem (this, itemName,
					pic_template.replace('%s', itemName),
					pic_template_h.replace('%s', itemName),
					itemLink, itemTooltip, itemHeight, itemWidth);

				// Build a possible submenu
				menuItem.buildFromXML (itemNode, pic_template,
					pic_template_h);
			}
		}
	}

	// CONSTRUCTOR
	this.items = new Array;
	this.parent = parent;
	this.tooltip = null;

	// If the parent can render, collapse else expand
	if (this.parent) {
		if (this.parent.render)
			this.collapse();
		else
			this.expand();
	}
}

/*-------------------------------------------------------------------
 *	MENU_ITEM CLASS
 *
 *	This class defines the MenuItem, which can be attached to a
 *	Menu object. This generates an Image object in the container,
 *	which will be linked to the Menu item object, which determines
 *	its behaviour. The MenuItem can be a Menu itself. In this case,
 *	its submenu attribute contains Menu items.
 *
 *	Properties:
 *	- name			Name/id of the Menu item;
 *	- link			Hyperlink associated with the Menu item;
 *	- parent		Menu to which this MenuItem belongs;
 *	- picture		Image representing the Menu in its normal state;
 *	- h_picture		Image representing the Menu in its highlighted state;
 *	- submenu		Menu object containing menu_items of the submenu;
 *	- image			Image in the document related to this MenuItem;
 *	- tooltip		Help text for the menu item and document title suffix;
 *	- height		The height of the pic associated with this menu item;
 *
 *	Methods:
 *  - getContainer	Returns the document node in which the menu is contained;
 *	- collapse		Collapses the submenu;
 *	- render		Draws the item if its menu is expanded, including
 *					its submenu;
 *	- redraw		Propagates the method to the parent;
 *	- expand		MNU_UP: propagates to the parent;
 *					MNU_DOWN: propagates to the submenu;
 *	- locate		Locates the item with the specified link and opens
 *					the path to this item.
 *	- buildFromXML	Builds the menu item from the specified XML object.
 *-------------------------------------------------------------------*/

function MenuItem (
	parent,			// Menu to which this Menu item belongs
	name,			// Name of the Menu item
	picture,		// File name of picture to show for this item
	h_picture,		// Highlighted picture
	link,			// Page to link to when the item is clicked
	tooltip,		// Tool tip for the menu item
	height,			// Menu item pic height
	width)			// Menu item pic width
{
	var container = null;

	// METHODS

	// Mouseover method
	this.mouseover = function () {
		this.src = this.menuitem.h_picture.src;
		this.width = this.menuitem.width;
		this.height = this.menuitem.height;
	}

	// MouseOut method
	this.mouseout = function () {
		this.src = this.menuitem.picture.src;
		this.width = this.menuitem.width;
		this.height = this.menuitem.height;
	}

	// onclick method
	this.onclick = function () {
		if (this.menuitem.link) {
			window.location.assign (this.menuitem.link);
		}
		else {
			this.menuitem.submenu.toggle();
			this.menuitem.parent.redraw();
		}
	}

	// getContainer method
	this.getContainer = function () {
		return this.parent.getContainer();
	}

	// Expand method
	this.expand = function (dir) {
		if (dir == MNU_UP)
			this.parent.expand (MNU_UP);
		else if (dir == MNU_DOWN)
			this.submenu.expand (MNU_DOWN);
	}

	// Locate function
	this.locate = function (link) {
		if (link == this.link) {
			this.parent.expand (MNU_UP);
			if (this.tooltip != null) {
				document.title = document.title + " - " + this.tooltip;
			}
			this.submenu.expand (MNU_DOWN);
		}
		else {
			this.submenu.locate (link);
		}
	}

	// render method
	this.render = function (x, y) {
		// Check if the menu containing this item is expanded
		if (parent.expanded == true) {

			// If the images have not been loaded, load them
			if (this.picture == null) {
				this.picture = new Image();
				this.picture.src = this.pic_filename;
			}
			
			if (this.h_picture == null) {
				this.h_picture = new Image();
				this.h_picture.src = this.h_pic_filename;
			}

			// Draw myself
			this.image.src = this.picture.src;
			if (y <= MNU_MAX_Y) {
				this.image.style.visibility = 'visible';
			}
			else {
				this.image.style.visibility = 'hidden';
			}
			this.image.style.left =  x + "px";
			this.image.style.top = y + "px";
			this.image.height = this.height;
			this.image.width = this.width;
			y = y + this.height * 1.1;
		}
		else {
			if (this.image != null) {
				// Make myself invisible
				this.image.style.visibility = 'hidden';
			}
		}

		// Draw my submenu
		y = this.submenu.render (x+15, y);

		return y;
	}

	// Collapse method
	this.collapse = function () {
		this.submenu.collapse();
	}

	// redraw method
	this.redraw = function () {
		this.parent.redraw();
	}

	// Builds the menu from the specified item node
	this.buildFromXML = function (itemNode, pic_template, pic_template_h) {
		var menuNode = null;

		menuNode = getFirstElementByTagName(itemNode, 'menu');
		if (menuNode != null) {
			this.submenu.buildFromXML (menuNode, pic_template,
				pic_template_h);
		}
	}

	// CONSTRUCTOR

	// Fill attributes
	this.name = name;
	this.link = link;
	this.parent = parent;
	this.tooltip = tooltip;
	this.height = height;
	this.width = width;

	// Store filenames
	this.pic_filename = picture;
	this.h_pic_filename = h_picture;

	//// Preload images
	//this.picture = new Image();
	//this.picture.src = picture;
	//this.h_picture = new Image();
	//this.h_picture.src = h_picture;

	// Create submenu
	this.submenu = new Menu(this);

	// Create image, and link it to this object
	this.image = new Image();

	this.image.menuitem = this;
	this.image.onmouseover = this.mouseover;
	this.image.onmouseout = this.mouseout;
	this.image.onclick = this.onclick;
	this.image.title = this.tooltip;
	this.image.style.position = "absolute";
	this.image.style.visibility = 'hidden';

	// Attach myself to the parent Menu
	parent.attachItem (this);

	// Add the image to the document
	container = this.getContainer();
	container.appendChild (this.image);
}

/*-------------------------------------------------------------------
 *	to_numeric()
 *
 *	Searches the characters in str_value until it finds a non-numeric
 *	value, and returns the numeric part.
 *-------------------------------------------------------------------*/

function to_numeric (
	str_value)
{
	var value = 0;
	var i = 0;
	var char = null;

	for (i=0; i<str_value.length; i++) {
		char = str_value.substr (i, 1);
		if (char >= "0" && char <= "9")
			value = value * 10 + (char - "0");
		else
			return value;
	}

	return value;
}

/*-------------------------------------------------------------------
 *	getFirstElementByTagName()
 *
 *	Returns the first child of the specified XML element with the
 *	specified tagName, or null if it is not found.
 *-------------------------------------------------------------------*/

function getFirstElementByTagName (
	elementNode,
	tagName)
{
	var i = 0;
	var childNode = null;

	for (i=0; i<elementNode.childNodes.length; i++) {
		if (elementNode.childNodes[i].tagName == tagName) {
			childNode = elementNode.childNodes[i]
			return childNode;
		}
	}

	return null;
}