Project

General

Profile

1
/*
2
Copyright (c) 2007, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.4.1
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
        var len=this.subscribers.length;
218
        if (!len && this.silent) {
219
            return true;
220
        }
221

    
222
        var args=[], ret=true, i, rebuild=false;
223

    
224
        for (i=0; i<arguments.length; ++i) {
225
            args.push(arguments[i]);
226
        }
227

    
228
        if (!this.silent) {
229
        }
230

    
231
        for (i=0; i<len; ++i) {
232
            var s = this.subscribers[i];
233
            if (!s) {
234
                rebuild=true;
235
            } else {
236
                if (!this.silent) {
237
                }
238

    
239
                var scope = s.getScope(this.scope);
240

    
241
                if (this.signature == YAHOO.util.CustomEvent.FLAT) {
242
                    var param = null;
243
                    if (args.length > 0) {
244
                        param = args[0];
245
                    }
246

    
247
                    try {
248
                        ret = s.fn.call(scope, param, s.obj);
249
                    } catch(e) {
250
                        this.lastError = e;
251
                    }
252
                } else {
253
                    try {
254
                        ret = s.fn.call(scope, this.type, args, s.obj);
255
                    } catch(ex) {
256
                        this.lastError = ex;
257
                    }
258
                }
259
                if (false === ret) {
260
                    if (!this.silent) {
261
                    }
262

    
263
                    //break;
264
                    return false;
265
                }
266
            }
267
        }
268

    
269
        if (rebuild) {
270
            var newlist=[],subs=this.subscribers;
271
            for (i=0,len=subs.length; i<len; i=i+1) {
272
                newlist.push(subs[i]);
273
            }
274

    
275
            this.subscribers=newlist;
276
        }
277

    
278
        return true;
279
    },
280

    
281
    /**
282
     * Removes all listeners
283
     * @method unsubscribeAll
284
     * @return {int} The number of listeners unsubscribed
285
     */
286
    unsubscribeAll: function() {
287
        for (var i=0, len=this.subscribers.length; i<len; ++i) {
288
            this._delete(len - 1 - i);
289
        }
290

    
291
        this.subscribers=[];
292

    
293
        return i;
294
    },
295

    
296
    /**
297
     * @method _delete
298
     * @private
299
     */
300
    _delete: function(index) {
301
        var s = this.subscribers[index];
302
        if (s) {
303
            delete s.fn;
304
            delete s.obj;
305
        }
306

    
307
        this.subscribers[index]=null;
308
    },
309

    
310
    /**
311
     * @method toString
312
     */
313
    toString: function() {
314
         return "CustomEvent: " + "'" + this.type  + "', " + 
315
             "scope: " + this.scope;
316

    
317
    }
318
};
319

    
320
/////////////////////////////////////////////////////////////////////
321

    
322
/**
323
 * Stores the subscriber information to be used when the event fires.
324
 * @param {Function} fn       The function to execute
325
 * @param {Object}   obj      An object to be passed along when the event fires
326
 * @param {boolean}  override If true, the obj passed in becomes the execution
327
 *                            scope of the listener
328
 * @class Subscriber
329
 * @constructor
330
 */
331
YAHOO.util.Subscriber = function(fn, obj, override) {
332

    
333
    /**
334
     * The callback that will be execute when the event fires
335
     * @property fn
336
     * @type function
337
     */
338
    this.fn = fn;
339

    
340
    /**
341
     * An optional custom object that will passed to the callback when
342
     * the event fires
343
     * @property obj
344
     * @type object
345
     */
346
    this.obj = YAHOO.lang.isUndefined(obj) ? null : obj;
347

    
348
    /**
349
     * The default execution scope for the event listener is defined when the
350
     * event is created (usually the object which contains the event).
351
     * By setting override to true, the execution scope becomes the custom
352
     * object passed in by the subscriber.  If override is an object, that 
353
     * object becomes the scope.
354
     * @property override
355
     * @type boolean|object
356
     */
357
    this.override = override;
358

    
359
};
360

    
361
/**
362
 * Returns the execution scope for this listener.  If override was set to true
363
 * the custom obj will be the scope.  If override is an object, that is the
364
 * scope, otherwise the default scope will be used.
365
 * @method getScope
366
 * @param {Object} defaultScope the scope to use if this listener does not
367
 *                              override it.
368
 */
369
YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
370
    if (this.override) {
371
        if (this.override === true) {
372
            return this.obj;
373
        } else {
374
            return this.override;
375
        }
376
    }
377
    return defaultScope;
378
};
379

    
380
/**
381
 * Returns true if the fn and obj match this objects properties.
382
 * Used by the unsubscribe method to match the right subscriber.
383
 *
384
 * @method contains
385
 * @param {Function} fn the function to execute
386
 * @param {Object} obj an object to be passed along when the event fires
387
 * @return {boolean} true if the supplied arguments match this 
388
 *                   subscriber's signature.
389
 */
390
YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {
391
    if (obj) {
392
        return (this.fn == fn && this.obj == obj);
393
    } else {
394
        return (this.fn == fn);
395
    }
396
};
397

    
398
/**
399
 * @method toString
400
 */
401
YAHOO.util.Subscriber.prototype.toString = function() {
402
    return "Subscriber { obj: " + this.obj  + 
403
           ", override: " +  (this.override || "no") + " }";
404
};
405

    
406
/**
407
 * The Event Utility provides utilities for managing DOM Events and tools
408
 * for building event systems
409
 *
410
 * @module event
411
 * @title Event Utility
412
 * @namespace YAHOO.util
413
 * @requires yahoo
414
 */
415

    
416
// The first instance of Event will win if it is loaded more than once.
417
// @TODO this needs to be changed so that only the state data that needs to
418
// be preserved is kept, while methods are overwritten/added as needed.
419
// This means that the module pattern can't be used.
420
if (!YAHOO.util.Event) {
421

    
422
/**
423
 * The event utility provides functions to add and remove event listeners,
424
 * event cleansing.  It also tries to automatically remove listeners it
425
 * registers during the unload event.
426
 *
427
 * @class Event
428
 * @static
429
 */
430
    YAHOO.util.Event = function() {
431

    
432
        /**
433
         * True after the onload event has fired
434
         * @property loadComplete
435
         * @type boolean
436
         * @static
437
         * @private
438
         */
439
        var loadComplete =  false;
440

    
441
        /**
442
         * Cache of wrapped listeners
443
         * @property listeners
444
         * @type array
445
         * @static
446
         * @private
447
         */
448
        var listeners = [];
449

    
450
        /**
451
         * User-defined unload function that will be fired before all events
452
         * are detached
453
         * @property unloadListeners
454
         * @type array
455
         * @static
456
         * @private
457
         */
458
        var unloadListeners = [];
459

    
460
        /**
461
         * Cache of DOM0 event handlers to work around issues with DOM2 events
462
         * in Safari
463
         * @property legacyEvents
464
         * @static
465
         * @private
466
         */
467
        var legacyEvents = [];
468

    
469
        /**
470
         * Listener stack for DOM0 events
471
         * @property legacyHandlers
472
         * @static
473
         * @private
474
         */
475
        var legacyHandlers = [];
476

    
477
        /**
478
         * The number of times to poll after window.onload.  This number is
479
         * increased if additional late-bound handlers are requested after
480
         * the page load.
481
         * @property retryCount
482
         * @static
483
         * @private
484
         */
485
        var retryCount = 0;
486

    
487
        /**
488
         * onAvailable listeners
489
         * @property onAvailStack
490
         * @static
491
         * @private
492
         */
493
        var onAvailStack = [];
494

    
495
        /**
496
         * Lookup table for legacy events
497
         * @property legacyMap
498
         * @static
499
         * @private
500
         */
501
        var legacyMap = [];
502

    
503
        /**
504
         * Counter for auto id generation
505
         * @property counter
506
         * @static
507
         * @private
508
         */
509
        var counter = 0;
510
        
511
        /**
512
         * Normalized keycodes for webkit/safari
513
         * @property webkitKeymap
514
         * @type {int: int}
515
         * @private
516
         * @static
517
         * @final
518
         */
519
        var webkitKeymap = {
520
            63232: 38, // up
521
            63233: 40, // down
522
            63234: 37, // left
523
            63235: 39, // right
524
            63276: 33, // page up
525
            63277: 34, // page down
526
            25: 9      // SHIFT-TAB (Safari provides a different key code in
527
                       // this case, even though the shiftKey modifier is set)
528
        };
529

    
530
        return {
531

    
532
            /**
533
             * The number of times we should look for elements that are not
534
             * in the DOM at the time the event is requested after the document
535
             * has been loaded.  The default is 4000@amp;10 ms, so it will poll
536
             * for 40 seconds or until all outstanding handlers are bound
537
             * (whichever comes first).
538
             * @property POLL_RETRYS
539
             * @type int
540
             * @static
541
             * @final
542
             */
543
            POLL_RETRYS: 4000,
544

    
545
            /**
546
             * The poll interval in milliseconds
547
             * @property POLL_INTERVAL
548
             * @type int
549
             * @static
550
             * @final
551
             */
552
            POLL_INTERVAL: 10,
553

    
554
            /**
555
             * Element to bind, int constant
556
             * @property EL
557
             * @type int
558
             * @static
559
             * @final
560
             */
561
            EL: 0,
562

    
563
            /**
564
             * Type of event, int constant
565
             * @property TYPE
566
             * @type int
567
             * @static
568
             * @final
569
             */
570
            TYPE: 1,
571

    
572
            /**
573
             * Function to execute, int constant
574
             * @property FN
575
             * @type int
576
             * @static
577
             * @final
578
             */
579
            FN: 2,
580

    
581
            /**
582
             * Function wrapped for scope correction and cleanup, int constant
583
             * @property WFN
584
             * @type int
585
             * @static
586
             * @final
587
             */
588
            WFN: 3,
589

    
590
            /**
591
             * Object passed in by the user that will be returned as a 
592
             * parameter to the callback, int constant.  Specific to
593
             * unload listeners
594
             * @property OBJ
595
             * @type int
596
             * @static
597
             * @final
598
             */
599
            UNLOAD_OBJ: 3,
600

    
601
            /**
602
             * Adjusted scope, either the element we are registering the event
603
             * on or the custom object passed in by the listener, int constant
604
             * @property ADJ_SCOPE
605
             * @type int
606
             * @static
607
             * @final
608
             */
609
            ADJ_SCOPE: 4,
610

    
611
            /**
612
             * The original obj passed into addListener
613
             * @property OBJ
614
             * @type int
615
             * @static
616
             * @final
617
             */
618
            OBJ: 5,
619

    
620
            /**
621
             * The original scope parameter passed into addListener
622
             * @property OVERRIDE
623
             * @type int
624
             * @static
625
             * @final
626
             */
627
            OVERRIDE: 6,
628

    
629
            /**
630
             * addListener/removeListener can throw errors in unexpected scenarios.
631
             * These errors are suppressed, the method returns false, and this property
632
             * is set
633
             * @property lastError
634
             * @static
635
             * @type Error
636
             */
637
            lastError: null,
638

    
639
            /**
640
             * Safari detection
641
             * @property isSafari
642
             * @private
643
             * @static
644
             * @deprecated use YAHOO.env.ua.webkit
645
             */
646
            isSafari: YAHOO.env.ua.webkit,
647
            
648
            /**
649
             * webkit version
650
             * @property webkit
651
             * @type string
652
             * @private
653
             * @static
654
             * @deprecated use YAHOO.env.ua.webkit
655
             */
656
            webkit: YAHOO.env.ua.webkit,
657
            
658
            /**
659
             * IE detection 
660
             * @property isIE
661
             * @private
662
             * @static
663
             * @deprecated use YAHOO.env.ua.ie
664
             */
665
            isIE: YAHOO.env.ua.ie,
666

    
667
            /**
668
             * poll handle
669
             * @property _interval
670
             * @static
671
             * @private
672
             */
673
            _interval: null,
674

    
675
            /**
676
             * document readystate poll handle
677
             * @property _dri
678
             * @static
679
             * @private
680
             */
681
             _dri: null,
682

    
683
            /**
684
             * True when the document is initially usable
685
             * @property DOMReady
686
             * @type boolean
687
             * @static
688
             */
689
            DOMReady: false,
690

    
691
            /**
692
             * @method startInterval
693
             * @static
694
             * @private
695
             */
696
            startInterval: function() {
697
                if (!this._interval) {
698
                    var self = this;
699
                    var callback = function() { self._tryPreloadAttach(); };
700
                    this._interval = setInterval(callback, this.POLL_INTERVAL);
701
                }
702
            },
703

    
704
            /**
705
             * Executes the supplied callback when the item with the supplied
706
             * id is found.  This is meant to be used to execute behavior as
707
             * soon as possible as the page loads.  If you use this after the
708
             * initial page load it will poll for a fixed time for the element.
709
             * The number of times it will poll and the frequency are
710
             * configurable.  By default it will poll for 10 seconds.
711
             *
712
             * <p>The callback is executed with a single parameter:
713
             * the custom object parameter, if provided.</p>
714
             *
715
             * @method onAvailable
716
             *
717
             * @param {string||string[]}   p_id the id of the element, or an array
718
             * of ids to look for.
719
             * @param {function} p_fn what to execute when the element is found.
720
             * @param {object}   p_obj an optional object to be passed back as
721
             *                   a parameter to p_fn.
722
             * @param {boolean|object}  p_override If set to true, p_fn will execute
723
             *                   in the scope of p_obj, if set to an object it
724
             *                   will execute in the scope of that object
725
             * @param checkContent {boolean} check child node readiness (onContentReady)
726
             * @static
727
             */
728
            onAvailable: function(p_id, p_fn, p_obj, p_override, checkContent) {
729

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

    
732
                for (var i=0; i<a.length; i=i+1) {
733
                    onAvailStack.push({id:         a[i], 
734
                                       fn:         p_fn, 
735
                                       obj:        p_obj, 
736
                                       override:   p_override, 
737
                                       checkReady: checkContent });
738
                }
739
                retryCount = this.POLL_RETRYS;
740
                this.startInterval();
741
            },
742

    
743
            /**
744
             * Works the same way as onAvailable, but additionally checks the
745
             * state of sibling elements to determine if the content of the
746
             * available element is safe to modify.
747
             *
748
             * <p>The callback is executed with a single parameter:
749
             * the custom object parameter, if provided.</p>
750
             *
751
             * @method onContentReady
752
             *
753
             * @param {string}   p_id the id of the element to look for.
754
             * @param {function} p_fn what to execute when the element is ready.
755
             * @param {object}   p_obj an optional object to be passed back as
756
             *                   a parameter to p_fn.
757
             * @param {boolean|object}  p_override If set to true, p_fn will execute
758
             *                   in the scope of p_obj.  If an object, p_fn will
759
             *                   exectute in the scope of that object
760
             *
761
             * @static
762
             */
763
            onContentReady: function(p_id, p_fn, p_obj, p_override) {
764
                this.onAvailable(p_id, p_fn, p_obj, p_override, true);
765
            },
766

    
767
            /**
768
             * Executes the supplied callback when the DOM is first usable.  This
769
             * will execute immediately if called after the DOMReady event has
770
             * fired.   @todo the DOMContentReady event does not fire when the
771
             * script is dynamically injected into the page.  This means the
772
             * DOMReady custom event will never fire in FireFox or Opera when the
773
             * library is injected.  It _will_ fire in Safari, and the IE 
774
             * implementation would allow for us to fire it if the defered script
775
             * is not available.  We want this to behave the same in all browsers.
776
             * Is there a way to identify when the script has been injected 
777
             * instead of included inline?  Is there a way to know whether the 
778
             * window onload event has fired without having had a listener attached 
779
             * to it when it did so?
780
             *
781
             * <p>The callback is a CustomEvent, so the signature is:</p>
782
             * <p>type &lt;string&gt;, args &lt;array&gt;, customobject &lt;object&gt;</p>
783
             * <p>For DOMReady events, there are no fire argments, so the
784
             * signature is:</p>
785
             * <p>"DOMReady", [], obj</p>
786
             *
787
             *
788
             * @method onDOMReady
789
             *
790
             * @param {function} p_fn what to execute when the element is found.
791
             * @param {object}   p_obj an optional object to be passed back as
792
             *                   a parameter to p_fn.
793
             * @param {boolean|object}  p_scope If set to true, p_fn will execute
794
             *                   in the scope of p_obj, if set to an object it
795
             *                   will execute in the scope of that object
796
             *
797
             * @static
798
             */
799
            onDOMReady: function(p_fn, p_obj, p_override) {
800
                if (this.DOMReady) {
801
                    setTimeout(function() {
802
                        var s = window;
803
                        if (p_override) {
804
                            if (p_override === true) {
805
                                s = p_obj;
806
                            } else {
807
                                s = p_override;
808
                            }
809
                        }
810
                        p_fn.call(s, "DOMReady", [], p_obj);
811
                    }, 0);
812
                } else {
813
                    this.DOMReadyEvent.subscribe(p_fn, p_obj, p_override);
814
                }
815
            },
816

    
817
            /**
818
             * Appends an event handler
819
             *
820
             * @method addListener
821
             *
822
             * @param {String|HTMLElement|Array|NodeList} el An id, an element 
823
             *  reference, or a collection of ids and/or elements to assign the 
824
             *  listener to.
825
             * @param {String}   sType     The type of event to append
826
             * @param {Function} fn        The method the event invokes
827
             * @param {Object}   obj    An arbitrary object that will be 
828
             *                             passed as a parameter to the handler
829
             * @param {Boolean|object}  override  If true, the obj passed in becomes
830
             *                             the execution scope of the listener. If an
831
             *                             object, this object becomes the execution
832
             *                             scope.
833
             * @return {Boolean} True if the action was successful or defered,
834
             *                        false if one or more of the elements 
835
             *                        could not have the listener attached,
836
             *                        or if the operation throws an exception.
837
             * @static
838
             */
839
            addListener: function(el, sType, fn, obj, override) {
840

    
841
                if (!fn || !fn.call) {
842
// throw new TypeError(sType + " addListener call failed, callback undefined");
843
                    return false;
844
                }
845

    
846
                // The el argument can be an array of elements or element ids.
847
                if ( this._isValidCollection(el)) {
848
                    var ok = true;
849
                    for (var i=0,len=el.length; i<len; ++i) {
850
                        ok = this.on(el[i], 
851
                                       sType, 
852
                                       fn, 
853
                                       obj, 
854
                                       override) && ok;
855
                    }
856
                    return ok;
857

    
858
                } else if (YAHOO.lang.isString(el)) {
859
                    var oEl = this.getEl(el);
860
                    // If the el argument is a string, we assume it is 
861
                    // actually the id of the element.  If the page is loaded
862
                    // we convert el to the actual element, otherwise we 
863
                    // defer attaching the event until onload event fires
864

    
865
                    // check to see if we need to delay hooking up the event 
866
                    // until after the page loads.
867
                    if (oEl) {
868
                        el = oEl;
869
                    } else {
870
                        // defer adding the event until the element is available
871
                        this.onAvailable(el, function() {
872
                           YAHOO.util.Event.on(el, sType, fn, obj, override);
873
                        });
874

    
875
                        return true;
876
                    }
877
                }
878

    
879
                // Element should be an html element or an array if we get 
880
                // here.
881
                if (!el) {
882
                    return false;
883
                }
884

    
885
                // we need to make sure we fire registered unload events 
886
                // prior to automatically unhooking them.  So we hang on to 
887
                // these instead of attaching them to the window and fire the
888
                // handles explicitly during our one unload event.
889
                if ("unload" == sType && obj !== this) {
890
                    unloadListeners[unloadListeners.length] =
891
                            [el, sType, fn, obj, override];
892
                    return true;
893
                }
894

    
895

    
896
                // if the user chooses to override the scope, we use the custom
897
                // object passed in, otherwise the executing scope will be the
898
                // HTML element that the event is registered on
899
                var scope = el;
900
                if (override) {
901
                    if (override === true) {
902
                        scope = obj;
903
                    } else {
904
                        scope = override;
905
                    }
906
                }
907

    
908
                // wrap the function so we can return the obj object when
909
                // the event fires;
910
                var wrappedFn = function(e) {
911
                        return fn.call(scope, YAHOO.util.Event.getEvent(e, el), 
912
                                obj);
913
                    };
914

    
915
                var li = [el, sType, fn, wrappedFn, scope, obj, override];
916
                var index = listeners.length;
917
                // cache the listener so we can try to automatically unload
918
                listeners[index] = li;
919

    
920
                if (this.useLegacyEvent(el, sType)) {
921
                    var legacyIndex = this.getLegacyIndex(el, sType);
922

    
923
                    // Add a new dom0 wrapper if one is not detected for this
924
                    // element
925
                    if ( legacyIndex == -1 || 
926
                                el != legacyEvents[legacyIndex][0] ) {
927

    
928
                        legacyIndex = legacyEvents.length;
929
                        legacyMap[el.id + sType] = legacyIndex;
930

    
931
                        // cache the signature for the DOM0 event, and 
932
                        // include the existing handler for the event, if any
933
                        legacyEvents[legacyIndex] = 
934
                            [el, sType, el["on" + sType]];
935
                        legacyHandlers[legacyIndex] = [];
936

    
937
                        el["on" + sType] = 
938
                            function(e) {
939
                                YAHOO.util.Event.fireLegacyEvent(
940
                                    YAHOO.util.Event.getEvent(e), legacyIndex);
941
                            };
942
                    }
943

    
944
                    // add a reference to the wrapped listener to our custom
945
                    // stack of events
946
                    //legacyHandlers[legacyIndex].push(index);
947
                    legacyHandlers[legacyIndex].push(li);
948

    
949
                } else {
950
                    try {
951
                        this._simpleAdd(el, sType, wrappedFn, false);
952
                    } catch(ex) {
953
                        // handle an error trying to attach an event.  If it fails
954
                        // we need to clean up the cache
955
                        this.lastError = ex;
956
                        this.removeListener(el, sType, fn);
957
                        return false;
958
                    }
959
                }
960

    
961
                return true;
962
                
963
            },
964

    
965
            /**
966
             * When using legacy events, the handler is routed to this object
967
             * so we can fire our custom listener stack.
968
             * @method fireLegacyEvent
969
             * @static
970
             * @private
971
             */
972
            fireLegacyEvent: function(e, legacyIndex) {
973
                var ok=true,le,lh,li,scope,ret;
974
                
975
                lh = legacyHandlers[legacyIndex];
976
                for (var i=0,len=lh.length; i<len; ++i) {
977
                    li = lh[i];
978
                    if ( li && li[this.WFN] ) {
979
                        scope = li[this.ADJ_SCOPE];
980
                        ret = li[this.WFN].call(scope, e);
981
                        ok = (ok && ret);
982
                    }
983
                }
984

    
985
                // Fire the original handler if we replaced one.  We fire this
986
                // after the other events to keep stopPropagation/preventDefault
987
                // that happened in the DOM0 handler from touching our DOM2
988
                // substitute
989
                le = legacyEvents[legacyIndex];
990
                if (le && le[2]) {
991
                    le[2](e);
992
                }
993
                
994
                return ok;
995
            },
996

    
997
            /**
998
             * Returns the legacy event index that matches the supplied 
999
             * signature
1000
             * @method getLegacyIndex
1001
             * @static
1002
             * @private
1003
             */
1004
            getLegacyIndex: function(el, sType) {
1005
                var key = this.generateId(el) + sType;
1006
                if (typeof legacyMap[key] == "undefined") { 
1007
                    return -1;
1008
                } else {
1009
                    return legacyMap[key];
1010
                }
1011
            },
1012

    
1013
            /**
1014
             * Logic that determines when we should automatically use legacy
1015
             * events instead of DOM2 events.  Currently this is limited to old
1016
             * Safari browsers with a broken preventDefault
1017
             * @method useLegacyEvent
1018
             * @static
1019
             * @private
1020
             */
1021
            useLegacyEvent: function(el, sType) {
1022
                if (this.webkit && ("click"==sType || "dblclick"==sType)) {
1023
                    var v = parseInt(this.webkit, 10);
1024
                    if (!isNaN(v) && v<418) {
1025
                        return true;
1026
                    }
1027
                }
1028
                return false;
1029
            },
1030
                    
1031
            /**
1032
             * Removes an event listener
1033
             *
1034
             * @method removeListener
1035
             *
1036
             * @param {String|HTMLElement|Array|NodeList} el An id, an element 
1037
             *  reference, or a collection of ids and/or elements to remove
1038
             *  the listener from.
1039
             * @param {String} sType the type of event to remove.
1040
             * @param {Function} fn the method the event invokes.  If fn is
1041
             *  undefined, then all event handlers for the type of event are 
1042
             *  removed.
1043
             * @return {boolean} true if the unbind was successful, false 
1044
             *  otherwise.
1045
             * @static
1046
             */
1047
            removeListener: function(el, sType, fn) {
1048
                var i, len, li;
1049

    
1050
                // The el argument can be a string
1051
                if (typeof el == "string") {
1052
                    el = this.getEl(el);
1053
                // The el argument can be an array of elements or element ids.
1054
                } else if ( this._isValidCollection(el)) {
1055
                    var ok = true;
1056
                    for (i=0,len=el.length; i<len; ++i) {
1057
                        ok = ( this.removeListener(el[i], sType, fn) && ok );
1058
                    }
1059
                    return ok;
1060
                }
1061

    
1062
                if (!fn || !fn.call) {
1063
                    //return false;
1064
                    return this.purgeElement(el, false, sType);
1065
                }
1066

    
1067
                if ("unload" == sType) {
1068

    
1069
                    for (i=0, len=unloadListeners.length; i<len; i++) {
1070
                        li = unloadListeners[i];
1071
                        if (li && 
1072
                            li[0] == el && 
1073
                            li[1] == sType && 
1074
                            li[2] == fn) {
1075
                                //unloadListeners.splice(i, 1);
1076
                                unloadListeners[i]=null;
1077
                                return true;
1078
                        }
1079
                    }
1080

    
1081
                    return false;
1082
                }
1083

    
1084
                var cacheItem = null;
1085

    
1086
                // The index is a hidden parameter; needed to remove it from
1087
                // the method signature because it was tempting users to
1088
                // try and take advantage of it, which is not possible.
1089
                var index = arguments[3];
1090
  
1091
                if ("undefined" === typeof index) {
1092
                    index = this._getCacheIndex(el, sType, fn);
1093
                }
1094

    
1095
                if (index >= 0) {
1096
                    cacheItem = listeners[index];
1097
                }
1098

    
1099
                if (!el || !cacheItem) {
1100
                    return false;
1101
                }
1102

    
1103

    
1104
                if (this.useLegacyEvent(el, sType)) {
1105
                    var legacyIndex = this.getLegacyIndex(el, sType);
1106
                    var llist = legacyHandlers[legacyIndex];
1107
                    if (llist) {
1108
                        for (i=0, len=llist.length; i<len; ++i) {
1109
                            li = llist[i];
1110
                            if (li && 
1111
                                li[this.EL] == el && 
1112
                                li[this.TYPE] == sType && 
1113
                                li[this.FN] == fn) {
1114
                                    //llist.splice(i, 1);
1115
                                    llist[i]=null;
1116
                                    break;
1117
                            }
1118
                        }
1119
                    }
1120

    
1121
                } else {
1122
                    try {
1123
                        this._simpleRemove(el, sType, cacheItem[this.WFN], false);
1124
                    } catch(ex) {
1125
                        this.lastError = ex;
1126
                        return false;
1127
                    }
1128
                }
1129

    
1130
                // removed the wrapped handler
1131
                delete listeners[index][this.WFN];
1132
                delete listeners[index][this.FN];
1133
                //listeners.splice(index, 1);
1134
                listeners[index]=null;
1135

    
1136
                return true;
1137

    
1138
            },
1139

    
1140
            /**
1141
             * Returns the event's target element.  Safari sometimes provides
1142
             * a text node, and this is automatically resolved to the text
1143
             * node's parent so that it behaves like other browsers.
1144
             * @method getTarget
1145
             * @param {Event} ev the event
1146
             * @param {boolean} resolveTextNode when set to true the target's
1147
             *                  parent will be returned if the target is a 
1148
             *                  text node.  @deprecated, the text node is
1149
             *                  now resolved automatically
1150
             * @return {HTMLElement} the event's target
1151
             * @static
1152
             */
1153
            getTarget: function(ev, resolveTextNode) {
1154
                var t = ev.target || ev.srcElement;
1155
                return this.resolveTextNode(t);
1156
            },
1157

    
1158
            /**
1159
             * In some cases, some browsers will return a text node inside
1160
             * the actual element that was targeted.  This normalizes the
1161
             * return value for getTarget and getRelatedTarget.
1162
             * @method resolveTextNode
1163
             * @param {HTMLElement} node node to resolve
1164
             * @return {HTMLElement} the normized node
1165
             * @static
1166
             */
1167
            resolveTextNode: function(node) {
1168
                if (node && 3 == node.nodeType) {
1169
                    return node.parentNode;
1170
                } else {
1171
                    return node;
1172
                }
1173
            },
1174

    
1175
            /**
1176
             * Returns the event's pageX
1177
             * @method getPageX
1178
             * @param {Event} ev the event
1179
             * @return {int} the event's pageX
1180
             * @static
1181
             */
1182
            getPageX: function(ev) {
1183
                var x = ev.pageX;
1184
                if (!x && 0 !== x) {
1185
                    x = ev.clientX || 0;
1186

    
1187
                    if ( this.isIE ) {
1188
                        x += this._getScrollLeft();
1189
                    }
1190
                }
1191

    
1192
                return x;
1193
            },
1194

    
1195
            /**
1196
             * Returns the event's pageY
1197
             * @method getPageY
1198
             * @param {Event} ev the event
1199
             * @return {int} the event's pageY
1200
             * @static
1201
             */
1202
            getPageY: function(ev) {
1203
                var y = ev.pageY;
1204
                if (!y && 0 !== y) {
1205
                    y = ev.clientY || 0;
1206

    
1207
                    if ( this.isIE ) {
1208
                        y += this._getScrollTop();
1209
                    }
1210
                }
1211

    
1212

    
1213
                return y;
1214
            },
1215

    
1216
            /**
1217
             * Returns the pageX and pageY properties as an indexed array.
1218
             * @method getXY
1219
             * @param {Event} ev the event
1220
             * @return {[x, y]} the pageX and pageY properties of the event
1221
             * @static
1222
             */
1223
            getXY: function(ev) {
1224
                return [this.getPageX(ev), this.getPageY(ev)];
1225
            },
1226

    
1227
            /**
1228
             * Returns the event's related target 
1229
             * @method getRelatedTarget
1230
             * @param {Event} ev the event
1231
             * @return {HTMLElement} the event's relatedTarget
1232
             * @static
1233
             */
1234
            getRelatedTarget: function(ev) {
1235
                var t = ev.relatedTarget;
1236
                if (!t) {
1237
                    if (ev.type == "mouseout") {
1238
                        t = ev.toElement;
1239
                    } else if (ev.type == "mouseover") {
1240
                        t = ev.fromElement;
1241
                    }
1242
                }
1243

    
1244
                return this.resolveTextNode(t);
1245
            },
1246

    
1247
            /**
1248
             * Returns the time of the event.  If the time is not included, the
1249
             * event is modified using the current time.
1250
             * @method getTime
1251
             * @param {Event} ev the event
1252
             * @return {Date} the time of the event
1253
             * @static
1254
             */
1255
            getTime: function(ev) {
1256
                if (!ev.time) {
1257
                    var t = new Date().getTime();
1258
                    try {
1259
                        ev.time = t;
1260
                    } catch(ex) { 
1261
                        this.lastError = ex;
1262
                        return t;
1263
                    }
1264
                }
1265

    
1266
                return ev.time;
1267
            },
1268

    
1269
            /**
1270
             * Convenience method for stopPropagation + preventDefault
1271
             * @method stopEvent
1272
             * @param {Event} ev the event
1273
             * @static
1274
             */
1275
            stopEvent: function(ev) {
1276
                this.stopPropagation(ev);
1277
                this.preventDefault(ev);
1278
            },
1279

    
1280
            /**
1281
             * Stops event propagation
1282
             * @method stopPropagation
1283
             * @param {Event} ev the event
1284
             * @static
1285
             */
1286
            stopPropagation: function(ev) {
1287
                if (ev.stopPropagation) {
1288
                    ev.stopPropagation();
1289
                } else {
1290
                    ev.cancelBubble = true;
1291
                }
1292
            },
1293

    
1294
            /**
1295
             * Prevents the default behavior of the event
1296
             * @method preventDefault
1297
             * @param {Event} ev the event
1298
             * @static
1299
             */
1300
            preventDefault: function(ev) {
1301
                if (ev.preventDefault) {
1302
                    ev.preventDefault();
1303
                } else {
1304
                    ev.returnValue = false;
1305
                }
1306
            },
1307
             
1308
            /**
1309
             * Finds the event in the window object, the caller's arguments, or
1310
             * in the arguments of another method in the callstack.  This is
1311
             * executed automatically for events registered through the event
1312
             * manager, so the implementer should not normally need to execute
1313
             * this function at all.
1314
             * @method getEvent
1315
             * @param {Event} e the event parameter from the handler
1316
             * @param {HTMLElement} boundEl the element the listener is attached to
1317
             * @return {Event} the event 
1318
             * @static
1319
             */
1320
            getEvent: function(e, boundEl) {
1321
                var ev = e || window.event;
1322

    
1323
                if (!ev) {
1324
                    var c = this.getEvent.caller;
1325
                    while (c) {
1326
                        ev = c.arguments[0];
1327
                        if (ev && Event == ev.constructor) {
1328
                            break;
1329
                        }
1330
                        c = c.caller;
1331
                    }
1332
                }
1333

    
1334
                // IE events that target non-browser objects (e.g., VML
1335
                // canvas) will sometimes throw errors when you try to
1336
                // inspect the properties of the event target.  We try to
1337
                // detect this condition, and provide a dummy target (the bound
1338
                // element) to eliminate spurious errors.  
1339

    
1340
                // the implementation caused unexpected results in some 
1341
                // implementations, so this has been rolled back for now
1342
                /* 
1343
                if (ev && this.isIE) {
1344

    
1345
                    try {
1346

    
1347
                        var el = ev.srcElement;
1348

    
1349
                    } catch(ex) {
1350

    
1351
                         
1352
                        ev.target = boundEl;
1353
                    }
1354

    
1355
                }
1356
                */
1357

    
1358
                return ev;
1359
            },
1360

    
1361
            /**
1362
             * Returns the charcode for an event
1363
             * @method getCharCode
1364
             * @param {Event} ev the event
1365
             * @return {int} the event's charCode
1366
             * @static
1367
             */
1368
            getCharCode: function(ev) {
1369
                var code = ev.keyCode || ev.charCode || 0;
1370

    
1371
                // webkit normalization
1372
                if (YAHOO.env.ua.webkit && (code in webkitKeymap)) {
1373
                    code = webkitKeymap[code];
1374
                }
1375
                return code;
1376
            },
1377

    
1378
            /**
1379
             * Locating the saved event handler data by function ref
1380
             *
1381
             * @method _getCacheIndex
1382
             * @static
1383
             * @private
1384
             */
1385
            _getCacheIndex: function(el, sType, fn) {
1386
                for (var i=0,len=listeners.length; i<len; ++i) {
1387
                    var li = listeners[i];
1388
                    if ( li                 && 
1389
                         li[this.FN] == fn  && 
1390
                         li[this.EL] == el  && 
1391
                         li[this.TYPE] == sType ) {
1392
                        return i;
1393
                    }
1394
                }
1395

    
1396
                return -1;
1397
            },
1398

    
1399
            /**
1400
             * Generates an unique ID for the element if it does not already 
1401
             * have one.
1402
             * @method generateId
1403
             * @param el the element to create the id for
1404
             * @return {string} the resulting id of the element
1405
             * @static
1406
             */
1407
            generateId: function(el) {
1408
                var id = el.id;
1409

    
1410
                if (!id) {
1411
                    id = "yuievtautoid-" + counter;
1412
                    ++counter;
1413
                    el.id = id;
1414
                }
1415

    
1416
                return id;
1417
            },
1418

    
1419

    
1420
            /**
1421
             * We want to be able to use getElementsByTagName as a collection
1422
             * to attach a group of events to.  Unfortunately, different 
1423
             * browsers return different types of collections.  This function
1424
             * tests to determine if the object is array-like.  It will also 
1425
             * fail if the object is an array, but is empty.
1426
             * @method _isValidCollection
1427
             * @param o the object to test
1428
             * @return {boolean} true if the object is array-like and populated
1429
             * @static
1430
             * @private
1431
             */
1432
            _isValidCollection: function(o) {
1433
                try {
1434
                    return ( o                     && // o is something
1435
                             typeof o !== "string" && // o is not a string
1436
                             o.length              && // o is indexed
1437
                             !o.tagName            && // o is not an HTML element
1438
                             !o.alert              && // o is not a window
1439
                             typeof o[0] !== "undefined" );
1440
                } catch(ex) {
1441
                    return false;
1442
                }
1443

    
1444
            },
1445

    
1446
            /**
1447
             * @private
1448
             * @property elCache
1449
             * DOM element cache
1450
             * @static
1451
             * @deprecated Elements are not cached due to issues that arise when
1452
             * elements are removed and re-added
1453
             */
1454
            elCache: {},
1455

    
1456
            /**
1457
             * We cache elements bound by id because when the unload event 
1458
             * fires, we can no longer use document.getElementById
1459
             * @method getEl
1460
             * @static
1461
             * @private
1462
             * @deprecated Elements are not cached any longer
1463
             */
1464
            getEl: function(id) {
1465
                return (typeof id === "string") ? document.getElementById(id) : id;
1466
            },
1467

    
1468
            /**
1469
             * Clears the element cache
1470
             * @deprecated Elements are not cached any longer
1471
             * @method clearCache
1472
             * @static
1473
             * @private
1474
             */
1475
            clearCache: function() { },
1476

    
1477
            /**
1478
             * Custom event the fires when the dom is initially usable
1479
             * @event DOMReadyEvent
1480
             */
1481
            DOMReadyEvent: new YAHOO.util.CustomEvent("DOMReady", this),
1482

    
1483
            /**
1484
             * hook up any deferred listeners
1485
             * @method _load
1486
             * @static
1487
             * @private
1488
             */
1489
            _load: function(e) {
1490

    
1491
                if (!loadComplete) {
1492
                    loadComplete = true;
1493
                    var EU = YAHOO.util.Event;
1494

    
1495
                    // Just in case DOMReady did not go off for some reason
1496
                    EU._ready();
1497

    
1498
                    // Available elements may not have been detected before the
1499
                    // window load event fires. Try to find them now so that the
1500
                    // the user is more likely to get the onAvailable notifications
1501
                    // before the window load notification
1502
                    EU._tryPreloadAttach();
1503

    
1504
                    // Remove the listener to assist with the IE memory issue, but not
1505
                    // for other browsers because FF 1.0x does not like it.
1506
                    //if (this.isIE) {
1507
                        //EU._simpleRemove(window, "load", EU._load);
1508
                    //}
1509
                }
1510
            },
1511

    
1512
            /**
1513
             * Fires the DOMReady event listeners the first time the document is
1514
             * usable.
1515
             * @method _ready
1516
             * @static
1517
             * @private
1518
             */
1519
            _ready: function(e) {
1520
                var EU = YAHOO.util.Event;
1521
                if (!EU.DOMReady) {
1522
                    EU.DOMReady=true;
1523

    
1524
                    // Fire the content ready custom event
1525
                    EU.DOMReadyEvent.fire();
1526

    
1527
                    // Remove the DOMContentLoaded (FF/Opera)
1528
                    EU._simpleRemove(document, "DOMContentLoaded", EU._ready);
1529
                }
1530
            },
1531

    
1532
            /**
1533
             * Polling function that runs before the onload event fires, 
1534
             * attempting to attach to DOM Nodes as soon as they are 
1535
             * available
1536
             * @method _tryPreloadAttach
1537
             * @static
1538
             * @private
1539
             */
1540
            _tryPreloadAttach: function() {
1541

    
1542
                if (this.locked) {
1543
                    return false;
1544
                }
1545

    
1546
                if (this.isIE) {
1547
                    // Hold off if DOMReady has not fired and check current
1548
                    // readyState to protect against the IE operation aborted
1549
                    // issue.
1550
                    //if (!this.DOMReady || "complete" !== document.readyState) {
1551
                    if (!this.DOMReady) {
1552
                        this.startInterval();
1553
                        return false;
1554
                    }
1555
                }
1556

    
1557
                this.locked = true;
1558

    
1559

    
1560
                // keep trying until after the page is loaded.  We need to 
1561
                // check the page load state prior to trying to bind the 
1562
                // elements so that we can be certain all elements have been 
1563
                // tested appropriately
1564
                var tryAgain = !loadComplete;
1565
                if (!tryAgain) {
1566
                    tryAgain = (retryCount > 0);
1567
                }
1568

    
1569
                // onAvailable
1570
                var notAvail = [];
1571

    
1572
                var executeItem = function (el, item) {
1573
                    var scope = el;
1574
                    if (item.override) {
1575
                        if (item.override === true) {
1576
                            scope = item.obj;
1577
                        } else {
1578
                            scope = item.override;
1579
                        }
1580
                    }
1581
                    item.fn.call(scope, item.obj);
1582
                };
1583

    
1584
                var i,len,item,el;
1585

    
1586
                // onAvailable
1587
                for (i=0,len=onAvailStack.length; i<len; ++i) {
1588
                    item = onAvailStack[i];
1589
                    if (item && !item.checkReady) {
1590
                        el = this.getEl(item.id);
1591
                        if (el) {
1592
                            executeItem(el, item);
1593
                            onAvailStack[i] = null;
1594
                        } else {
1595
                            notAvail.push(item);
1596
                        }
1597
                    }
1598
                }
1599

    
1600
                // onContentReady
1601
                for (i=0,len=onAvailStack.length; i<len; ++i) {
1602
                    item = onAvailStack[i];
1603
                    if (item && item.checkReady) {
1604
                        el = this.getEl(item.id);
1605

    
1606
                        if (el) {
1607
                            // The element is available, but not necessarily ready
1608
                            // @todo should we test parentNode.nextSibling?
1609
                            if (loadComplete || el.nextSibling) {
1610
                                executeItem(el, item);
1611
                                onAvailStack[i] = null;
1612
                            }
1613
                        } else {
1614
                            notAvail.push(item);
1615
                        }
1616
                    }
1617
                }
1618

    
1619
                retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;
1620

    
1621
                if (tryAgain) {
1622
                    // we may need to strip the nulled out items here
1623
                    this.startInterval();
1624
                } else {
1625
                    clearInterval(this._interval);
1626
                    this._interval = null;
1627
                }
1628

    
1629
                this.locked = false;
1630

    
1631
                return true;
1632

    
1633
            },
1634

    
1635
            /**
1636
             * Removes all listeners attached to the given element via addListener.
1637
             * Optionally, the node's children can also be purged.
1638
             * Optionally, you can specify a specific type of event to remove.
1639
             * @method purgeElement
1640
             * @param {HTMLElement} el the element to purge
1641
             * @param {boolean} recurse recursively purge this element's children
1642
             * as well.  Use with caution.
1643
             * @param {string} sType optional type of listener to purge. If
1644
             * left out, all listeners will be removed
1645
             * @static
1646
             */
1647
            purgeElement: function(el, recurse, sType) {
1648
                var oEl = (YAHOO.lang.isString(el)) ? this.getEl(el) : el;
1649
                var elListeners = this.getListeners(oEl, sType), i, len;
1650
                if (elListeners) {
1651
                    for (i=0,len=elListeners.length; i<len ; ++i) {
1652
                        var l = elListeners[i];
1653
                        // can't use the index on the changing collection
1654
                        this.removeListener(oEl, l.type, l.fn, l.index);
1655
                        //this.removeListener(oEl, l.type, l.fn);
1656
                    }
1657
                }
1658

    
1659
                if (recurse && oEl && oEl.childNodes) {
1660
                    for (i=0,len=oEl.childNodes.length; i<len ; ++i) {
1661
                        this.purgeElement(oEl.childNodes[i], recurse, sType);
1662
                    }
1663
                }
1664
            },
1665

    
1666
            /**
1667
             * Returns all listeners attached to the given element via addListener.
1668
             * Optionally, you can specify a specific type of event to return.
1669
             * @method getListeners
1670
             * @param el {HTMLElement|string} the element or element id to inspect 
1671
             * @param sType {string} optional type of listener to return. If
1672
             * left out, all listeners will be returned
1673
             * @return {Object} the listener. Contains the following fields:
1674
             * &nbsp;&nbsp;type:   (string)   the type of event
1675
             * &nbsp;&nbsp;fn:     (function) the callback supplied to addListener
1676
             * &nbsp;&nbsp;obj:    (object)   the custom object supplied to addListener
1677
             * &nbsp;&nbsp;adjust: (boolean|object)  whether or not to adjust the default scope
1678
             * &nbsp;&nbsp;scope: (boolean)  the derived scope based on the adjust parameter
1679
             * &nbsp;&nbsp;index:  (int)      its position in the Event util listener cache
1680
             * @static
1681
             */           
1682
            getListeners: function(el, sType) {
1683
                var results=[], searchLists;
1684
                if (!sType) {
1685
                    searchLists = [listeners, unloadListeners];
1686
                } else if (sType === "unload") {
1687
                    searchLists = [unloadListeners];
1688
                } else {
1689
                    searchLists = [listeners];
1690
                }
1691

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

    
1694
                for (var j=0;j<searchLists.length; j=j+1) {
1695
                    var searchList = searchLists[j];
1696
                    if (searchList && searchList.length > 0) {
1697
                        for (var i=0,len=searchList.length; i<len ; ++i) {
1698
                            var l = searchList[i];
1699
                            if ( l  && l[this.EL] === oEl && 
1700
                                    (!sType || sType === l[this.TYPE]) ) {
1701
                                results.push({
1702
                                    type:   l[this.TYPE],
1703
                                    fn:     l[this.FN],
1704
                                    obj:    l[this.OBJ],
1705
                                    adjust: l[this.OVERRIDE],
1706
                                    scope:  l[this.ADJ_SCOPE],
1707
                                    index:  i
1708
                                });
1709
                            }
1710
                        }
1711
                    }
1712
                }
1713

    
1714
                return (results.length) ? results : null;
1715
            },
1716

    
1717
            /**
1718
             * Removes all listeners registered by pe.event.  Called 
1719
             * automatically during the unload event.
1720
             * @method _unload
1721
             * @static
1722
             * @private
1723
             */
1724
            _unload: function(e) {
1725

    
1726
                var EU = YAHOO.util.Event, i, j, l, len, index;
1727

    
1728
                // execute and clear stored unload listeners
1729
                for (i=0,len=unloadListeners.length; i<len; ++i) {
1730
                    l = unloadListeners[i];
1731
                    if (l) {
1732
                        var scope = window;
1733
                        if (l[EU.ADJ_SCOPE]) {
1734
                            if (l[EU.ADJ_SCOPE] === true) {
1735
                                scope = l[EU.UNLOAD_OBJ];
1736
                            } else {
1737
                                scope = l[EU.ADJ_SCOPE];
1738
                            }
1739
                        }
1740
                        l[EU.FN].call(scope, EU.getEvent(e, l[EU.EL]), l[EU.UNLOAD_OBJ] );
1741
                        unloadListeners[i] = null;
1742
                        l=null;
1743
                        scope=null;
1744
                    }
1745
                }
1746

    
1747
                unloadListeners = null;
1748

    
1749
                // call clearAttributes or remove listeners to handle IE memory leaks
1750
                if (YAHOO.env.ua.ie && listeners && listeners.length > 0) {
1751
                    j = listeners.length;
1752
                    while (j) {
1753
                        index = j-1;
1754
                        l = listeners[index];
1755
                        if (l) {
1756
                            //try {
1757
                                //l[EU.EL].clearAttributes(); // errors on window objects
1758
                            //} catch(ex) {
1759
                            EU.removeListener(l[EU.EL], l[EU.TYPE], l[EU.FN], index);
1760
                            //}
1761
                        } 
1762
                        j--;
1763
                    }
1764
                    l=null;
1765
                }
1766

    
1767
                /*
1768
                // remove all listeners
1769
                if (listeners && listeners.length > 0) {
1770
                    j = listeners.length;
1771
                    while (j) {
1772
                        index = j-1;
1773
                        l = listeners[index];
1774
                        if (l) {
1775
                            EU.removeListener(l[EU.EL], l[EU.TYPE], l[EU.FN], index);
1776
                        } 
1777
                        j = j - 1;
1778
                    }
1779
                    l=null;
1780
                }
1781
                */
1782

    
1783
                /*
1784
                // kill legacy events
1785
                for (i=0,len=legacyEvents.length; i<len; ++i) {
1786
                    // dereference the element
1787
                    //delete legacyEvents[i][0];
1788
                    legacyEvents[i][0] = null;
1789

    
1790
                    // delete the array item
1791
                    //delete legacyEvents[i];
1792
                    legacyEvents[i] = null;
1793
                }
1794

    
1795
                */
1796

    
1797
                legacyEvents = null;
1798

    
1799
                EU._simpleRemove(window, "unload", EU._unload);
1800

    
1801
            },
1802

    
1803
            /**
1804
             * Returns scrollLeft
1805
             * @method _getScrollLeft
1806
             * @static
1807
             * @private
1808
             */
1809
            _getScrollLeft: function() {
1810
                return this._getScroll()[1];
1811
            },
1812

    
1813
            /**
1814
             * Returns scrollTop
1815
             * @method _getScrollTop
1816
             * @static
1817
             * @private
1818
             */
1819
            _getScrollTop: function() {
1820
                return this._getScroll()[0];
1821
            },
1822

    
1823
            /**
1824
             * Returns the scrollTop and scrollLeft.  Used to calculate the 
1825
             * pageX and pageY in Internet Explorer
1826
             * @method _getScroll
1827
             * @static
1828
             * @private
1829
             */
1830
            _getScroll: function() {
1831
                var dd = document.documentElement, db = document.body;
1832
                if (dd && (dd.scrollTop || dd.scrollLeft)) {
1833
                    return [dd.scrollTop, dd.scrollLeft];
1834
                } else if (db) {
1835
                    return [db.scrollTop, db.scrollLeft];
1836
                } else {
1837
                    return [0, 0];
1838
                }
1839
            },
1840
            
1841
            /**
1842
             * Used by old versions of CustomEvent, restored for backwards
1843
             * compatibility
1844
             * @method regCE
1845
             * @private
1846
             * @static
1847
             * @deprecated still here for backwards compatibility
1848
             */
1849
            regCE: function() {
1850
                // does nothing
1851
            },
1852

    
1853
/*
1854
            testIEReady: function (){
1855
                var n = document.createElement('p'), ready = false;
1856
                try {
1857
                    // throws an error until the doc is ready
1858
                    n.doScroll('left'); 
1859
                    ready = true;
1860
                } catch(ex){ 
1861
                    // document is not ready
1862
                }
1863

    
1864
                n = null;
1865
                return ready;
1866
            },
1867
*/
1868

    
1869
            /**
1870
             * Adds a DOM event directly without the caching, cleanup, scope adj, etc
1871
             *
1872
             * @method _simpleAdd
1873
             * @param {HTMLElement} el      the element to bind the handler to
1874
             * @param {string}      sType   the type of event handler
1875
             * @param {function}    fn      the callback to invoke
1876
             * @param {boolen}      capture capture or bubble phase
1877
             * @static
1878
             * @private
1879
             */
1880
            _simpleAdd: function () {
1881
                if (window.addEventListener) {
1882
                    return function(el, sType, fn, capture) {
1883
                        el.addEventListener(sType, fn, (capture));
1884
                    };
1885
                } else if (window.attachEvent) {
1886
                    return function(el, sType, fn, capture) {
1887
                        el.attachEvent("on" + sType, fn);
1888
                    };
1889
                } else {
1890
                    return function(){};
1891
                }
1892
            }(),
1893

    
1894
            /**
1895
             * Basic remove listener
1896
             *
1897
             * @method _simpleRemove
1898
             * @param {HTMLElement} el      the element to bind the handler to
1899
             * @param {string}      sType   the type of event handler
1900
             * @param {function}    fn      the callback to invoke
1901
             * @param {boolen}      capture capture or bubble phase
1902
             * @static
1903
             * @private
1904
             */
1905
            _simpleRemove: function() {
1906
                if (window.removeEventListener) {
1907
                    return function (el, sType, fn, capture) {
1908
                        el.removeEventListener(sType, fn, (capture));
1909
                    };
1910
                } else if (window.detachEvent) {
1911
                    return function (el, sType, fn) {
1912
                        el.detachEvent("on" + sType, fn);
1913
                    };
1914
                } else {
1915
                    return function(){};
1916
                }
1917
            }()
1918
        };
1919

    
1920
    }();
1921

    
1922
    (function() {
1923
        var EU = YAHOO.util.Event;
1924

    
1925
        /**
1926
         * YAHOO.util.Event.on is an alias for addListener
1927
         * @method on
1928
         * @see addListener
1929
         * @static
1930
         */
1931
        EU.on = EU.addListener;
1932

    
1933
        /////////////////////////////////////////////////////////////
1934
        // DOMReady
1935
        // based on work by: Dean Edwards/John Resig/Matthias Miller 
1936

    
1937
        // Internet Explorer: use the readyState of a defered script.
1938
        // This isolates what appears to be a safe moment to manipulate
1939
        // the DOM prior to when the document's readyState suggests
1940
        // it is safe to do so.
1941
        if (EU.isIE) {
1942

    
1943
            // Process onAvailable/onContentReady items when when the 
1944
            // DOM is ready.
1945
            YAHOO.util.Event.onDOMReady(
1946
                    YAHOO.util.Event._tryPreloadAttach,
1947
                    YAHOO.util.Event, true);
1948

    
1949
            /*
1950

    
1951

    
1952
            var el, d=document, b=d.body;
1953

    
1954
            // If the library is being injected after window.onload, it
1955
            // is not safe to document.write the script tag.  Detecting
1956
            // this state doesn't appear possible, so we expect a flag
1957
            // in YAHOO_config to be set if the library is being injected.
1958
            if (("undefined" !== typeof YAHOO_config) && YAHOO_config.injecting) {
1959

    
1960
                el = document.createElement("script");
1961
                var p=d.getElementsByTagName("head")[0] || b;
1962
                p.insertBefore(el, p.firstChild);
1963

    
1964
            } else {
1965
    d.write('<scr'+'ipt id="_yui_eu_dr" defer="true" src="//:"><'+'/script>');
1966
                el=document.getElementById("_yui_eu_dr");
1967
            }
1968
            
1969

    
1970
            if (el) {
1971
                el.onreadystatechange = function() {
1972
                    if ("complete" === this.readyState) {
1973
                        this.parentNode.removeChild(this);
1974
                        YAHOO.util.Event._ready();
1975
                    }
1976
                };
1977
            } else {
1978
                // The library was likely injected into the page
1979
                // rendering onDOMReady unreliable
1980
                // YAHOO.util.Event._ready();
1981
            }
1982

    
1983
            el=null;
1984

    
1985
            */
1986

    
1987
/*
1988
            (function (){
1989
                var n = document.createElement('p');  
1990
                try {
1991
                    // throws an error if doc is not ready
1992
                    n.doScroll('left');
1993
                    n = null;
1994
                    YAHOO.util.Event._ready();
1995
                } catch (ex){
1996
                    n = null;
1997
setTimeout(arguments.callee, YAHOO.util.Event.POLL_INTERVAL);
1998
                }
1999
            })();
2000
*/
2001

    
2002
            EU._dri = setInterval(function() {
2003
                var n = document.createElement('p');  
2004
                try {
2005
                    // throws an error if doc is not ready
2006
                    n.doScroll('left');
2007
                    clearInterval(EU._dri);
2008
                    EU._dri = null;
2009
                    EU._ready();
2010
                    n = null;
2011
                } catch (ex) { 
2012
                    n = null;
2013
                }
2014
            }, EU.POLL_INTERVAL); 
2015

    
2016
        
2017
        // Safari: The document's readyState in Safari currently will
2018
        // change to loaded/complete before images are loaded.
2019
        //} else if (EU.webkit) {
2020
        } else if (EU.webkit) {
2021

    
2022
            EU._dri = setInterval(function() {
2023
                var rs=document.readyState;
2024
                if ("loaded" == rs || "complete" == rs) {
2025
                    clearInterval(EU._dri);
2026
                    EU._dri = null;
2027
                    EU._ready();
2028
                }
2029
            }, EU.POLL_INTERVAL); 
2030

    
2031
        // FireFox and Opera: These browsers provide a event for this
2032
        // moment.
2033
        } else {
2034

    
2035
            // @todo will this fire when the library is injected?
2036

    
2037
            EU._simpleAdd(document, "DOMContentLoaded", EU._ready);
2038

    
2039
        }
2040
        /////////////////////////////////////////////////////////////
2041

    
2042

    
2043
        EU._simpleAdd(window, "load", EU._load);
2044
        EU._simpleAdd(window, "unload", EU._unload);
2045
        EU._tryPreloadAttach();
2046
    })();
2047

    
2048
}
2049
/**
2050
 * EventProvider is designed to be used with YAHOO.augment to wrap 
2051
 * CustomEvents in an interface that allows events to be subscribed to 
2052
 * and fired by name.  This makes it possible for implementing code to
2053
 * subscribe to an event that either has not been created yet, or will
2054
 * not be created at all.
2055
 *
2056
 * @Class EventProvider
2057
 */
2058
YAHOO.util.EventProvider = function() { };
2059

    
2060
YAHOO.util.EventProvider.prototype = {
2061

    
2062
    /**
2063
     * Private storage of custom events
2064
     * @property __yui_events
2065
     * @type Object[]
2066
     * @private
2067
     */
2068
    __yui_events: null,
2069

    
2070
    /**
2071
     * Private storage of custom event subscribers
2072
     * @property __yui_subscribers
2073
     * @type Object[]
2074
     * @private
2075
     */
2076
    __yui_subscribers: null,
2077
    
2078
    /**
2079
     * Subscribe to a CustomEvent by event type
2080
     *
2081
     * @method subscribe
2082
     * @param p_type     {string}   the type, or name of the event
2083
     * @param p_fn       {function} the function to exectute when the event fires
2084
     * @param p_obj      {Object}   An object to be passed along when the event 
2085
     *                              fires
2086
     * @param p_override {boolean}  If true, the obj passed in becomes the 
2087
     *                              execution scope of the listener
2088
     */
2089
    subscribe: function(p_type, p_fn, p_obj, p_override) {
2090

    
2091
        this.__yui_events = this.__yui_events || {};
2092
        var ce = this.__yui_events[p_type];
2093

    
2094
        if (ce) {
2095
            ce.subscribe(p_fn, p_obj, p_override);
2096
        } else {
2097
            this.__yui_subscribers = this.__yui_subscribers || {};
2098
            var subs = this.__yui_subscribers;
2099
            if (!subs[p_type]) {
2100
                subs[p_type] = [];
2101
            }
2102
            subs[p_type].push(
2103
                { fn: p_fn, obj: p_obj, override: p_override } );
2104
        }
2105
    },
2106

    
2107
    /**
2108
     * Unsubscribes one or more listeners the from the specified event
2109
     * @method unsubscribe
2110
     * @param p_type {string}   The type, or name of the event.  If the type
2111
     *                          is not specified, it will attempt to remove
2112
     *                          the listener from all hosted events.
2113
     * @param p_fn   {Function} The subscribed function to unsubscribe, if not
2114
     *                          supplied, all subscribers will be removed.
2115
     * @param p_obj  {Object}   The custom object passed to subscribe.  This is
2116
     *                        optional, but if supplied will be used to
2117
     *                        disambiguate multiple listeners that are the same
2118
     *                        (e.g., you subscribe many object using a function
2119
     *                        that lives on the prototype)
2120
     * @return {boolean} true if the subscriber was found and detached.
2121
     */
2122
    unsubscribe: function(p_type, p_fn, p_obj) {
2123
        this.__yui_events = this.__yui_events || {};
2124
        var evts = this.__yui_events;
2125
        if (p_type) {
2126
            var ce = evts[p_type];
2127
            if (ce) {
2128
                return ce.unsubscribe(p_fn, p_obj);
2129
            }
2130
        } else {
2131
            var ret = true;
2132
            for (var i in evts) {
2133
                if (YAHOO.lang.hasOwnProperty(evts, i)) {
2134
                    ret = ret && evts[i].unsubscribe(p_fn, p_obj);
2135
                }
2136
            }
2137
            return ret;
2138
        }
2139

    
2140
        return false;
2141
    },
2142
    
2143
    /**
2144
     * Removes all listeners from the specified event.  If the event type
2145
     * is not specified, all listeners from all hosted custom events will
2146
     * be removed.
2147
     * @method unsubscribeAll
2148
     * @param p_type {string}   The type, or name of the event
2149
     */
2150
    unsubscribeAll: function(p_type) {
2151
        return this.unsubscribe(p_type);
2152
    },
2153

    
2154
    /**
2155
     * Creates a new custom event of the specified type.  If a custom event
2156
     * by that name already exists, it will not be re-created.  In either
2157
     * case the custom event is returned. 
2158
     *
2159
     * @method createEvent
2160
     *
2161
     * @param p_type {string} the type, or name of the event
2162
     * @param p_config {object} optional config params.  Valid properties are:
2163
     *
2164
     *  <ul>
2165
     *    <li>
2166
     *      scope: defines the default execution scope.  If not defined
2167
     *      the default scope will be this instance.
2168
     *    </li>
2169
     *    <li>
2170
     *      silent: if true, the custom event will not generate log messages.
2171
     *      This is false by default.
2172
     *    </li>
2173
     *    <li>
2174
     *      onSubscribeCallback: specifies a callback to execute when the
2175
     *      event has a new subscriber.  This will fire immediately for
2176
     *      each queued subscriber if any exist prior to the creation of
2177
     *      the event.
2178
     *    </li>
2179
     *  </ul>
2180
     *
2181
     *  @return {CustomEvent} the custom event
2182
     *
2183
     */
2184
    createEvent: function(p_type, p_config) {
2185

    
2186
        this.__yui_events = this.__yui_events || {};
2187
        var opts = p_config || {};
2188
        var events = this.__yui_events;
2189

    
2190
        if (events[p_type]) {
2191
        } else {
2192

    
2193
            var scope  = opts.scope  || this;
2194
            var silent = (opts.silent);
2195

    
2196
            var ce = new YAHOO.util.CustomEvent(p_type, scope, silent,
2197
                    YAHOO.util.CustomEvent.FLAT);
2198
            events[p_type] = ce;
2199

    
2200
            if (opts.onSubscribeCallback) {
2201
                ce.subscribeEvent.subscribe(opts.onSubscribeCallback);
2202
            }
2203

    
2204
            this.__yui_subscribers = this.__yui_subscribers || {};
2205
            var qs = this.__yui_subscribers[p_type];
2206

    
2207
            if (qs) {
2208
                for (var i=0; i<qs.length; ++i) {
2209
                    ce.subscribe(qs[i].fn, qs[i].obj, qs[i].override);
2210
                }
2211
            }
2212
        }
2213

    
2214
        return events[p_type];
2215
    },
2216

    
2217

    
2218
   /**
2219
     * Fire a custom event by name.  The callback functions will be executed
2220
     * from the scope specified when the event was created, and with the 
2221
     * following parameters:
2222
     *   <ul>
2223
     *   <li>The first argument fire() was executed with</li>
2224
     *   <li>The custom object (if any) that was passed into the subscribe() 
2225
     *       method</li>
2226
     *   </ul>
2227
     * If the custom event has not been explicitly created, it will be
2228
     * created now with the default config, scoped to the host object
2229
     * @method fireEvent
2230
     * @param p_type    {string}  the type, or name of the event
2231
     * @param arguments {Object*} an arbitrary set of parameters to pass to 
2232
     *                            the handler.
2233
     * @return {boolean} the return value from CustomEvent.fire
2234
     *                   
2235
     */
2236
    fireEvent: function(p_type, arg1, arg2, etc) {
2237

    
2238
        this.__yui_events = this.__yui_events || {};
2239
        var ce = this.__yui_events[p_type];
2240

    
2241
        if (!ce) {
2242
            return null;
2243
        }
2244

    
2245
        var args = [];
2246
        for (var i=1; i<arguments.length; ++i) {
2247
            args.push(arguments[i]);
2248
        }
2249
        return ce.fire.apply(ce, args);
2250
    },
2251

    
2252
    /**
2253
     * Returns true if the custom event of the provided type has been created
2254
     * with createEvent.
2255
     * @method hasEvent
2256
     * @param type {string} the type, or name of the event
2257
     */
2258
    hasEvent: function(type) {
2259
        if (this.__yui_events) {
2260
            if (this.__yui_events[type]) {
2261
                return true;
2262
            }
2263
        }
2264
        return false;
2265
    }
2266

    
2267
};
2268

    
2269
/**
2270
* KeyListener is a utility that provides an easy interface for listening for
2271
* keydown/keyup events fired against DOM elements.
2272
* @namespace YAHOO.util
2273
* @class KeyListener
2274
* @constructor
2275
* @param {HTMLElement} attachTo The element or element ID to which the key 
2276
*                               event should be attached
2277
* @param {String}      attachTo The element or element ID to which the key
2278
*                               event should be attached
2279
* @param {Object}      keyData  The object literal representing the key(s) 
2280
*                               to detect. Possible attributes are 
2281
*                               shift(boolean), alt(boolean), ctrl(boolean) 
2282
*                               and keys(either an int or an array of ints 
2283
*                               representing keycodes).
2284
* @param {Function}    handler  The CustomEvent handler to fire when the 
2285
*                               key event is detected
2286
* @param {Object}      handler  An object literal representing the handler. 
2287
* @param {String}      event    Optional. The event (keydown or keyup) to 
2288
*                               listen for. Defaults automatically to keydown.
2289
*
2290
* @knownissue the "keypress" event is completely broken in Safari 2.x and below.
2291
*             the workaround is use "keydown" for key listening.  However, if
2292
*             it is desired to prevent the default behavior of the keystroke,
2293
*             that can only be done on the keypress event.  This makes key
2294
*             handling quite ugly.
2295
* @knownissue keydown is also broken in Safari 2.x and below for the ESC key.
2296
*             There currently is no workaround other than choosing another
2297
*             key to listen for.
2298
*/
2299
YAHOO.util.KeyListener = function(attachTo, keyData, handler, event) {
2300
    if (!attachTo) {
2301
    } else if (!keyData) {
2302
    } else if (!handler) {
2303
    } 
2304
    
2305
    if (!event) {
2306
        event = YAHOO.util.KeyListener.KEYDOWN;
2307
    }
2308

    
2309
    /**
2310
    * The CustomEvent fired internally when a key is pressed
2311
    * @event keyEvent
2312
    * @private
2313
    * @param {Object} keyData The object literal representing the key(s) to 
2314
    *                         detect. Possible attributes are shift(boolean), 
2315
    *                         alt(boolean), ctrl(boolean) and keys(either an 
2316
    *                         int or an array of ints representing keycodes).
2317
    */
2318
    var keyEvent = new YAHOO.util.CustomEvent("keyPressed");
2319
    
2320
    /**
2321
    * The CustomEvent fired when the KeyListener is enabled via the enable() 
2322
    * function
2323
    * @event enabledEvent
2324
    * @param {Object} keyData The object literal representing the key(s) to 
2325
    *                         detect. Possible attributes are shift(boolean), 
2326
    *                         alt(boolean), ctrl(boolean) and keys(either an 
2327
    *                         int or an array of ints representing keycodes).
2328
    */
2329
    this.enabledEvent = new YAHOO.util.CustomEvent("enabled");
2330

    
2331
    /**
2332
    * The CustomEvent fired when the KeyListener is disabled via the 
2333
    * disable() function
2334
    * @event disabledEvent
2335
    * @param {Object} keyData The object literal representing the key(s) to 
2336
    *                         detect. Possible attributes are shift(boolean), 
2337
    *                         alt(boolean), ctrl(boolean) and keys(either an 
2338
    *                         int or an array of ints representing keycodes).
2339
    */
2340
    this.disabledEvent = new YAHOO.util.CustomEvent("disabled");
2341

    
2342
    if (typeof attachTo == 'string') {
2343
        attachTo = document.getElementById(attachTo);
2344
    }
2345

    
2346
    if (typeof handler == 'function') {
2347
        keyEvent.subscribe(handler);
2348
    } else {
2349
        keyEvent.subscribe(handler.fn, handler.scope, handler.correctScope);
2350
    }
2351

    
2352
    /**
2353
    * Handles the key event when a key is pressed.
2354
    * @method handleKeyPress
2355
    * @param {DOMEvent} e   The keypress DOM event
2356
    * @param {Object}   obj The DOM event scope object
2357
    * @private
2358
    */
2359
    function handleKeyPress(e, obj) {
2360
        if (! keyData.shift) {  
2361
            keyData.shift = false; 
2362
        }
2363
        if (! keyData.alt) {    
2364
            keyData.alt = false;
2365
        }
2366
        if (! keyData.ctrl) {
2367
            keyData.ctrl = false;
2368
        }
2369

    
2370
        // check held down modifying keys first
2371
        if (e.shiftKey == keyData.shift && 
2372
            e.altKey   == keyData.alt &&
2373
            e.ctrlKey  == keyData.ctrl) { // if we pass this, all modifiers match
2374
            
2375
            var dataItem;
2376

    
2377
            if (keyData.keys instanceof Array) {
2378
                for (var i=0;i<keyData.keys.length;i++) {
2379
                    dataItem = keyData.keys[i];
2380

    
2381
                    if (dataItem == e.charCode ) {
2382
                        keyEvent.fire(e.charCode, e);
2383
                        break;
2384
                    } else if (dataItem == e.keyCode) {
2385
                        keyEvent.fire(e.keyCode, e);
2386
                        break;
2387
                    }
2388
                }
2389
            } else {
2390
                dataItem = keyData.keys;
2391
                if (dataItem == e.charCode ) {
2392
                    keyEvent.fire(e.charCode, e);
2393
                } else if (dataItem == e.keyCode) {
2394
                    keyEvent.fire(e.keyCode, e);
2395
                }
2396
            }
2397
        }
2398
    }
2399

    
2400
    /**
2401
    * Enables the KeyListener by attaching the DOM event listeners to the 
2402
    * target DOM element
2403
    * @method enable
2404
    */
2405
    this.enable = function() {
2406
        if (! this.enabled) {
2407
            YAHOO.util.Event.addListener(attachTo, event, handleKeyPress);
2408
            this.enabledEvent.fire(keyData);
2409
        }
2410
        /**
2411
        * Boolean indicating the enabled/disabled state of the Tooltip
2412
        * @property enabled
2413
        * @type Boolean
2414
        */
2415
        this.enabled = true;
2416
    };
2417

    
2418
    /**
2419
    * Disables the KeyListener by removing the DOM event listeners from the 
2420
    * target DOM element
2421
    * @method disable
2422
    */
2423
    this.disable = function() {
2424
        if (this.enabled) {
2425
            YAHOO.util.Event.removeListener(attachTo, event, handleKeyPress);
2426
            this.disabledEvent.fire(keyData);
2427
        }
2428
        this.enabled = false;
2429
    };
2430

    
2431
    /**
2432
    * Returns a String representation of the object.
2433
    * @method toString
2434
    * @return {String}  The string representation of the KeyListener
2435
    */ 
2436
    this.toString = function() {
2437
        return "KeyListener [" + keyData.keys + "] " + attachTo.tagName + 
2438
                (attachTo.id ? "[" + attachTo.id + "]" : "");
2439
    };
2440

    
2441
};
2442

    
2443
/**
2444
* Constant representing the DOM "keydown" event.
2445
* @property YAHOO.util.KeyListener.KEYDOWN
2446
* @static
2447
* @final
2448
* @type String
2449
*/
2450
YAHOO.util.KeyListener.KEYDOWN = "keydown";
2451

    
2452
/**
2453
* Constant representing the DOM "keyup" event.
2454
* @property YAHOO.util.KeyListener.KEYUP
2455
* @static
2456
* @final
2457
* @type String
2458
*/
2459
YAHOO.util.KeyListener.KEYUP = "keyup";
2460

    
2461
/**
2462
 * keycode constants for a subset of the special keys
2463
 * @property KEY
2464
 * @static
2465
 * @final
2466
 */
2467
YAHOO.util.KeyListener.KEY = {
2468
    ALT          : 18,
2469
    BACK_SPACE   : 8,
2470
    CAPS_LOCK    : 20,
2471
    CONTROL      : 17,
2472
    DELETE       : 46,
2473
    DOWN         : 40,
2474
    END          : 35,
2475
    ENTER        : 13,
2476
    ESCAPE       : 27,
2477
    HOME         : 36,
2478
    LEFT         : 37,
2479
    META         : 224,
2480
    NUM_LOCK     : 144,
2481
    PAGE_DOWN    : 34,
2482
    PAGE_UP      : 33, 
2483
    PAUSE        : 19,
2484
    PRINTSCREEN  : 44,
2485
    RIGHT        : 39,
2486
    SCROLL_LOCK  : 145,
2487
    SHIFT        : 16,
2488
    SPACE        : 32,
2489
    TAB          : 9,
2490
    UP           : 38
2491
};
2492
YAHOO.register("event", YAHOO.util.Event, {version: "2.4.1", build: "742"});
(4-4/4)