/**
 * 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.
 *
 * 02/01/07 JLS P7054  Copied from O'Reilly's book: JavaScript: The 
 *              Definitive Guide, 5th Edition, Example 17-2: "An event
 *              compatibility layer for IE".  This code was titled 
 *              "Handler.js", I updated the file name to 
 *              "EventHandler.js" to clarify it's purpose, but the 
 *              Object remains Handler.
 */
var Handler = {};

// In DOM-compliant browsers, our functions are trivial wrappers around
// addEventListener( ) and removeEventListener( ).
if (document.addEventListener) {
    Handler.add = function(element, eventType, handler) {
        element.addEventListener(eventType, handler, false);
    };

    Handler.remove = function(element, eventType, handler) {
        element.removeEventListener(eventType, handler, false);
    };
}
// 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) {
    Handler.add = function(element, eventType, handler) {
        // Don't allow duplicate handler registrations
        // _find( ) is a private utility function defined below.
        if (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 = 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", Handler._removeAllHandlers);
        }
    };

    Handler.remove = function(element, eventType, handler) {
        // Find this handler in the element._handlers[] array.
        var i = 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
    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
    };

    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
    Handler._counter = 0;
    Handler._uid = function( ) { return "h" + Handler._counter++; };
}

