Merge commit 'catalyst/MOODLE_19_STABLE' into mdl19-linuxchix
[moodle-linuxchix.git] / lib / yui / event / event.js
blob050b3bccf8ce0aac1a5cbe85f04dbdb27af49787
1 /*
2 Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.5.2
6 */
8 /**
9  * The CustomEvent class lets you define events for your application
10  * that can be subscribed to by one or more independent component.
11  *
12  * @param {String}  type The type of event, which is passed to the callback
13  *                  when the event fires
14  * @param {Object}  oScope The context the event will fire from.  "this" will
15  *                  refer to this object in the callback.  Default value: 
16  *                  the window object.  The listener can override this.
17  * @param {boolean} silent pass true to prevent the event from writing to
18  *                  the debugsystem
19  * @param {int}     signature the signature that the custom event subscriber
20  *                  will receive. YAHOO.util.CustomEvent.LIST or 
21  *                  YAHOO.util.CustomEvent.FLAT.  The default is
22  *                  YAHOO.util.CustomEvent.LIST.
23  * @namespace YAHOO.util
24  * @class CustomEvent
25  * @constructor
26  */
27 YAHOO.util.CustomEvent = function(type, oScope, silent, signature) {
29     /**
30      * The type of event, returned to subscribers when the event fires
31      * @property type
32      * @type string
33      */
34     this.type = type;
36     /**
37      * The scope the the event will fire from by default.  Defaults to the window 
38      * obj
39      * @property scope
40      * @type object
41      */
42     this.scope = oScope || window;
44     /**
45      * By default all custom events are logged in the debug build, set silent
46      * to true to disable debug outpu for this event.
47      * @property silent
48      * @type boolean
49      */
50     this.silent = silent;
52     /**
53      * Custom events support two styles of arguments provided to the event
54      * subscribers.  
55      * <ul>
56      * <li>YAHOO.util.CustomEvent.LIST: 
57      *   <ul>
58      *   <li>param1: event name</li>
59      *   <li>param2: array of arguments sent to fire</li>
60      *   <li>param3: <optional> a custom object supplied by the subscriber</li>
61      *   </ul>
62      * </li>
63      * <li>YAHOO.util.CustomEvent.FLAT
64      *   <ul>
65      *   <li>param1: the first argument passed to fire.  If you need to
66      *           pass multiple parameters, use and array or object literal</li>
67      *   <li>param2: <optional> a custom object supplied by the subscriber</li>
68      *   </ul>
69      * </li>
70      * </ul>
71      *   @property signature
72      *   @type int
73      */
74     this.signature = signature || YAHOO.util.CustomEvent.LIST;
76     /**
77      * The subscribers to this event
78      * @property subscribers
79      * @type Subscriber[]
80      */
81     this.subscribers = [];
83     if (!this.silent) {
84     }
86     var onsubscribeType = "_YUICEOnSubscribe";
88     // Only add subscribe events for events that are not generated by 
89     // CustomEvent
90     if (type !== onsubscribeType) {
92         /**
93          * Custom events provide a custom event that fires whenever there is
94          * a new subscriber to the event.  This provides an opportunity to
95          * handle the case where there is a non-repeating event that has
96          * already fired has a new subscriber.  
97          *
98          * @event subscribeEvent
99          * @type YAHOO.util.CustomEvent
100          * @param {Function} fn The function to execute
101          * @param {Object}   obj An object to be passed along when the event 
102          *                       fires
103          * @param {boolean|Object}  override If true, the obj passed in becomes 
104          *                                   the execution scope of the listener.
105          *                                   if an object, that object becomes the
106          *                                   the execution scope.
107          */
108         this.subscribeEvent = 
109                 new YAHOO.util.CustomEvent(onsubscribeType, this, true);
111     } 
114     /**
115      * In order to make it possible to execute the rest of the subscriber
116      * stack when one thows an exception, the subscribers exceptions are
117      * caught.  The most recent exception is stored in this property
118      * @property lastError
119      * @type Error
120      */
121     this.lastError = null;
125  * Subscriber listener sigature constant.  The LIST type returns three
126  * parameters: the event type, the array of args passed to fire, and
127  * the optional custom object
128  * @property YAHOO.util.CustomEvent.LIST
129  * @static
130  * @type int
131  */
132 YAHOO.util.CustomEvent.LIST = 0;
135  * Subscriber listener sigature constant.  The FLAT type returns two
136  * parameters: the first argument passed to fire and the optional 
137  * custom object
138  * @property YAHOO.util.CustomEvent.FLAT
139  * @static
140  * @type int
141  */
142 YAHOO.util.CustomEvent.FLAT = 1;
144 YAHOO.util.CustomEvent.prototype = {
146     /**
147      * Subscribes the caller to this event
148      * @method subscribe
149      * @param {Function} fn        The function to execute
150      * @param {Object}   obj       An object to be passed along when the event 
151      *                             fires
152      * @param {boolean|Object}  override If true, the obj passed in becomes 
153      *                                   the execution scope of the listener.
154      *                                   if an object, that object becomes the
155      *                                   the execution scope.
156      */
157     subscribe: function(fn, obj, override) {
159         if (!fn) {
160 throw new Error("Invalid callback for subscriber to '" + this.type + "'");
161         }
163         if (this.subscribeEvent) {
164             this.subscribeEvent.fire(fn, obj, override);
165         }
167         this.subscribers.push( new YAHOO.util.Subscriber(fn, obj, override) );
168     },
170     /**
171      * Unsubscribes subscribers.
172      * @method unsubscribe
173      * @param {Function} fn  The subscribed function to remove, if not supplied
174      *                       all will be removed
175      * @param {Object}   obj  The custom object passed to subscribe.  This is
176      *                        optional, but if supplied will be used to
177      *                        disambiguate multiple listeners that are the same
178      *                        (e.g., you subscribe many object using a function
179      *                        that lives on the prototype)
180      * @return {boolean} True if the subscriber was found and detached.
181      */
182     unsubscribe: function(fn, obj) {
184         if (!fn) {
185             return this.unsubscribeAll();
186         }
188         var found = false;
189         for (var i=0, len=this.subscribers.length; i<len; ++i) {
190             var s = this.subscribers[i];
191             if (s && s.contains(fn, obj)) {
192                 this._delete(i);
193                 found = true;
194             }
195         }
197         return found;
198     },
200     /**
201      * Notifies the subscribers.  The callback functions will be executed
202      * from the scope specified when the event was created, and with the 
203      * following parameters:
204      *   <ul>
205      *   <li>The type of event</li>
206      *   <li>All of the arguments fire() was executed with as an array</li>
207      *   <li>The custom object (if any) that was passed into the subscribe() 
208      *       method</li>
209      *   </ul>
210      * @method fire 
211      * @param {Object*} arguments an arbitrary set of parameters to pass to 
212      *                            the handler.
213      * @return {boolean} false if one of the subscribers returned false, 
214      *                   true otherwise
215      */
216     fire: function() {
218         this.lastError = null;
220         var errors = [],
221             len=this.subscribers.length;
223         if (!len && this.silent) {
224             return true;
225         }
227         var args=[].slice.call(arguments, 0), ret=true, i, rebuild=false;
229         if (!this.silent) {
230         }
232         // make a copy of the subscribers so that there are
233         // no index problems if one subscriber removes another.
234         var subs = this.subscribers.slice(), throwErrors = YAHOO.util.Event.throwErrors;
236         for (i=0; i<len; ++i) {
237             var s = subs[i];
238             if (!s) {
239                 rebuild=true;
240             } else {
241                 if (!this.silent) {
242                 }
244                 var scope = s.getScope(this.scope);
246                 if (this.signature == YAHOO.util.CustomEvent.FLAT) {
247                     var param = null;
248                     if (args.length > 0) {
249                         param = args[0];
250                     }
252                     try {
253                         ret = s.fn.call(scope, param, s.obj);
254                     } catch(e) {
255                         this.lastError = e;
256                         // errors.push(e);
257                         if (throwErrors) {
258                             throw e;
259                         }
260                     }
261                 } else {
262                     try {
263                         ret = s.fn.call(scope, this.type, args, s.obj);
264                     } catch(ex) {
265                         this.lastError = ex;
266                         if (throwErrors) {
267                             throw ex;
268                         }
269                     }
270                 }
272                 if (false === ret) {
273                     if (!this.silent) {
274                     }
276                     break;
277                     // return false;
278                 }
279             }
280         }
282         return (ret !== false);
283     },
285     /**
286      * Removes all listeners
287      * @method unsubscribeAll
288      * @return {int} The number of listeners unsubscribed
289      */
290     unsubscribeAll: function() {
291         for (var i=this.subscribers.length-1; i>-1; i--) {
292             this._delete(i);
293         }
295         this.subscribers=[];
297         return i;
298     },
300     /**
301      * @method _delete
302      * @private
303      */
304     _delete: function(index) {
305         var s = this.subscribers[index];
306         if (s) {
307             delete s.fn;
308             delete s.obj;
309         }
311         // this.subscribers[index]=null;
312         this.subscribers.splice(index, 1);
313     },
315     /**
316      * @method toString
317      */
318     toString: function() {
319          return "CustomEvent: " + "'" + this.type  + "', " + 
320              "scope: " + this.scope;
322     }
325 /////////////////////////////////////////////////////////////////////
328  * Stores the subscriber information to be used when the event fires.
329  * @param {Function} fn       The function to execute
330  * @param {Object}   obj      An object to be passed along when the event fires
331  * @param {boolean}  override If true, the obj passed in becomes the execution
332  *                            scope of the listener
333  * @class Subscriber
334  * @constructor
335  */
336 YAHOO.util.Subscriber = function(fn, obj, override) {
338     /**
339      * The callback that will be execute when the event fires
340      * @property fn
341      * @type function
342      */
343     this.fn = fn;
345     /**
346      * An optional custom object that will passed to the callback when
347      * the event fires
348      * @property obj
349      * @type object
350      */
351     this.obj = YAHOO.lang.isUndefined(obj) ? null : obj;
353     /**
354      * The default execution scope for the event listener is defined when the
355      * event is created (usually the object which contains the event).
356      * By setting override to true, the execution scope becomes the custom
357      * object passed in by the subscriber.  If override is an object, that 
358      * object becomes the scope.
359      * @property override
360      * @type boolean|object
361      */
362     this.override = override;
367  * Returns the execution scope for this listener.  If override was set to true
368  * the custom obj will be the scope.  If override is an object, that is the
369  * scope, otherwise the default scope will be used.
370  * @method getScope
371  * @param {Object} defaultScope the scope to use if this listener does not
372  *                              override it.
373  */
374 YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
375     if (this.override) {
376         if (this.override === true) {
377             return this.obj;
378         } else {
379             return this.override;
380         }
381     }
382     return defaultScope;
386  * Returns true if the fn and obj match this objects properties.
387  * Used by the unsubscribe method to match the right subscriber.
389  * @method contains
390  * @param {Function} fn the function to execute
391  * @param {Object} obj an object to be passed along when the event fires
392  * @return {boolean} true if the supplied arguments match this 
393  *                   subscriber's signature.
394  */
395 YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {
396     if (obj) {
397         return (this.fn == fn && this.obj == obj);
398     } else {
399         return (this.fn == fn);
400     }
404  * @method toString
405  */
406 YAHOO.util.Subscriber.prototype.toString = function() {
407     return "Subscriber { obj: " + this.obj  + 
408            ", override: " +  (this.override || "no") + " }";
412  * The Event Utility provides utilities for managing DOM Events and tools
413  * for building event systems
415  * @module event
416  * @title Event Utility
417  * @namespace YAHOO.util
418  * @requires yahoo
419  */
421 // The first instance of Event will win if it is loaded more than once.
422 // @TODO this needs to be changed so that only the state data that needs to
423 // be preserved is kept, while methods are overwritten/added as needed.
424 // This means that the module pattern can't be used.
425 if (!YAHOO.util.Event) {
428  * The event utility provides functions to add and remove event listeners,
429  * event cleansing.  It also tries to automatically remove listeners it
430  * registers during the unload event.
432  * @class Event
433  * @static
434  */
435     YAHOO.util.Event = function() {
437         /**
438          * True after the onload event has fired
439          * @property loadComplete
440          * @type boolean
441          * @static
442          * @private
443          */
444         var loadComplete =  false;
446         /**
447          * Cache of wrapped listeners
448          * @property listeners
449          * @type array
450          * @static
451          * @private
452          */
453         var listeners = [];
455         /**
456          * User-defined unload function that will be fired before all events
457          * are detached
458          * @property unloadListeners
459          * @type array
460          * @static
461          * @private
462          */
463         var unloadListeners = [];
465         /**
466          * Cache of DOM0 event handlers to work around issues with DOM2 events
467          * in Safari
468          * @property legacyEvents
469          * @static
470          * @private
471          */
472         var legacyEvents = [];
474         /**
475          * Listener stack for DOM0 events
476          * @property legacyHandlers
477          * @static
478          * @private
479          */
480         var legacyHandlers = [];
482         /**
483          * The number of times to poll after window.onload.  This number is
484          * increased if additional late-bound handlers are requested after
485          * the page load.
486          * @property retryCount
487          * @static
488          * @private
489          */
490         var retryCount = 0;
492         /**
493          * onAvailable listeners
494          * @property onAvailStack
495          * @static
496          * @private
497          */
498         var onAvailStack = [];
500         /**
501          * Lookup table for legacy events
502          * @property legacyMap
503          * @static
504          * @private
505          */
506         var legacyMap = [];
508         /**
509          * Counter for auto id generation
510          * @property counter
511          * @static
512          * @private
513          */
514         var counter = 0;
515         
516         /**
517          * Normalized keycodes for webkit/safari
518          * @property webkitKeymap
519          * @type {int: int}
520          * @private
521          * @static
522          * @final
523          */
524         var webkitKeymap = {
525             63232: 38, // up
526             63233: 40, // down
527             63234: 37, // left
528             63235: 39, // right
529             63276: 33, // page up
530             63277: 34, // page down
531             25: 9      // SHIFT-TAB (Safari provides a different key code in
532                        // this case, even though the shiftKey modifier is set)
533         };
535         return {
537             /**
538              * The number of times we should look for elements that are not
539              * in the DOM at the time the event is requested after the document
540              * has been loaded.  The default is 2000@amp;20 ms, so it will poll
541              * for 40 seconds or until all outstanding handlers are bound
542              * (whichever comes first).
543              * @property POLL_RETRYS
544              * @type int
545              * @static
546              * @final
547              */
548             POLL_RETRYS: 2000,
550             /**
551              * The poll interval in milliseconds
552              * @property POLL_INTERVAL
553              * @type int
554              * @static
555              * @final
556              */
557             POLL_INTERVAL: 20,
559             /**
560              * Element to bind, int constant
561              * @property EL
562              * @type int
563              * @static
564              * @final
565              */
566             EL: 0,
568             /**
569              * Type of event, int constant
570              * @property TYPE
571              * @type int
572              * @static
573              * @final
574              */
575             TYPE: 1,
577             /**
578              * Function to execute, int constant
579              * @property FN
580              * @type int
581              * @static
582              * @final
583              */
584             FN: 2,
586             /**
587              * Function wrapped for scope correction and cleanup, int constant
588              * @property WFN
589              * @type int
590              * @static
591              * @final
592              */
593             WFN: 3,
595             /**
596              * Object passed in by the user that will be returned as a 
597              * parameter to the callback, int constant.  Specific to
598              * unload listeners
599              * @property OBJ
600              * @type int
601              * @static
602              * @final
603              */
604             UNLOAD_OBJ: 3,
606             /**
607              * Adjusted scope, either the element we are registering the event
608              * on or the custom object passed in by the listener, int constant
609              * @property ADJ_SCOPE
610              * @type int
611              * @static
612              * @final
613              */
614             ADJ_SCOPE: 4,
616             /**
617              * The original obj passed into addListener
618              * @property OBJ
619              * @type int
620              * @static
621              * @final
622              */
623             OBJ: 5,
625             /**
626              * The original scope parameter passed into addListener
627              * @property OVERRIDE
628              * @type int
629              * @static
630              * @final
631              */
632             OVERRIDE: 6,
634             /**
635              * addListener/removeListener can throw errors in unexpected scenarios.
636              * These errors are suppressed, the method returns false, and this property
637              * is set
638              * @property lastError
639              * @static
640              * @type Error
641              */
642             lastError: null,
644             /**
645              * Safari detection
646              * @property isSafari
647              * @private
648              * @static
649              * @deprecated use YAHOO.env.ua.webkit
650              */
651             isSafari: YAHOO.env.ua.webkit,
652             
653             /**
654              * webkit version
655              * @property webkit
656              * @type string
657              * @private
658              * @static
659              * @deprecated use YAHOO.env.ua.webkit
660              */
661             webkit: YAHOO.env.ua.webkit,
662             
663             /**
664              * IE detection 
665              * @property isIE
666              * @private
667              * @static
668              * @deprecated use YAHOO.env.ua.ie
669              */
670             isIE: YAHOO.env.ua.ie,
672             /**
673              * poll handle
674              * @property _interval
675              * @static
676              * @private
677              */
678             _interval: null,
680             /**
681              * document readystate poll handle
682              * @property _dri
683              * @static
684              * @private
685              */
686              _dri: null,
688             /**
689              * True when the document is initially usable
690              * @property DOMReady
691              * @type boolean
692              * @static
693              */
694             DOMReady: false,
696             /**
697              * Errors thrown by subscribers of custom events are caught
698              * and the error message is written to the debug console.  If
699              * this property is set to true, it will also re-throw the
700              * error.
701              * @property throwErrors
702              * @type boolean
703              * @default false
704              */
705             throwErrors: false,
707             /**
708              * @method startInterval
709              * @static
710              * @private
711              */
712             startInterval: function() {
713                 if (!this._interval) {
714                     var self = this;
715                     var callback = function() { self._tryPreloadAttach(); };
716                     this._interval = setInterval(callback, this.POLL_INTERVAL);
717                 }
718             },
720             /**
721              * Executes the supplied callback when the item with the supplied
722              * id is found.  This is meant to be used to execute behavior as
723              * soon as possible as the page loads.  If you use this after the
724              * initial page load it will poll for a fixed time for the element.
725              * The number of times it will poll and the frequency are
726              * configurable.  By default it will poll for 10 seconds.
727              *
728              * <p>The callback is executed with a single parameter:
729              * the custom object parameter, if provided.</p>
730              *
731              * @method onAvailable
732              *
733              * @param {string||string[]}   p_id the id of the element, or an array
734              * of ids to look for.
735              * @param {function} p_fn what to execute when the element is found.
736              * @param {object}   p_obj an optional object to be passed back as
737              *                   a parameter to p_fn.
738              * @param {boolean|object}  p_override If set to true, p_fn will execute
739              *                   in the scope of p_obj, if set to an object it
740              *                   will execute in the scope of that object
741              * @param checkContent {boolean} check child node readiness (onContentReady)
742              * @static
743              */
744             onAvailable: function(p_id, p_fn, p_obj, p_override, checkContent) {
746                 var a = (YAHOO.lang.isString(p_id)) ? [p_id] : p_id;
748                 for (var i=0; i<a.length; i=i+1) {
749                     onAvailStack.push({id:         a[i], 
750                                        fn:         p_fn, 
751                                        obj:        p_obj, 
752                                        override:   p_override, 
753                                        checkReady: checkContent });
754                 }
756                 retryCount = this.POLL_RETRYS;
758                 this.startInterval();
759             },
761             /**
762              * Works the same way as onAvailable, but additionally checks the
763              * state of sibling elements to determine if the content of the
764              * available element is safe to modify.
765              *
766              * <p>The callback is executed with a single parameter:
767              * the custom object parameter, if provided.</p>
768              *
769              * @method onContentReady
770              *
771              * @param {string}   p_id the id of the element to look for.
772              * @param {function} p_fn what to execute when the element is ready.
773              * @param {object}   p_obj an optional object to be passed back as
774              *                   a parameter to p_fn.
775              * @param {boolean|object}  p_override If set to true, p_fn will execute
776              *                   in the scope of p_obj.  If an object, p_fn will
777              *                   exectute in the scope of that object
778              *
779              * @static
780              */
781             onContentReady: function(p_id, p_fn, p_obj, p_override) {
782                 this.onAvailable(p_id, p_fn, p_obj, p_override, true);
783             },
785             /**
786              * Executes the supplied callback when the DOM is first usable.  This
787              * will execute immediately if called after the DOMReady event has
788              * fired.   @todo the DOMContentReady event does not fire when the
789              * script is dynamically injected into the page.  This means the
790              * DOMReady custom event will never fire in FireFox or Opera when the
791              * library is injected.  It _will_ fire in Safari, and the IE 
792              * implementation would allow for us to fire it if the defered script
793              * is not available.  We want this to behave the same in all browsers.
794              * Is there a way to identify when the script has been injected 
795              * instead of included inline?  Is there a way to know whether the 
796              * window onload event has fired without having had a listener attached 
797              * to it when it did so?
798              *
799              * <p>The callback is a CustomEvent, so the signature is:</p>
800              * <p>type &lt;string&gt;, args &lt;array&gt;, customobject &lt;object&gt;</p>
801              * <p>For DOMReady events, there are no fire argments, so the
802              * signature is:</p>
803              * <p>"DOMReady", [], obj</p>
804              *
805              *
806              * @method onDOMReady
807              *
808              * @param {function} p_fn what to execute when the element is found.
809              * @param {object}   p_obj an optional object to be passed back as
810              *                   a parameter to p_fn.
811              * @param {boolean|object}  p_scope If set to true, p_fn will execute
812              *                   in the scope of p_obj, if set to an object it
813              *                   will execute in the scope of that object
814              *
815              * @static
816              */
817             onDOMReady: function(p_fn, p_obj, p_override) {
818                 if (this.DOMReady) {
819                     setTimeout(function() {
820                         var s = window;
821                         if (p_override) {
822                             if (p_override === true) {
823                                 s = p_obj;
824                             } else {
825                                 s = p_override;
826                             }
827                         }
828                         p_fn.call(s, "DOMReady", [], p_obj);
829                     }, 0);
830                 } else {
831                     this.DOMReadyEvent.subscribe(p_fn, p_obj, p_override);
832                 }
833             },
835             /**
836              * Appends an event handler
837              *
838              * @method addListener
839              *
840              * @param {String|HTMLElement|Array|NodeList} el An id, an element 
841              *  reference, or a collection of ids and/or elements to assign the 
842              *  listener to.
843              * @param {String}   sType     The type of event to append
844              * @param {Function} fn        The method the event invokes
845              * @param {Object}   obj    An arbitrary object that will be 
846              *                             passed as a parameter to the handler
847              * @param {Boolean|object}  override  If true, the obj passed in becomes
848              *                             the execution scope of the listener. If an
849              *                             object, this object becomes the execution
850              *                             scope.
851              * @return {Boolean} True if the action was successful or defered,
852              *                        false if one or more of the elements 
853              *                        could not have the listener attached,
854              *                        or if the operation throws an exception.
855              * @static
856              */
857             addListener: function(el, sType, fn, obj, override) {
859                 if (!fn || !fn.call) {
860                     return false;
861                 }
863                 // The el argument can be an array of elements or element ids.
864                 if ( this._isValidCollection(el)) {
865                     var ok = true;
866                     for (var i=0,len=el.length; i<len; ++i) {
867                         ok = this.on(el[i], 
868                                        sType, 
869                                        fn, 
870                                        obj, 
871                                        override) && ok;
872                     }
873                     return ok;
875                 } else if (YAHOO.lang.isString(el)) {
876                     var oEl = this.getEl(el);
877                     // If the el argument is a string, we assume it is 
878                     // actually the id of the element.  If the page is loaded
879                     // we convert el to the actual element, otherwise we 
880                     // defer attaching the event until onload event fires
882                     // check to see if we need to delay hooking up the event 
883                     // until after the page loads.
884                     if (oEl) {
885                         el = oEl;
886                     } else {
887                         // defer adding the event until the element is available
888                         this.onAvailable(el, function() {
889                            YAHOO.util.Event.on(el, sType, fn, obj, override);
890                         });
892                         return true;
893                     }
894                 }
896                 // Element should be an html element or an array if we get 
897                 // here.
898                 if (!el) {
899                     return false;
900                 }
902                 // we need to make sure we fire registered unload events 
903                 // prior to automatically unhooking them.  So we hang on to 
904                 // these instead of attaching them to the window and fire the
905                 // handles explicitly during our one unload event.
906                 if ("unload" == sType && obj !== this) {
907                     unloadListeners[unloadListeners.length] =
908                             [el, sType, fn, obj, override];
909                     return true;
910                 }
913                 // if the user chooses to override the scope, we use the custom
914                 // object passed in, otherwise the executing scope will be the
915                 // HTML element that the event is registered on
916                 var scope = el;
917                 if (override) {
918                     if (override === true) {
919                         scope = obj;
920                     } else {
921                         scope = override;
922                     }
923                 }
925                 // wrap the function so we can return the obj object when
926                 // the event fires;
927                 var wrappedFn = function(e) {
928                         return fn.call(scope, YAHOO.util.Event.getEvent(e, el), 
929                                 obj);
930                     };
932                 var li = [el, sType, fn, wrappedFn, scope, obj, override];
933                 var index = listeners.length;
934                 // cache the listener so we can try to automatically unload
935                 listeners[index] = li;
937                 if (this.useLegacyEvent(el, sType)) {
938                     var legacyIndex = this.getLegacyIndex(el, sType);
940                     // Add a new dom0 wrapper if one is not detected for this
941                     // element
942                     if ( legacyIndex == -1 || 
943                                 el != legacyEvents[legacyIndex][0] ) {
945                         legacyIndex = legacyEvents.length;
946                         legacyMap[el.id + sType] = legacyIndex;
948                         // cache the signature for the DOM0 event, and 
949                         // include the existing handler for the event, if any
950                         legacyEvents[legacyIndex] = 
951                             [el, sType, el["on" + sType]];
952                         legacyHandlers[legacyIndex] = [];
954                         el["on" + sType] = 
955                             function(e) {
956                                 YAHOO.util.Event.fireLegacyEvent(
957                                     YAHOO.util.Event.getEvent(e), legacyIndex);
958                             };
959                     }
961                     // add a reference to the wrapped listener to our custom
962                     // stack of events
963                     //legacyHandlers[legacyIndex].push(index);
964                     legacyHandlers[legacyIndex].push(li);
966                 } else {
967                     try {
968                         this._simpleAdd(el, sType, wrappedFn, false);
969                     } catch(ex) {
970                         // handle an error trying to attach an event.  If it fails
971                         // we need to clean up the cache
972                         this.lastError = ex;
973                         this.removeListener(el, sType, fn);
974                         return false;
975                     }
976                 }
978                 return true;
979                 
980             },
982             /**
983              * When using legacy events, the handler is routed to this object
984              * so we can fire our custom listener stack.
985              * @method fireLegacyEvent
986              * @static
987              * @private
988              */
989             fireLegacyEvent: function(e, legacyIndex) {
990                 var ok=true, le, lh, li, scope, ret;
991                 
992                 lh = legacyHandlers[legacyIndex].slice();
993                 for (var i=0, len=lh.length; i<len; ++i) {
994                 // for (var i in lh.length) {
995                     li = lh[i];
996                     if ( li && li[this.WFN] ) {
997                         scope = li[this.ADJ_SCOPE];
998                         ret = li[this.WFN].call(scope, e);
999                         ok = (ok && ret);
1000                     }
1001                 }
1003                 // Fire the original handler if we replaced one.  We fire this
1004                 // after the other events to keep stopPropagation/preventDefault
1005                 // that happened in the DOM0 handler from touching our DOM2
1006                 // substitute
1007                 le = legacyEvents[legacyIndex];
1008                 if (le && le[2]) {
1009                     le[2](e);
1010                 }
1011                 
1012                 return ok;
1013             },
1015             /**
1016              * Returns the legacy event index that matches the supplied 
1017              * signature
1018              * @method getLegacyIndex
1019              * @static
1020              * @private
1021              */
1022             getLegacyIndex: function(el, sType) {
1023                 var key = this.generateId(el) + sType;
1024                 if (typeof legacyMap[key] == "undefined") { 
1025                     return -1;
1026                 } else {
1027                     return legacyMap[key];
1028                 }
1029             },
1031             /**
1032              * Logic that determines when we should automatically use legacy
1033              * events instead of DOM2 events.  Currently this is limited to old
1034              * Safari browsers with a broken preventDefault
1035              * @method useLegacyEvent
1036              * @static
1037              * @private
1038              */
1039             useLegacyEvent: function(el, sType) {
1040                 if (this.webkit && ("click"==sType || "dblclick"==sType)) {
1041                     var v = parseInt(this.webkit, 10);
1042                     if (!isNaN(v) && v<418) {
1043                         return true;
1044                     }
1045                 }
1046                 return false;
1047             },
1048                     
1049             /**
1050              * Removes an event listener
1051              *
1052              * @method removeListener
1053              *
1054              * @param {String|HTMLElement|Array|NodeList} el An id, an element 
1055              *  reference, or a collection of ids and/or elements to remove
1056              *  the listener from.
1057              * @param {String} sType the type of event to remove.
1058              * @param {Function} fn the method the event invokes.  If fn is
1059              *  undefined, then all event handlers for the type of event are 
1060              *  removed.
1061              * @return {boolean} true if the unbind was successful, false 
1062              *  otherwise.
1063              * @static
1064              */
1065             removeListener: function(el, sType, fn) {
1066                 var i, len, li;
1068                 // The el argument can be a string
1069                 if (typeof el == "string") {
1070                     el = this.getEl(el);
1071                 // The el argument can be an array of elements or element ids.
1072                 } else if ( this._isValidCollection(el)) {
1073                     var ok = true;
1074                     for (i=el.length-1; i>-1; i--) {
1075                         ok = ( this.removeListener(el[i], sType, fn) && ok );
1076                     }
1077                     return ok;
1078                 }
1080                 if (!fn || !fn.call) {
1081                     //return false;
1082                     return this.purgeElement(el, false, sType);
1083                 }
1085                 if ("unload" == sType) {
1087                     for (i=unloadListeners.length-1; i>-1; i--) {
1088                         li = unloadListeners[i];
1089                         if (li && 
1090                             li[0] == el && 
1091                             li[1] == sType && 
1092                             li[2] == fn) {
1093                                 unloadListeners.splice(i, 1);
1094                                 // unloadListeners[i]=null;
1095                                 return true;
1096                         }
1097                     }
1099                     return false;
1100                 }
1102                 var cacheItem = null;
1104                 // The index is a hidden parameter; needed to remove it from
1105                 // the method signature because it was tempting users to
1106                 // try and take advantage of it, which is not possible.
1107                 var index = arguments[3];
1108   
1109                 if ("undefined" === typeof index) {
1110                     index = this._getCacheIndex(el, sType, fn);
1111                 }
1113                 if (index >= 0) {
1114                     cacheItem = listeners[index];
1115                 }
1117                 if (!el || !cacheItem) {
1118                     return false;
1119                 }
1122                 if (this.useLegacyEvent(el, sType)) {
1123                     var legacyIndex = this.getLegacyIndex(el, sType);
1124                     var llist = legacyHandlers[legacyIndex];
1125                     if (llist) {
1126                         for (i=0, len=llist.length; i<len; ++i) {
1127                         // for (i in llist.length) {
1128                             li = llist[i];
1129                             if (li && 
1130                                 li[this.EL] == el && 
1131                                 li[this.TYPE] == sType && 
1132                                 li[this.FN] == fn) {
1133                                     llist.splice(i, 1);
1134                                     // llist[i]=null;
1135                                     break;
1136                             }
1137                         }
1138                     }
1140                 } else {
1141                     try {
1142                         this._simpleRemove(el, sType, cacheItem[this.WFN], false);
1143                     } catch(ex) {
1144                         this.lastError = ex;
1145                         return false;
1146                     }
1147                 }
1149                 // removed the wrapped handler
1150                 delete listeners[index][this.WFN];
1151                 delete listeners[index][this.FN];
1152                 listeners.splice(index, 1);
1153                 // listeners[index]=null;
1155                 return true;
1157             },
1159             /**
1160              * Returns the event's target element.  Safari sometimes provides
1161              * a text node, and this is automatically resolved to the text
1162              * node's parent so that it behaves like other browsers.
1163              * @method getTarget
1164              * @param {Event} ev the event
1165              * @param {boolean} resolveTextNode when set to true the target's
1166              *                  parent will be returned if the target is a 
1167              *                  text node.  @deprecated, the text node is
1168              *                  now resolved automatically
1169              * @return {HTMLElement} the event's target
1170              * @static
1171              */
1172             getTarget: function(ev, resolveTextNode) {
1173                 var t = ev.target || ev.srcElement;
1174                 return this.resolveTextNode(t);
1175             },
1177             /**
1178              * In some cases, some browsers will return a text node inside
1179              * the actual element that was targeted.  This normalizes the
1180              * return value for getTarget and getRelatedTarget.
1181              * @method resolveTextNode
1182              * @param {HTMLElement} node node to resolve
1183              * @return {HTMLElement} the normized node
1184              * @static
1185              */
1186             resolveTextNode: function(n) {
1187                 try {
1188                     if (n && 3 == n.nodeType) {
1189                         return n.parentNode;
1190                     }
1191                 } catch(e) { }
1193                 return n;
1194             },
1196             /**
1197              * Returns the event's pageX
1198              * @method getPageX
1199              * @param {Event} ev the event
1200              * @return {int} the event's pageX
1201              * @static
1202              */
1203             getPageX: function(ev) {
1204                 var x = ev.pageX;
1205                 if (!x && 0 !== x) {
1206                     x = ev.clientX || 0;
1208                     if ( this.isIE ) {
1209                         x += this._getScrollLeft();
1210                     }
1211                 }
1213                 return x;
1214             },
1216             /**
1217              * Returns the event's pageY
1218              * @method getPageY
1219              * @param {Event} ev the event
1220              * @return {int} the event's pageY
1221              * @static
1222              */
1223             getPageY: function(ev) {
1224                 var y = ev.pageY;
1225                 if (!y && 0 !== y) {
1226                     y = ev.clientY || 0;
1228                     if ( this.isIE ) {
1229                         y += this._getScrollTop();
1230                     }
1231                 }
1234                 return y;
1235             },
1237             /**
1238              * Returns the pageX and pageY properties as an indexed array.
1239              * @method getXY
1240              * @param {Event} ev the event
1241              * @return {[x, y]} the pageX and pageY properties of the event
1242              * @static
1243              */
1244             getXY: function(ev) {
1245                 return [this.getPageX(ev), this.getPageY(ev)];
1246             },
1248             /**
1249              * Returns the event's related target 
1250              * @method getRelatedTarget
1251              * @param {Event} ev the event
1252              * @return {HTMLElement} the event's relatedTarget
1253              * @static
1254              */
1255             getRelatedTarget: function(ev) {
1256                 var t = ev.relatedTarget;
1257                 if (!t) {
1258                     if (ev.type == "mouseout") {
1259                         t = ev.toElement;
1260                     } else if (ev.type == "mouseover") {
1261                         t = ev.fromElement;
1262                     }
1263                 }
1265                 return this.resolveTextNode(t);
1266             },
1268             /**
1269              * Returns the time of the event.  If the time is not included, the
1270              * event is modified using the current time.
1271              * @method getTime
1272              * @param {Event} ev the event
1273              * @return {Date} the time of the event
1274              * @static
1275              */
1276             getTime: function(ev) {
1277                 if (!ev.time) {
1278                     var t = new Date().getTime();
1279                     try {
1280                         ev.time = t;
1281                     } catch(ex) { 
1282                         this.lastError = ex;
1283                         return t;
1284                     }
1285                 }
1287                 return ev.time;
1288             },
1290             /**
1291              * Convenience method for stopPropagation + preventDefault
1292              * @method stopEvent
1293              * @param {Event} ev the event
1294              * @static
1295              */
1296             stopEvent: function(ev) {
1297                 this.stopPropagation(ev);
1298                 this.preventDefault(ev);
1299             },
1301             /**
1302              * Stops event propagation
1303              * @method stopPropagation
1304              * @param {Event} ev the event
1305              * @static
1306              */
1307             stopPropagation: function(ev) {
1308                 if (ev.stopPropagation) {
1309                     ev.stopPropagation();
1310                 } else {
1311                     ev.cancelBubble = true;
1312                 }
1313             },
1315             /**
1316              * Prevents the default behavior of the event
1317              * @method preventDefault
1318              * @param {Event} ev the event
1319              * @static
1320              */
1321             preventDefault: function(ev) {
1322                 if (ev.preventDefault) {
1323                     ev.preventDefault();
1324                 } else {
1325                     ev.returnValue = false;
1326                 }
1327             },
1328              
1329             /**
1330              * Finds the event in the window object, the caller's arguments, or
1331              * in the arguments of another method in the callstack.  This is
1332              * executed automatically for events registered through the event
1333              * manager, so the implementer should not normally need to execute
1334              * this function at all.
1335              * @method getEvent
1336              * @param {Event} e the event parameter from the handler
1337              * @param {HTMLElement} boundEl the element the listener is attached to
1338              * @return {Event} the event 
1339              * @static
1340              */
1341             getEvent: function(e, boundEl) {
1342                 var ev = e || window.event;
1344                 if (!ev) {
1345                     var c = this.getEvent.caller;
1346                     while (c) {
1347                         ev = c.arguments[0];
1348                         if (ev && Event == ev.constructor) {
1349                             break;
1350                         }
1351                         c = c.caller;
1352                     }
1353                 }
1355                 return ev;
1356             },
1358             /**
1359              * Returns the charcode for an event
1360              * @method getCharCode
1361              * @param {Event} ev the event
1362              * @return {int} the event's charCode
1363              * @static
1364              */
1365             getCharCode: function(ev) {
1366                 var code = ev.keyCode || ev.charCode || 0;
1368                 // webkit key normalization
1369                 if (YAHOO.env.ua.webkit && (code in webkitKeymap)) {
1370                     code = webkitKeymap[code];
1371                 }
1372                 return code;
1373             },
1375             /**
1376              * Locating the saved event handler data by function ref
1377              *
1378              * @method _getCacheIndex
1379              * @static
1380              * @private
1381              */
1382             _getCacheIndex: function(el, sType, fn) {
1383                 for (var i=0, l=listeners.length; i<l; i=i+1) {
1384                     var li = listeners[i];
1385                     if ( li                 && 
1386                          li[this.FN] == fn  && 
1387                          li[this.EL] == el  && 
1388                          li[this.TYPE] == sType ) {
1389                         return i;
1390                     }
1391                 }
1393                 return -1;
1394             },
1396             /**
1397              * Generates an unique ID for the element if it does not already 
1398              * have one.
1399              * @method generateId
1400              * @param el the element to create the id for
1401              * @return {string} the resulting id of the element
1402              * @static
1403              */
1404             generateId: function(el) {
1405                 var id = el.id;
1407                 if (!id) {
1408                     id = "yuievtautoid-" + counter;
1409                     ++counter;
1410                     el.id = id;
1411                 }
1413                 return id;
1414             },
1417             /**
1418              * We want to be able to use getElementsByTagName as a collection
1419              * to attach a group of events to.  Unfortunately, different 
1420              * browsers return different types of collections.  This function
1421              * tests to determine if the object is array-like.  It will also 
1422              * fail if the object is an array, but is empty.
1423              * @method _isValidCollection
1424              * @param o the object to test
1425              * @return {boolean} true if the object is array-like and populated
1426              * @static
1427              * @private
1428              */
1429             _isValidCollection: function(o) {
1430                 try {
1431                     return ( o                     && // o is something
1432                              typeof o !== "string" && // o is not a string
1433                              o.length              && // o is indexed
1434                              !o.tagName            && // o is not an HTML element
1435                              !o.alert              && // o is not a window
1436                              typeof o[0] !== "undefined" );
1437                 } catch(ex) {
1438                     return false;
1439                 }
1441             },
1443             /**
1444              * @private
1445              * @property elCache
1446              * DOM element cache
1447              * @static
1448              * @deprecated Elements are not cached due to issues that arise when
1449              * elements are removed and re-added
1450              */
1451             elCache: {},
1453             /**
1454              * We cache elements bound by id because when the unload event 
1455              * fires, we can no longer use document.getElementById
1456              * @method getEl
1457              * @static
1458              * @private
1459              * @deprecated Elements are not cached any longer
1460              */
1461             getEl: function(id) {
1462                 return (typeof id === "string") ? document.getElementById(id) : id;
1463             },
1465             /**
1466              * Clears the element cache
1467              * @deprecated Elements are not cached any longer
1468              * @method clearCache
1469              * @static
1470              * @private
1471              */
1472             clearCache: function() { },
1474             /**
1475              * Custom event the fires when the dom is initially usable
1476              * @event DOMReadyEvent
1477              */
1478             DOMReadyEvent: new YAHOO.util.CustomEvent("DOMReady", this),
1480             /**
1481              * hook up any deferred listeners
1482              * @method _load
1483              * @static
1484              * @private
1485              */
1486             _load: function(e) {
1488                 if (!loadComplete) {
1489                     loadComplete = true;
1490                     var EU = YAHOO.util.Event;
1492                     // Just in case DOMReady did not go off for some reason
1493                     EU._ready();
1495                     // Available elements may not have been detected before the
1496                     // window load event fires. Try to find them now so that the
1497                     // the user is more likely to get the onAvailable notifications
1498                     // before the window load notification
1499                     EU._tryPreloadAttach();
1501                 }
1502             },
1504             /**
1505              * Fires the DOMReady event listeners the first time the document is
1506              * usable.
1507              * @method _ready
1508              * @static
1509              * @private
1510              */
1511             _ready: function(e) {
1512                 var EU = YAHOO.util.Event;
1513                 if (!EU.DOMReady) {
1514                     EU.DOMReady=true;
1516                     // Fire the content ready custom event
1517                     EU.DOMReadyEvent.fire();
1519                     // Remove the DOMContentLoaded (FF/Opera)
1520                     EU._simpleRemove(document, "DOMContentLoaded", EU._ready);
1521                 }
1522             },
1524             /**
1525              * Polling function that runs before the onload event fires, 
1526              * attempting to attach to DOM Nodes as soon as they are 
1527              * available
1528              * @method _tryPreloadAttach
1529              * @static
1530              * @private
1531              */
1532             _tryPreloadAttach: function() {
1534                 if (onAvailStack.length === 0) {
1535                     retryCount = 0;
1536                     clearInterval(this._interval);
1537                     this._interval = null;
1538                     return;
1539                 }
1541                 if (this.locked) {
1542                     return;
1543                 }
1545                 if (this.isIE) {
1546                     // Hold off if DOMReady has not fired and check current
1547                     // readyState to protect against the IE operation aborted
1548                     // issue.
1549                     if (!this.DOMReady) {
1550                         this.startInterval();
1551                         return;
1552                     }
1553                 }
1555                 this.locked = true;
1558                 // keep trying until after the page is loaded.  We need to 
1559                 // check the page load state prior to trying to bind the 
1560                 // elements so that we can be certain all elements have been 
1561                 // tested appropriately
1562                 var tryAgain = !loadComplete;
1563                 if (!tryAgain) {
1564                     tryAgain = (retryCount > 0 && onAvailStack.length > 0);
1565                 }
1567                 // onAvailable
1568                 var notAvail = [];
1570                 var executeItem = function (el, item) {
1571                     var scope = el;
1572                     if (item.override) {
1573                         if (item.override === true) {
1574                             scope = item.obj;
1575                         } else {
1576                             scope = item.override;
1577                         }
1578                     }
1579                     item.fn.call(scope, item.obj);
1580                 };
1582                 var i, len, item, el, ready=[];
1584                 // onAvailable onContentReady
1585                 for (i=0, len=onAvailStack.length; i<len; i=i+1) {
1586                     item = onAvailStack[i];
1587                     if (item) {
1588                         el = this.getEl(item.id);
1589                         if (el) {
1590                             if (item.checkReady) {
1591                                 if (loadComplete || el.nextSibling || !tryAgain) {
1592                                     ready.push(item);
1593                                     onAvailStack[i] = null;
1594                                 }
1595                             } else {
1596                                 executeItem(el, item);
1597                                 onAvailStack[i] = null;
1598                             }
1599                         } else {
1600                             notAvail.push(item);
1601                         }
1602                     }
1603                 }
1604                 
1605                 // make sure onContentReady fires after onAvailable
1606                 for (i=0, len=ready.length; i<len; i=i+1) {
1607                     item = ready[i];
1608                     executeItem(this.getEl(item.id), item);
1609                 }
1612                 retryCount--;
1614                 if (tryAgain) {
1615                     for (i=onAvailStack.length-1; i>-1; i--) {
1616                         item = onAvailStack[i];
1617                         if (!item || !item.id) {
1618                             onAvailStack.splice(i, 1);
1619                         }
1620                     }
1622                     this.startInterval();
1623                 } else {
1624                     clearInterval(this._interval);
1625                     this._interval = null;
1626                 }
1628                 this.locked = false;
1630             },
1632             /**
1633              * Removes all listeners attached to the given element via addListener.
1634              * Optionally, the node's children can also be purged.
1635              * Optionally, you can specify a specific type of event to remove.
1636              * @method purgeElement
1637              * @param {HTMLElement} el the element to purge
1638              * @param {boolean} recurse recursively purge this element's children
1639              * as well.  Use with caution.
1640              * @param {string} sType optional type of listener to purge. If
1641              * left out, all listeners will be removed
1642              * @static
1643              */
1644             purgeElement: function(el, recurse, sType) {
1645                 var oEl = (YAHOO.lang.isString(el)) ? this.getEl(el) : el;
1646                 var elListeners = this.getListeners(oEl, sType), i, len;
1647                 if (elListeners) {
1648                     for (i=elListeners.length-1; i>-1; i--) {
1649                         var l = elListeners[i];
1650                         this.removeListener(oEl, l.type, l.fn);
1651                     }
1652                 }
1654                 if (recurse && oEl && oEl.childNodes) {
1655                     for (i=0,len=oEl.childNodes.length; i<len ; ++i) {
1656                         this.purgeElement(oEl.childNodes[i], recurse, sType);
1657                     }
1658                 }
1659             },
1661             /**
1662              * Returns all listeners attached to the given element via addListener.
1663              * Optionally, you can specify a specific type of event to return.
1664              * @method getListeners
1665              * @param el {HTMLElement|string} the element or element id to inspect 
1666              * @param sType {string} optional type of listener to return. If
1667              * left out, all listeners will be returned
1668              * @return {Object} the listener. Contains the following fields:
1669              * &nbsp;&nbsp;type:   (string)   the type of event
1670              * &nbsp;&nbsp;fn:     (function) the callback supplied to addListener
1671              * &nbsp;&nbsp;obj:    (object)   the custom object supplied to addListener
1672              * &nbsp;&nbsp;adjust: (boolean|object)  whether or not to adjust the default scope
1673              * &nbsp;&nbsp;scope: (boolean)  the derived scope based on the adjust parameter
1674              * &nbsp;&nbsp;index:  (int)      its position in the Event util listener cache
1675              * @static
1676              */           
1677             getListeners: function(el, sType) {
1678                 var results=[], searchLists;
1679                 if (!sType) {
1680                     searchLists = [listeners, unloadListeners];
1681                 } else if (sType === "unload") {
1682                     searchLists = [unloadListeners];
1683                 } else {
1684                     searchLists = [listeners];
1685                 }
1687                 var oEl = (YAHOO.lang.isString(el)) ? this.getEl(el) : el;
1689                 for (var j=0;j<searchLists.length; j=j+1) {
1690                     var searchList = searchLists[j];
1691                     if (searchList) {
1692                         for (var i=0,len=searchList.length; i<len ; ++i) {
1693                             var l = searchList[i];
1694                             if ( l  && l[this.EL] === oEl && 
1695                                     (!sType || sType === l[this.TYPE]) ) {
1696                                 results.push({
1697                                     type:   l[this.TYPE],
1698                                     fn:     l[this.FN],
1699                                     obj:    l[this.OBJ],
1700                                     adjust: l[this.OVERRIDE],
1701                                     scope:  l[this.ADJ_SCOPE],
1702                                     index:  i
1703                                 });
1704                             }
1705                         }
1706                     }
1707                 }
1709                 return (results.length) ? results : null;
1710             },
1712             /**
1713              * Removes all listeners registered by pe.event.  Called 
1714              * automatically during the unload event.
1715              * @method _unload
1716              * @static
1717              * @private
1718              */
1719             _unload: function(e) {
1721                 var EU = YAHOO.util.Event, i, j, l, len, index,
1722                          ul = unloadListeners.slice();
1724                 // execute and clear stored unload listeners
1725                 for (i=0,len=unloadListeners.length; i<len; ++i) {
1726                     l = ul[i];
1727                     if (l) {
1728                         var scope = window;
1729                         if (l[EU.ADJ_SCOPE]) {
1730                             if (l[EU.ADJ_SCOPE] === true) {
1731                                 scope = l[EU.UNLOAD_OBJ];
1732                             } else {
1733                                 scope = l[EU.ADJ_SCOPE];
1734                             }
1735                         }
1736                         l[EU.FN].call(scope, EU.getEvent(e, l[EU.EL]), l[EU.UNLOAD_OBJ] );
1737                         ul[i] = null;
1738                         l=null;
1739                         scope=null;
1740                     }
1741                 }
1743                 unloadListeners = null;
1745                 // Remove listeners to handle IE memory leaks
1746                 //if (YAHOO.env.ua.ie && listeners && listeners.length > 0) {
1747                 
1748                 // 2.5.0 listeners are removed for all browsers again.  FireFox preserves
1749                 // at least some listeners between page refreshes, potentially causing
1750                 // errors during page load (mouseover listeners firing before they
1751                 // should if the user moves the mouse at the correct moment).
1752                 if (listeners) {
1753                     for (j=listeners.length-1; j>-1; j--) {
1754                         l = listeners[j];
1755                         if (l) {
1756                             EU.removeListener(l[EU.EL], l[EU.TYPE], l[EU.FN], j);
1757                         } 
1758                     }
1759                     l=null;
1760                 }
1762                 legacyEvents = null;
1764                 EU._simpleRemove(window, "unload", EU._unload);
1766             },
1768             /**
1769              * Returns scrollLeft
1770              * @method _getScrollLeft
1771              * @static
1772              * @private
1773              */
1774             _getScrollLeft: function() {
1775                 return this._getScroll()[1];
1776             },
1778             /**
1779              * Returns scrollTop
1780              * @method _getScrollTop
1781              * @static
1782              * @private
1783              */
1784             _getScrollTop: function() {
1785                 return this._getScroll()[0];
1786             },
1788             /**
1789              * Returns the scrollTop and scrollLeft.  Used to calculate the 
1790              * pageX and pageY in Internet Explorer
1791              * @method _getScroll
1792              * @static
1793              * @private
1794              */
1795             _getScroll: function() {
1796                 var dd = document.documentElement, db = document.body;
1797                 if (dd && (dd.scrollTop || dd.scrollLeft)) {
1798                     return [dd.scrollTop, dd.scrollLeft];
1799                 } else if (db) {
1800                     return [db.scrollTop, db.scrollLeft];
1801                 } else {
1802                     return [0, 0];
1803                 }
1804             },
1805             
1806             /**
1807              * Used by old versions of CustomEvent, restored for backwards
1808              * compatibility
1809              * @method regCE
1810              * @private
1811              * @static
1812              * @deprecated still here for backwards compatibility
1813              */
1814             regCE: function() {
1815                 // does nothing
1816             },
1818             /**
1819              * Adds a DOM event directly without the caching, cleanup, scope adj, etc
1820              *
1821              * @method _simpleAdd
1822              * @param {HTMLElement} el      the element to bind the handler to
1823              * @param {string}      sType   the type of event handler
1824              * @param {function}    fn      the callback to invoke
1825              * @param {boolen}      capture capture or bubble phase
1826              * @static
1827              * @private
1828              */
1829             _simpleAdd: function () {
1830                 if (window.addEventListener) {
1831                     return function(el, sType, fn, capture) {
1832                         el.addEventListener(sType, fn, (capture));
1833                     };
1834                 } else if (window.attachEvent) {
1835                     return function(el, sType, fn, capture) {
1836                         el.attachEvent("on" + sType, fn);
1837                     };
1838                 } else {
1839                     return function(){};
1840                 }
1841             }(),
1843             /**
1844              * Basic remove listener
1845              *
1846              * @method _simpleRemove
1847              * @param {HTMLElement} el      the element to bind the handler to
1848              * @param {string}      sType   the type of event handler
1849              * @param {function}    fn      the callback to invoke
1850              * @param {boolen}      capture capture or bubble phase
1851              * @static
1852              * @private
1853              */
1854             _simpleRemove: function() {
1855                 if (window.removeEventListener) {
1856                     return function (el, sType, fn, capture) {
1857                         el.removeEventListener(sType, fn, (capture));
1858                     };
1859                 } else if (window.detachEvent) {
1860                     return function (el, sType, fn) {
1861                         el.detachEvent("on" + sType, fn);
1862                     };
1863                 } else {
1864                     return function(){};
1865                 }
1866             }()
1867         };
1869     }();
1871     (function() {
1872         var EU = YAHOO.util.Event;
1874         /**
1875          * YAHOO.util.Event.on is an alias for addListener
1876          * @method on
1877          * @see addListener
1878          * @static
1879          */
1880         EU.on = EU.addListener;
1882 /*! DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller */
1884         // Internet Explorer: use the readyState of a defered script.
1885         // This isolates what appears to be a safe moment to manipulate
1886         // the DOM prior to when the document's readyState suggests
1887         // it is safe to do so.
1888         if (EU.isIE) {
1890             // Process onAvailable/onContentReady items when the 
1891             // DOM is ready.
1892             YAHOO.util.Event.onDOMReady(
1893                     YAHOO.util.Event._tryPreloadAttach,
1894                     YAHOO.util.Event, true);
1895             
1896             var n = document.createElement('p');  
1898             EU._dri = setInterval(function() {
1899                 try {
1900                     // throws an error if doc is not ready
1901                     n.doScroll('left');
1902                     clearInterval(EU._dri);
1903                     EU._dri = null;
1904                     EU._ready();
1905                     n = null;
1906                 } catch (ex) { 
1907                 }
1908             }, EU.POLL_INTERVAL); 
1910         
1911         // The document's readyState in Safari currently will
1912         // change to loaded/complete before images are loaded.
1913         } else if (EU.webkit && EU.webkit < 525) {
1915             EU._dri = setInterval(function() {
1916                 var rs=document.readyState;
1917                 if ("loaded" == rs || "complete" == rs) {
1918                     clearInterval(EU._dri);
1919                     EU._dri = null;
1920                     EU._ready();
1921                 }
1922             }, EU.POLL_INTERVAL); 
1924         // FireFox and Opera: These browsers provide a event for this
1925         // moment.  The latest WebKit releases now support this event.
1926         } else {
1928             EU._simpleAdd(document, "DOMContentLoaded", EU._ready);
1930         }
1931         /////////////////////////////////////////////////////////////
1934         EU._simpleAdd(window, "load", EU._load);
1935         EU._simpleAdd(window, "unload", EU._unload);
1936         EU._tryPreloadAttach();
1937     })();
1941  * EventProvider is designed to be used with YAHOO.augment to wrap 
1942  * CustomEvents in an interface that allows events to be subscribed to 
1943  * and fired by name.  This makes it possible for implementing code to
1944  * subscribe to an event that either has not been created yet, or will
1945  * not be created at all.
1947  * @Class EventProvider
1948  */
1949 YAHOO.util.EventProvider = function() { };
1951 YAHOO.util.EventProvider.prototype = {
1953     /**
1954      * Private storage of custom events
1955      * @property __yui_events
1956      * @type Object[]
1957      * @private
1958      */
1959     __yui_events: null,
1961     /**
1962      * Private storage of custom event subscribers
1963      * @property __yui_subscribers
1964      * @type Object[]
1965      * @private
1966      */
1967     __yui_subscribers: null,
1968     
1969     /**
1970      * Subscribe to a CustomEvent by event type
1971      *
1972      * @method subscribe
1973      * @param p_type     {string}   the type, or name of the event
1974      * @param p_fn       {function} the function to exectute when the event fires
1975      * @param p_obj      {Object}   An object to be passed along when the event 
1976      *                              fires
1977      * @param p_override {boolean}  If true, the obj passed in becomes the 
1978      *                              execution scope of the listener
1979      */
1980     subscribe: function(p_type, p_fn, p_obj, p_override) {
1982         this.__yui_events = this.__yui_events || {};
1983         var ce = this.__yui_events[p_type];
1985         if (ce) {
1986             ce.subscribe(p_fn, p_obj, p_override);
1987         } else {
1988             this.__yui_subscribers = this.__yui_subscribers || {};
1989             var subs = this.__yui_subscribers;
1990             if (!subs[p_type]) {
1991                 subs[p_type] = [];
1992             }
1993             subs[p_type].push(
1994                 { fn: p_fn, obj: p_obj, override: p_override } );
1995         }
1996     },
1998     /**
1999      * Unsubscribes one or more listeners the from the specified event
2000      * @method unsubscribe
2001      * @param p_type {string}   The type, or name of the event.  If the type
2002      *                          is not specified, it will attempt to remove
2003      *                          the listener from all hosted events.
2004      * @param p_fn   {Function} The subscribed function to unsubscribe, if not
2005      *                          supplied, all subscribers will be removed.
2006      * @param p_obj  {Object}   The custom object passed to subscribe.  This is
2007      *                        optional, but if supplied will be used to
2008      *                        disambiguate multiple listeners that are the same
2009      *                        (e.g., you subscribe many object using a function
2010      *                        that lives on the prototype)
2011      * @return {boolean} true if the subscriber was found and detached.
2012      */
2013     unsubscribe: function(p_type, p_fn, p_obj) {
2014         this.__yui_events = this.__yui_events || {};
2015         var evts = this.__yui_events;
2016         if (p_type) {
2017             var ce = evts[p_type];
2018             if (ce) {
2019                 return ce.unsubscribe(p_fn, p_obj);
2020             }
2021         } else {
2022             var ret = true;
2023             for (var i in evts) {
2024                 if (YAHOO.lang.hasOwnProperty(evts, i)) {
2025                     ret = ret && evts[i].unsubscribe(p_fn, p_obj);
2026                 }
2027             }
2028             return ret;
2029         }
2031         return false;
2032     },
2033     
2034     /**
2035      * Removes all listeners from the specified event.  If the event type
2036      * is not specified, all listeners from all hosted custom events will
2037      * be removed.
2038      * @method unsubscribeAll
2039      * @param p_type {string}   The type, or name of the event
2040      */
2041     unsubscribeAll: function(p_type) {
2042         return this.unsubscribe(p_type);
2043     },
2045     /**
2046      * Creates a new custom event of the specified type.  If a custom event
2047      * by that name already exists, it will not be re-created.  In either
2048      * case the custom event is returned. 
2049      *
2050      * @method createEvent
2051      *
2052      * @param p_type {string} the type, or name of the event
2053      * @param p_config {object} optional config params.  Valid properties are:
2054      *
2055      *  <ul>
2056      *    <li>
2057      *      scope: defines the default execution scope.  If not defined
2058      *      the default scope will be this instance.
2059      *    </li>
2060      *    <li>
2061      *      silent: if true, the custom event will not generate log messages.
2062      *      This is false by default.
2063      *    </li>
2064      *    <li>
2065      *      onSubscribeCallback: specifies a callback to execute when the
2066      *      event has a new subscriber.  This will fire immediately for
2067      *      each queued subscriber if any exist prior to the creation of
2068      *      the event.
2069      *    </li>
2070      *  </ul>
2071      *
2072      *  @return {CustomEvent} the custom event
2073      *
2074      */
2075     createEvent: function(p_type, p_config) {
2077         this.__yui_events = this.__yui_events || {};
2078         var opts = p_config || {};
2079         var events = this.__yui_events;
2081         if (events[p_type]) {
2082         } else {
2084             var scope  = opts.scope  || this;
2085             var silent = (opts.silent);
2087             var ce = new YAHOO.util.CustomEvent(p_type, scope, silent,
2088                     YAHOO.util.CustomEvent.FLAT);
2089             events[p_type] = ce;
2091             if (opts.onSubscribeCallback) {
2092                 ce.subscribeEvent.subscribe(opts.onSubscribeCallback);
2093             }
2095             this.__yui_subscribers = this.__yui_subscribers || {};
2096             var qs = this.__yui_subscribers[p_type];
2098             if (qs) {
2099                 for (var i=0; i<qs.length; ++i) {
2100                     ce.subscribe(qs[i].fn, qs[i].obj, qs[i].override);
2101                 }
2102             }
2103         }
2105         return events[p_type];
2106     },
2109    /**
2110      * Fire a custom event by name.  The callback functions will be executed
2111      * from the scope specified when the event was created, and with the 
2112      * following parameters:
2113      *   <ul>
2114      *   <li>The first argument fire() was executed with</li>
2115      *   <li>The custom object (if any) that was passed into the subscribe() 
2116      *       method</li>
2117      *   </ul>
2118      * @method fireEvent
2119      * @param p_type    {string}  the type, or name of the event
2120      * @param arguments {Object*} an arbitrary set of parameters to pass to 
2121      *                            the handler.
2122      * @return {boolean} the return value from CustomEvent.fire
2123      *                   
2124      */
2125     fireEvent: function(p_type, arg1, arg2, etc) {
2127         this.__yui_events = this.__yui_events || {};
2128         var ce = this.__yui_events[p_type];
2130         if (!ce) {
2131             return null;
2132         }
2134         var args = [];
2135         for (var i=1; i<arguments.length; ++i) {
2136             args.push(arguments[i]);
2137         }
2138         return ce.fire.apply(ce, args);
2139     },
2141     /**
2142      * Returns true if the custom event of the provided type has been created
2143      * with createEvent.
2144      * @method hasEvent
2145      * @param type {string} the type, or name of the event
2146      */
2147     hasEvent: function(type) {
2148         if (this.__yui_events) {
2149             if (this.__yui_events[type]) {
2150                 return true;
2151             }
2152         }
2153         return false;
2154     }
2159 * KeyListener is a utility that provides an easy interface for listening for
2160 * keydown/keyup events fired against DOM elements.
2161 * @namespace YAHOO.util
2162 * @class KeyListener
2163 * @constructor
2164 * @param {HTMLElement} attachTo The element or element ID to which the key 
2165 *                               event should be attached
2166 * @param {String}      attachTo The element or element ID to which the key
2167 *                               event should be attached
2168 * @param {Object}      keyData  The object literal representing the key(s) 
2169 *                               to detect. Possible attributes are 
2170 *                               shift(boolean), alt(boolean), ctrl(boolean) 
2171 *                               and keys(either an int or an array of ints 
2172 *                               representing keycodes).
2173 * @param {Function}    handler  The CustomEvent handler to fire when the 
2174 *                               key event is detected
2175 * @param {Object}      handler  An object literal representing the handler. 
2176 * @param {String}      event    Optional. The event (keydown or keyup) to 
2177 *                               listen for. Defaults automatically to keydown.
2179 * @knownissue the "keypress" event is completely broken in Safari 2.x and below.
2180 *             the workaround is use "keydown" for key listening.  However, if
2181 *             it is desired to prevent the default behavior of the keystroke,
2182 *             that can only be done on the keypress event.  This makes key
2183 *             handling quite ugly.
2184 * @knownissue keydown is also broken in Safari 2.x and below for the ESC key.
2185 *             There currently is no workaround other than choosing another
2186 *             key to listen for.
2188 YAHOO.util.KeyListener = function(attachTo, keyData, handler, event) {
2189     if (!attachTo) {
2190     } else if (!keyData) {
2191     } else if (!handler) {
2192     } 
2193     
2194     if (!event) {
2195         event = YAHOO.util.KeyListener.KEYDOWN;
2196     }
2198     /**
2199     * The CustomEvent fired internally when a key is pressed
2200     * @event keyEvent
2201     * @private
2202     * @param {Object} keyData The object literal representing the key(s) to 
2203     *                         detect. Possible attributes are shift(boolean), 
2204     *                         alt(boolean), ctrl(boolean) and keys(either an 
2205     *                         int or an array of ints representing keycodes).
2206     */
2207     var keyEvent = new YAHOO.util.CustomEvent("keyPressed");
2208     
2209     /**
2210     * The CustomEvent fired when the KeyListener is enabled via the enable() 
2211     * function
2212     * @event enabledEvent
2213     * @param {Object} keyData The object literal representing the key(s) to 
2214     *                         detect. Possible attributes are shift(boolean), 
2215     *                         alt(boolean), ctrl(boolean) and keys(either an 
2216     *                         int or an array of ints representing keycodes).
2217     */
2218     this.enabledEvent = new YAHOO.util.CustomEvent("enabled");
2220     /**
2221     * The CustomEvent fired when the KeyListener is disabled via the 
2222     * disable() function
2223     * @event disabledEvent
2224     * @param {Object} keyData The object literal representing the key(s) to 
2225     *                         detect. Possible attributes are shift(boolean), 
2226     *                         alt(boolean), ctrl(boolean) and keys(either an 
2227     *                         int or an array of ints representing keycodes).
2228     */
2229     this.disabledEvent = new YAHOO.util.CustomEvent("disabled");
2231     if (typeof attachTo == 'string') {
2232         attachTo = document.getElementById(attachTo);
2233     }
2235     if (typeof handler == 'function') {
2236         keyEvent.subscribe(handler);
2237     } else {
2238         keyEvent.subscribe(handler.fn, handler.scope, handler.correctScope);
2239     }
2241     /**
2242     * Handles the key event when a key is pressed.
2243     * @method handleKeyPress
2244     * @param {DOMEvent} e   The keypress DOM event
2245     * @param {Object}   obj The DOM event scope object
2246     * @private
2247     */
2248     function handleKeyPress(e, obj) {
2249         if (! keyData.shift) {  
2250             keyData.shift = false; 
2251         }
2252         if (! keyData.alt) {    
2253             keyData.alt = false;
2254         }
2255         if (! keyData.ctrl) {
2256             keyData.ctrl = false;
2257         }
2259         // check held down modifying keys first
2260         if (e.shiftKey == keyData.shift && 
2261             e.altKey   == keyData.alt &&
2262             e.ctrlKey  == keyData.ctrl) { // if we pass this, all modifiers match
2263             
2264             var dataItem;
2266             if (keyData.keys instanceof Array) {
2267                 for (var i=0;i<keyData.keys.length;i++) {
2268                     dataItem = keyData.keys[i];
2270                     if (dataItem == e.charCode ) {
2271                         keyEvent.fire(e.charCode, e);
2272                         break;
2273                     } else if (dataItem == e.keyCode) {
2274                         keyEvent.fire(e.keyCode, e);
2275                         break;
2276                     }
2277                 }
2278             } else {
2279                 dataItem = keyData.keys;
2280                 if (dataItem == e.charCode ) {
2281                     keyEvent.fire(e.charCode, e);
2282                 } else if (dataItem == e.keyCode) {
2283                     keyEvent.fire(e.keyCode, e);
2284                 }
2285             }
2286         }
2287     }
2289     /**
2290     * Enables the KeyListener by attaching the DOM event listeners to the 
2291     * target DOM element
2292     * @method enable
2293     */
2294     this.enable = function() {
2295         if (! this.enabled) {
2296             YAHOO.util.Event.addListener(attachTo, event, handleKeyPress);
2297             this.enabledEvent.fire(keyData);
2298         }
2299         /**
2300         * Boolean indicating the enabled/disabled state of the Tooltip
2301         * @property enabled
2302         * @type Boolean
2303         */
2304         this.enabled = true;
2305     };
2307     /**
2308     * Disables the KeyListener by removing the DOM event listeners from the 
2309     * target DOM element
2310     * @method disable
2311     */
2312     this.disable = function() {
2313         if (this.enabled) {
2314             YAHOO.util.Event.removeListener(attachTo, event, handleKeyPress);
2315             this.disabledEvent.fire(keyData);
2316         }
2317         this.enabled = false;
2318     };
2320     /**
2321     * Returns a String representation of the object.
2322     * @method toString
2323     * @return {String}  The string representation of the KeyListener
2324     */ 
2325     this.toString = function() {
2326         return "KeyListener [" + keyData.keys + "] " + attachTo.tagName + 
2327                 (attachTo.id ? "[" + attachTo.id + "]" : "");
2328     };
2333 * Constant representing the DOM "keydown" event.
2334 * @property YAHOO.util.KeyListener.KEYDOWN
2335 * @static
2336 * @final
2337 * @type String
2339 YAHOO.util.KeyListener.KEYDOWN = "keydown";
2342 * Constant representing the DOM "keyup" event.
2343 * @property YAHOO.util.KeyListener.KEYUP
2344 * @static
2345 * @final
2346 * @type String
2348 YAHOO.util.KeyListener.KEYUP = "keyup";
2351  * keycode constants for a subset of the special keys
2352  * @property KEY
2353  * @static
2354  * @final
2355  */
2356 YAHOO.util.KeyListener.KEY = {
2357     ALT          : 18,
2358     BACK_SPACE   : 8,
2359     CAPS_LOCK    : 20,
2360     CONTROL      : 17,
2361     DELETE       : 46,
2362     DOWN         : 40,
2363     END          : 35,
2364     ENTER        : 13,
2365     ESCAPE       : 27,
2366     HOME         : 36,
2367     LEFT         : 37,
2368     META         : 224,
2369     NUM_LOCK     : 144,
2370     PAGE_DOWN    : 34,
2371     PAGE_UP      : 33, 
2372     PAUSE        : 19,
2373     PRINTSCREEN  : 44,
2374     RIGHT        : 39,
2375     SCROLL_LOCK  : 145,
2376     SHIFT        : 16,
2377     SPACE        : 32,
2378     TAB          : 9,
2379     UP           : 38
2381 YAHOO.register("event", YAHOO.util.Event, {version: "2.5.2", build: "1076"});