Project

General

Profile

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