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