/// Adds an event handler to an object, allowing for multiple handlers
/// @param element The element to add the handler to
/// @param eventName The name of the event, e.g. "onmouseup"
/// @param newFunction The function to call on the event
function addHandler(element, eventName, newFunction)
{
	// Tell the event handler to handle all events
	if (!element[eventName])
	{
		element[eventName] = function(event)
		{
			var value;
			var i = 0;
			while (element[eventName + "_" + i] != null)
			{
				value = element[eventName + "_" + i](event);
				i++;
			}
			
			return value;
		};
	}

	// Add the event
	var i = 0;	
	while (element[eventName + "_" + i] != null)
	{
		i++;
	}
	element[eventName + "_" + i] = newFunction;
}

/// Determines whether an object has a given object somewhere above it
/// @param object The object (not an identifier) to check for a parent of
/// @param testParentId The id of the object to see if 'object' is a nested child of
function hasAsParent(object, testParentId)
{
	while (object.parentNode)
	{
		if (object.parentNode.id == testParentId)
		{
			return true;
		}
		else
		{
			object = object.parentNode;
		}
	}

	return false;
}

/// Cross-browser compatible method for getting an XMLHttp[Request] object
/// @returns Either an XMLHTTP object or an XMLHttpRequest object
function getAJAXObject()
{
	try
	{
		if (window.ActiveXObject)
		{
			return new ActiveXObject("MSXML2.XMLHTTP.3.0");
		}
		else
		{
			return new XMLHttpRequest();
		}
	}
	catch (e)
	{
		return null;
	}
}

/// Cross-browser compatible method for getting an XML DOM Parser object
/// @returns Either a DOMParser object or an XMLDOM object
function getXMLParser(text)
{
	try
	{
		if (window.ActiveXObject)
		{
			var xml = new ActiveXObject("Microsoft.XMLDOM");
			if (text)
			{
				xml.async = false;
				xml.loadXML(text);
			}
		}
		else
		{
			if (text)
			{
				var parser = new DOMParser();
				var xml = parser.parseFromString(text, "text/xml");
			}
			else
			{
				var xml = new DOMParser();
			}
		}

		return xml;
	}
	catch (e)
	{
		return null;
	}
}

/// Recurses up to calculate an element's offset from the top-left of the page
/// @param element The element to get the left offset of
function calcOffsetX(element)
{
	var offsetX = 0;

	while (element != null)
	{
		if (!isNaN(element.offsetLeft))
		{
			offsetX += element.offsetLeft;
		}
		if (element.style && element.style.position == "absolute")
		{
			break;
		}

		element = element.offsetParent;
	}

	return offsetX;
}

/// Recurses up to calculate an element's offset from the top-left of the page
/// @param element The element to get the top offset of
function calcOffsetY(element)
{
	var offsetY = 0;

	while (element != null)
	{
		if (!isNaN(element.offsetTop))
		{
			offsetY += element.offsetTop;
		}
		if (element.style && element.style.position == "absolute")
		{
			break;
		}

		element = element.offsetParent;
	}

	return offsetY;
}

/// Starts fading an element in
/// @param item The element to fade in (not an identifier)
/// @param stopAt The opacity to fade to
/// @param change The change in opacity per iteration
function startFadeIn(item, stopAt, change)
{
	item.fading = "in";
	if (item.fadingTimeout)
	{
		window.clearTimeout(item.fadingTimeout);
	}
	fadeInOpacity(item, stopAt, change);
}

/// Starts fading an element out
/// @param item The element to fade out (not an identifier)
/// @param stopAt The opacity to fade to
/// @param change The change in opacity per iteration
/// @param hide If the opacity hits zero, hide the element
/// @param onDone Optional. Called when the fade out is completed
function startFadeOut(item, stopAt, change, hide, onDone)
{
	item.fading = "out";
	if (item.fadingTimeout)
	{
		window.clearTimeout(item.fadingTimeout);
	}
	fadeOutOpacity(item, stopAt, change, hide, onDone);
}

/// Handles the fade in loop
/// @param item The element to fade in (not an identifier)
/// @param stopAt The opacity to fade to
/// @param change The change in opacity per iteration
function fadeInOpacity(item, stopAt, change)
{
	if (item.fading == "in")
	{
		item.style.opacity = parseFloat(item.style.opacity) + change;

		// *Sigh*
		if (window.navigator.appName == "Microsoft Internet Explorer")
		{
			item.style.filter = "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + (item.style.opacity * 100) + ")";
		}
		
		if (item.style.opacity < stopAt)
		{
			item.fadingTimeout = window.setTimeout(function() { fadeInOpacity(item, stopAt, change); }, 10);
		}
	}
}

/// Handles the fade out loop
/// @param item The element to fade out (not an identifier)
/// @param stopAt The opacity to fade to
/// @param change The change in opacity per iteration
/// @param hide If the opacity hits zero, hide the element
function fadeOutOpacity(item, stopAt, change, hide, onDone)
{
	if (item.fading == "out")
	{
		item.style.opacity = parseFloat(item.style.opacity) - change;
		
		// *Sigh*
		if (window.navigator.appName == "Microsoft Internet Explorer")
		{
			item.style.filter = "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + (item.style.opacity * 100) + ")";
		}
		
		if (parseFloat(item.style.opacity) > parseFloat(stopAt))
		{
			item.fadingTimeout = window.setTimeout(function() { fadeOutOpacity(item, stopAt, change, hide, onDone); }, 10);
		}
		else
		{
			if (hide == true)
			{
				item.style.visibility = "hidden";
			}
			if (onDone)
			{
				onDone();
			}
		}
	}
}

/// Mimics the PHP function of the same name, and converts special characters
/// into their HTML entity equivalent.
/// @param str The string encode
function htmlentities(str)
{
	var i, output = "", len, chr = '';
	len = str.length;
	
	for (i = 0; i < len; i++)
	{
		chr = str.charCodeAt(i);
		if ((chr > 47 && chr < 58) || (chr > 62 && chr < 127))
		{
			output += str.charAt(i);
		}
		else
		{
			output += "&#" + chr + ";";
		}
	}
	
	return output;
}