source: branches/rel/html/test/kakinaka/js/event/event.js @ 12157

Revision 12157, 44.8 KB checked in by uehara, 17 years ago (diff)
Line 
1/*
2Copyright (c) 2006, Yahoo! Inc. All rights reserved.
3Code licensed under the BSD License:
4http://developer.yahoo.net/yui/license.txt
5Version: 0.11.4
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 log system
19 * @namespace YAHOO.util
20 * @class CustomEvent
21 * @constructor
22 */
23YAHOO.util.CustomEvent = function(type, oScope, silent) {
24
25    /**
26     * The type of event, returned to subscribers when the event fires
27     * @property type
28     * @type string
29     */
30    this.type = type;
31
32    /**
33     * The scope the the event will fire from by default.  Defaults to the window
34     * obj
35     * @property scope
36     * @type object
37     */
38    this.scope = oScope || window;
39
40    /**
41     * By default all custom events are logged in the debug build, set silent
42     * to true to disable logging for this event.
43     * @property silent
44     * @type boolean
45     */
46    this.silent = silent;
47
48    /**
49     * The subscribers to this event
50     * @property subscribers
51     * @type Subscriber[]
52     */
53    this.subscribers = [];
54
55    if (!this.silent) {
56    }
57
58    // Only add subscribe events for events that are not generated by CustomEvent
59    //if (oScope && (oScope.constructor != this.constructor)) {
60
61        /*
62         * Custom events provide a custom event that fires whenever there is
63         * a new subscriber to the event.  This provides an opportunity to
64         * handle the case where there is a non-repeating event that has
65         * already fired has a new subscriber.
66         *
67         * type CustomEvent
68         */
69        //this.subscribeEvent =
70                //new YAHOO.util.CustomEvent("subscribe", this, true);
71
72    //}
73};
74
75YAHOO.util.CustomEvent.prototype = {
76    /**
77     * Subscribes the caller to this event
78     * @method subscribe
79     * @param {Function} fn       The function to execute
80     * @param {Object}   obj      An object to be passed along when the event fires
81     * @param {boolean}  bOverride If true, the obj passed in becomes the execution
82     *                            scope of the listener
83     */
84    subscribe: function(fn, obj, bOverride) {
85        //if (this.subscribeEvent) {
86            //this.subscribeEvent.fire(fn, obj, bOverride);
87        //}
88
89        this.subscribers.push( new YAHOO.util.Subscriber(fn, obj, bOverride) );
90    },
91
92    /**
93     * Unsubscribes the caller from this event
94     * @method unsubscribe
95     * @param {Function} fn  The function to execute
96     * @param {Object}   obj An object to be passed along when the event fires
97     * @return {boolean} True if the subscriber was found and detached.
98     */
99    unsubscribe: function(fn, obj) {
100        var found = false;
101        for (var i=0, len=this.subscribers.length; i<len; ++i) {
102            var s = this.subscribers[i];
103            if (s && s.contains(fn, obj)) {
104                this._delete(i);
105                found = true;
106            }
107        }
108
109        return found;
110    },
111
112    /**
113     * Notifies the subscribers.  The callback functions will be executed
114     * from the scope specified when the event was created, and with the following
115     * parameters:
116     *   <pre>
117     *   - The type of event
118     *   - All of the arguments fire() was executed with as an array
119     *   - The custom object (if any) that was passed into the subscribe() method
120     *   </pre>
121     * @method fire
122     * @param {Array} an arbitrary set of parameters to pass to the handler
123     */
124    fire: function() {
125        var len=this.subscribers.length;
126        if (!len && this.silent) {
127            return;
128        }
129
130        var args = [];
131
132        for (var i=0; i<arguments.length; ++i) {
133            args.push(arguments[i]);
134        }
135
136        if (!this.silent) {
137        }
138
139        for (i=0; i<len; ++i) {
140            var s = this.subscribers[i];
141            if (s) {
142                if (!this.silent) {
143                }
144                var scope = (s.override) ? s.obj : this.scope;
145                s.fn.call(scope, this.type, args, s.obj);
146            }
147        }
148    },
149
150    /**
151     * Removes all listeners
152     * @method unsubscribeAll
153     */
154    unsubscribeAll: function() {
155        for (var i=0, len=this.subscribers.length; i<len; ++i) {
156            this._delete(len - 1 - i);
157        }
158    },
159
160    /**
161     * @method _delete
162     * @private
163     */
164    _delete: function(index) {
165        var s = this.subscribers[index];
166        if (s) {
167            delete s.fn;
168            delete s.obj;
169        }
170
171        // delete this.subscribers[index];
172        this.subscribers.splice(index, 1);
173    },
174
175    /**
176     * @method toString
177     */
178    toString: function() {
179         return "CustomEvent: " + "'" + this.type  + "', " +
180             "scope: " + this.scope;
181
182    }
183};
184
185/////////////////////////////////////////////////////////////////////
186
187/**
188 * Stores the subscriber information to be used when the event fires.
189 * @param {Function} fn       The function to execute
190 * @param {Object}   obj      An object to be passed along when the event fires
191 * @param {boolean}  bOverride If true, the obj passed in becomes the execution
192 *                            scope of the listener
193 * @class Subscriber
194 * @constructor
195 */
196YAHOO.util.Subscriber = function(fn, obj, bOverride) {
197
198    /**
199     * The callback that will be execute when the event fires
200     * @property fn
201     * @type function
202     */
203    this.fn = fn;
204
205    /**
206     * An optional custom object that will passed to the callback when
207     * the event fires
208     * @property obj
209     * @type object
210     */
211    this.obj = obj || null;
212
213    /**
214     * The default execution scope for the event listener is defined when the
215     * event is created (usually the object which contains the event).
216     * By setting override to true, the execution scope becomes the custom
217     * object passed in by the subscriber
218     * @property override
219     * @type boolean
220     */
221    this.override = (bOverride);
222};
223
224/**
225 * Returns true if the fn and obj match this objects properties.
226 * Used by the unsubscribe method to match the right subscriber.
227 *
228 * @method contains
229 * @param {Function} fn the function to execute
230 * @param {Object} obj an object to be passed along when the event fires
231 * @return {boolean} true if the supplied arguments match this
232 *                   subscriber's signature.
233 */
234YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {
235    return (this.fn == fn && this.obj == obj);
236};
237
238/**
239 * @method toString
240 */
241YAHOO.util.Subscriber.prototype.toString = function() {
242    return "Subscriber { obj: " + (this.obj || "")  +
243           ", override: " +  (this.override || "no") + " }";
244};
245
246// The first instance of Event will win if it is loaded more than once.
247if (!YAHOO.util.Event) {
248
249/**
250 * The event utility provides functions to add and remove event listeners,
251 * event cleansing.  It also tries to automatically remove listeners it
252 * registers during the unload event.
253 * @namespace YAHOO.util
254 * @class Event
255 */
256    YAHOO.util.Event = function() {
257
258        /**
259         * True after the onload event has fired
260         * @property loadComplete
261         * @type boolean
262         * @private
263         */
264        var loadComplete =  false;
265
266        /**
267         * Cache of wrapped listeners
268         * @property listeners
269         * @type array
270         * @private
271         */
272        var listeners = [];
273
274        /**
275         * Listeners that will be attached during the onload event
276         * @property delayedListeners
277         * @type array
278         * @private
279         */
280        var delayedListeners = [];
281
282        /**
283         * User-defined unload function that will be fired before all events
284         * are detached
285         * @property unloadListeners
286         * @type array
287         * @private
288         */
289        var unloadListeners = [];
290
291        /**
292         * Cache of DOM0 event handlers to work around issues with DOM2 events
293         * in Safari
294         * @property legacyEvents
295         * @private
296         */
297        var legacyEvents = [];
298
299        /**
300         * Listener stack for DOM0 events
301         * @property legacyHandlers
302         * @private
303         */
304        var legacyHandlers = [];
305
306        /**
307         * The number of times to poll after window.onload.  This number is
308         * increased if additional late-bound handlers are requested after
309         * the page load.
310         * @property retryCount
311         * @private
312         */
313        var retryCount = 0;
314
315        /**
316         * onAvailable listeners
317         * @property onAvailStack
318         * @private
319         */
320        var onAvailStack = [];
321
322        /**
323         * Lookup table for legacy events
324         * @property legacyMap
325         * @private
326         */
327        var legacyMap = [];
328
329        /**
330         * Counter for auto id generation
331         * @property counter
332         * @private
333         */
334        var counter = 0;
335
336        return { // PREPROCESS
337
338            /**
339             * The number of times we should look for elements that are not
340             * in the DOM at the time the event is requested after the document
341             * has been loaded.  The default is 200@amp;50 ms, so it will poll
342             * for 10 seconds or until all outstanding handlers are bound
343             * (whichever comes first).
344             * @property POLL_RETRYS
345             * @type int
346             */
347            POLL_RETRYS: 200,
348
349            /**
350             * The poll interval in milliseconds
351             * @property POLL_INTERVAL
352             * @type int
353             */
354            POLL_INTERVAL: 50,
355
356            /**
357             * Element to bind, int constant
358             * @property EL
359             * @type int
360             */
361            EL: 0,
362
363            /**
364             * Type of event, int constant
365             * @property TYPE
366             * @type int
367             */
368            TYPE: 1,
369
370            /**
371             * Function to execute, int constant
372             * @property FN
373             * @type int
374             */
375            FN: 2,
376
377            /**
378             * Function wrapped for scope correction and cleanup, int constant
379             * @property WFN
380             * @type int
381             */
382            WFN: 3,
383
384            /**
385             * Object passed in by the user that will be returned as a
386             * parameter to the callback, int constant
387             * @property SCOPE
388             * @type int
389             */
390            SCOPE: 3,
391
392            /**
393             * Adjusted scope, either the element we are registering the event
394             * on or the custom object passed in by the listener, int constant
395             * @property ADJ_SCOPE
396             * @type int
397             */
398            ADJ_SCOPE: 4,
399
400            /**
401             * Safari detection is necessary to work around the preventDefault
402             * bug that makes it so you can't cancel a href click from the
403             * handler.  There is not a capabilities check we can use here.
404             * @property isSafari
405             * @private
406             */
407            isSafari: (/Safari|Konqueror|KHTML/gi).test(navigator.userAgent),
408
409            /**
410             * IE detection needed to properly calculate pageX and pageY.
411             * capabilities checking didn't seem to work because another
412             * browser that does not provide the properties have the values
413             * calculated in a different manner than IE.
414             * @property isIE
415             * @private
416             */
417            isIE: (!this.isSafari && !navigator.userAgent.match(/opera/gi) &&
418                    navigator.userAgent.match(/msie/gi)),
419
420            /**
421             * @method addDelayedListener
422             * @private
423             */
424            addDelayedListener: function(el, sType, fn, oScope, bOverride) {
425                delayedListeners[delayedListeners.length] =
426                    [el, sType, fn, oScope, bOverride];
427
428                // If this happens after the inital page load, we need to
429                // reset the poll counter so that we continue to search for
430                // the element for a fixed period of time.
431                if (loadComplete) {
432                    retryCount = this.POLL_RETRYS;
433                    this.startTimeout(0);
434                    // this._tryPreloadAttach();
435                }
436            },
437
438            /**
439             * @method startTimeout
440             * @private
441             */
442            startTimeout: function(interval) {
443                var i = (interval || interval === 0) ? interval : this.POLL_INTERVAL;
444                var self = this;
445                var callback = function() { self._tryPreloadAttach(); };
446                this.timeout = setTimeout(callback, i);
447            },
448
449            /**
450             * Executes the supplied callback when the item with the supplied
451             * id is found.  This is meant to be used to execute behavior as
452             * soon as possible as the page loads.  If you use this after the
453             * initial page load it will poll for a fixed time for the element.
454             * The number of times it will poll and the frequency are
455             * configurable.  By default it will poll for 10 seconds.
456             *
457             * @method onAvailable
458             *
459             * @param {string}   p_id the id of the element to look for.
460             * @param {function} p_fn what to execute when the element is found.
461             * @param {object}   p_obj an optional object to be passed back as
462             *                   a parameter to p_fn.
463             * @param {boolean}  p_override If set to true, p_fn will execute
464             *                   in the scope of p_obj
465             *
466             */
467            onAvailable: function(p_id, p_fn, p_obj, p_override) {
468                onAvailStack.push( { id:       p_id,
469                                     fn:       p_fn,
470                                     obj:      p_obj,
471                                     override: p_override } );
472
473                retryCount = this.POLL_RETRYS;
474                this.startTimeout(0);
475                // this._tryPreloadAttach();
476            },
477
478            /**
479             * Appends an event handler
480             *
481             * @method addListener
482             *
483             * @param {Object}   el        The html element to assign the
484             *                             event to
485             * @param {String}   sType     The type of event to append
486             * @param {Function} fn        The method the event invokes
487             * @param {Object}   oScope    An arbitrary object that will be
488             *                             passed as a parameter to the handler
489             * @param {boolean}  bOverride If true, the obj passed in becomes
490             *                             the execution scope of the listener
491             * @return {boolean} True if the action was successful or defered,
492             *                        false if one or more of the elements
493             *                        could not have the event bound to it.
494             */
495            addListener: function(el, sType, fn, oScope, bOverride) {
496
497                if (!fn || !fn.call) {
498                    return false;
499                }
500
501                // The el argument can be an array of elements or element ids.
502                if ( this._isValidCollection(el)) {
503                    var ok = true;
504                    for (var i=0,len=el.length; i<len; ++i) {
505                        ok = ( this.on(el[i],
506                                       sType,
507                                       fn,
508                                       oScope,
509                                       bOverride) && ok );
510                    }
511                    return ok;
512
513                } else if (typeof el == "string") {
514                    var oEl = this.getEl(el);
515                    // If the el argument is a string, we assume it is
516                    // actually the id of the element.  If the page is loaded
517                    // we convert el to the actual element, otherwise we
518                    // defer attaching the event until onload event fires
519
520                    // check to see if we need to delay hooking up the event
521                    // until after the page loads.
522                    if (loadComplete && oEl) {
523                        el = oEl;
524                    } else {
525                        // defer adding the event until onload fires
526                        this.addDelayedListener(el,
527                                                sType,
528                                                fn,
529                                                oScope,
530                                                bOverride);
531
532                        return true;
533                    }
534                }
535
536                // Element should be an html element or an array if we get
537                // here.
538                if (!el) {
539                    return false;
540                }
541
542                // we need to make sure we fire registered unload events
543                // prior to automatically unhooking them.  So we hang on to
544                // these instead of attaching them to the window and fire the
545                // handles explicitly during our one unload event.
546                if ("unload" == sType && oScope !== this) {
547                    unloadListeners[unloadListeners.length] =
548                            [el, sType, fn, oScope, bOverride];
549                    return true;
550                }
551
552                // if the user chooses to override the scope, we use the custom
553                // object passed in, otherwise the executing scope will be the
554                // HTML element that the event is registered on
555                var scope = (bOverride) ? oScope : el;
556
557                // wrap the function so we can return the oScope object when
558                // the event fires;
559                var wrappedFn = function(e) {
560                        return fn.call(scope, YAHOO.util.Event.getEvent(e),
561                                oScope);
562                    };
563
564                var li = [el, sType, fn, wrappedFn, scope];
565                var index = listeners.length;
566                // cache the listener so we can try to automatically unload
567                listeners[index] = li;
568
569                if (this.useLegacyEvent(el, sType)) {
570                    var legacyIndex = this.getLegacyIndex(el, sType);
571
572                    // Add a new dom0 wrapper if one is not detected for this
573                    // element
574                    if ( legacyIndex == -1 ||
575                                el != legacyEvents[legacyIndex][0] ) {
576
577                        legacyIndex = legacyEvents.length;
578                        legacyMap[el.id + sType] = legacyIndex;
579
580                        // cache the signature for the DOM0 event, and
581                        // include the existing handler for the event, if any
582                        legacyEvents[legacyIndex] =
583                            [el, sType, el["on" + sType]];
584                        legacyHandlers[legacyIndex] = [];
585
586                        el["on" + sType] =
587                            function(e) {
588                                YAHOO.util.Event.fireLegacyEvent(
589                                    YAHOO.util.Event.getEvent(e), legacyIndex);
590                            };
591                    }
592
593                    // add a reference to the wrapped listener to our custom
594                    // stack of events
595                    //legacyHandlers[legacyIndex].push(index);
596                    legacyHandlers[legacyIndex].push(li);
597
598                // DOM2 Event model
599                } else if (el.addEventListener) {
600                    el.addEventListener(sType, wrappedFn, false);
601                // IE
602                } else if (el.attachEvent) {
603                    el.attachEvent("on" + sType, wrappedFn);
604                }
605
606                return true;
607
608            },
609
610            /**
611             * When using legacy events, the handler is routed to this object
612             * so we can fire our custom listener stack.
613             * @method fireLegacyEvent
614             * @private
615             */
616            fireLegacyEvent: function(e, legacyIndex) {
617                var ok = true;
618
619                var le = legacyHandlers[legacyIndex];
620                for (var i=0,len=le.length; i<len; ++i) {
621                    var li = le[i];
622                    if ( li && li[this.WFN] ) {
623                        var scope = li[this.ADJ_SCOPE];
624                        var ret = li[this.WFN].call(scope, e);
625                        ok = (ok && ret);
626                    }
627                }
628
629                return ok;
630            },
631
632            /**
633             * Returns the legacy event index that matches the supplied
634             * signature
635             * @method getLegacyIndex
636             * @private
637             */
638            getLegacyIndex: function(el, sType) {
639                var key = this.generateId(el) + sType;
640                if (typeof legacyMap[key] == "undefined") {
641                    return -1;
642                } else {
643                    return legacyMap[key];
644                }
645            },
646
647            /**
648             * Logic that determines when we should automatically use legacy
649             * events instead of DOM2 events.
650             * @method useLegacyEvent
651             * @private
652             */
653            useLegacyEvent: function(el, sType) {
654                if (!el.addEventListener && !el.attachEvent) {
655                    return true;
656                } else if (this.isSafari) {
657                    if ("click" == sType || "dblclick" == sType) {
658                        return true;
659                    }
660                }
661                return false;
662            },
663
664            /**
665             * Removes an event handler
666             *
667             * @method removeListener
668             *
669             * @param {Object} el the html element or the id of the element to
670             * assign the event to.
671             * @param {String} sType the type of event to remove
672             * @param {Function} fn the method the event invokes
673             * @return {boolean} true if the unbind was successful, false
674             * otherwise
675             */
676            removeListener: function(el, sType, fn, index) {
677
678                if (!fn || !fn.call) {
679                    return false;
680                }
681
682                var i, len;
683
684                // The el argument can be a string
685                if (typeof el == "string") {
686                    el = this.getEl(el);
687                // The el argument can be an array of elements or element ids.
688                } else if ( this._isValidCollection(el)) {
689                    var ok = true;
690                    for (i=0,len=el.length; i<len; ++i) {
691                        ok = ( this.removeListener(el[i], sType, fn) && ok );
692                    }
693                    return ok;
694                }
695
696                if ("unload" == sType) {
697
698                    for (i=0, len=unloadListeners.length; i<len; i++) {
699                        var li = unloadListeners[i];
700                        if (li &&
701                            li[0] == el &&
702                            li[1] == sType &&
703                            li[2] == fn) {
704                                unloadListeners.splice(i, 1);
705                                return true;
706                        }
707                    }
708
709                    return false;
710                }
711
712                var cacheItem = null;
713
714                //var index = arguments[3];
715
716                if ("undefined" == typeof index) {
717                    index = this._getCacheIndex(el, sType, fn);
718                }
719
720                if (index >= 0) {
721                    cacheItem = listeners[index];
722                }
723
724                if (!el || !cacheItem) {
725                    return false;
726                }
727
728                if (this.useLegacyEvent(el, sType)) {
729                    var legacyIndex = this.getLegacyIndex(el, sType);
730                    var llist = legacyHandlers[legacyIndex];
731                    if (llist) {
732                        for (i=0, len=llist.length; i<len; ++i) {
733                            li = llist[i];
734                            if (li &&
735                                li[this.EL] == el &&
736                                li[this.TYPE] == sType &&
737                                li[this.FN] == fn) {
738                                    llist.splice(i, 1);
739                            }
740                        }
741                    }
742
743                } else if (el.removeEventListener) {
744                    el.removeEventListener(sType, cacheItem[this.WFN], false);
745                } else if (el.detachEvent) {
746                    el.detachEvent("on" + sType, cacheItem[this.WFN]);
747                }
748
749                // removed the wrapped handler
750                delete listeners[index][this.WFN];
751                delete listeners[index][this.FN];
752                listeners.splice(index, 1);
753
754                return true;
755
756            },
757
758            /**
759             * Returns the event's target element
760             * @method getTarget
761             * @param {Event} ev the event
762             * @param {boolean} resolveTextNode when set to true the target's
763             *                  parent will be returned if the target is a
764             *                  text node.  @deprecated, the text node is
765             *                  now resolved automatically
766             * @return {HTMLElement} the event's target
767             */
768            getTarget: function(ev, resolveTextNode) {
769                var t = ev.target || ev.srcElement;
770                return this.resolveTextNode(t);
771            },
772
773            /**
774             * In some cases, some browsers will return a text node inside
775             * the actual element that was targeted.  This normalizes the
776             * return value for getTarget and getRelatedTarget.
777             * @method resolveTextNode
778             * @param {HTMLElement} node to resolve
779             * @return  the normized node
780             */
781            resolveTextNode: function(node) {
782                if (node && node.nodeName &&
783                        "#TEXT" == node.nodeName.toUpperCase()) {
784                    return node.parentNode;
785                } else {
786                    return node;
787                }
788            },
789
790            /**
791             * Returns the event's pageX
792             * @method getPageX
793             * @param {Event} ev the event
794             * @return {int} the event's pageX
795             */
796            getPageX: function(ev) {
797                var x = ev.pageX;
798                if (!x && 0 !== x) {
799                    x = ev.clientX || 0;
800
801                    if ( this.isIE ) {
802                        x += this._getScrollLeft();
803                    }
804                }
805
806                return x;
807            },
808
809            /**
810             * Returns the event's pageY
811             * @method getPageY
812             * @param {Event} ev the event
813             * @return {int} the event's pageY
814             */
815            getPageY: function(ev) {
816                var y = ev.pageY;
817                if (!y && 0 !== y) {
818                    y = ev.clientY || 0;
819
820                    if ( this.isIE ) {
821                        y += this._getScrollTop();
822                    }
823                }
824
825                return y;
826            },
827
828            /**
829             * Returns the pageX and pageY properties as an indexed array.
830             * @method getXY
831             * @type int[]
832             */
833            getXY: function(ev) {
834                return [this.getPageX(ev), this.getPageY(ev)];
835            },
836
837            /**
838             * Returns the event's related target
839             * @method getRelatedTarget
840             * @param {Event} ev the event
841             * @return {HTMLElement} the event's relatedTarget
842             */
843            getRelatedTarget: function(ev) {
844                var t = ev.relatedTarget;
845                if (!t) {
846                    if (ev.type == "mouseout") {
847                        t = ev.toElement;
848                    } else if (ev.type == "mouseover") {
849                        t = ev.fromElement;
850                    }
851                }
852
853                return this.resolveTextNode(t);
854            },
855
856            /**
857             * Returns the time of the event.  If the time is not included, the
858             * event is modified using the current time.
859             * @method getTime
860             * @param {Event} ev the event
861             * @return {Date} the time of the event
862             */
863            getTime: function(ev) {
864                if (!ev.time) {
865                    var t = new Date().getTime();
866                    try {
867                        ev.time = t;
868                    } catch(e) {
869                        // can't set the time property
870                        return t;
871                    }
872                }
873
874                return ev.time;
875            },
876
877            /**
878             * Convenience method for stopPropagation + preventDefault
879             * @method stopEvent
880             * @param {Event} ev the event
881             */
882            stopEvent: function(ev) {
883                this.stopPropagation(ev);
884                this.preventDefault(ev);
885            },
886
887            /**
888             * Stops event propagation
889             * @method stopPropagation
890             * @param {Event} ev the event
891             */
892            stopPropagation: function(ev) {
893                if (ev.stopPropagation) {
894                    ev.stopPropagation();
895                } else {
896                    ev.cancelBubble = true;
897                }
898            },
899
900            /**
901             * Prevents the default behavior of the event
902             * @method preventDefault
903             * @param {Event} ev the event
904             */
905            preventDefault: function(ev) {
906                if (ev.preventDefault) {
907                    ev.preventDefault();
908                } else {
909                    ev.returnValue = false;
910                }
911            },
912
913            /**
914             * Finds the event in the window object, the caller's arguments, or
915             * in the arguments of another method in the callstack.  This is
916             * executed automatically for events registered through the event
917             * manager, so the implementer should not normally need to execute
918             * this function at all.
919             * @method getEvent
920             * @param {Event} the event parameter from the handler
921             * @return {Event} the event
922             */
923            getEvent: function(e) {
924                var ev = e || window.event;
925
926                if (!ev) {
927                    var c = this.getEvent.caller;
928                    while (c) {
929                        ev = c.arguments[0];
930                        if (ev && Event == ev.constructor) {
931                            break;
932                        }
933                        c = c.caller;
934                    }
935                }
936
937                return ev;
938            },
939
940            /**
941             * Returns the charcode for an event
942             * @method getCharCode
943             * @param {Event} ev the event
944             * @return {int} the event's charCode
945             */
946            getCharCode: function(ev) {
947                return ev.charCode || ((ev.type == "keypress") ? ev.keyCode : 0);
948            },
949
950            /**
951             * Locating the saved event handler data by function ref
952             *
953             * @method _getCacheIndex
954             * @private
955             */
956            _getCacheIndex: function(el, sType, fn) {
957                for (var i=0,len=listeners.length; i<len; ++i) {
958                    var li = listeners[i];
959                    if ( li                 &&
960                         li[this.FN] == fn  &&
961                         li[this.EL] == el  &&
962                         li[this.TYPE] == sType ) {
963                        return i;
964                    }
965                }
966
967                return -1;
968            },
969
970            /**
971             * Generates an unique ID for the element if it does not already
972             * have one.
973             * @method generateId
974             * @param el the element
975             * @return {string} the id of the element
976             */
977            generateId: function(el) {
978                var id = el.id;
979
980                if (!id) {
981                    id = "yuievtautoid-" + counter;
982                    ++counter;
983                    el.id = id;
984                }
985
986                return id;
987            },
988
989            /**
990             * We want to be able to use getElementsByTagName as a collection
991             * to attach a group of events to.  Unfortunately, different
992             * browsers return different types of collections.  This function
993             * tests to determine if the object is array-like.  It will also
994             * fail if the object is an array, but is empty.
995             * @method _isValidCollection
996             * @param o the object to test
997             * @return {boolean} true if the object is array-like and populated
998             * @private
999             */
1000            _isValidCollection: function(o) {
1001
1002                return ( o                    && // o is something
1003                         o.length             && // o is indexed
1004                         typeof o != "string" && // o is not a string
1005                         !o.tagName           && // o is not an HTML element
1006                         !o.alert             && // o is not a window
1007                         typeof o[0] != "undefined" );
1008
1009            },
1010
1011            /**
1012             * @private
1013             * @property elCache
1014             * DOM element cache
1015             */
1016            elCache: {},
1017
1018            /**
1019             * We cache elements bound by id because when the unload event
1020             * fires, we can no longer use document.getElementById
1021             * @method getEl
1022             * @private
1023             */
1024            getEl: function(id) {
1025                return document.getElementById(id);
1026            },
1027
1028            /**
1029             * Clears the element cache
1030             * @deprecated
1031             * @private
1032             */
1033            clearCache: function() { },
1034
1035            /**
1036             * hook up any deferred listeners
1037             * @method _load
1038             * @private
1039             */
1040            _load: function(e) {
1041                loadComplete = true;
1042                var EU = YAHOO.util.Event;
1043                EU._simpleRemove(window, "load", EU._load);
1044            },
1045
1046            /**
1047             * Polling function that runs before the onload event fires,
1048             * attempting to attach to DOM Nodes as soon as they are
1049             * available
1050             * @method _tryPreloadAttach
1051             * @private
1052             */
1053            _tryPreloadAttach: function() {
1054
1055                if (this.locked) {
1056                    return false;
1057                }
1058
1059                this.locked = true;
1060
1061                // keep trying until after the page is loaded.  We need to
1062                // check the page load state prior to trying to bind the
1063                // elements so that we can be certain all elements have been
1064                // tested appropriately
1065                var tryAgain = !loadComplete;
1066                if (!tryAgain) {
1067                    tryAgain = (retryCount > 0);
1068                }
1069
1070                // Delayed listeners
1071                var stillDelayed = [];
1072
1073                for (var i=0,len=delayedListeners.length; i<len; ++i) {
1074                    var d = delayedListeners[i];
1075                    // There may be a race condition here, so we need to
1076                    // verify the array element is usable.
1077                    if (d) {
1078
1079                        // el will be null if document.getElementById did not
1080                        // work
1081                        var el = this.getEl(d[this.EL]);
1082
1083                        if (el) {
1084                            this.on(el, d[this.TYPE], d[this.FN],
1085                                    d[this.SCOPE], d[this.ADJ_SCOPE]);
1086                            delete delayedListeners[i];
1087                        } else {
1088                            stillDelayed.push(d);
1089                        }
1090                    }
1091                }
1092
1093                delayedListeners = stillDelayed;
1094
1095                // onAvailable
1096                var notAvail = [];
1097                for (i=0,len=onAvailStack.length; i<len ; ++i) {
1098                    var item = onAvailStack[i];
1099                    if (item) {
1100                        el = this.getEl(item.id);
1101
1102                        if (el) {
1103                            var scope = (item.override) ? item.obj : el;
1104                            item.fn.call(scope, item.obj);
1105                            delete onAvailStack[i];
1106                        } else {
1107                            notAvail.push(item);
1108                        }
1109                    }
1110                }
1111
1112                retryCount = (stillDelayed.length === 0 &&
1113                                    notAvail.length === 0) ? 0 : retryCount - 1;
1114
1115                if (tryAgain) {
1116                    this.startTimeout();
1117                }
1118
1119                this.locked = false;
1120
1121                return true;
1122
1123            },
1124
1125            /**
1126             * Removes all listeners attached to the given element via addListener.
1127             * Optionally, the node's children can also be purged.
1128             * Optionally, you can specify a specific type of event to remove.
1129             * @method purgeElement
1130             * @param {HTMLElement} el the element to purge
1131             * @param {boolean} recurse recursively purge this element's children
1132             * as well.  Use with caution.
1133             * @param {string} sType optional type of listener to purge. If
1134             * left out, all listeners will be removed
1135             */
1136            purgeElement: function(el, recurse, sType) {
1137                var elListeners = this.getListeners(el, sType);
1138                if (elListeners) {
1139                    for (var i=0,len=elListeners.length; i<len ; ++i) {
1140                        var l = elListeners[i];
1141                        // can't use the index on the changing collection
1142                        //this.removeListener(el, l.type, l.fn, l.index);
1143                        this.removeListener(el, l.type, l.fn);
1144                    }
1145                }
1146
1147                if (recurse && el && el.childNodes) {
1148                    for (i=0,len=el.childNodes.length; i<len ; ++i) {
1149                        this.purgeElement(el.childNodes[i], recurse, sType);
1150                    }
1151                }
1152            },
1153
1154            /**
1155             * Returns all listeners attached to the given element via addListener.
1156             * Optionally, you can specify a specific type of event to return.
1157             * @method getListeners
1158             * @param el {HTMLElement} the element to inspect
1159             * @param sType {string} optional type of listener to return. If
1160             * left out, all listeners will be returned
1161             * @return {Object} the listener. Contains the following fields:
1162             *    type:   (string)   the type of event
1163             *    fn:     (function) the callback supplied to addListener
1164             *    obj:    (object)   the custom object supplied to addListener
1165             *    adjust: (boolean)  whether or not to adjust the default scope
1166             *    index:  (int)      its position in the Event util listener cache
1167             */
1168            getListeners: function(el, sType) {
1169                var elListeners = [];
1170                if (listeners && listeners.length > 0) {
1171                    for (var i=0,len=listeners.length; i<len ; ++i) {
1172                        var l = listeners[i];
1173                        if ( l  && l[this.EL] === el &&
1174                                (!sType || sType === l[this.TYPE]) ) {
1175                            elListeners.push({
1176                                type:   l[this.TYPE],
1177                                fn:     l[this.FN],
1178                                obj:    l[this.SCOPE],
1179                                adjust: l[this.ADJ_SCOPE],
1180                                index:  i
1181                            });
1182                        }
1183                    }
1184                }
1185
1186                return (elListeners.length) ? elListeners : null;
1187            },
1188
1189            /**
1190             * Removes all listeners registered by pe.event.  Called
1191             * automatically during the unload event.
1192             * @method _unload
1193             * @private
1194             */
1195            _unload: function(e) {
1196
1197                var EU = YAHOO.util.Event;
1198
1199                for (var i=0,len=unloadListeners.length; i<len; ++i) {
1200                    var l = unloadListeners[i];
1201                    if (l) {
1202                        var scope = (l[EU.ADJ_SCOPE]) ? l[EU.SCOPE]: window;
1203                        l[EU.FN].call(scope, EU.getEvent(e), l[EU.SCOPE] );
1204                        delete unloadListeners[i];
1205                        l=null;
1206                    }
1207                }
1208
1209                if (listeners && listeners.length > 0) {
1210                    //for (i=0,len=listeners.length; i<len ; ++i) {
1211                    var j = listeners.length;
1212                    while (j) {
1213                        var index = j-1;
1214                        l = listeners[index];
1215                        if (l) {
1216                            EU.removeListener(l[EU.EL], l[EU.TYPE],
1217                                    l[EU.FN], index);
1218                        }
1219
1220                        l=null;
1221
1222                        j = j - 1;
1223                    }
1224
1225                    EU.clearCache();
1226                }
1227
1228                for (i=0,len=legacyEvents.length; i<len; ++i) {
1229                    // dereference the element
1230                    delete legacyEvents[i][0];
1231                    // delete the array item
1232                    delete legacyEvents[i];
1233                }
1234
1235                EU._simpleRemove(window, "unload", EU._unload);
1236
1237            },
1238
1239            /**
1240             * Returns scrollLeft
1241             * @method _getScrollLeft
1242             * @private
1243             */
1244            _getScrollLeft: function() {
1245                return this._getScroll()[1];
1246            },
1247
1248            /**
1249             * Returns scrollTop
1250             * @method _getScrollTop
1251             * @private
1252             */
1253            _getScrollTop: function() {
1254                return this._getScroll()[0];
1255            },
1256
1257            /**
1258             * Returns the scrollTop and scrollLeft.  Used to calculate the
1259             * pageX and pageY in Internet Explorer
1260             * @method _getScroll
1261             * @private
1262             */
1263            _getScroll: function() {
1264                var dd = document.documentElement, db = document.body;
1265                if (dd && (dd.scrollTop || dd.scrollLeft)) {
1266                    return [dd.scrollTop, dd.scrollLeft];
1267                } else if (db) {
1268                    return [db.scrollTop, db.scrollLeft];
1269                } else {
1270                    return [0, 0];
1271                }
1272            },
1273
1274            /**
1275             * Adds a DOM event directly without the caching, cleanup, scope adj, etc
1276             *
1277             * @param el the elment to bind the handler to
1278             * @param {string} sType the type of event handler
1279             * @param {function} fn the callback to invoke
1280             * @param {boolen} capture or bubble phase
1281             * @private
1282             */
1283            _simpleAdd: function (el, sType, fn, capture) {
1284                if (el.addEventListener) {
1285                    el.addEventListener(sType, fn, (capture));
1286                } else if (el.attachEvent) {
1287                    el.attachEvent("on" + sType, fn);
1288                }
1289            },
1290
1291            /**
1292             * Basic remove listener
1293             *
1294             * @param el the elment to bind the handler to
1295             * @param {string} sType the type of event handler
1296             * @param {function} fn the callback to invoke
1297             * @param {boolen} capture or bubble phase
1298             * @private
1299             */
1300            _simpleRemove: function (el, sType, fn, capture) {
1301                if (el.removeEventListener) {
1302                    el.removeEventListener(sType, fn, (capture));
1303                } else if (el.detachEvent) {
1304                    el.detachEvent("on" + sType, fn);
1305                }
1306            }
1307        };
1308
1309    } ();
1310
1311    /**
1312     * YAHOO.util.Event.on is an alias for addListener
1313     * @method on
1314     * @see addListener
1315     */
1316    YAHOO.util.Event.on = YAHOO.util.Event.addListener;
1317
1318    if (document && document.body) {
1319        YAHOO.util.Event._load();
1320    } else {
1321        YAHOO.util.Event._simpleAdd(window, "load", YAHOO.util.Event._load);
1322    }
1323    YAHOO.util.Event._simpleAdd(window, "unload", YAHOO.util.Event._unload);
1324    YAHOO.util.Event._tryPreloadAttach();
1325}
1326
Note: See TracBrowser for help on using the repository browser.