# event.js

    /*
    Copyright (c) 2006, Yahoo! Inc. All rights reserved.
    Code licensed under the BSD License:
    http://developer.yahoo.net/yui/license.txt
    version: 0.12.0
    */
    // holder
    /**
     * The CustomEvent class lets you define events for your application
     * that can be subscribed to by one or more independent component.
     *
     * @param {String}  type The type of event, which is passed to the callback
     *                  when the event fires
     * @param {Object}  oScope The context the event will fire from.  "this" will
     *                  refer to this object in the callback.  Default value:
     *                  the window object.  The listener can override this.
     * @param {boolean} silent pass true to prevent the event from writing to
     *                  the log system
     * @namespace YAHOO.util
     * @class CustomEvent
     * @constructor
     */
    YAHOO.util.CustomEvent = function(type, oScope, silent, signature) {

        /**
         * The type of event, returned to subscribers when the event fires
         * @property type
         * @type string
         */
        this.type = type;

        /**
         * The scope the the event will fire from by default.  Defaults to the window
         * obj
         * @property scope
         * @type object
         */
        this.scope = oScope || window;

        /**
         * By default all custom events are logged in the debug build, set silent
         * to true to disable logging for this event.
         * @property silent
         * @type boolean
         */
        this.silent = silent;

        /**
         * Custom events support two styles of arguments provided to the event
         * subscribers.
         * <ul>
         * <li>YAHOO.util.CustomEvent.LIST:
         *   <ul>
         *   <li>param1: event name</li>
         *   <li>param2: array of arguments sent to fire</li>
         *   <li>param3: <optional> a custom object supplied by the subscriber</li>
         *   </ul>
         * </li>
         * <li>YAHOO.util.CustomEvent.FLAT
         *   <ul>
         *   <li>param1: the first argument passed to fire.  If you need to
         *           pass multiple parameters, use and array or object literal</li>
         *   <li>param2: <optional> a custom object supplied by the subscriber</li>
         *   </ul>
         * </li>
         * </ul>
         *   @property signature
         *   @type int
         */
        this.signature = signature || YAHOO.util.CustomEvent.LIST;

        /**
         * The subscribers to this event
         * @property subscribers
         * @type Subscriber[]
         */
        this.subscribers = [];

        if (!this.silent) {
        }

        var onsubscribeType = "_YUICEOnSubscribe";

        // Only add subscribe events for events that are not generated by
        // CustomEvent
        if (type !== onsubscribeType) {

            /**
             * Custom events provide a custom event that fires whenever there is
             * a new subscriber to the event.  This provides an opportunity to
             * handle the case where there is a non-repeating event that has
             * already fired has a new subscriber.
             *
             * @event subscribeEvent
             * @type YAHOO.util.CustomEvent
             * @param {Function} fn The function to execute
             * @param {Object}   obj An object to be passed along when the event
             *                       fires
             * @param {boolean|Object}  override If true, the obj passed in becomes
             *                                   the execution scope of the listener.
             *                                   if an object, that object becomes the
             *                                   the execution scope.
             */
            this.subscribeEvent =
                    new YAHOO.util.CustomEvent(onsubscribeType, this, true);

        }
    };

    /**
     * Subscriber listener sigature constant.  The LIST type returns three
     * parameters: the event type, the array of args passed to fire, and
     * the optional custom object
     * @property YAHOO.util.CustomEvent.LIST
     * @static
     * @type int
     */
    YAHOO.util.CustomEvent.LIST = 0;

    /**
     * Subscriber listener sigature constant.  The FLAT type returns two
     * parameters: the first argument passed to fire and the optional
     * custom object
     * @property YAHOO.util.CustomEvent.FLAT
     * @static
     * @type int
     */
    YAHOO.util.CustomEvent.FLAT = 1;

    YAHOO.util.CustomEvent.prototype = {

        /**
         * Subscribes the caller to this event
         * @method subscribe
         * @param {Function} fn        The function to execute
         * @param {Object}   obj       An object to be passed along when the event
         *                             fires
         * @param {boolean|Object}  override If true, the obj passed in becomes
         *                                   the execution scope of the listener.
         *                                   if an object, that object becomes the
         *                                   the execution scope.
         */
        subscribe: function(fn, obj, override) {
            if (this.subscribeEvent) {
                this.subscribeEvent.fire(fn, obj, override);
            }

            this.subscribers.push( new YAHOO.util.Subscriber(fn, obj, override) );
        },

        /**
         * Unsubscribes the caller from this event
         * @method unsubscribe
         * @param {Function} fn  The function to execute
         * @param {Object}   obj  The custom object passed to subscribe (optional)
         * @return {boolean} True if the subscriber was found and detached.
         */
        unsubscribe: function(fn, obj) {
            var found = false;
            for (var i=0, len=this.subscribers.length; i<len; ++i) {
                var s = this.subscribers[i];
                if (s && s.contains(fn, obj)) {
                    this._delete(i);
                    found = true;
                }
            }

            return found;
        },

        /**
         * Notifies the subscribers.  The callback functions will be executed
         * from the scope specified when the event was created, and with the
         * following parameters:
         *   <ul>
         *   <li>The type of event</li>
         *   <li>All of the arguments fire() was executed with as an array</li>
         *   <li>The custom object (if any) that was passed into the subscribe()
         *       method</li>
         *   </ul>
         * @method fire
         * @param {Object*} arguments an arbitrary set of parameters to pass to
         *                            the handler.
         */
        fire: function() {
            var len=this.subscribers.length;
            if (!len && this.silent) {
                return true;
            }

            var args=[], ret=true, i;

            for (i=0; i<arguments.length; ++i) {
                args.push(arguments[i]);
            }

            var argslength = args.length;

            if (!this.silent) {
            }

            for (i=0; i<len; ++i) {
                var s = this.subscribers[i];
                if (s) {
                    if (!this.silent) {
                    }

                    var scope = s.getScope(this.scope);

                    if (this.signature == YAHOO.util.CustomEvent.FLAT) {
                        var param = null;
                        if (args.length > 0) {
                            param = args[0];
                        }
                        ret = s.fn.call(scope, param, s.obj);
                    } else {
                        ret = s.fn.call(scope, this.type, args, s.obj);
                    }
                    if (false === ret) {
                        if (!this.silent) {
                        }

                        //break;
                        return false;
                    }
                }
            }

            return true;
        },

        /**
         * Removes all listeners
         * @method unsubscribeAll
         */
        unsubscribeAll: function() {
            for (var i=0, len=this.subscribers.length; i<len; ++i) {
                this._delete(len - 1 - i);
            }
        },

        /**
         * @method _delete
         * @private
         */
        _delete: function(index) {
            var s = this.subscribers[index];
            if (s) {
                delete s.fn;
                delete s.obj;
            }

            // delete this.subscribers[index];
            this.subscribers.splice(index, 1);
        },

        /**
         * @method toString
         */
        toString: function() {
             return "CustomEvent: " + "'" + this.type  + "', " +
                 "scope: " + this.scope;

        }
    };

    /////////////////////////////////////////////////////////////////////

    /**
     * Stores the subscriber information to be used when the event fires.
     * @param {Function} fn       The function to execute
     * @param {Object}   obj      An object to be passed along when the event fires
     * @param {boolean}  override If true, the obj passed in becomes the execution
     *                            scope of the listener
     * @class Subscriber
     * @constructor
     */
    YAHOO.util.Subscriber = function(fn, obj, override) {

        /**
         * The callback that will be execute when the event fires
         * @property fn
         * @type function
         */
        this.fn = fn;

        /**
         * An optional custom object that will passed to the callback when
         * the event fires
         * @property obj
         * @type object
         */
        this.obj = obj || null;

        /**
         * The default execution scope for the event listener is defined when the
         * event is created (usually the object which contains the event).
         * By setting override to true, the execution scope becomes the custom
         * object passed in by the subscriber.  If override is an object, that
         * object becomes the scope.
         * @property override
         * @type boolean|object
         */
        this.override = override;

    };

    /**
     * Returns the execution scope for this listener.  If override was set to true
     * the custom obj will be the scope.  If override is an object, that is the
     * scope, otherwise the default scope will be used.
     * @method getScope
     * @param {Object} defaultScope the scope to use if this listener does not
     *                              override it.
     */
    YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
        if (this.override) {
            if (this.override === true) {
                return this.obj;
            } else {
                return this.override;
            }
        }
        return defaultScope;
    };

    /**
     * Returns true if the fn and obj match this objects properties.
     * Used by the unsubscribe method to match the right subscriber.
     *
     * @method contains
     * @param {Function} fn the function to execute
     * @param {Object} obj an object to be passed along when the event fires
     * @return {boolean} true if the supplied arguments match this
     *                   subscriber's signature.
     */
    YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {
        if (obj) {
            return (this.fn == fn && this.obj == obj);
        } else {
            return (this.fn == fn);
        }
    };

    /**
     * @method toString
     */
    YAHOO.util.Subscriber.prototype.toString = function() {
        return "Subscriber { obj: " + (this.obj || "")  +
               ", override: " +  (this.override || "no") + " }";
    };

    /**
     * The Event Utility provides utilities for managing DOM Events and tools
     * for building event systems
     *
     * @module event
     * @title Event Utility
     * @namespace YAHOO.util
     * @requires yahoo
     */

    // The first instance of Event will win if it is loaded more than once.
    if (!YAHOO.util.Event) {

    /**
     * The event utility provides functions to add and remove event listeners,
     * event cleansing.  It also tries to automatically remove listeners it
     * registers during the unload event.
     *
     * @class Event
     * @static
     */
        YAHOO.util.Event = function() {

            /**
             * True after the onload event has fired
             * @property loadComplete
             * @type boolean
             * @static
             * @private
             */
            var loadComplete =  false;

            /**
             * Cache of wrapped listeners
             * @property listeners
             * @type array
             * @static
             * @private
             */
            var listeners = [];

            /**
             * User-defined unload function that will be fired before all events
             * are detached
             * @property unloadListeners
             * @type array
             * @static
             * @private
             */
            var unloadListeners = [];

            /**
             * Cache of DOM0 event handlers to work around issues with DOM2 events
             * in Safari
             * @property legacyEvents
             * @static
             * @private
             */
            var legacyEvents = [];

            /**
             * Listener stack for DOM0 events
             * @property legacyHandlers
             * @static
             * @private
             */
            var legacyHandlers = [];

            /**
             * The number of times to poll after window.onload.  This number is
             * increased if additional late-bound handlers are requested after
             * the page load.
             * @property retryCount
             * @static
             * @private
             */
            var retryCount = 0;

            /**
             * onAvailable listeners
             * @property onAvailStack
             * @static
             * @private
             */
            var onAvailStack = [];

            /**
             * Lookup table for legacy events
             * @property legacyMap
             * @static
             * @private
             */
            var legacyMap = [];

            /**
             * Counter for auto id generation
             * @property counter
             * @static
             * @private
             */
            var counter = 0;

            return { // PREPROCESS

                /**
                 * The number of times we should look for elements that are not
                 * in the DOM at the time the event is requested after the document
                 * has been loaded.  The default is 200@amp;50 ms, so it will poll
                 * for 10 seconds or until all outstanding handlers are bound
                 * (whichever comes first).
                 * @property POLL_RETRYS
                 * @type int
                 * @static
                 * @final
                 */
                POLL_RETRYS: 200,

                /**
                 * The poll interval in milliseconds
                 * @property POLL_INTERVAL
                 * @type int
                 * @static
                 * @final
                 */
                POLL_INTERVAL: 20,

                /**
                 * Element to bind, int constant
                 * @property EL
                 * @type int
                 * @static
                 * @final
                 */
                EL: 0,

                /**
                 * Type of event, int constant
                 * @property TYPE
                 * @type int
                 * @static
                 * @final
                 */
                TYPE: 1,

                /**
                 * Function to execute, int constant
                 * @property FN
                 * @type int
                 * @static
                 * @final
                 */
                FN: 2,

                /**
                 * Function wrapped for scope correction and cleanup, int constant
                 * @property WFN
                 * @type int
                 * @static
                 * @final
                 */
                WFN: 3,

                /**
                 * Object passed in by the user that will be returned as a
                 * parameter to the callback, int constant
                 * @property OBJ
                 * @type int
                 * @static
                 * @final
                 */
                OBJ: 3,

                /**
                 * Adjusted scope, either the element we are registering the event
                 * on or the custom object passed in by the listener, int constant
                 * @property ADJ_SCOPE
                 * @type int
                 * @static
                 * @final
                 */
                ADJ_SCOPE: 4,

                /**
                 * Safari detection is necessary to work around the preventDefault
                 * bug that makes it so you can't cancel a href click from the
                 * handler.  There is not a capabilities check we can use here.
                 * @property isSafari
                 * @private
                 * @static
                 */
                isSafari: (/Safari|Konqueror|KHTML/gi).test(navigator.userAgent),

                /**
                 * IE detection needed to properly calculate pageX and pageY.
                 * capabilities checking didn't seem to work because another
                 * browser that does not provide the properties have the values
                 * calculated in a different manner than IE.
                 * @property isIE
                 * @private
                 * @static
                 */
                isIE: (!this.isSafari && !navigator.userAgent.match(/opera/gi) &&
                        navigator.userAgent.match(/msie/gi)),

                /**
                 * poll handle
                 * @property _interval
                 * @private
                 */
                _interval: null,

                /**
                 * @method startInterval
                 * @static
                 * @private
                 */
                startInterval: function() {
                    if (!this._interval) {
                        var self = this;
                        var callback = function() { self._tryPreloadAttach(); };
                        this._interval = setInterval(callback, this.POLL_INTERVAL);
                        // this.timeout = setTimeout(callback, i);
                    }
                },

                /**
                 * Executes the supplied callback when the item with the supplied
                 * id is found.  This is meant to be used to execute behavior as
                 * soon as possible as the page loads.  If you use this after the
                 * initial page load it will poll for a fixed time for the element.
                 * The number of times it will poll and the frequency are
                 * configurable.  By default it will poll for 10 seconds.
                 *
                 * @method onAvailable
                 *
                 * @param {string}   p_id the id of the element to look for.
                 * @param {function} p_fn what to execute when the element is found.
                 * @param {object}   p_obj an optional object to be passed back as
                 *                   a parameter to p_fn.
                 * @param {boolean}  p_override If set to true, p_fn will execute
                 *                   in the scope of p_obj
                 *
                 * @static
                 */
                onAvailable: function(p_id, p_fn, p_obj, p_override) {
                    onAvailStack.push( { id:         p_id,
                                         fn:         p_fn,
                                         obj:        p_obj,
                                         override:   p_override,
                                         checkReady: false    } );

                    retryCount = this.POLL_RETRYS;
                    this.startInterval();
                },

                /**
                 * Works the same way as onAvailable, but additionally checks the
                 * state of sibling elements to determine if the content of the
                 * available element is safe to modify.
                 *
                 * @method onContentReady
                 *
                 * @param {string}   p_id the id of the element to look for.
                 * @param {function} p_fn what to execute when the element is ready.
                 * @param {object}   p_obj an optional object to be passed back as
                 *                   a parameter to p_fn.
                 * @param {boolean}  p_override If set to true, p_fn will execute
                 *                   in the scope of p_obj
                 *
                 * @static
                 */
                onContentReady: function(p_id, p_fn, p_obj, p_override) {
                    onAvailStack.push( { id:         p_id,
                                         fn:         p_fn,
                                         obj:        p_obj,
                                         override:   p_override,
                                         checkReady: true      } );

                    retryCount = this.POLL_RETRYS;
                    this.startInterval();
                },

                /**
                 * Appends an event handler
                 *
                 * @method addListener
                 *
                 * @param {Object}   el        The html element to assign the
                 *                             event to
                 * @param {String}   sType     The type of event to append
                 * @param {Function} fn        The method the event invokes
                 * @param {Object}   obj    An arbitrary object that will be
                 *                             passed as a parameter to the handler
                 * @param {boolean}  override  If true, the obj passed in becomes
                 *                             the execution scope of the listener
                 * @return {boolean} True if the action was successful or defered,
                 *                        false if one or more of the elements
                 *                        could not have the event bound to it.
                 * @static
                 */
                addListener: function(el, sType, fn, obj, override) {

                    if (!fn || !fn.call) {
                        return false;
                    }

                    // The el argument can be an array of elements or element ids.
                    if ( this._isValidCollection(el)) {
                        var ok = true;
                        for (var i=0,len=el.length; i<len; ++i) {
                            ok = this.on(el[i],
                                           sType,
                                           fn,
                                           obj,
                                           override) && ok;
                        }
                        return ok;

                    } else if (typeof el == "string") {
                        var oEl = this.getEl(el);
                        // If the el argument is a string, we assume it is
                        // actually the id of the element.  If the page is loaded
                        // we convert el to the actual element, otherwise we
                        // defer attaching the event until onload event fires

                        // check to see if we need to delay hooking up the event
                        // until after the page loads.
                        if (oEl) {
                            el = oEl;
                        } else {
                            // defer adding the event until the element is available
                            this.onAvailable(el, function() {
                               YAHOO.util.Event.on(el, sType, fn, obj, override);
                            });

                            return true;
                        }
                    }

                    // Element should be an html element or an array if we get
                    // here.
                    if (!el) {
                        return false;
                    }

                    // we need to make sure we fire registered unload events
                    // prior to automatically unhooking them.  So we hang on to
                    // these instead of attaching them to the window and fire the
                    // handles explicitly during our one unload event.
                    if ("unload" == sType && obj !== this) {
                        unloadListeners[unloadListeners.length] =
                                [el, sType, fn, obj, override];
                        return true;
                    }

                    // if the user chooses to override the scope, we use the custom
                    // object passed in, otherwise the executing scope will be the
                    // HTML element that the event is registered on
                    var scope = el;
                    if (override) {
                        if (override === true) {
                            scope = obj;
                        } else {
                            scope = override;
                        }
                    }

                    // wrap the function so we can return the obj object when
                    // the event fires;
                    var wrappedFn = function(e) {
                            return fn.call(scope, YAHOO.util.Event.getEvent(e),
                                    obj);
                        };

                    var li = [el, sType, fn, wrappedFn, scope];
                    var index = listeners.length;
                    // cache the listener so we can try to automatically unload
                    listeners[index] = li;

                    if (this.useLegacyEvent(el, sType)) {
                        var legacyIndex = this.getLegacyIndex(el, sType);

                        // Add a new dom0 wrapper if one is not detected for this
                        // element
                        if ( legacyIndex == -1 ||
                                    el != legacyEvents[legacyIndex][0] ) {

                            legacyIndex = legacyEvents.length;
                            legacyMap[el.id + sType] = legacyIndex;

                            // cache the signature for the DOM0 event, and
                            // include the existing handler for the event, if any
                            legacyEvents[legacyIndex] =
                                [el, sType, el["on" + sType]];
                            legacyHandlers[legacyIndex] = [];

                            el["on" + sType] =
                                function(e) {
                                    YAHOO.util.Event.fireLegacyEvent(
                                        YAHOO.util.Event.getEvent(e), legacyIndex);
                                };
                        }

                        // add a reference to the wrapped listener to our custom
                        // stack of events
                        //legacyHandlers[legacyIndex].push(index);
                        legacyHandlers[legacyIndex].push(li);

                    } else {
                        this._simpleAdd(el, sType, wrappedFn, false);
                    }

                    return true;

                },

                /**
                 * When using legacy events, the handler is routed to this object
                 * so we can fire our custom listener stack.
                 * @method fireLegacyEvent
                 * @static
                 * @private
                 */
                fireLegacyEvent: function(e, legacyIndex) {
                    var ok = true;

                    var le = legacyHandlers[legacyIndex];
                    for (var i=0,len=le.length; i<len; ++i) {
                        var li = le[i];
                        if ( li && li[this.WFN] ) {
                            var scope = li[this.ADJ_SCOPE];
                            var ret = li[this.WFN].call(scope, e);
                            ok = (ok && ret);
                        }
                    }

                    return ok;
                },

                /**
                 * Returns the legacy event index that matches the supplied
                 * signature
                 * @method getLegacyIndex
                 * @static
                 * @private
                 */
                getLegacyIndex: function(el, sType) {
                    var key = this.generateId(el) + sType;
                    if (typeof legacyMap[key] == "undefined") {
                        return -1;
                    } else {
                        return legacyMap[key];
                    }
                },

                /**
                 * Logic that determines when we should automatically use legacy
                 * events instead of DOM2 events.
                 * @method useLegacyEvent
                 * @static
                 * @private
                 */
                useLegacyEvent: function(el, sType) {
                    if (!el.addEventListener && !el.attachEvent) {
                        return true;
                    } else if (this.isSafari) {
                        if ("click" == sType || "dblclick" == sType) {
                            return true;
                        }
                    }
                    return false;
                },

                /**
                 * Removes an event handler
                 *
                 * @method removeListener
                 *
                 * @param {Object} el the html element or the id of the element to
                 * assign the event to.
                 * @param {String} sType the type of event to remove.
                 * @param {Function} fn the method the event invokes.  If fn is
                 * undefined, then all event handlers for the type of event are
                 * removed.
                 * @return {boolean} true if the unbind was successful, false
                 * otherwise.
                 * @static
                 */
                removeListener: function(el, sType, fn) {
                    var i, len;

                    // The el argument can be a string
                    if (typeof el == "string") {
                        el = this.getEl(el);
                    // The el argument can be an array of elements or element ids.
                    } else if ( this._isValidCollection(el)) {
                        var ok = true;
                        for (i=0,len=el.length; i<len; ++i) {
                            ok = ( this.removeListener(el[i], sType, fn) && ok );
                        }
                        return ok;
                    }

                    if (!fn || !fn.call) {
                        //return false;
                        return this.purgeElement(el, false, sType);
                    }

                    if ("unload" == sType) {

                        for (i=0, len=unloadListeners.length; i<len; i++) {
                            var li = unloadListeners[i];
                            if (li &&
                                li[0] == el &&
                                li[1] == sType &&
                                li[2] == fn) {
                                    unloadListeners.splice(i, 1);
                                    return true;
                            }
                        }

                        return false;
                    }

                    var cacheItem = null;

                    // The index is a hidden parameter; needed to remove it from
                    // the method signature because it was tempting users to
                    // try and take advantage of it, which is not possible.
                    var index = arguments[3];

                    if ("undefined" == typeof index) {
                        index = this._getCacheIndex(el, sType, fn);
                    }

                    if (index >= 0) {
                        cacheItem = listeners[index];
                    }

                    if (!el || !cacheItem) {
                        return false;
                    }

                    if (this.useLegacyEvent(el, sType)) {
                        var legacyIndex = this.getLegacyIndex(el, sType);
                        var llist = legacyHandlers[legacyIndex];
                        if (llist) {
                            for (i=0, len=llist.length; i<len; ++i) {
                                li = llist[i];
                                if (li &&
                                    li[this.EL] == el &&
                                    li[this.TYPE] == sType &&
                                    li[this.FN] == fn) {
                                        llist.splice(i, 1);
                                        break;
                                }
                            }
                        }

                    } else {
                        this._simpleRemove(el, sType, cacheItem[this.WFN], false);
                    }

                    // removed the wrapped handler
                    delete listeners[index][this.WFN];
                    delete listeners[index][this.FN];
                    listeners.splice(index, 1);

                    return true;

                },

                /**
                 * Returns the event's target element
                 * @method getTarget
                 * @param {Event} ev the event
                 * @param {boolean} resolveTextNode when set to true the target's
                 *                  parent will be returned if the target is a
                 *                  text node.  @deprecated, the text node is
                 *                  now resolved automatically
                 * @return {HTMLElement} the event's target
                 * @static
                 */
                getTarget: function(ev, resolveTextNode) {
                    var t = ev.target || ev.srcElement;
                    return this.resolveTextNode(t);
                },

                /**
                 * In some cases, some browsers will return a text node inside
                 * the actual element that was targeted.  This normalizes the
                 * return value for getTarget and getRelatedTarget.
                 * @method resolveTextNode
                 * @param {HTMLElement} node node to resolve
                 * @return {HTMLElement} the normized node
                 * @static
                 */
                resolveTextNode: function(node) {
                    // if (node && node.nodeName &&
                            // "#TEXT" == node.nodeName.toUpperCase()) {
                    if (node && 3 == node.nodeType) {
                        return node.parentNode;
                    } else {
                        return node;
                    }
                },

                /**
                 * Returns the event's pageX
                 * @method getPageX
                 * @param {Event} ev the event
                 * @return {int} the event's pageX
                 * @static
                 */
                getPageX: function(ev) {
                    var x = ev.pageX;
                    if (!x && 0 !== x) {
                        x = ev.clientX || 0;

                        if ( this.isIE ) {
                            x += this._getScrollLeft();
                        }
                    }

                    return x;
                },

                /**
                 * Returns the event's pageY
                 * @method getPageY
                 * @param {Event} ev the event
                 * @return {int} the event's pageY
                 * @static
                 */
                getPageY: function(ev) {
                    var y = ev.pageY;
                    if (!y && 0 !== y) {
                        y = ev.clientY || 0;

                        if ( this.isIE ) {
                            y += this._getScrollTop();
                        }
                    }

                    return y;
                },

                /**
                 * Returns the pageX and pageY properties as an indexed array.
                 * @method getXY
                 * @type int[]
                 * @static
                 */
                getXY: function(ev) {
                    return [this.getPageX(ev), this.getPageY(ev)];
                },

                /**
                 * Returns the event's related target
                 * @method getRelatedTarget
                 * @param {Event} ev the event
                 * @return {HTMLElement} the event's relatedTarget
                 * @static
                 */
                getRelatedTarget: function(ev) {
                    var t = ev.relatedTarget;
                    if (!t) {
                        if (ev.type == "mouseout") {
                            t = ev.toElement;
                        } else if (ev.type == "mouseover") {
                            t = ev.fromElement;
                        }
                    }

                    return this.resolveTextNode(t);
                },

                /**
                 * Returns the time of the event.  If the time is not included, the
                 * event is modified using the current time.
                 * @method getTime
                 * @param {Event} ev the event
                 * @return {Date} the time of the event
                 * @static
                 */
                getTime: function(ev) {
                    if (!ev.time) {
                        var t = new Date().getTime();
                        try {
                            ev.time = t;
                        } catch(e) {
                            return t;
                        }
                    }

                    return ev.time;
                },

                /**
                 * Convenience method for stopPropagation + preventDefault
                 * @method stopEvent
                 * @param {Event} ev the event
                 * @static
                 */
                stopEvent: function(ev) {
                    this.stopPropagation(ev);
                    this.preventDefault(ev);
                },

                /**
                 * Stops event propagation
                 * @method stopPropagation
                 * @param {Event} ev the event
                 * @static
                 */
                stopPropagation: function(ev) {
                    if (ev.stopPropagation) {
                        ev.stopPropagation();
                    } else {
                        ev.cancelBubble = true;
                    }
                },

                /**
                 * Prevents the default behavior of the event
                 * @method preventDefault
                 * @param {Event} ev the event
                 * @static
                 */
                preventDefault: function(ev) {
                    if (ev.preventDefault) {
                        ev.preventDefault();
                    } else {
                        ev.returnValue = false;
                    }
                },

                /**
                 * Finds the event in the window object, the caller's arguments, or
                 * in the arguments of another method in the callstack.  This is
                 * executed automatically for events registered through the event
                 * manager, so the implementer should not normally need to execute
                 * this function at all.
                 * @method getEvent
                 * @param {Event} e the event parameter from the handler
                 * @return {Event} the event
                 * @static
                 */
                getEvent: function(e) {
                    var ev = e || window.event;

                    if (!ev) {
                        var c = this.getEvent.caller;
                        while (c) {
                            ev = c.arguments[0];
                            if (ev && Event == ev.constructor) {
                                break;
                            }
                            c = c.caller;
                        }
                    }

                    return ev;
                },

                /**
                 * Returns the charcode for an event
                 * @method getCharCode
                 * @param {Event} ev the event
                 * @return {int} the event's charCode
                 * @static
                 */
                getCharCode: function(ev) {
                    return ev.charCode || ev.keyCode || 0;
                },

                /**
                 * Locating the saved event handler data by function ref
                 *
                 * @method _getCacheIndex
                 * @static
                 * @private
                 */
                _getCacheIndex: function(el, sType, fn) {
                    for (var i=0,len=listeners.length; i<len; ++i) {
                        var li = listeners[i];
                        if ( li                 &&
                             li[this.FN] == fn  &&
                             li[this.EL] == el  &&
                             li[this.TYPE] == sType ) {
                            return i;
                        }
                    }

                    return -1;
                },

                /**
                 * Generates an unique ID for the element if it does not already
                 * have one.
                 * @method generateId
                 * @param el the element to create the id for
                 * @return {string} the resulting id of the element
                 * @static
                 */
                generateId: function(el) {
                    var id = el.id;

                    if (!id) {
                        id = "yuievtautoid-" + counter;
                        ++counter;
                        el.id = id;
                    }

                    return id;
                },

                /**
                 * We want to be able to use getElementsByTagName as a collection
                 * to attach a group of events to.  Unfortunately, different
                 * browsers return different types of collections.  This function
                 * tests to determine if the object is array-like.  It will also
                 * fail if the object is an array, but is empty.
                 * @method _isValidCollection
                 * @param o the object to test
                 * @return {boolean} true if the object is array-like and populated
                 * @static
                 * @private
                 */
                _isValidCollection: function(o) {
                    // this.logger.debug(o.constructor.toString())
                    // this.logger.debug(typeof o)

                    return ( o                    && // o is something
                             o.length             && // o is indexed
                             typeof o != "string" && // o is not a string
                             !o.tagName           && // o is not an HTML element
                             !o.alert             && // o is not a window
                             typeof o[0] != "undefined" );

                },

                /**
                 * @private
                 * @property elCache
                 * DOM element cache
                 * @static
                 */
                elCache: {},

                /**
                 * We cache elements bound by id because when the unload event
                 * fires, we can no longer use document.getElementById
                 * @method getEl
                 * @static
                 * @private
                 */
                getEl: function(id) {
                    return document.getElementById(id);
                },

                /**
                 * Clears the element cache
                 * @deprecated Elements are not cached any longer
                 * @method clearCache
                 * @static
                 * @private
                 */
                clearCache: function() { },

                /**
                 * hook up any deferred listeners
                 * @method _load
                 * @static
                 * @private
                 */
                _load: function(e) {
                    loadComplete = true;
                    var EU = YAHOO.util.Event;
                    // Remove the listener to assist with the IE memory issue, but not
                    // for other browsers because FF 1.0x does not like it.
                    if (this.isIE) {
                        EU._simpleRemove(window, "load", EU._load);
                    }
                },

                /**
                 * Polling function that runs before the onload event fires,
                 * attempting to attach to DOM Nodes as soon as they are
                 * available
                 * @method _tryPreloadAttach
                 * @static
                 * @private
                 */
                _tryPreloadAttach: function() {

                    if (this.locked) {
                        return false;
                    }

                    this.locked = true;

                    // keep trying until after the page is loaded.  We need to
                    // check the page load state prior to trying to bind the
                    // elements so that we can be certain all elements have been
                    // tested appropriately
                    var tryAgain = !loadComplete;
                    if (!tryAgain) {
                        tryAgain = (retryCount > 0);
                    }

                    // onAvailable
                    var notAvail = [];
                    for (var i=0,len=onAvailStack.length; i<len ; ++i) {
                        var item = onAvailStack[i];
                        if (item) {
                            var el = this.getEl(item.id);

                            if (el) {
                                // The element is available, but not necessarily ready

                                if ( !item.checkReady ||
                                        loadComplete ||
                                        el.nextSibling ||
                                        (document && document.body) ) {

                                    var scope = el;
                                    if (item.override) {
                                        if (item.override === true) {
                                            scope = item.obj;
                                        } else {
                                            scope = item.override;
                                        }
                                    }
                                    item.fn.call(scope, item.obj);
                                    delete onAvailStack[i];
                                }
                            } else {
                                notAvail.push(item);
                            }
                        }
                    }

                    retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;

                    if (tryAgain) {
                        this.startInterval();
                    } else {
                        clearInterval(this._interval);
                        this._interval = null;
                    }

                    this.locked = false;

                    return true;

                },

                /**
                 * Removes all listeners attached to the given element via addListener.
                 * Optionally, the node's children can also be purged.
                 * Optionally, you can specify a specific type of event to remove.
                 * @method purgeElement
                 * @param {HTMLElement} el the element to purge
                 * @param {boolean} recurse recursively purge this element's children
                 * as well.  Use with caution.
                 * @param {string} sType optional type of listener to purge. If
                 * left out, all listeners will be removed
                 * @static
                 */
                purgeElement: function(el, recurse, sType) {
                    var elListeners = this.getListeners(el, sType);
                    if (elListeners) {
                        for (var i=0,len=elListeners.length; i<len ; ++i) {
                            var l = elListeners[i];
                            // can't use the index on the changing collection
                            //this.removeListener(el, l.type, l.fn, l.index);
                            this.removeListener(el, l.type, l.fn);
                        }
                    }

                    if (recurse && el && el.childNodes) {
                        for (i=0,len=el.childNodes.length; i<len ; ++i) {
                            this.purgeElement(el.childNodes[i], recurse, sType);
                        }
                    }
                },

                /**
                 * Returns all listeners attached to the given element via addListener.
                 * Optionally, you can specify a specific type of event to return.
                 * @method getListeners
                 * @param el {HTMLElement} the element to inspect
                 * @param sType {string} optional type of listener to return. If
                 * left out, all listeners will be returned
                 * @return {Object} the listener. Contains the following fields:
                 * &nbsp;&nbsp;type:   (string)   the type of event
                 * &nbsp;&nbsp;fn:     (function) the callback supplied to addListener
                 * &nbsp;&nbsp;obj:    (object)   the custom object supplied to addListener
                 * &nbsp;&nbsp;adjust: (boolean)  whether or not to adjust the default scope
                 * &nbsp;&nbsp;index:  (int)      its position in the Event util listener cache
                 * @static
                 */
                getListeners: function(el, sType) {
                    var elListeners = [];
                    if (listeners && listeners.length > 0) {
                        for (var i=0,len=listeners.length; i<len ; ++i) {
                            var l = listeners[i];
                            if ( l  && l[this.EL] === el &&
                                    (!sType || sType === l[this.TYPE]) ) {
                                elListeners.push({
                                    type:   l[this.TYPE],
                                    fn:     l[this.FN],
                                    obj:    l[this.OBJ],
                                    adjust: l[this.ADJ_SCOPE],
                                    index:  i
                                });
                            }
                        }
                    }

                    return (elListeners.length) ? elListeners : null;
                },

                /**
                 * Removes all listeners registered by pe.event.  Called
                 * automatically during the unload event.
                 * @method _unload
                 * @static
                 * @private
                 */
                _unload: function(e) {

                    var EU = YAHOO.util.Event, i, j, l, len, index;

                    for (i=0,len=unloadListeners.length; i<len; ++i) {
                        l = unloadListeners[i];
                        if (l) {
                            var scope = window;
                            if (l[EU.ADJ_SCOPE]) {
                                if (l[EU.ADJ_SCOPE] === true) {
                                    scope = l[EU.OBJ];
                                } else {
                                    scope = l[EU.ADJ_SCOPE];
                                }
                            }
                            l[EU.FN].call(scope, EU.getEvent(e), l[EU.OBJ] );
                            delete unloadListeners[i];
                            l=null;
                            scope=null;
                        }
                    }

                    if (listeners && listeners.length > 0) {
                        j = listeners.length;
                        while (j) {
                            index = j-1;
                            l = listeners[index];
                            if (l) {
                                EU.removeListener(l[EU.EL], l[EU.TYPE],
                                        l[EU.FN], index);
                            }
                            j = j - 1;
                        }
                        l=null;

                        EU.clearCache();
                    }

                    for (i=0,len=legacyEvents.length; i<len; ++i) {
                        // dereference the element
                        delete legacyEvents[i][0];
                        // delete the array item
                        delete legacyEvents[i];
                    }

                    EU._simpleRemove(window, "unload", EU._unload);

                },

                /**
                 * Returns scrollLeft
                 * @method _getScrollLeft
                 * @static
                 * @private
                 */
                _getScrollLeft: function() {
                    return this._getScroll()[1];
                },

                /**
                 * Returns scrollTop
                 * @method _getScrollTop
                 * @static
                 * @private
                 */
                _getScrollTop: function() {
                    return this._getScroll()[0];
                },

                /**
                 * Returns the scrollTop and scrollLeft.  Used to calculate the
                 * pageX and pageY in Internet Explorer
                 * @method _getScroll
                 * @static
                 * @private
                 */
                _getScroll: function() {
                    var dd = document.documentElement, db = document.body;
                    if (dd && (dd.scrollTop || dd.scrollLeft)) {
                        return [dd.scrollTop, dd.scrollLeft];
                    } else if (db) {
                        return [db.scrollTop, db.scrollLeft];
                    } else {
                        return [0, 0];
                    }
                },

                /**
                 * Adds a DOM event directly without the caching, cleanup, scope adj, etc
                 *
                 * @method _simpleAdd
                 * @param {HTMLElement} el      the element to bind the handler to
                 * @param {string}      sType   the type of event handler
                 * @param {function}    fn      the callback to invoke
                 * @param {boolen}      capture capture or bubble phase
                 * @static
                 * @private
                 */
                _simpleAdd: function () {
                    if (window.addEventListener) {
                        return function(el, sType, fn, capture) {
                            el.addEventListener(sType, fn, (capture));
                        };
                    } else if (window.attachEvent) {
                        return function(el, sType, fn, capture) {
                            el.attachEvent("on" + sType, fn);
                        };
                    } else {
                        return function(){};
                    }
                }(),

                /**
                 * Basic remove listener
                 *
                 * @method _simpleRemove
                 * @param {HTMLElement} el      the element to bind the handler to
                 * @param {string}      sType   the type of event handler
                 * @param {function}    fn      the callback to invoke
                 * @param {boolen}      capture capture or bubble phase
                 * @static
                 * @private
                 */
                _simpleRemove: function() {
                    if (window.removeEventListener) {
                        return function (el, sType, fn, capture) {
                            el.removeEventListener(sType, fn, (capture));
                        };
                    } else if (window.detachEvent) {
                        return function (el, sType, fn) {
                            el.detachEvent("on" + sType, fn);
                        };
                    } else {
                        return function(){};
                    }
                }()
            };

        }();

        (function() {
            var EU = YAHOO.util.Event;

            /**
             * YAHOO.util.Event.on is an alias for addListener
             * @method on
             * @see addListener
             * @static
             */
            EU.on = EU.addListener;

            // YAHOO.mix(EU, YAHOO.util.EventProvider.prototype);
            // EU.createEvent("DOMContentReady");
            // EU.subscribe("DOMContentReady", EU._load);

            if (document && document.body) {
                EU._load();
            } else {
                // EU._simpleAdd(document, "DOMContentLoaded", EU._load);
                EU._simpleAdd(window, "load", EU._load);
            }
            EU._simpleAdd(window, "unload", EU._unload);
            EU._tryPreloadAttach();
        })();
    }

    /**
     * EventProvider is designed to be used with YAHOO.augment to wrap
     * CustomEvents in an interface that allows events to be subscribed to
     * and fired by name.  This makes it possible for implementing code to
     * subscribe to an event that either has not been created yet, or will
     * not be created at all.
     *
     * @class EventProvider
     */
    YAHOO.util.EventProvider = function() { };

    YAHOO.util.EventProvider.prototype = {

        /**
         * Private storage of custom events
         * @property __yui_events
         * @type Object[]
         * @private
         */
        __yui_events: null,

        /**
         * Private storage of custom event subscribers
         * @property __yui_subscribers
         * @type Object[]
         * @private
         */
        __yui_subscribers: null,

        /**
         * Subscribe to a CustomEvent by event type
         *
         * @method subscribe
         * @param p_type     {string}   the type, or name of the event
         * @param p_fn       {function} the function to exectute when the event fires
         * @param p_obj
         * @param p_obj      {Object}   An object to be passed along when the event
         *                              fires
         * @param p_override {boolean}  If true, the obj passed in becomes the
         *                              execution scope of the listener
         */
        subscribe: function(p_type, p_fn, p_obj, p_override) {

            this.__yui_events = this.__yui_events || {};
            var ce = this.__yui_events[p_type];

            if (ce) {
                ce.subscribe(p_fn, p_obj, p_override);
            } else {
                this.__yui_subscribers = this.__yui_subscribers || {};
                var subs = this.__yui_subscribers;
                if (!subs[p_type]) {
                    subs[p_type] = [];
                }
                subs[p_type].push(
                    { fn: p_fn, obj: p_obj, override: p_override } );
            }
        },

        /**
         * Unsubscribes the from the specified event
         * @method unsubscribe
         * @param p_type {string}   The type, or name of the event
         * @param p_fn   {Function} The function to execute
         * @param p_obj  {Object}   The custom object passed to subscribe (optional)
         * @return {boolean} true if the subscriber was found and detached.
         */
        unsubscribe: function(p_type, p_fn, p_obj) {
            this.__yui_events = this.__yui_events || {};
            var ce = this.__yui_events[p_type];
            if (ce) {
                return ce.unsubscribe(p_fn, p_obj);
            } else {
                return false;
            }
        },

        /**
         * Creates a new custom event of the specified type.  If a custom event
         * by that name already exists, it will not be re-created.  In either
         * case the custom event is returned.
         *
         * @method createEvent
         *
         * @param p_type {string} the type, or name of the event
         * @param p_config {object} optional config params.  Valid properties are:
         *
         *  <ul>
         *    <li>
         *      scope: defines the default execution scope.  If not defined
         *      the default scope will be this instance.
         *    </li>
         *    <li>
         *      silent: if true, the custom event will not generate log messages.
         *      This is false by default.
         *    </li>
         *    <li>
         *      onSubscribeCallback: specifies a callback to execute when the
         *      event has a new subscriber.  This will fire immediately for
         *      each queued subscriber if any exist prior to the creation of
         *      the event.
         *    </li>
         *  </ul>
         *
         *  @return {CustomEvent} the custom event
         *
         */
        createEvent: function(p_type, p_config) {

            this.__yui_events = this.__yui_events || {};
            var opts = p_config || {};
            var events = this.__yui_events;

            if (events[p_type]) {
            } else {

                var scope  = opts.scope  || this;
                var silent = opts.silent || null;

                var ce = new YAHOO.util.CustomEvent(p_type, scope, silent,
                        YAHOO.util.CustomEvent.FLAT);
                events[p_type] = ce;

                if (opts.onSubscribeCallback) {
                    ce.subscribeEvent.subscribe(opts.onSubscribeCallback);
                }

                this.__yui_subscribers = this.__yui_subscribers || {};
                var qs = this.__yui_subscribers[p_type];

                if (qs) {
                    for (var i=0; i<qs.length; ++i) {
                        ce.subscribe(qs[i].fn, qs[i].obj, qs[i].override);
                    }
                }
            }

            return events[p_type];
        },

       /**
         * Fire a custom event by name.  The callback functions will be executed
         * from the scope specified when the event was created, and with the
         * following parameters:
         *   <ul>
         *   <li>The first argument fire() was executed with</li>
         *   <li>The custom object (if any) that was passed into the subscribe()
         *       method</li>
         *   </ul>
         * @method fireEvent
         * @param p_type    {string}  the type, or name of the event
         * @param arguments {Object*} an arbitrary set of parameters to pass to
         *                            the handler.
         * @return {boolean} the return value from CustomEvent.fire, or null if
         *                   the custom event does not exist.
         */
        fireEvent: function(p_type, arg1, arg2, etc) {

            this.__yui_events = this.__yui_events || {};
            var ce = this.__yui_events[p_type];

            if (ce) {
                var args = [];
                for (var i=1; i<arguments.length; ++i) {
                    args.push(arguments[i]);
                }
                return ce.fire.apply(ce, args);
            } else {
                return null;
            }
        },

        /**
         * Returns true if the custom event of the provided type has been created
         * with createEvent.
         * @method hasEvent
         * @param type {string} the type, or name of the event
         */
        hasEvent: function(type) {
            if (this.__yui_events) {
                if (this.__yui_events[type]) {
                    return true;
                }
            }
            return false;
        }

    };

yui-ext - Copyright © 2006 Jack Slocum. | Yahoo! UI - Copyright © 2006 Yahoo! Inc.
All rights reserved.