var FNXJS;
if (!FNXJS) FNXJS = {};
if (!FNXJS.Events) FNXJS.Events = {};
if (!FNXJS.Events.Handler) FNXJS.Events.Handler = {};


/*
 * EventHandler.js -- Portable event-handler registration functions
 *
 * This module defines event-handler registration and deregistration functions
 * Handler.add( ) and Handler.remove( ). Both functions take three arguments:
 *
 *   element: the DOM element, document, or window on which the handler
 *      is to be added or removed.
 *
 *   eventType: a string that specifies the type of event for which the
 *      handler is to be invoked. Use DOM-standard type names, which do
 *      not include an "on" prefix. Examples: "click", "load", "mouseover".
 *
 *   handler: The function to be invoked when an event of the specified type
 *      occurs on the specified element. This function will be invoked as
 *      a method of the element on which it is registered, and the "this"
 *      keyword will refer to that element. The handler function will be
 *      passed an event object as its sole argument. This event object will
 *      either be a DOM-standard Event object or a simulated one. If a
 *      simulated event object is passed, it will have the following DOM-
 *      compliant properties: type, target, currentTarget, relatedTarget,
 *      eventPhase, clientX, clientY, screenX, screenY, altKey, ctrlKey,
 *      shiftKey, charCode, stopPropagation( ), and preventDefault( )
 *
 * Handler.add( ) and Handler.remove( ) have no return value.
 *
 * Handler.add( ) ignores duplicate registrations of the same handler for
 * the same event type and element. Handler.remove( ) does nothing if called
 * to remove a handler that has not been registered.
 *
 * Implementation notes:
 *
 * In browsers that support the DOM standard addEventListener( ) and
 * removeEventListener( ) event-registration functions, Handler.add( ) and
 * Handler.remove( ) simply invoke these functions, passing false as the
 * third argument (meaning that the event handlers are never registered as
 * capturing event handlers).
 *
 * In versions of Internet Explorer that support attachEvent( ), Handler.add( )
 * and Handler.remove() use attachEvent( ) and detachEvent( ). To
 * invoke the handler function with the correct this keyword, a closure is
 * used. Since closures of this sort cause memory leaks in Internet Explorer,
 * Handler.add( ) automatically registers an onunload handler to deregister
 * all event handlers when the page is unloaded. To keep track of
 * registered handlers, Handler.add( ) creates a property named _allHandlers on
 * the window object and creates a property named _handlers on any element on
 * which a handler is registered.
 */

// In DOM-compliant browsers, our functions are trivial wrappers around
// addEventListener( ) and removeEventListener( ).
if (document.addEventListener) 
{
	FNXJS.Events.Handler.add = function(element, eventType, handler, isCapturingPhase ) 
	{
		element.addEventListener(eventType, handler, Boolean(isCapturingPhase));
	};

	FNXJS.Events.Handler.remove = function(element, eventType, handler, isCapturingPhase) 
	{
		element.removeEventListener(eventType, handler, Boolean(isCapturingPhase));
	};
}

// In IE 5 and later, we use attachEvent( ) and detachEvent( ), with a number of
// hacks to make them compatible with addEventListener and removeEventListener.
else if (document.attachEvent) 
{
	FNXJS.Events.Handler.add = function(element, eventType, handler) 
	{
		// Don't allow duplicate handler registrations
		// _find( ) is a private utility function defined below.
		if (FNXJS.Events.Handler._find(element, eventType, handler) != -1) return;

		// To invoke the handler function as a method of the
		// element, we've got to define this nested function and register
		// it instead of the handler function itself.
		var wrappedHandler = function(e) 
		{
			if (!e) e = window.event;

			// Create a synthetic event object with partial compatibility
			// with DOM events.
			var event = 
			{
				_event: e,    // In case we really want the IE event object
				type: e.type,           // Event type
				target: e.srcElement,   // Where the event happened
				currentTarget: element, // Where we're handling it
				relatedTarget: e.fromElement ? e.fromElement : e.toElement,
				eventPhase: (e.srcElement == element) ? 2 : 3,

				// Mouse coordinates
				clientX: e.clientX, clientY: e.clientY,
				screenX: e.screenX, screenY: e.screenY,
				// Key state
				altKey: e.altKey, ctrlKey: e.ctrlKey,
				shiftKey: e.shiftKey, charCode: e.keyCode,

				// Event-management functions
				stopPropagation: function( ) {this._event.cancelBubble = true;},
				preventDefault: function( ) {this._event.returnValue = false;}
			}

			// Invoke the handler function as a method of the element, passing
			// the synthetic event object as its single argument.
			// Use Function.call( ) if defined; otherwise do a hack
			if (Function.prototype.call)
			{
				handler.call(element, event);
			}
			else 
			{
				// If we don't have Function.call, fake it like this.
				element._currentHandler = handler;
				element._currentHandler(event);
				element._currentHandler = null;
			}
		};

		// Now register that nested function as our event handler.
		element.attachEvent("on" + eventType, wrappedHandler);

		// Now we must do some record keeping to associate the user-supplied
		// handler function and the nested function that invokes it.
		// We have to do this so that we can deregister the handler with the
		// remove( ) method and also deregister it automatically on page unload.

		// Store all info about this handler into an object.
		var h = 
		{
			element: element,
			eventType: eventType,
			handler: handler,
			wrappedHandler: wrappedHandler
		};

		// Figure out what document this handler is part of.
		// If the element has no "document" property, it is not
		// a window or a document element, so it must be the document
		// object itself.
		var d = element.document || element;
		// Now get the window associated with that document.
		var w = d.parentWindow;
		// We have to associate this handler with the window,
		// so we can remove it when the window is unloaded.
		var id = FNXJS.Events.Handler._uid( );  // Generate a unique property name
		if (!w._allHandlers) w._allHandlers = {};  // Create object if needed
		w._allHandlers[id] = h; // Store the handler info in this object

		// And associate the id of the handler info with this element as well.
		if (!element._handlers) element._handlers = [];
		element._handlers.push(id);

		// If there is not an onunload handler associated with the window,
		// register one now.
		if (!w._onunloadHandlerRegistered) 
		{
			w._onunloadHandlerRegistered = true;
			w.attachEvent("onunload", FNXJS.Events.Handler._removeAllHandlers);
		}
	};

	FNXJS.Events.Handler.remove = function(element, eventType, handler) 
	{
		// Find this handler in the element._handlers[] array.
		var i = FNXJS.Events.Handler._find(element, eventType, handler);
		if (i == -1) return;  // If the handler was not registered, do nothing

		// Get the window of this element.
		var d = element.document || element;
		var w = d.parentWindow;

		// Look up the unique id of this handler.
		var handlerId = element._handlers[i];
		// And use that to look up the handler info.
		var h = w._allHandlers[handlerId];
		// Using that info, we can detach the handler from the element.
		element.detachEvent("on" + eventType, h.wrappedHandler);
		// Remove one element from the element._handlers array.
		element._handlers.splice(i, 1);
		// And delete the handler info from the per-window _allHandlers object.
		delete w._allHandlers[handlerId];
    };

    // A utility function to find a handler in the element._handlers array
    // Returns an array index or -1 if no matching handler is found
	FNXJS.Events.Handler._find = function(element, eventType, handler) 
	{
		var handlers = element._handlers;
		if (!handlers) return -1;  // if no handlers registered, nothing found

		// Get the window of this element
		var d = element.document || element;
		var w = d.parentWindow;

		// Loop through the handlers associated with this element, looking
		// for one with the right type and function.
		// We loop backward because the most recently registered handler
		// is most likely to be the first removed one.
		for(var i = handlers.length-1; i >= 0; i--) 
		{
			var handlerId = handlers[i];        // get handler id
			var h = w._allHandlers[handlerId];  // get handler info
			// If handler info matches type and handler function, we found it.
			if (h.eventType == eventType && h.handler == handler)
				return i;
		}
		return -1;  // No match found
	};

    FNXJS.Events.Handler._removeAllHandlers = function( ) 
	{
		// This function is registered as the onunload handler with
		// attachEvent. This means that the this keyword refers to the
		// window in which the event occurred.
		var w = this;

		// Iterate through all registered handlers
		for(id in w._allHandlers) 
		{
			// Get handler info for this handler id
			var h = w._allHandlers[id];
			// Use the info to detach the handler
			h.element.detachEvent("on" + h.eventType, h.wrappedHandler);
			// Delete the handler info from the window
			delete w._allHandlers[id];
		}
	}

    // Private utility to generate unique handler ids
	FNXJS.Events.Handler._counter = 0;
	FNXJS.Events.Handler._uid = function( ) { return "h" + FNXJS.Events.Handler._counter++; };
};

__handler = new Object();
__handler.add = FNXJS.Events.Handler.add;
__handler.remove = FNXJS.Events.Handler.remove;
