/** 
  * Crossbrowser event handling functions.
  *
  * A set of functions to easily attach and detach event handlers to HTML elements.
  * These functions work around the shortcomings of the traditional method ( element.onevent = function; )
  * where only 1 handler could be attached for a certain event on the object, and mimic the DOM level 2
  * event methods addEventListener and removeEventListener for browsers that do not support these
  * methods (e.g. Internet Explorer) without resorting to propriety methods such as attachEvent and detachEvent
  * that have a whole set of their own shortcomings.
  * Created as an entry for the 'contest' at quirksmode.org: http://www.quirksmode.org/blog/archives/2005/09/addevent_recodi.html
  *
  * @author Tino Zijdel ( crisp@xs4all.nl )
  * @version 1.1
  * @date 2005-10-19
  */


/** 
  * addEvent
  *
  * Generic function to attach event listeners to HTML elements.
  * This function does NOT use attachEvent but creates an own stack of function references
  * in the DOM space of the element. This prevents closures and therefor possible memory leaks.
  * Also because of the way the function references are stored they will get executed in the
  * same order as they where attached - matching the behavior of addEventListener.
  *
  * @param obj The object to which the event should be attached.
  * @param evType The eventtype, eg. 'click', 'mousemove' etcetera.
  * @param fn The function to be executed when the event fires.
  * @param useCapture (optional) Whether to use event capturing, or event bubbling (default).
  */
function addEvent(obj, evType, fn, useCapture)
{
	//-- Default to event bubbling
	if (!useCapture) useCapture = false;

	//-- DOM level 2 method
	if (obj.addEventListener)
	{
		obj.addEventListener(evType, fn, useCapture);
	}
	else
	{
		//-- event capturing not supported
		if (useCapture)
		{
			alert('This browser does not support event capturing!');
		}
		else
		{
			var evTypeRef = '__' + evType;

			//-- create function stack in the DOM space of the element; seperate stacks for each event type
			if (!obj[evTypeRef])
			{
				//-- create the stack if it doesn't exist yet
				obj[evTypeRef] = [];

				//-- if there is an inline event defined store it in the stack
				var orgEvent = obj['on'+evType];
				if (orgEvent) obj[evTypeRef][0] = orgEvent;

				//-- attach helper function using the DOM level 0 method
				obj['on'+evType] = IEEventHandler;
			}
			else
			{
				//-- check if handler is not already attached, don't attach the same function twice to match behavior of addEventListener
				if (obj[evTypeRef].indexOf(fn) > -1) return;
			}

			//-- add reference to the function to the stack
			obj[evTypeRef][obj[evTypeRef].length] = fn;
		}
	}
}

/** 
  * removeEvent
  *
  * Generic function to remove previously attached event listeners.
  *
  * @param obj The object to which the event listener was attached.
  * @param evType The eventtype, eg. 'click', 'mousemove' etcetera.
  * @param fn The listener function.
  * @param useCapture (optional) Whether event capturing, or event bubbling (default) was used.
  */
function removeEvent(obj, evType, fn, useCapture)
{
	//-- Default to event bubbling
	if (!useCapture) useCapture = false;

	//-- DOM level 2 method
	if (obj.removeEventListener)
	{
		obj.removeEventListener(evType, fn, useCapture);
	}
	else
	{
		var evTypeRef = '__' + evType;

		//-- Check if there is a stack of function references for this event type on the object
		if (obj[evTypeRef])
		{
			//-- check if function is present in the stack
			var i = obj[evTypeRef].indexOf(fn);
			if (i > -1)
			{
				try
				{
					delete obj[evTypeRef][i];
				}
				catch(e)
				{
					obj[evTypeRef][i] = null;
				}

				return;
			}
		}
	}
}

/** 
  * IEEventHandler
  * 
  * IE helper function to execute the attached handlers for events.
  * Because of the way this helperfunction is attached to the object (using the DOM level 0 method)
  * the 'this' keyword will correctely point to the element that the handler was defined on.
  *
  * @param e (optional) Event object, defaults to window.event object when not passed as argument (IE).
  */
function IEEventHandler(e)
{
	e = e || window.event;
	var evTypeRef = '__' + e.type, obj;

	//-- check if there is a custom function stack defined for this event type on the object
	if (this[evTypeRef])
	{
		//-- iterate through the stack and execute each function in the scope of the object by using function.call
		for (var i = 0, j = this[evTypeRef].length; i < j; i++)
		{
			if (this[evTypeRef][i])
			{
				if (Function.call)
				{
					this[evTypeRef][i].call(this, e);
				}
				else
				{
					//-- IE 5.0 doesn't support call or apply, so use this
					this.__fn = this[evTypeRef][i];
					this.__fn(e);
					this.__fn = null;
				}
			}
		}
	}
}

//-- prototype the indexOf method for arrays; this method is native in Gecko 1.8+
if (!Array.prototype.indexOf)
{
	Array.prototype.indexOf = function(searchElement, fromIndex)
	{
		var l = this.length, i = 0;
		if (fromIndex)
		{
			i = fromIndex;
			if (i < 0)
			{
				i += l;
				if (i < 0) i = 0;
			}
		}

		while (i < l)
		{
			if (this[i] === searchElement) return i;
			i++;
		}

		return -1;
	}
}
