/*

Copyright (c) 2008, Yahoo! Inc. All rights reserved.

Code licensed under the BSD License:

http://developer.yahoo.net/yui/license.txt

version: 2.5.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=[].slice.call(arguments, 0), ret=true, i, rebuild=false;



        if (!this.silent) {

        }



        // make a copy of the subscribers so that there are

        // no index problems if one subscriber removes another.

        var subs = this.subscribers.slice();



        for (i=0; i<len; ++i) {

            var s = subs[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(ex) {

                        this.lastError = ex;

                    }

                }

                if (false === ret) {

                    if (!this.silent) {

                    }



                    //break;

                    return false;

                }

            }

        }



        

        // if (rebuild) {

        //     var newlist=this.,subs=this.subscribers;

        //     for (i=0,len=subs.length; i<len; i=i+1) {

        //         // this wasn't doing anything before

        //         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=this.subscribers.length-1; i>-1; i--) {

            this._delete(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;

        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 = 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;



        /**

         * 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

            63276: 33, // page up

            63277: 34, // page down

            25: 9      // SHIFT-TAB (Safari provides a different key code in

                       // this case, even though the shiftKey modifier is set)

        };



        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 2000@amp;20 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: 2000,



            /**

             * 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.  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,



            /**

             * document readystate poll handle

             * @property _dri

             * @static

             * @private

             */

             _dri: null,



            /**

             * True when the document is initially usable

             * @property DOMReady

             * @type boolean

             * @static

             */

            DOMReady: false,



            /**

             * @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||string[]}   p_id the id of the element, or an array

             * of ids 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

             * @param checkContent {boolean} check child node readiness (onContentReady)

             * @static

             */

            onAvailable: function(p_id, p_fn, p_obj, p_override, checkContent) {



                var a = (YAHOO.lang.isString(p_id)) ? [p_id] : p_id;



                for (var i=0; i<a.length; i=i+1) {

                    onAvailStack.push({id:         a[i], 

                                       fn:         p_fn, 

                                       obj:        p_obj, 

                                       override:   p_override, 

                                       checkReady: checkContent });

                }



                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.

             *

             * <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) {

                this.onAvailable(p_id, p_fn, p_obj, p_override, true);

            },



            /**

             * 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 &lt;string&gt;, args &lt;array&gt;, customobject &lt;object&gt;</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 (this.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);

                }

            },



            /**

             * 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) {

                    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].slice();

                for (var i=0, len=lh.length; i<len; ++i) {

                // for (var i in lh.length) {

                    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=el.length-1; i>-1; 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=unloadListeners.length-1; i>-1; 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) {

                        // for (i in llist.length) {

                            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(n) {

                try {

                    if (n && 3 == n.nodeType) {

                        return n.parentNode;

                    }

                } catch(e) { }



                return n;

            },



            /**

             * 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;

                    }

                }



                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 key 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, l=listeners.length; i<l; i=i+1) {

                    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 ( o                     && // o is something

                             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(ex) {

                    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();



                }

            },



            /**

             * Fires the DOMReady event listeners the first time the document is

             * usable.

             * @method _ready

             * @static

             * @private

             */

            _ready: function(e) {

                var EU = YAHOO.util.Event;

                if (!EU.DOMReady) {

                    EU.DOMReady=true;



                    // 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 (onAvailStack.length === 0) {

                    retryCount = 0;

                    clearInterval(this._interval);

                    this._interval = null;

                    return;

                }



                if (this.locked) {

                    return;

                }



                if (this.isIE) {

                    // Hold off if DOMReady has not fired and check current

                    // readyState to protect against the IE operation aborted

                    // issue.

                    if (!this.DOMReady) {

                        this.startInterval();

                        return;

                    }

                }



                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 && onAvailStack.length > 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, ready=[];



                // onAvailable onContentReady

                for (i=0, len=onAvailStack.length; i<len; i=i+1) {

                    item = onAvailStack[i];

                    if (item) {

                        el = this.getEl(item.id);

                        if (el) {

                            if (item.checkReady) {

                                if (loadComplete || el.nextSibling || !tryAgain) {

                                    ready.push(item);

                                    onAvailStack[i] = null;

                                }

                            } else {

                                executeItem(el, item);

                                onAvailStack[i] = null;

                            }

                        } else {

                            notAvail.push(item);

                        }

                    }

                }

                

                // make sure onContentReady fires after onAvailable

                for (i=0, len=ready.length; i<len; i=i+1) {

                    item = ready[i];

                    executeItem(this.getEl(item.id), item);

                }





                retryCount--;



                if (tryAgain) {

                    for (i=onAvailStack.length-1; i>-1; i--) {

                        item = onAvailStack[i];

                        if (!item || !item.id) {

                            onAvailStack.splice(i, 1);

                        }

                    }



                    this.startInterval();

                } else {

                    clearInterval(this._interval);

                    this._interval = null;

                }



                this.locked = false;



            },



            /**

             * 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 oEl = (YAHOO.lang.isString(el)) ? this.getEl(el) : el;

                var elListeners = this.getListeners(oEl, sType), i, len;

                if (elListeners) {

                    for (i=elListeners.length-1; i>-1; i--) {

                        var l = elListeners[i];

                        this.removeListener(oEl, l.type, l.fn);

                    }

                }



                if (recurse && oEl && oEl.childNodes) {

                    for (i=0,len=oEl.childNodes.length; i<len ; ++i) {

                        this.purgeElement(oEl.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|string} the element or element id 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];

                }



                var oEl = (YAHOO.lang.isString(el)) ? this.getEl(el) : el;



                for (var j=0;j<searchLists.length; j=j+1) {

                    var searchList = searchLists[j];

                    if (searchList) {

                        for (var i=0,len=searchList.length; i<len ; ++i) {

                            var l = searchList[i];

                            if ( l  && l[this.EL] === oEl && 

                                    (!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,

                         ul = unloadListeners.slice();



                // execute and clear stored unload listeners

                for (i=0,len=unloadListeners.length; i<len; ++i) {

                    l = ul[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] );

                        ul[i] = null;

                        l=null;

                        scope=null;

                    }

                }



                unloadListeners = null;



                // Remove listeners to handle IE memory leaks

                //if (YAHOO.env.ua.ie && listeners && listeners.length > 0) {

                

                // 2.5.0 listeners are removed for all browsers again.  FireFox preserves

                // at least some listeners between page refreshes, potentially causing

                // errors during page load (mouseover listeners firing before they

                // should if the user moves the mouse at the correct moment).

                if (listeners) {

                    for (j=listeners.length-1; j>-1; j--) {

                        l = listeners[j];

                        if (l) {

                            EU.removeListener(l[EU.EL], l[EU.TYPE], l[EU.FN], j);

                        } 

                    }

                    l=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 the 

            // DOM is ready.

            YAHOO.util.Event.onDOMReady(

                    YAHOO.util.Event._tryPreloadAttach,

                    YAHOO.util.Event, true);

            

            var n = document.createElement('p');  



            EU._dri = setInterval(function() {

                try {

                    // throws an error if doc is not ready

                    n.doScroll('left');

                    clearInterval(EU._dri);

                    EU._dri = null;

                    EU._ready();

                    n = null;

                } catch (ex) { 

                }

            }, EU.POLL_INTERVAL); 



        

        // The document's readyState in Safari currently will

        // change to loaded/complete before images are loaded.

        } else if (EU.webkit && EU.webkit < 525) {



            EU._dri = setInterval(function() {

                var rs=document.readyState;

                if ("loaded" == rs || "complete" == rs) {

                    clearInterval(EU._dri);

                    EU._dri = null;

                    EU._ready();

                }

            }, EU.POLL_INTERVAL); 



        // FireFox and Opera: These browsers provide a event for this

        // moment.  The latest WebKit releases now support this event.

        } else {



            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;



            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";



/**

 * keycode constants for a subset of the special keys

 * @property KEY

 * @static

 * @final

 */

YAHOO.util.KeyListener.KEY = {

    ALT          : 18,

    BACK_SPACE   : 8,

    CAPS_LOCK    : 20,

    CONTROL      : 17,

    DELETE       : 46,

    DOWN         : 40,

    END          : 35,

    ENTER        : 13,

    ESCAPE       : 27,

    HOME         : 36,

    LEFT         : 37,

    META         : 224,

    NUM_LOCK     : 144,

    PAGE_DOWN    : 34,

    PAGE_UP      : 33, 

    PAUSE        : 19,

    PRINTSCREEN  : 44,

    RIGHT        : 39,

    SCROLL_LOCK  : 145,

    SHIFT        : 16,

    SPACE        : 32,

    TAB          : 9,

    UP           : 38

};

YAHOO.register("event", YAHOO.util.Event, {version: "2.5.1", build: "984"});


