﻿//<![CDATA[
/*
	Library for user-agent-agnostic DOM operations
	----------------------------------------------

	Derived from DOMhelp 1.0, written by Chris Heilmann, http://www.wait-till-i.com;

	v2.0	2008-05-09	Code completely overhauled (mainly for API consistency reasons), 
						commented, and extended. Namespace object renamed.
						By and large incompatible with v1.0 due to API changes!
	v2.1	2008-05-09	Visual Studio 2008 JavaScript XML comments added.
	v2.2	2008-06-05	domLib.$ added.
	v2.3	2008-06-11	getElementsByClassName added, getChildElements added, walkTheDom added
	v2.4	2008-06-19	Comment bug fixes
*/
var domLib = {
	debugWindowId: "domHelpDebug",

	nodeTypes: {
		ELEMENT_NODE: 1,
		ATTRIBUTE_NODE: 2,
		TEXT_NODE: 3,
		CDATA_SECTION_NODE: 4,
		ENTITY_REFERENCE_NODE: 5,
		ENTITY_NODE: 6,
		PROCESSING_INSTRUCTION_NODE: 7,
		COMMENT_NODE: 8,
		DOCUMENT_NODE: 9,
		DOCUMENT_TYPE_NODE: 10,
		DOCUMENT_FRAGMENT_NODE: 11,
		NOTATION_NODE: 12
	},

	cssActions: {
		SWAP: 1,
		ADD: 2,
		REMOVE: 3,
		CHECK: 4
	},

	directions: {
		FORWARD: 1,
		BACKWARD: -1
	},

	init: function() {
		/// <summary></summary>
		if (!document.getElementById || !document.createTextNode) {
			return;
		}
	},

	getLastSibling: function(node) {
		/// <summary>Finds the last sibling of a DOM element node.</summary>
		/// <param name="node" domElement="true" optional="false">The DOM element node to find the last sibling node for.</param>
		/// <returns domElement="true" mayBeNull="true">A DOM element node, if existent; otherwise, <c>null</c>.</returns>
		var o = node.parentNode.lastChild;

		while (o.nodeType !== domLib.nodeTypes.ELEMENT_NODE && o.previousSibling !== null) {
			o = o.previousSibling;
		}

		return (o.nodeType === domLib.nodeTypes.ELEMENT_NODE) ? o : null;
	},

	getFirstSibling: function(node) {
		/// <summary>Finds the first sibling of a DOM element node.</summary>
		/// <param name="node" domElement="true" optional="false">The DOM element node to find the last sibling node for.</param>
		/// <returns domElement="true" mayBeNull="true">A DOM element node, if existent; otherwise, <c>null</c>.</returns>
		var o = node.parentNode.firstChild;

		while (o.nodeType !== domLib.nodeTypes.ELEMENT_NODE && o.nextSibling !== null) {
			o = o.nextSibling;
		}

		return (o.nodeType === domLib.nodeTypes.ELEMENT_NODE) ? o : null;
	},

	getClosestSibling: function(node, direction) {
		/// <summary>Finds the closest sibling of a DOM element node in the specified direction.</summary>
		/// <param name="node" domElement="true" optional="false">The DOM element node to find the sibling for.</param>
		/// <param name="direction" integer="true" optional="false">The direction to search for (a ).</param>
		/// <returns domElement="true" mayBeNull="true">The closest sibling node, if existent; otherwise, <c>null</c>.</returns>
		var o;

		if (direction === domLib.directions.BACKWARD && node.previousSibling !== null) {
			o = node.previousSibling;
			while (o.nodeType !== domLib.nodeTypes.ELEMENT_NODE && o.previousSibling !== null) {
				o = o.previousSibling;
			}
		}
		else if (direction === domLib.directions.FORWARD && node.nextSibling !== null) {
			o = node.nextSibling;
			while (domLib.nodeTypes.ELEMENT_NODE !== o.nodeType && null !== o.nextSibling) {
				o = o.nextSibling;
			}
		}

		return o.nodeType === domLib.nodeTypes.ELEMENT_NODE ? o : null;
	},

	getText: function(node) {
		/// <summary>Retrieves the nodeValue of a DOM text node.</summary>
		/// <param name="node" domElement="true" optional="false">The DOM node to find a text node and retrieve its nodeValue for.</param>
		/// <returns domElement="true" mayBeNull="true">The text node's nodeValue, if existent; otherwise, <c>null</c>.</returns>
		if (!node.hasChildNodes()) {
			return null;
		}

		var reg = /^\s+$/;
		var o = node.firstChild;

		while (o.nodeType !== domLib.nodeTypes.TEXT_NODE && o.nextSibling !== null || reg.test(o.nodeValue)) {
			o = o.nextSibling;
		}

		return o.nodeType === domLib.nodeTypes.TEXT_NODE ? o.nodeValue : null;
	},

	setText: function(node, txt) {
		/// <summary>Sets the nodeValue of a DOM text node.</summary>
		/// <param name="node" domElement="true" optional="false">The DOM node to find a text node and sets the nodeValue for.</param>
		/// <param name="text" type="String" optional="false">The text for the text node to be set.</param>
		/// <returns type="Boolean"><c>true</c>, if the operation succeeded; otherwise, <c>false</c>.</returns>
		if (!node.hasChildNodes()) {
			return false;
		}

		var reg = /^\s+$/;
		var o = node.firstChild;

		while (o.nodeType !== domLib.nodeTypes.TEXT_NODE && o.nextSibling !== null || reg.test(o.nodeValue)) {
			o = o.nextSibling;
		}

		if (o.nodeType === domLib.nodeTypes.TEXT_NODE) {
			o.nodeValue = txt;
			return true;
		}
		else {
			return false;
		}
	},

	createAnchor: function(uri, txt) {
		/// <summary>Creates a DOM anchor element node.</summary>
		/// <param name="uri" type="String" optional="false">The anchor's target URL.</param>
		/// <param name="txt" type="String" optional="false">The anchor's text.</param>
		/// <returns domElement="true" mayBeNull="false">The anchor element node created.</returns>
		var o = document.createElement("a");

		o.appendChild(document.createTextNode(txt));
		o.setAttribute("href", uri);

		return o;
	},

	createImage: function(id, uri, alt) {
		/// <summary>Creates a DOM image element node.</summary>
		/// <param name="id" type="String" optional="false">The image's id attribute value.</param>
		/// <param name="uri" type="String" optional="false">The image's src attribute value.</param>
		/// <param name="alt" type="String" optional="false">The image's alt attribute value.</param>
		/// <returns domElement="true" mayBeNull="false">The image element node created.</returns>
		var o = document.createElement("img");

		o.setAttribute("id", id);
		o.setAttribute("src", uri);
		o.setAttribute("alt", alt);

		return o;
	},

	createTextElement: function(name, txt) {
		/// <summary>Creates a DOM element node and sets its text value.</summary>
		/// <param name="name" type="String" optional="false">The name of the DOM element node to create.</param>
		/// <param name="txt" type="String" optional="false">The element's text.</param>
		/// <returns domElement="true" mayBeNull="false">The element node created.</returns>
		var o = document.createElement(name);

		o.appendChild(document.createTextNode(txt));

		return o;
	},

	getChildElements: function(elementNode, deep) {
		/// <summary>Finds all element child nodes in the DOM, starting with the element node provided.</summary>
		/// <param name="elementNode" domElement="true" mayBeNull="false" optional="false">The root DOM element node 
		/// starting the DOM walk with.</param>
		/// <param name="deep" type="Boolean" optional="false">Specifies whether only direct child element nodes are 
		/// found or a deep DOM walk is done.</param>
		/// <returns type="Array" elementDomElement="true" elementMayBeNull="false">A list of element node references.</returns>
		var results = [];

		if (elementNode.nodeType !== domLib.nodeTypes.ELEMENT_NODE) {
			return results;
		}

		if (!deep) {
			var node, nodes;
			nodes = elementNode.childNodes;
			for (node in nodes) {
				if (nodes[node].nodeType === domLib.nodeTypes.ELEMENT_NODE) {
					results.push(nodes[node]);
				}
			}
		} else {
			domLib.walkTheDom(elementNode, function(node) {
				if (node.nodeType === domLib.nodeTypes.ELEMENT_NODE) {
					results.push(node);
				}
			});
		}

		return results;
	},

	getElementsByClassName: function(elementNode, className) {
		/// <summary>Finds all element child nodes in the DOM with the specified class property value, starting with the 
		/// element node provided.</summary>
		/// <param name="elementNode" domElement="true" mayBeNull="false" optional="false">The root DOM element node 
		/// starting the DOM walk with.</param>
		/// <param name="className" type="String" optional="false">The class name or string of blank-separated class 
		/// names to find the element nodes for.</param>
		/// <returns type="Array" elementDomElement="true" elementMayBeNull="false">An array of element node references.</returns>
		var results = [];
		var nodes, i;

		if (document.getElementsByClassName) {
			nodes = elementNode.getElementsByClassName(className);

			// Iterates the HTMLCollection of element nodes and builds up
			// a JavaScript array for return type consistency
			for (i = 0; i < nodes.length; i++) {
				results.push(nodes[i]);
			}
		}
		else {
			if (elementNode.nodeType === domLib.nodeTypes.ELEMENT_NODE) {
				domLib.walkTheDom(elementNode, function(node) {
					if (domLib.manageCss(domLib.cssActions.CHECK, node, className)) {
						results.push(node);
					}
				});
			}
		}

		return results;
	},

	walkTheDom: function(node, func) {
		/// <summary>Walks the DOM for the specified node, executing the function on every child node.</summary>
		/// <param name="node" domElement="false" mayBeNull="false" optional="false">The DOM node starting the DOM walk with.</param>
		/// <param name="func" type="Function" optional="false">The function to execute on every child node.</param>
		/// <remarks>In ECMAScript, Functions are first-class values and Closures are supported!</remarks>
		func(node);

		node = node.firstChild;
		while (node) {
			domLib.walkTheDom(node, func);
			node = node.nextSibling;
		}
	},

	initDebug: function(rows, cols) {
		/// <summary>Creates a user interface for debug output.</summary>
		/// <param name="rows" type="Number" integer="true" optional="false">The number of rows.</param>
		/// <param name="cols" type="Number" integer="true" optional="false">The number of columns.</param>
		/// <remarks>The debug output is a textarea inserted as first child of the document body.</remarks>
		if (domLib.debug) {
			domLib.stopDebug();
		}

		domLib.debug = document.createElement("textarea");
		domLib.debug.setAttribute("id", domLib.debugWindowId);
		domLib.debug.setAttribute("rows", rows);
		domLib.debug.setAttribute("cols", cols);
		domLib.debug.style.border = "1px solid lightgrey";
		domLib.debug.style.backgroundColor = "whitesmoke";
		domLib.debug.style.fontFamily = "Consolas,Courier New,monospace";
		domLib.debug.style.fontSize = "8pt";
		domLib.debug.style.color = "green";
		document.body.insertBefore(domLib.debug, document.body.firstChild);
	},

	setDebug: function(message) {
		/// <summary>Adds a debug message to the debug output element.</summary>
		/// <param name="message" type="string" optional="false">The message to be shown.</param>
		/// <remarks>The message is inserted at the top.</remarks>
		if (!domLib.debug) {
			domLib.initDebug();
		}

		domLib.debug.value = message + "\n" + domLib.debug.value;
	},

	stopDebug: function() {
		/// <summary>Removes the debug output user interface and sets the debug object to null./summary>
		if (domLib.debug) {
			domLib.debug.parentNode.removeChild(domLib.debug);
			domLib.debug = null;
		}
	},

	getEventKeyCode: function(e) {
		/// <summary>Finds the event's keyCode property value.</summary>
		/// <param name="e" type="DOMEvent" mayBeNull="false" optional="true">The event object in 
		/// DOM 2 compliant user agents; otherwise, null.</param>
		/// <returns type="Number" integer="true" mayBeNull="true">The event's keyCode value 
		/// if available; otherwise, <c>null<c>.</returns>
		return window.event ? window.event.keyCode : e ? e.keyCode : null;
	},

	getEventType: function(e) {
		/// <summary>Determines the event type name.</summary>
		/// <param name="e" type="DOMEvent" mayBeNull="false" optional="true">The event object in 
		/// DOM 2 compliant user agents; otherwise, null.</param>
		/// <returns type="String" mayBeNull="true">The event's type value if available; otherwise, 
		/// <c>null<c>.</returns>
		return window.event ? window.event.type : e ? e.type : null;
	},

	getEventTarget: function(e) {
		/// <summary>Determines the event target.</summary>
		/// <param name="e" type="DOMEvent" mayBeNull="false" optional="true">The event object in 
		/// DOM 2 compliant user agents; otherwise, null.</param>
		/// <returns domElement="true" mayBeNull="true">The DOM element node the event is targeted to, 
		/// if available; otherwise, <c>null<c>.</returns>
		var target = window.event ? window.event.srcElement : e ? e.target : null;

		if (!target) {
			return null;
		}

		while (target.nodeType !== domLib.nodeTypes.ELEMENT_NODE && target.nodeName.toLowerCase() !== "body") {
			target = target.parentNode;
		}

		return target;
	},

	stopBubble: function(e) {
		/// <summary>Stops event propagation</summary>
		/// <param name="e" type="DOMEvent" mayBeNull="false" optional="true">The event object in 
		/// DOM 2 compliant user agents; otherwise, null.</param>
		if (window.event) {
			window.event.cancelBubble = true;
			return;
		}

		if (e && e.stopPropagation) {
			e.stopPropagation();
			return;
		}
	},

	stopDefault: function(e) {
		/// <summary>Signals that any default action normally taken by the 
		/// implementation as a result of the event will not occur.</summary>
		/// <param name="e" type="DOMEvent" mayBeNull="false" optional="true">The event object in 
		/// DOM 2 compliant user agents; otherwise, null.</param>
		if (window.event) {
			// Microsoft Internet Explorer
			window.event.returnValue = false;
			return;
		}

		if (e && e.preventDefault) {
			// DOM 2
			e.preventDefault();
			return;
		}
	},

	cancelClick: function(e) {
		/// <summary>Cancels event propagation/bubbling and prohibits the use of a default value/action.</summary>
		/// <param name="e" type="DOMEvent" mayBeNull="false" optional="true">The event object in 
		/// DOM 2 compliant user agents; otherwise, null.</param>
		/// <remarks>To suppress the default value/action only, use stopDefault().</remarks>
		if (window.event) {
			// Microsoft Internet Explorer
			window.event.cancelBubble = true;
			window.event.returnValue = false;
			return;
		}

		if (e && e.stopPropagation && e.preventDefault) {
			// DOM 2 event compatible user agents
			e.stopPropagation();
			e.preventDefault();
			return;
		}
	},

	addEvent: function(elementNode, eventType, handler, useCapture) {
		/// <summary>Binds an event handler function to an event fired on/targeted to a DOM element node.</summary>
		/// <param name="elementNode" domElement="true" optional="false">The DOM element node used as event target.</param>
		/// <param name="eventType" type="String" optional="false">The event name.</param>
		/// <param name="handler" type="String" optional="false">The name of the event handler function (the 
		/// function pointer).</param>
		/// <param name="useCapture" type="Boolean" optional="false">Boolean indicating whether to 
		/// bind the event as it is propagating towards the target node, (event capturing), or as the 
		/// event bubbles upwards from the target (event bubbling).</param>
		/// <returns type="Boolean"><c>true</c>, if the event binding succeeded; otherwise, <c>false</c>.</returns>
		if (elementNode.addEventListener) {
			// DOM Level 2 compliant user agents
			elementNode.addEventListener(eventType, handler, useCapture);
			return true;
		}
		else if (elementNode.attachEvent) {
			// Microsoft Internet Explorer
			return elementNode.attachEvent("on" + eventType, handler);
		}
		else {
			// DOM Level 1 compliant user agents
			elementNode["on" + eventType] = handler;
			return true;
		}

		return false;
	},

	fireEvent: function(eventType, eventSource) {
		/// <summary>Fires an event on a DOM element node.</summary>
		/// <param name="eventType" type="String" optional="false">The event name.</param>
		/// <param name="eventSource" domElement="true" optional="false">The DOM element node which should fire the event.</param>
		var evt;

		if (document.createEvent && (eventType.indexOf("mouse") !== -1 || eventType === "click")) {
			evt = document.createEvent("MouseEvents");
			evt.initMouseEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
			eventSource.dispatchEvent(evt);
		}
		else if (document.createEvent) {
			evt = document.createEvent("Events");
			evt.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
			eventSource.dispatchEvent(evt);
		}
		else if (document.createEventObject) {
			// Microsoft Internet Explorer
			evt = document.createEventObject();
			eventSource.fireEvent("on" + eventType, evt);
		}
	},

	manageCss: function(action, elementNode, class1, class2) {
		/// <summary>Manage the className attribute value of a DOM element.</summary>
		/// <param name="action" type="String" optional="false">A domLib.cssAction value</param>
		/// <param name="elementNode" domElement="true" optional="false">The DOM element node for which 
		/// the CSS classes should be managed.</param>
		/// <param name="class1" type="String" optional="false">A class name.</param>
		/// <param name="class2" type="String" optional="true">A class name.</param>
		/// <returns></returns>
		/// <remarks><c>class2</c> is mandatory for domLib.cssActions.SWAP only.</returns>
		var result = false;

		if (elementNode.nodeType !== domLib.nodeTypes.ELEMENT_NODE) {
			return result;
		}

		switch (action) {
			case domLib.cssActions.SWAP:
				// Swap class1 and class2 in className property
				elementNode.className = domLib.manageCss(domLib.cssActions.CHECK, elementNode, class1) ?
										elementNode.className.replace(class1, class2) :
										elementNode.className.replace(class2, class1);
				result = true;
				break;
			case domLib.cssActions.ADD:
				// Add class1 to className property
				if (!domLib.manageCss(domLib.cssActions.CHECK, elementNode, class1)) {
					elementNode.className += (elementNode.className.length === 0) ? class1 : " " + class1;
				}
				result = true;
				break;
			case domLib.cssActions.REMOVE:
				// Remove class1 from className property
				var replaceString = elementNode.className.match(" " + class1) ? " " + class1 : class1;
				elementNode.className = elementNode.className.replace(replaceString, "");
				result = true;
				break;
			case domLib.cssActions.CHECK:
				// Check for class1 in className property
				var arr = elementNode.className.split(" ");
				for (var i = 0; i < arr.length; i++) {
					if (arr[i] == class1) {
						result = true;
						break;
					}
				}
				break;
		}

		return result;
	},

	$: function(id) {
		/// <summary>Short cut function for document.getElementById</summary>
		/// <param name="id" domElement="true" optional="false">The id of the DOM element to find.</param>
		/// <returns domElement="true">The DOM element, if the operation succeeded; otherwise, <c>null</c>.</returns>
		return document.getElementById(id);
	}
}

// Initialize Library
domLib.addEvent(window, "load", domLib.init, false);
//]]>
