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