﻿/*
Copyright (c) 2007, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
version: 2.3.1
*/

/**
* 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 debugsystem
* @param {int}     signature the signature that the custom event subscriber
*                  will receive. YAHOO.util.CustomEvent.LIST or 
*                  YAHOO.util.CustomEvent.FLAT.  The default is
*                  YAHOO.util.CustomEvent.LIST.
* @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 debug outpu 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);

    }


    /**
    * In order to make it possible to execute the rest of the subscriber
    * stack when one thows an exception, the subscribers exceptions are
    * caught.  The most recent exception is stored in this property
    * @property lastError
    * @type Error
    */
    this.lastError = null;
};

/**
* 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 (!fn) {
            throw new Error("Invalid callback for subscriber to '" + this.type + "'");
        }

        if (this.subscribeEvent) {
            this.subscribeEvent.fire(fn, obj, override);
        }

        this.subscribers.push(new YAHOO.util.Subscriber(fn, obj, override));
    },

    /**
    * Unsubscribes subscribers.
    * @method unsubscribe
    * @param {Function} fn  The subscribed function to remove, if not supplied
    *                       all will be removed
    * @param {Object}   obj  The custom object passed to subscribe.  This is
    *                        optional, but if supplied will be used to
    *                        disambiguate multiple listeners that are the same
    *                        (e.g., you subscribe many object using a function
    *                        that lives on the prototype)
    * @return {boolean} True if the subscriber was found and detached.
    */
    unsubscribe: function(fn, obj) {

        if (!fn) {
            return this.unsubscribeAll();
        }

        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.
    * @return {boolean} false if one of the subscribers returned false, 
    *                   true otherwise
    */
    fire: function() {
        var len = this.subscribers.length;
        if (!len && this.silent) {
            return true;
        }

        var args = [], ret = true, i, rebuild = false;

        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) {
                rebuild = true;
            } else {
                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];
                    }

                    try {
                        ret = s.fn.call(scope, param, s.obj);
                    } catch (e) {
                        this.lastError = e;
                    }
                } else {
                    try {
                        ret = s.fn.call(scope, this.type, args, s.obj);
                    } catch (e) {
                        this.lastError = e;
                    }
                }
                if (false === ret) {
                    if (!this.silent) {
                    }

                    //break;
                    return false;
                }
            }
        }

        if (rebuild) {
            var newlist = [], subs = this.subscribers;
            for (i = 0, len = subs.length; i < len; i = i + 1) {
                newlist.push(subs[i]);
            }

            this.subscribers = newlist;
        }

        return true;
    },

    /**
    * Removes all listeners
    * @method unsubscribeAll
    * @return {int} The number of listeners unsubscribed
    */
    unsubscribeAll: function() {
        for (var i = 0, len = this.subscribers.length; i < len; ++i) {
            this._delete(len - 1 - i);
        }

        this.subscribers = [];

        return i;
    },

    /**
    * @method _delete
    * @private
    */
    _delete: function(index) {
        var s = this.subscribers[index];
        if (s) {
            delete s.fn;
            delete s.obj;
        }

        this.subscribers[index] = null;
    },

    /**
    * @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 = YAHOO.lang.isUndefined(obj) ? null : obj;

    /**
    * 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.
// @TODO this needs to be changed so that only the state data that needs to
// be preserved is kept, while methods are overwritten/added as needed.
// This means that the module pattern can't be used.
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;

        /**
        * True when the document is initially usable
        * @property DOMReady
        * @type boolean
        * @static
        * @private
        */
        var DOMReady = 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;

        /**
        * Normalized keycodes for webkit/safari
        * @property webkitKeymap
        * @type {int: int}
        * @private
        * @static
        * @final
        */
        var webkitKeymap = {
            63232: 38, // up
            63233: 40, // down
            63234: 37, // left
            63235: 39  // right
        };

        return {

            /**
            * 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 4000@amp;10 ms, so it will poll
            * for 40 seconds or until all outstanding handlers are bound
            * (whichever comes first).
            * @property POLL_RETRYS
            * @type int
            * @static
            * @final
            */
            POLL_RETRYS: 4000,

            /**
            * The poll interval in milliseconds
            * @property POLL_INTERVAL
            * @type int
            * @static
            * @final
            */
            POLL_INTERVAL: 10,

            /**
            * 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.  Specific to
            * unload listeners
            * @property OBJ
            * @type int
            * @static
            * @final
            */
            UNLOAD_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,

            /**
            * The original obj passed into addListener
            * @property OBJ
            * @type int
            * @static
            * @final
            */
            OBJ: 5,

            /**
            * The original scope parameter passed into addListener
            * @property OVERRIDE
            * @type int
            * @static
            * @final
            */
            OVERRIDE: 6,

            /**
            * addListener/removeListener can throw errors in unexpected scenarios.
            * These errors are suppressed, the method returns false, and this property
            * is set
            * @property lastError
            * @static
            * @type Error
            */
            lastError: null,

            /**
            * Safari detection
            * @property isSafari
            * @private
            * @static
            * @deprecated use YAHOO.env.ua.webkit
            */
            isSafari: YAHOO.env.ua.webkit,

            /**
            * webkit version
            * @property webkit
            * @type string
            * @private
            * @static
            * @deprecated use YAHOO.env.ua.webkit
            */
            webkit: YAHOO.env.ua.webkit,

            /**
            * IE detection 
            * @property isIE
            * @private
            * @static
            * @deprecated use YAHOO.env.ua.ie
            */
            isIE: YAHOO.env.ua.ie,

            /**
            * poll handle
            * @property _interval
            * @static
            * @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);
                }
            },

            /**
            * 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.
            *
            * <p>The callback is executed with a single parameter:
            * the custom object parameter, if provided.</p>
            *
            * @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|object}  p_override If set to true, p_fn will execute
            *                   in the scope of p_obj, if set to an object it
            *                   will execute in the scope of that object
            *
            * @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();
            },

            /**
            * Executes the supplied callback when the DOM is first usable.  This
            * will execute immediately if called after the DOMReady event has
            * fired.   @todo the DOMContentReady event does not fire when the
            * script is dynamically injected into the page.  This means the
            * DOMReady custom event will never fire in FireFox or Opera when the
            * library is injected.  It _will_ fire in Safari, and the IE 
            * implementation would allow for us to fire it if the defered script
            * is not available.  We want this to behave the same in all browsers.
            * Is there a way to identify when the script has been injected 
            * instead of included inline?  Is there a way to know whether the 
            * window onload event has fired without having had a listener attached 
            * to it when it did so?
            *
            * <p>The callback is a CustomEvent, so the signature is:</p>
            * <p>type <string>, args <array>, customobject <object></p>
            * <p>For DOMReady events, there are no fire argments, so the
            * signature is:</p>
            * <p>"DOMReady", [], obj</p>
            *
            *
            * @method onDOMReady
            *
            * @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|object}  p_scope If set to true, p_fn will execute
            *                   in the scope of p_obj, if set to an object it
            *                   will execute in the scope of that object
            *
            * @static
            */
            onDOMReady: function(p_fn, p_obj, p_override) {
                if (DOMReady) {
                    setTimeout(function() {
                        var s = window;
                        if (p_override) {
                            if (p_override === true) {
                                s = p_obj;
                            } else {
                                s = p_override;
                            }
                        }
                        p_fn.call(s, "DOMReady", [], p_obj);
                    }, 0);
                } else {
                    this.DOMReadyEvent.subscribe(p_fn, p_obj, p_override);
                }
            },

            /**
            * 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.
            *
            * <p>The callback is executed with a single parameter:
            * the custom object parameter, if provided.</p>
            *
            * @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|object}  p_override If set to true, p_fn will execute
            *                   in the scope of p_obj.  If an object, p_fn will
            *                   exectute in the scope of that object
            *
            * @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 {String|HTMLElement|Array|NodeList} el An id, an element 
            *  reference, or a collection of ids and/or elements to assign the 
            *  listener 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|object}  override  If true, the obj passed in becomes
            *                             the execution scope of the listener. If an
            *                             object, this object becomes the execution
            *                             scope.
            * @return {Boolean} True if the action was successful or defered,
            *                        false if one or more of the elements 
            *                        could not have the listener attached,
            *                        or if the operation throws an exception.
            * @static
            */
            addListener: function(el, sType, fn, obj, override) {

                if (!fn || !fn.call) {
                    // throw new TypeError(sType + " addListener call failed, callback undefined");
                    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 (YAHOO.lang.isString(el)) {
                    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, el),
                                obj);
                };

                var li = [el, sType, fn, wrappedFn, scope, obj, override];
                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 {
                    try {
                        this._simpleAdd(el, sType, wrappedFn, false);
                    } catch (ex) {
                        // handle an error trying to attach an event.  If it fails
                        // we need to clean up the cache
                        this.lastError = ex;
                        this.removeListener(el, sType, fn);
                        return 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, le, lh, li, scope, ret;

                lh = legacyHandlers[legacyIndex];
                for (var i = 0, len = lh.length; i < len; ++i) {
                    li = lh[i];
                    if (li && li[this.WFN]) {
                        scope = li[this.ADJ_SCOPE];
                        ret = li[this.WFN].call(scope, e);
                        ok = (ok && ret);
                    }
                }

                // Fire the original handler if we replaced one.  We fire this
                // after the other events to keep stopPropagation/preventDefault
                // that happened in the DOM0 handler from touching our DOM2
                // substitute
                le = legacyEvents[legacyIndex];
                if (le && le[2]) {
                    le[2](e);
                }

                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.  Currently this is limited to old
            * Safari browsers with a broken preventDefault
            * @method useLegacyEvent
            * @static
            * @private
            */
            useLegacyEvent: function(el, sType) {
                if (this.webkit && ("click" == sType || "dblclick" == sType)) {
                    var v = parseInt(this.webkit, 10);
                    if (!isNaN(v) && v < 418) {
                        return true;
                    }
                }
                return false;
            },

            /**
            * Removes an event listener
            *
            * @method removeListener
            *
            * @param {String|HTMLElement|Array|NodeList} el An id, an element 
            *  reference, or a collection of ids and/or elements to remove
            *  the listener from.
            * @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, li;

                // 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++) {
                        li = unloadListeners[i];
                        if (li &&
                            li[0] == el &&
                            li[1] == sType &&
                            li[2] == fn) {
                            //unloadListeners.splice(i, 1);
                            unloadListeners[i] = null;
                            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);
                                llist[i] = null;
                                break;
                            }
                        }
                    }

                } else {
                    try {
                        this._simpleRemove(el, sType, cacheItem[this.WFN], false);
                    } catch (ex) {
                        this.lastError = ex;
                        return false;
                    }
                }

                // removed the wrapped handler
                delete listeners[index][this.WFN];
                delete listeners[index][this.FN];
                //listeners.splice(index, 1);
                listeners[index] = null;

                return true;

            },

            /**
            * Returns the event's target element.  Safari sometimes provides
            * a text node, and this is automatically resolved to the text
            * node's parent so that it behaves like other browsers.
            * @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 && 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
            * @param {Event} ev the event
            * @return {[x, y]} the pageX and pageY properties of the event
            * @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 (ex) {
                        this.lastError = ex;
                        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
            * @param {HTMLElement} boundEl the element the listener is attached to
            * @return {Event} the event 
            * @static
            */
            getEvent: function(e, boundEl) {
                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;
                    }
                }

                // IE events that target non-browser objects (e.g., VML
                // canvas) will sometimes throw errors when you try to
                // inspect the properties of the event target.  We try to
                // detect this condition, and provide a dummy target (the bound
                // element) to eliminate spurious errors.  
                if (ev && this.isIE) {

                    try {

                        var el = ev.srcElement;
                        if (el) {
                            var type = el.type;
                        }

                    } catch (ex) {


                        ev.target = boundEl;
                    }

                }

                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) {
                var code = ev.keyCode || ev.charCode || 0;

                // webkit normalization
                if (YAHOO.env.ua.webkit && (code in webkitKeymap)) {
                    code = webkitKeymap[code];
                }
                return code;
            },

            /**
            * 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) {
                try {
                    return (typeof o !== "string" && // o is not a string
                             o.length && // o is indexed
                             !o.tagName && // o is not an HTML element
                             !o.alert && // o is not a window
                             typeof o[0] !== "undefined");
                } catch (e) {
                    return false;
                }

            },

            /**
            * @private
            * @property elCache
            * DOM element cache
            * @static
            * @deprecated Elements are not cached due to issues that arise when
            * elements are removed and re-added
            */
            elCache: {},

            /**
            * We cache elements bound by id because when the unload event 
            * fires, we can no longer use document.getElementById
            * @method getEl
            * @static
            * @private
            * @deprecated Elements are not cached any longer
            */
            getEl: function(id) {
                return (typeof id === "string") ? document.getElementById(id) : id;
            },

            /**
            * Clears the element cache
            * @deprecated Elements are not cached any longer
            * @method clearCache
            * @static
            * @private
            */
            clearCache: function() { },

            /**
            * Custom event the fires when the dom is initially usable
            * @event DOMReadyEvent
            */
            DOMReadyEvent: new YAHOO.util.CustomEvent("DOMReady", this),

            /**
            * hook up any deferred listeners
            * @method _load
            * @static
            * @private
            */
            _load: function(e) {

                if (!loadComplete) {
                    loadComplete = true;
                    var EU = YAHOO.util.Event;

                    // Just in case DOMReady did not go off for some reason
                    EU._ready();

                    // Available elements may not have been detected before the
                    // window load event fires. Try to find them now so that the
                    // the user is more likely to get the onAvailable notifications
                    // before the window load notification
                    EU._tryPreloadAttach();

                    // 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);
                    //}
                }
            },

            /**
            * Fires the DOMReady event listeners the first time the document is
            * usable.
            * @method _ready
            * @static
            * @private
            */
            _ready: function(e) {
                if (!DOMReady) {
                    DOMReady = true;
                    var EU = YAHOO.util.Event;

                    // Fire the content ready custom event
                    EU.DOMReadyEvent.fire();

                    // Remove the DOMContentLoaded (FF/Opera)
                    EU._simpleRemove(document, "DOMContentLoaded", EU._ready);
                }
            },

            /**
            * 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;
                }

                if (this.isIE) {
                    // Hold off if DOMReady has not fired and check current
                    // readyState to protect against the IE operation aborted
                    // issue.
                    //if (!DOMReady || "complete" !== document.readyState) {
                    if (!DOMReady) {
                        this.startInterval();
                        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 = [];

                var executeItem = function(el, item) {
                    var scope = el;
                    if (item.override) {
                        if (item.override === true) {
                            scope = item.obj;
                        } else {
                            scope = item.override;
                        }
                    }
                    item.fn.call(scope, item.obj);
                };

                var i, len, item, el;

                // onAvailable
                for (i = 0, len = onAvailStack.length; i < len; ++i) {
                    item = onAvailStack[i];
                    if (item && !item.checkReady) {
                        el = this.getEl(item.id);
                        if (el) {
                            executeItem(el, item);
                            onAvailStack[i] = null;
                        } else {
                            notAvail.push(item);
                        }
                    }
                }

                // onContentReady
                for (i = 0, len = onAvailStack.length; i < len; ++i) {
                    item = onAvailStack[i];
                    if (item && item.checkReady) {
                        el = this.getEl(item.id);

                        if (el) {
                            // The element is available, but not necessarily ready
                            // @todo should we test parentNode.nextSibling?
                            if (loadComplete || el.nextSibling) {
                                executeItem(el, item);
                                onAvailStack[i] = null;
                            }
                        } else {
                            notAvail.push(item);
                        }
                    }
                }

                retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;

                if (tryAgain) {
                    // we may need to strip the nulled out items here
                    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), i, len;
                if (elListeners) {
                    for (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|object)  whether or not to adjust the default scope
            * &nbsp;&nbsp;scope: (boolean)  the derived scope based on the adjust parameter
            * &nbsp;&nbsp;index:  (int)      its position in the Event util listener cache
            * @static
            */
            getListeners: function(el, sType) {
                var results = [], searchLists;
                if (!sType) {
                    searchLists = [listeners, unloadListeners];
                } else if (sType == "unload") {
                    searchLists = [unloadListeners];
                } else {
                    searchLists = [listeners];
                }

                for (var j = 0; j < searchLists.length; j = j + 1) {
                    var searchList = searchLists[j];
                    if (searchList && searchList.length > 0) {
                        for (var i = 0, len = searchList.length; i < len; ++i) {
                            var l = searchList[i];
                            if (l && l[this.EL] === el &&
                                    (!sType || sType === l[this.TYPE])) {
                                results.push({
                                    type: l[this.TYPE],
                                    fn: l[this.FN],
                                    obj: l[this.OBJ],
                                    adjust: l[this.OVERRIDE],
                                    scope: l[this.ADJ_SCOPE],
                                    index: i
                                });
                            }
                        }
                    }
                }

                return (results.length) ? results : 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.UNLOAD_OBJ];
                            } else {
                                scope = l[EU.ADJ_SCOPE];
                            }
                        }
                        l[EU.FN].call(scope, EU.getEvent(e, l[EU.EL]), l[EU.UNLOAD_OBJ]);
                        unloadListeners[i] = null;
                        l = null;
                        scope = null;
                    }
                }

                unloadListeners = 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];
                    legacyEvents[i][0] = null;

                    // delete the array item
                    //delete legacyEvents[i];
                    legacyEvents[i] = null;
                }

                legacyEvents = null;

                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];
                }
            },

            /**
            * Used by old versions of CustomEvent, restored for backwards
            * compatibility
            * @method regCE
            * @private
            * @static
            * @deprecated still here for backwards compatibility
            */
            regCE: function() {
                // does nothing
            },

            /**
            * 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;

        /////////////////////////////////////////////////////////////
        // DOMReady
        // based on work by: Dean Edwards/John Resig/Matthias Miller 

        // Internet Explorer: use the readyState of a defered script.
        // This isolates what appears to be a safe moment to manipulate
        // the DOM prior to when the document's readyState suggests
        // it is safe to do so.
        if (EU.isIE) {

            // Process onAvailable/onContentReady items when when the 
            // DOM is ready.
            YAHOO.util.Event.onDOMReady(
                    YAHOO.util.Event._tryPreloadAttach,
                    YAHOO.util.Event, true);


            var el, d = document, b = d.body;

            // If the library is being injected after window.onload, it
            // is not safe to document.write the script tag.  Detecting
            // this state doesn't appear possible, so we expect a flag
            // in YAHOO_config to be set if the library is being injected.
            if (("undefined" !== typeof YAHOO_config) && YAHOO_config.injecting) {

                el = document.createElement("script");
                var p = d.getElementsByTagName("head")[0] || b;
                p.insertBefore(el, p.firstChild);

            } else {
                d.write('<scr' + 'ipt id="_yui_eu_dr" defer="true" src="//:"><' + '/script>');
                el = document.getElementById("_yui_eu_dr");
            }


            if (el) {
                el.onreadystatechange = function() {
                    if ("complete" === this.readyState) {
                        this.parentNode.removeChild(this);
                        YAHOO.util.Event._ready();
                    }
                };
            } else {
                // The library was likely injected into the page
                // rendering onDOMReady unreliable
                // YAHOO.util.Event._ready();
            }

            el = null;


            // Safari: The document's readyState in Safari currently will
            // change to loaded/complete before images are loaded.
            //} else if (EU.webkit) {
        } else if (EU.webkit) {

            EU._drwatch = setInterval(function() {
                var rs = document.readyState;
                if ("loaded" == rs || "complete" == rs) {
                    clearInterval(EU._drwatch);
                    EU._drwatch = null;
                    EU._ready();
                }
            }, EU.POLL_INTERVAL);

            // FireFox and Opera: These browsers provide a event for this
            // moment.
        } else {

            // @todo will this fire when the library is injected?

            EU._simpleAdd(document, "DOMContentLoaded", EU._ready);

        }
        /////////////////////////////////////////////////////////////


        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      {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 one or more listeners the from the specified event
    * @method unsubscribe
    * @param p_type {string}   The type, or name of the event.  If the type
    *                          is not specified, it will attempt to remove
    *                          the listener from all hosted events.
    * @param p_fn   {Function} The subscribed function to unsubscribe, if not
    *                          supplied, all subscribers will be removed.
    * @param p_obj  {Object}   The custom object passed to subscribe.  This is
    *                        optional, but if supplied will be used to
    *                        disambiguate multiple listeners that are the same
    *                        (e.g., you subscribe many object using a function
    *                        that lives on the prototype)
    * @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 evts = this.__yui_events;
        if (p_type) {
            var ce = evts[p_type];
            if (ce) {
                return ce.unsubscribe(p_fn, p_obj);
            }
        } else {
            var ret = true;
            for (var i in evts) {
                if (YAHOO.lang.hasOwnProperty(evts, i)) {
                    ret = ret && evts[i].unsubscribe(p_fn, p_obj);
                }
            }
            return ret;
        }

        return false;
    },

    /**
    * Removes all listeners from the specified event.  If the event type
    * is not specified, all listeners from all hosted custom events will
    * be removed.
    * @method unsubscribeAll
    * @param p_type {string}   The type, or name of the event
    */
    unsubscribeAll: function(p_type) {
        return this.unsubscribe(p_type);
    },

    /**
    * 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);

            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>
    * If the custom event has not been explicitly created, it will be
    * created now with the default config, scoped to the host object
    * @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
    *                   
    */
    fireEvent: function(p_type, arg1, arg2, etc) {

        this.__yui_events = this.__yui_events || {};
        var ce = this.__yui_events[p_type];

        if (!ce) {
            return null;
        }

        var args = [];
        for (var i = 1; i < arguments.length; ++i) {
            args.push(arguments[i]);
        }
        return ce.fire.apply(ce, args);
    },

    /**
    * 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;
    }

};

/**
* KeyListener is a utility that provides an easy interface for listening for
* keydown/keyup events fired against DOM elements.
* @namespace YAHOO.util
* @class KeyListener
* @constructor
* @param {HTMLElement} attachTo The element or element ID to which the key 
*                               event should be attached
* @param {String}      attachTo The element or element ID to which the key
*                               event should be attached
* @param {Object}      keyData  The object literal representing the key(s) 
*                               to detect. Possible attributes are 
*                               shift(boolean), alt(boolean), ctrl(boolean) 
*                               and keys(either an int or an array of ints 
*                               representing keycodes).
* @param {Function}    handler  The CustomEvent handler to fire when the 
*                               key event is detected
* @param {Object}      handler  An object literal representing the handler. 
* @param {String}      event    Optional. The event (keydown or keyup) to 
*                               listen for. Defaults automatically to keydown.
*
* @knownissue the "keypress" event is completely broken in Safari 2.x and below.
*             the workaround is use "keydown" for key listening.  However, if
*             it is desired to prevent the default behavior of the keystroke,
*             that can only be done on the keypress event.  This makes key
*             handling quite ugly.
* @knownissue keydown is also broken in Safari 2.x and below for the ESC key.
*             There currently is no workaround other than choosing another
*             key to listen for.
*/
YAHOO.util.KeyListener = function(attachTo, keyData, handler, event) {
    if (!attachTo) {
    } else if (!keyData) {
    } else if (!handler) {
    }

    if (!event) {
        event = YAHOO.util.KeyListener.KEYDOWN;
    }

    /**
    * The CustomEvent fired internally when a key is pressed
    * @event keyEvent
    * @private
    * @param {Object} keyData The object literal representing the key(s) to 
    *                         detect. Possible attributes are shift(boolean), 
    *                         alt(boolean), ctrl(boolean) and keys(either an 
    *                         int or an array of ints representing keycodes).
    */
    var keyEvent = new YAHOO.util.CustomEvent("keyPressed");

    /**
    * The CustomEvent fired when the KeyListener is enabled via the enable() 
    * function
    * @event enabledEvent
    * @param {Object} keyData The object literal representing the key(s) to 
    *                         detect. Possible attributes are shift(boolean), 
    *                         alt(boolean), ctrl(boolean) and keys(either an 
    *                         int or an array of ints representing keycodes).
    */
    this.enabledEvent = new YAHOO.util.CustomEvent("enabled");

    /**
    * The CustomEvent fired when the KeyListener is disabled via the 
    * disable() function
    * @event disabledEvent
    * @param {Object} keyData The object literal representing the key(s) to 
    *                         detect. Possible attributes are shift(boolean), 
    *                         alt(boolean), ctrl(boolean) and keys(either an 
    *                         int or an array of ints representing keycodes).
    */
    this.disabledEvent = new YAHOO.util.CustomEvent("disabled");

    if (typeof attachTo == 'string') {
        attachTo = document.getElementById(attachTo);
    }

    if (typeof handler == 'function') {
        keyEvent.subscribe(handler);
    } else {
        keyEvent.subscribe(handler.fn, handler.scope, handler.correctScope);
    }

    /**
    * Handles the key event when a key is pressed.
    * @method handleKeyPress
    * @param {DOMEvent} e   The keypress DOM event
    * @param {Object}   obj The DOM event scope object
    * @private
    */
    function handleKeyPress(e, obj) {
        if (!keyData.shift) {
            keyData.shift = false;
        }
        if (!keyData.alt) {
            keyData.alt = false;
        }
        if (!keyData.ctrl) {
            keyData.ctrl = false;
        }

        // check held down modifying keys first
        if (e.shiftKey == keyData.shift &&
            e.altKey == keyData.alt &&
            e.ctrlKey == keyData.ctrl) { // if we pass this, all modifiers match

            var dataItem;
            var keyPressed;

            if (keyData.keys instanceof Array) {
                for (var i = 0; i < keyData.keys.length; i++) {
                    dataItem = keyData.keys[i];

                    if (dataItem == e.charCode) {
                        keyEvent.fire(e.charCode, e);
                        break;
                    } else if (dataItem == e.keyCode) {
                        keyEvent.fire(e.keyCode, e);
                        break;
                    }
                }
            } else {
                dataItem = keyData.keys;
                if (dataItem == e.charCode) {
                    keyEvent.fire(e.charCode, e);
                } else if (dataItem == e.keyCode) {
                    keyEvent.fire(e.keyCode, e);
                }
            }
        }
    }

    /**
    * Enables the KeyListener by attaching the DOM event listeners to the 
    * target DOM element
    * @method enable
    */
    this.enable = function() {
        if (!this.enabled) {
            YAHOO.util.Event.addListener(attachTo, event, handleKeyPress);
            this.enabledEvent.fire(keyData);
        }
        /**
        * Boolean indicating the enabled/disabled state of the Tooltip
        * @property enabled
        * @type Boolean
        */
        this.enabled = true;
    };

    /**
    * Disables the KeyListener by removing the DOM event listeners from the 
    * target DOM element
    * @method disable
    */
    this.disable = function() {
        if (this.enabled) {
            YAHOO.util.Event.removeListener(attachTo, event, handleKeyPress);
            this.disabledEvent.fire(keyData);
        }
        this.enabled = false;
    };

    /**
    * Returns a String representation of the object.
    * @method toString
    * @return {String}  The string representation of the KeyListener
    */
    this.toString = function() {
        return "KeyListener [" + keyData.keys + "] " + attachTo.tagName +
                (attachTo.id ? "[" + attachTo.id + "]" : "");
    };

};

/**
* Constant representing the DOM "keydown" event.
* @property YAHOO.util.KeyListener.KEYDOWN
* @static
* @final
* @type String
*/
YAHOO.util.KeyListener.KEYDOWN = "keydown";

/**
* Constant representing the DOM "keyup" event.
* @property YAHOO.util.KeyListener.KEYUP
* @static
* @final
* @type String
*/
YAHOO.util.KeyListener.KEYUP = "keyup";
YAHOO.register("event", YAHOO.util.Event, { version: "2.3.1", build: "541" });
