MDL-10604:
[moodle-linuxchix.git] / lib / yui / event / event.js
blob22c97ca2b3503c6c31a4c317db1b24fbb0465200
1 /*
2 Copyright (c) 2006, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 0.12.2
6 */
7 /**
8 * The CustomEvent class lets you define events for your application
9 * that can be subscribed to by one or more independent component.
11 * @param {String} type The type of event, which is passed to the callback
12 * when the event fires
13 * @param {Object} oScope The context the event will fire from. "this" will
14 * refer to this object in the callback. Default value:
15 * the window object. The listener can override this.
16 * @param {boolean} silent pass true to prevent the event from writing to
17 * the debugsystem
18 * @param {int} signature the signature that the custom event subscriber
19 * will receive. YAHOO.util.CustomEvent.LIST or
20 * YAHOO.util.CustomEvent.FLAT. The default is
21 * YAHOO.util.CustomEvent.LIST.
22 * @namespace YAHOO.util
23 * @class CustomEvent
24 * @constructor
26 YAHOO.util.CustomEvent = function(type, oScope, silent, signature) {
28 /**
29 * The type of event, returned to subscribers when the event fires
30 * @property type
31 * @type string
33 this.type = type;
35 /**
36 * The scope the the event will fire from by default. Defaults to the window
37 * obj
38 * @property scope
39 * @type object
41 this.scope = oScope || window;
43 /**
44 * By default all custom events are logged in the debug build, set silent
45 * to true to disable debug outpu for this event.
46 * @property silent
47 * @type boolean
49 this.silent = silent;
51 /**
52 * Custom events support two styles of arguments provided to the event
53 * subscribers.
54 * <ul>
55 * <li>YAHOO.util.CustomEvent.LIST:
56 * <ul>
57 * <li>param1: event name</li>
58 * <li>param2: array of arguments sent to fire</li>
59 * <li>param3: <optional> a custom object supplied by the subscriber</li>
60 * </ul>
61 * </li>
62 * <li>YAHOO.util.CustomEvent.FLAT
63 * <ul>
64 * <li>param1: the first argument passed to fire. If you need to
65 * pass multiple parameters, use and array or object literal</li>
66 * <li>param2: <optional> a custom object supplied by the subscriber</li>
67 * </ul>
68 * </li>
69 * </ul>
70 * @property signature
71 * @type int
73 this.signature = signature || YAHOO.util.CustomEvent.LIST;
75 /**
76 * The subscribers to this event
77 * @property subscribers
78 * @type Subscriber[]
80 this.subscribers = [];
82 if (!this.silent) {
85 var onsubscribeType = "_YUICEOnSubscribe";
87 // Only add subscribe events for events that are not generated by
88 // CustomEvent
89 if (type !== onsubscribeType) {
91 /**
92 * Custom events provide a custom event that fires whenever there is
93 * a new subscriber to the event. This provides an opportunity to
94 * handle the case where there is a non-repeating event that has
95 * already fired has a new subscriber.
97 * @event subscribeEvent
98 * @type YAHOO.util.CustomEvent
99 * @param {Function} fn The function to execute
100 * @param {Object} obj An object to be passed along when the event
101 * fires
102 * @param {boolean|Object} override If true, the obj passed in becomes
103 * the execution scope of the listener.
104 * if an object, that object becomes the
105 * the execution scope.
107 this.subscribeEvent =
108 new YAHOO.util.CustomEvent(onsubscribeType, this, true);
114 * Subscriber listener sigature constant. The LIST type returns three
115 * parameters: the event type, the array of args passed to fire, and
116 * the optional custom object
117 * @property YAHOO.util.CustomEvent.LIST
118 * @static
119 * @type int
121 YAHOO.util.CustomEvent.LIST = 0;
124 * Subscriber listener sigature constant. The FLAT type returns two
125 * parameters: the first argument passed to fire and the optional
126 * custom object
127 * @property YAHOO.util.CustomEvent.FLAT
128 * @static
129 * @type int
131 YAHOO.util.CustomEvent.FLAT = 1;
133 YAHOO.util.CustomEvent.prototype = {
136 * Subscribes the caller to this event
137 * @method subscribe
138 * @param {Function} fn The function to execute
139 * @param {Object} obj An object to be passed along when the event
140 * fires
141 * @param {boolean|Object} override If true, the obj passed in becomes
142 * the execution scope of the listener.
143 * if an object, that object becomes the
144 * the execution scope.
146 subscribe: function(fn, obj, override) {
147 if (this.subscribeEvent) {
148 this.subscribeEvent.fire(fn, obj, override);
151 this.subscribers.push( new YAHOO.util.Subscriber(fn, obj, override) );
155 * Unsubscribes the caller from this event
156 * @method unsubscribe
157 * @param {Function} fn The function to execute
158 * @param {Object} obj The custom object passed to subscribe (optional)
159 * @return {boolean} True if the subscriber was found and detached.
161 unsubscribe: function(fn, obj) {
162 var found = false;
163 for (var i=0, len=this.subscribers.length; i<len; ++i) {
164 var s = this.subscribers[i];
165 if (s && s.contains(fn, obj)) {
166 this._delete(i);
167 found = true;
171 return found;
175 * Notifies the subscribers. The callback functions will be executed
176 * from the scope specified when the event was created, and with the
177 * following parameters:
178 * <ul>
179 * <li>The type of event</li>
180 * <li>All of the arguments fire() was executed with as an array</li>
181 * <li>The custom object (if any) that was passed into the subscribe()
182 * method</li>
183 * </ul>
184 * @method fire
185 * @param {Object*} arguments an arbitrary set of parameters to pass to
186 * the handler.
187 * @return {boolean} false if one of the subscribers returned false,
188 * true otherwise
190 fire: function() {
191 var len=this.subscribers.length;
192 if (!len && this.silent) {
193 return true;
196 var args=[], ret=true, i;
198 for (i=0; i<arguments.length; ++i) {
199 args.push(arguments[i]);
202 var argslength = args.length;
204 if (!this.silent) {
207 for (i=0; i<len; ++i) {
208 var s = this.subscribers[i];
209 if (s) {
210 if (!this.silent) {
213 var scope = s.getScope(this.scope);
215 if (this.signature == YAHOO.util.CustomEvent.FLAT) {
216 var param = null;
217 if (args.length > 0) {
218 param = args[0];
220 ret = s.fn.call(scope, param, s.obj);
221 } else {
222 ret = s.fn.call(scope, this.type, args, s.obj);
224 if (false === ret) {
225 if (!this.silent) {
228 //break;
229 return false;
234 return true;
238 * Removes all listeners
239 * @method unsubscribeAll
241 unsubscribeAll: function() {
242 for (var i=0, len=this.subscribers.length; i<len; ++i) {
243 this._delete(len - 1 - i);
248 * @method _delete
249 * @private
251 _delete: function(index) {
252 var s = this.subscribers[index];
253 if (s) {
254 delete s.fn;
255 delete s.obj;
258 // delete this.subscribers[index];
259 this.subscribers.splice(index, 1);
263 * @method toString
265 toString: function() {
266 return "CustomEvent: " + "'" + this.type + "', " +
267 "scope: " + this.scope;
272 /////////////////////////////////////////////////////////////////////
275 * Stores the subscriber information to be used when the event fires.
276 * @param {Function} fn The function to execute
277 * @param {Object} obj An object to be passed along when the event fires
278 * @param {boolean} override If true, the obj passed in becomes the execution
279 * scope of the listener
280 * @class Subscriber
281 * @constructor
283 YAHOO.util.Subscriber = function(fn, obj, override) {
286 * The callback that will be execute when the event fires
287 * @property fn
288 * @type function
290 this.fn = fn;
293 * An optional custom object that will passed to the callback when
294 * the event fires
295 * @property obj
296 * @type object
298 this.obj = obj || null;
301 * The default execution scope for the event listener is defined when the
302 * event is created (usually the object which contains the event).
303 * By setting override to true, the execution scope becomes the custom
304 * object passed in by the subscriber. If override is an object, that
305 * object becomes the scope.
306 * @property override
307 * @type boolean|object
309 this.override = override;
314 * Returns the execution scope for this listener. If override was set to true
315 * the custom obj will be the scope. If override is an object, that is the
316 * scope, otherwise the default scope will be used.
317 * @method getScope
318 * @param {Object} defaultScope the scope to use if this listener does not
319 * override it.
321 YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
322 if (this.override) {
323 if (this.override === true) {
324 return this.obj;
325 } else {
326 return this.override;
329 return defaultScope;
333 * Returns true if the fn and obj match this objects properties.
334 * Used by the unsubscribe method to match the right subscriber.
336 * @method contains
337 * @param {Function} fn the function to execute
338 * @param {Object} obj an object to be passed along when the event fires
339 * @return {boolean} true if the supplied arguments match this
340 * subscriber's signature.
342 YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {
343 if (obj) {
344 return (this.fn == fn && this.obj == obj);
345 } else {
346 return (this.fn == fn);
351 * @method toString
353 YAHOO.util.Subscriber.prototype.toString = function() {
354 return "Subscriber { obj: " + (this.obj || "") +
355 ", override: " + (this.override || "no") + " }";
359 * The Event Utility provides utilities for managing DOM Events and tools
360 * for building event systems
362 * @module event
363 * @title Event Utility
364 * @namespace YAHOO.util
365 * @requires yahoo
368 // The first instance of Event will win if it is loaded more than once.
369 if (!YAHOO.util.Event) {
372 * The event utility provides functions to add and remove event listeners,
373 * event cleansing. It also tries to automatically remove listeners it
374 * registers during the unload event.
376 * @class Event
377 * @static
379 YAHOO.util.Event = function() {
382 * True after the onload event has fired
383 * @property loadComplete
384 * @type boolean
385 * @static
386 * @private
388 var loadComplete = false;
391 * Cache of wrapped listeners
392 * @property listeners
393 * @type array
394 * @static
395 * @private
397 var listeners = [];
400 * User-defined unload function that will be fired before all events
401 * are detached
402 * @property unloadListeners
403 * @type array
404 * @static
405 * @private
407 var unloadListeners = [];
410 * Cache of DOM0 event handlers to work around issues with DOM2 events
411 * in Safari
412 * @property legacyEvents
413 * @static
414 * @private
416 var legacyEvents = [];
419 * Listener stack for DOM0 events
420 * @property legacyHandlers
421 * @static
422 * @private
424 var legacyHandlers = [];
427 * The number of times to poll after window.onload. This number is
428 * increased if additional late-bound handlers are requested after
429 * the page load.
430 * @property retryCount
431 * @static
432 * @private
434 var retryCount = 0;
437 * onAvailable listeners
438 * @property onAvailStack
439 * @static
440 * @private
442 var onAvailStack = [];
445 * Lookup table for legacy events
446 * @property legacyMap
447 * @static
448 * @private
450 var legacyMap = [];
453 * Counter for auto id generation
454 * @property counter
455 * @static
456 * @private
458 var counter = 0;
460 return { // PREPROCESS
463 * The number of times we should look for elements that are not
464 * in the DOM at the time the event is requested after the document
465 * has been loaded. The default is 200@amp;50 ms, so it will poll
466 * for 10 seconds or until all outstanding handlers are bound
467 * (whichever comes first).
468 * @property POLL_RETRYS
469 * @type int
470 * @static
471 * @final
473 POLL_RETRYS: 200,
476 * The poll interval in milliseconds
477 * @property POLL_INTERVAL
478 * @type int
479 * @static
480 * @final
482 POLL_INTERVAL: 20,
485 * Element to bind, int constant
486 * @property EL
487 * @type int
488 * @static
489 * @final
491 EL: 0,
494 * Type of event, int constant
495 * @property TYPE
496 * @type int
497 * @static
498 * @final
500 TYPE: 1,
503 * Function to execute, int constant
504 * @property FN
505 * @type int
506 * @static
507 * @final
509 FN: 2,
512 * Function wrapped for scope correction and cleanup, int constant
513 * @property WFN
514 * @type int
515 * @static
516 * @final
518 WFN: 3,
521 * Object passed in by the user that will be returned as a
522 * parameter to the callback, int constant
523 * @property OBJ
524 * @type int
525 * @static
526 * @final
528 OBJ: 3,
531 * Adjusted scope, either the element we are registering the event
532 * on or the custom object passed in by the listener, int constant
533 * @property ADJ_SCOPE
534 * @type int
535 * @static
536 * @final
538 ADJ_SCOPE: 4,
541 * Safari detection is necessary to work around the preventDefault
542 * bug that makes it so you can't cancel a href click from the
543 * handler. There is not a capabilities check we can use here.
544 * @property isSafari
545 * @private
546 * @static
548 isSafari: (/Safari|Konqueror|KHTML/gi).test(navigator.userAgent),
551 * IE detection needed to properly calculate pageX and pageY.
552 * capabilities checking didn't seem to work because another
553 * browser that does not provide the properties have the values
554 * calculated in a different manner than IE.
555 * @property isIE
556 * @private
557 * @static
559 isIE: (!this.isSafari && !navigator.userAgent.match(/opera/gi) &&
560 navigator.userAgent.match(/msie/gi)),
563 * poll handle
564 * @property _interval
565 * @private
567 _interval: null,
570 * @method startInterval
571 * @static
572 * @private
574 startInterval: function() {
575 if (!this._interval) {
576 var self = this;
577 var callback = function() { self._tryPreloadAttach(); };
578 this._interval = setInterval(callback, this.POLL_INTERVAL);
579 // this.timeout = setTimeout(callback, i);
584 * Executes the supplied callback when the item with the supplied
585 * id is found. This is meant to be used to execute behavior as
586 * soon as possible as the page loads. If you use this after the
587 * initial page load it will poll for a fixed time for the element.
588 * The number of times it will poll and the frequency are
589 * configurable. By default it will poll for 10 seconds.
591 * @method onAvailable
593 * @param {string} p_id the id of the element to look for.
594 * @param {function} p_fn what to execute when the element is found.
595 * @param {object} p_obj an optional object to be passed back as
596 * a parameter to p_fn.
597 * @param {boolean} p_override If set to true, p_fn will execute
598 * in the scope of p_obj
600 * @static
602 onAvailable: function(p_id, p_fn, p_obj, p_override) {
603 onAvailStack.push( { id: p_id,
604 fn: p_fn,
605 obj: p_obj,
606 override: p_override,
607 checkReady: false } );
609 retryCount = this.POLL_RETRYS;
610 this.startInterval();
614 * Works the same way as onAvailable, but additionally checks the
615 * state of sibling elements to determine if the content of the
616 * available element is safe to modify.
618 * @method onContentReady
620 * @param {string} p_id the id of the element to look for.
621 * @param {function} p_fn what to execute when the element is ready.
622 * @param {object} p_obj an optional object to be passed back as
623 * a parameter to p_fn.
624 * @param {boolean} p_override If set to true, p_fn will execute
625 * in the scope of p_obj
627 * @static
629 onContentReady: function(p_id, p_fn, p_obj, p_override) {
630 onAvailStack.push( { id: p_id,
631 fn: p_fn,
632 obj: p_obj,
633 override: p_override,
634 checkReady: true } );
636 retryCount = this.POLL_RETRYS;
637 this.startInterval();
641 * Appends an event handler
643 * @method addListener
645 * @param {Object} el The html element to assign the
646 * event to
647 * @param {String} sType The type of event to append
648 * @param {Function} fn The method the event invokes
649 * @param {Object} obj An arbitrary object that will be
650 * passed as a parameter to the handler
651 * @param {boolean} override If true, the obj passed in becomes
652 * the execution scope of the listener
653 * @return {boolean} True if the action was successful or defered,
654 * false if one or more of the elements
655 * could not have the listener attached,
656 * or if the operation throws an exception.
657 * @static
659 addListener: function(el, sType, fn, obj, override) {
661 if (!fn || !fn.call) {
662 return false;
665 // The el argument can be an array of elements or element ids.
666 if ( this._isValidCollection(el)) {
667 var ok = true;
668 for (var i=0,len=el.length; i<len; ++i) {
669 ok = this.on(el[i],
670 sType,
671 fn,
672 obj,
673 override) && ok;
675 return ok;
677 } else if (typeof el == "string") {
678 var oEl = this.getEl(el);
679 // If the el argument is a string, we assume it is
680 // actually the id of the element. If the page is loaded
681 // we convert el to the actual element, otherwise we
682 // defer attaching the event until onload event fires
684 // check to see if we need to delay hooking up the event
685 // until after the page loads.
686 if (oEl) {
687 el = oEl;
688 } else {
689 // defer adding the event until the element is available
690 this.onAvailable(el, function() {
691 YAHOO.util.Event.on(el, sType, fn, obj, override);
694 return true;
698 // Element should be an html element or an array if we get
699 // here.
700 if (!el) {
701 return false;
704 // we need to make sure we fire registered unload events
705 // prior to automatically unhooking them. So we hang on to
706 // these instead of attaching them to the window and fire the
707 // handles explicitly during our one unload event.
708 if ("unload" == sType && obj !== this) {
709 unloadListeners[unloadListeners.length] =
710 [el, sType, fn, obj, override];
711 return true;
715 // if the user chooses to override the scope, we use the custom
716 // object passed in, otherwise the executing scope will be the
717 // HTML element that the event is registered on
718 var scope = el;
719 if (override) {
720 if (override === true) {
721 scope = obj;
722 } else {
723 scope = override;
727 // wrap the function so we can return the obj object when
728 // the event fires;
729 var wrappedFn = function(e) {
730 return fn.call(scope, YAHOO.util.Event.getEvent(e),
731 obj);
734 var li = [el, sType, fn, wrappedFn, scope];
735 var index = listeners.length;
736 // cache the listener so we can try to automatically unload
737 listeners[index] = li;
739 if (this.useLegacyEvent(el, sType)) {
740 var legacyIndex = this.getLegacyIndex(el, sType);
742 // Add a new dom0 wrapper if one is not detected for this
743 // element
744 if ( legacyIndex == -1 ||
745 el != legacyEvents[legacyIndex][0] ) {
747 legacyIndex = legacyEvents.length;
748 legacyMap[el.id + sType] = legacyIndex;
750 // cache the signature for the DOM0 event, and
751 // include the existing handler for the event, if any
752 legacyEvents[legacyIndex] =
753 [el, sType, el["on" + sType]];
754 legacyHandlers[legacyIndex] = [];
756 el["on" + sType] =
757 function(e) {
758 YAHOO.util.Event.fireLegacyEvent(
759 YAHOO.util.Event.getEvent(e), legacyIndex);
763 // add a reference to the wrapped listener to our custom
764 // stack of events
765 //legacyHandlers[legacyIndex].push(index);
766 legacyHandlers[legacyIndex].push(li);
768 } else {
769 try {
770 this._simpleAdd(el, sType, wrappedFn, false);
771 } catch(e) {
772 // handle an error trying to attach an event. If it fails
773 // we need to clean up the cache
774 this.removeListener(el, sType, fn);
775 return false;
779 return true;
784 * When using legacy events, the handler is routed to this object
785 * so we can fire our custom listener stack.
786 * @method fireLegacyEvent
787 * @static
788 * @private
790 fireLegacyEvent: function(e, legacyIndex) {
791 var ok = true;
793 var le = legacyHandlers[legacyIndex];
794 for (var i=0,len=le.length; i<len; ++i) {
795 var li = le[i];
796 if ( li && li[this.WFN] ) {
797 var scope = li[this.ADJ_SCOPE];
798 var ret = li[this.WFN].call(scope, e);
799 ok = (ok && ret);
803 return ok;
807 * Returns the legacy event index that matches the supplied
808 * signature
809 * @method getLegacyIndex
810 * @static
811 * @private
813 getLegacyIndex: function(el, sType) {
814 var key = this.generateId(el) + sType;
815 if (typeof legacyMap[key] == "undefined") {
816 return -1;
817 } else {
818 return legacyMap[key];
823 * Logic that determines when we should automatically use legacy
824 * events instead of DOM2 events.
825 * @method useLegacyEvent
826 * @static
827 * @private
829 useLegacyEvent: function(el, sType) {
830 if (!el.addEventListener && !el.attachEvent) {
831 return true;
832 } else if (this.isSafari) {
833 if ("click" == sType || "dblclick" == sType) {
834 return true;
837 return false;
841 * Removes an event handler
843 * @method removeListener
845 * @param {Object} el the html element or the id of the element to
846 * assign the event to.
847 * @param {String} sType the type of event to remove.
848 * @param {Function} fn the method the event invokes. If fn is
849 * undefined, then all event handlers for the type of event are
850 * removed.
851 * @return {boolean} true if the unbind was successful, false
852 * otherwise.
853 * @static
855 removeListener: function(el, sType, fn) {
856 var i, len;
858 // The el argument can be a string
859 if (typeof el == "string") {
860 el = this.getEl(el);
861 // The el argument can be an array of elements or element ids.
862 } else if ( this._isValidCollection(el)) {
863 var ok = true;
864 for (i=0,len=el.length; i<len; ++i) {
865 ok = ( this.removeListener(el[i], sType, fn) && ok );
867 return ok;
870 if (!fn || !fn.call) {
871 //return false;
872 return this.purgeElement(el, false, sType);
875 if ("unload" == sType) {
877 for (i=0, len=unloadListeners.length; i<len; i++) {
878 var li = unloadListeners[i];
879 if (li &&
880 li[0] == el &&
881 li[1] == sType &&
882 li[2] == fn) {
883 unloadListeners.splice(i, 1);
884 return true;
888 return false;
891 var cacheItem = null;
893 // The index is a hidden parameter; needed to remove it from
894 // the method signature because it was tempting users to
895 // try and take advantage of it, which is not possible.
896 var index = arguments[3];
898 if ("undefined" == typeof index) {
899 index = this._getCacheIndex(el, sType, fn);
902 if (index >= 0) {
903 cacheItem = listeners[index];
906 if (!el || !cacheItem) {
907 return false;
911 if (this.useLegacyEvent(el, sType)) {
912 var legacyIndex = this.getLegacyIndex(el, sType);
913 var llist = legacyHandlers[legacyIndex];
914 if (llist) {
915 for (i=0, len=llist.length; i<len; ++i) {
916 li = llist[i];
917 if (li &&
918 li[this.EL] == el &&
919 li[this.TYPE] == sType &&
920 li[this.FN] == fn) {
921 llist.splice(i, 1);
922 break;
927 } else {
928 try {
929 this._simpleRemove(el, sType, cacheItem[this.WFN], false);
930 } catch(e) {
931 return false;
935 // removed the wrapped handler
936 delete listeners[index][this.WFN];
937 delete listeners[index][this.FN];
938 listeners.splice(index, 1);
940 return true;
945 * Returns the event's target element
946 * @method getTarget
947 * @param {Event} ev the event
948 * @param {boolean} resolveTextNode when set to true the target's
949 * parent will be returned if the target is a
950 * text node. @deprecated, the text node is
951 * now resolved automatically
952 * @return {HTMLElement} the event's target
953 * @static
955 getTarget: function(ev, resolveTextNode) {
956 var t = ev.target || ev.srcElement;
957 return this.resolveTextNode(t);
961 * In some cases, some browsers will return a text node inside
962 * the actual element that was targeted. This normalizes the
963 * return value for getTarget and getRelatedTarget.
964 * @method resolveTextNode
965 * @param {HTMLElement} node node to resolve
966 * @return {HTMLElement} the normized node
967 * @static
969 resolveTextNode: function(node) {
970 // if (node && node.nodeName &&
971 // "#TEXT" == node.nodeName.toUpperCase()) {
972 if (node && 3 == node.nodeType) {
973 return node.parentNode;
974 } else {
975 return node;
980 * Returns the event's pageX
981 * @method getPageX
982 * @param {Event} ev the event
983 * @return {int} the event's pageX
984 * @static
986 getPageX: function(ev) {
987 var x = ev.pageX;
988 if (!x && 0 !== x) {
989 x = ev.clientX || 0;
991 if ( this.isIE ) {
992 x += this._getScrollLeft();
996 return x;
1000 * Returns the event's pageY
1001 * @method getPageY
1002 * @param {Event} ev the event
1003 * @return {int} the event's pageY
1004 * @static
1006 getPageY: function(ev) {
1007 var y = ev.pageY;
1008 if (!y && 0 !== y) {
1009 y = ev.clientY || 0;
1011 if ( this.isIE ) {
1012 y += this._getScrollTop();
1016 return y;
1020 * Returns the pageX and pageY properties as an indexed array.
1021 * @method getXY
1022 * @param {Event} ev the event
1023 * @return {[x, y]} the pageX and pageY properties of the event
1024 * @static
1026 getXY: function(ev) {
1027 return [this.getPageX(ev), this.getPageY(ev)];
1031 * Returns the event's related target
1032 * @method getRelatedTarget
1033 * @param {Event} ev the event
1034 * @return {HTMLElement} the event's relatedTarget
1035 * @static
1037 getRelatedTarget: function(ev) {
1038 var t = ev.relatedTarget;
1039 if (!t) {
1040 if (ev.type == "mouseout") {
1041 t = ev.toElement;
1042 } else if (ev.type == "mouseover") {
1043 t = ev.fromElement;
1047 return this.resolveTextNode(t);
1051 * Returns the time of the event. If the time is not included, the
1052 * event is modified using the current time.
1053 * @method getTime
1054 * @param {Event} ev the event
1055 * @return {Date} the time of the event
1056 * @static
1058 getTime: function(ev) {
1059 if (!ev.time) {
1060 var t = new Date().getTime();
1061 try {
1062 ev.time = t;
1063 } catch(e) {
1064 return t;
1068 return ev.time;
1072 * Convenience method for stopPropagation + preventDefault
1073 * @method stopEvent
1074 * @param {Event} ev the event
1075 * @static
1077 stopEvent: function(ev) {
1078 this.stopPropagation(ev);
1079 this.preventDefault(ev);
1083 * Stops event propagation
1084 * @method stopPropagation
1085 * @param {Event} ev the event
1086 * @static
1088 stopPropagation: function(ev) {
1089 if (ev.stopPropagation) {
1090 ev.stopPropagation();
1091 } else {
1092 ev.cancelBubble = true;
1097 * Prevents the default behavior of the event
1098 * @method preventDefault
1099 * @param {Event} ev the event
1100 * @static
1102 preventDefault: function(ev) {
1103 if (ev.preventDefault) {
1104 ev.preventDefault();
1105 } else {
1106 ev.returnValue = false;
1111 * Finds the event in the window object, the caller's arguments, or
1112 * in the arguments of another method in the callstack. This is
1113 * executed automatically for events registered through the event
1114 * manager, so the implementer should not normally need to execute
1115 * this function at all.
1116 * @method getEvent
1117 * @param {Event} e the event parameter from the handler
1118 * @return {Event} the event
1119 * @static
1121 getEvent: function(e) {
1122 var ev = e || window.event;
1124 if (!ev) {
1125 var c = this.getEvent.caller;
1126 while (c) {
1127 ev = c.arguments[0];
1128 if (ev && Event == ev.constructor) {
1129 break;
1131 c = c.caller;
1135 return ev;
1139 * Returns the charcode for an event
1140 * @method getCharCode
1141 * @param {Event} ev the event
1142 * @return {int} the event's charCode
1143 * @static
1145 getCharCode: function(ev) {
1146 return ev.charCode || ev.keyCode || 0;
1150 * Locating the saved event handler data by function ref
1152 * @method _getCacheIndex
1153 * @static
1154 * @private
1156 _getCacheIndex: function(el, sType, fn) {
1157 for (var i=0,len=listeners.length; i<len; ++i) {
1158 var li = listeners[i];
1159 if ( li &&
1160 li[this.FN] == fn &&
1161 li[this.EL] == el &&
1162 li[this.TYPE] == sType ) {
1163 return i;
1167 return -1;
1171 * Generates an unique ID for the element if it does not already
1172 * have one.
1173 * @method generateId
1174 * @param el the element to create the id for
1175 * @return {string} the resulting id of the element
1176 * @static
1178 generateId: function(el) {
1179 var id = el.id;
1181 if (!id) {
1182 id = "yuievtautoid-" + counter;
1183 ++counter;
1184 el.id = id;
1187 return id;
1191 * We want to be able to use getElementsByTagName as a collection
1192 * to attach a group of events to. Unfortunately, different
1193 * browsers return different types of collections. This function
1194 * tests to determine if the object is array-like. It will also
1195 * fail if the object is an array, but is empty.
1196 * @method _isValidCollection
1197 * @param o the object to test
1198 * @return {boolean} true if the object is array-like and populated
1199 * @static
1200 * @private
1202 _isValidCollection: function(o) {
1203 // this.logger.debug(o.constructor.toString())
1204 // this.logger.debug(typeof o)
1206 return ( o && // o is something
1207 o.length && // o is indexed
1208 typeof o != "string" && // o is not a string
1209 !o.tagName && // o is not an HTML element
1210 !o.alert && // o is not a window
1211 typeof o[0] != "undefined" );
1216 * @private
1217 * @property elCache
1218 * DOM element cache
1219 * @static
1221 elCache: {},
1224 * We cache elements bound by id because when the unload event
1225 * fires, we can no longer use document.getElementById
1226 * @method getEl
1227 * @static
1228 * @private
1230 getEl: function(id) {
1231 return document.getElementById(id);
1235 * Clears the element cache
1236 * @deprecated Elements are not cached any longer
1237 * @method clearCache
1238 * @static
1239 * @private
1241 clearCache: function() { },
1244 * hook up any deferred listeners
1245 * @method _load
1246 * @static
1247 * @private
1249 _load: function(e) {
1250 loadComplete = true;
1251 var EU = YAHOO.util.Event;
1252 // Remove the listener to assist with the IE memory issue, but not
1253 // for other browsers because FF 1.0x does not like it.
1254 if (this.isIE) {
1255 EU._simpleRemove(window, "load", EU._load);
1260 * Polling function that runs before the onload event fires,
1261 * attempting to attach to DOM Nodes as soon as they are
1262 * available
1263 * @method _tryPreloadAttach
1264 * @static
1265 * @private
1267 _tryPreloadAttach: function() {
1269 if (this.locked) {
1270 return false;
1273 this.locked = true;
1276 // keep trying until after the page is loaded. We need to
1277 // check the page load state prior to trying to bind the
1278 // elements so that we can be certain all elements have been
1279 // tested appropriately
1280 var tryAgain = !loadComplete;
1281 if (!tryAgain) {
1282 tryAgain = (retryCount > 0);
1285 // onAvailable
1286 var notAvail = [];
1287 for (var i=0,len=onAvailStack.length; i<len ; ++i) {
1288 var item = onAvailStack[i];
1289 if (item) {
1290 var el = this.getEl(item.id);
1292 if (el) {
1293 // The element is available, but not necessarily ready
1294 // @todo verify IE7 compatibility
1295 // @todo should we test parentNode.nextSibling?
1296 // @todo re-evaluate global content ready
1297 if ( !item.checkReady ||
1298 loadComplete ||
1299 el.nextSibling ||
1300 (document && document.body) ) {
1302 var scope = el;
1303 if (item.override) {
1304 if (item.override === true) {
1305 scope = item.obj;
1306 } else {
1307 scope = item.override;
1310 item.fn.call(scope, item.obj);
1311 //delete onAvailStack[i];
1312 // null out instead of delete for Opera
1313 onAvailStack[i] = null;
1315 } else {
1316 notAvail.push(item);
1321 retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;
1323 if (tryAgain) {
1324 // we may need to strip the nulled out items here
1325 this.startInterval();
1326 } else {
1327 clearInterval(this._interval);
1328 this._interval = null;
1331 this.locked = false;
1333 return true;
1338 * Removes all listeners attached to the given element via addListener.
1339 * Optionally, the node's children can also be purged.
1340 * Optionally, you can specify a specific type of event to remove.
1341 * @method purgeElement
1342 * @param {HTMLElement} el the element to purge
1343 * @param {boolean} recurse recursively purge this element's children
1344 * as well. Use with caution.
1345 * @param {string} sType optional type of listener to purge. If
1346 * left out, all listeners will be removed
1347 * @static
1349 purgeElement: function(el, recurse, sType) {
1350 var elListeners = this.getListeners(el, sType);
1351 if (elListeners) {
1352 for (var i=0,len=elListeners.length; i<len ; ++i) {
1353 var l = elListeners[i];
1354 // can't use the index on the changing collection
1355 //this.removeListener(el, l.type, l.fn, l.index);
1356 this.removeListener(el, l.type, l.fn);
1360 if (recurse && el && el.childNodes) {
1361 for (i=0,len=el.childNodes.length; i<len ; ++i) {
1362 this.purgeElement(el.childNodes[i], recurse, sType);
1368 * Returns all listeners attached to the given element via addListener.
1369 * Optionally, you can specify a specific type of event to return.
1370 * @method getListeners
1371 * @param el {HTMLElement} the element to inspect
1372 * @param sType {string} optional type of listener to return. If
1373 * left out, all listeners will be returned
1374 * @return {Object} the listener. Contains the following fields:
1375 * &nbsp;&nbsp;type: (string) the type of event
1376 * &nbsp;&nbsp;fn: (function) the callback supplied to addListener
1377 * &nbsp;&nbsp;obj: (object) the custom object supplied to addListener
1378 * &nbsp;&nbsp;adjust: (boolean) whether or not to adjust the default scope
1379 * &nbsp;&nbsp;index: (int) its position in the Event util listener cache
1380 * @static
1382 getListeners: function(el, sType) {
1383 var elListeners = [];
1384 if (listeners && listeners.length > 0) {
1385 for (var i=0,len=listeners.length; i<len ; ++i) {
1386 var l = listeners[i];
1387 if ( l && l[this.EL] === el &&
1388 (!sType || sType === l[this.TYPE]) ) {
1389 elListeners.push({
1390 type: l[this.TYPE],
1391 fn: l[this.FN],
1392 obj: l[this.OBJ],
1393 adjust: l[this.ADJ_SCOPE],
1394 index: i
1400 return (elListeners.length) ? elListeners : null;
1404 * Removes all listeners registered by pe.event. Called
1405 * automatically during the unload event.
1406 * @method _unload
1407 * @static
1408 * @private
1410 _unload: function(e) {
1412 var EU = YAHOO.util.Event, i, j, l, len, index;
1414 for (i=0,len=unloadListeners.length; i<len; ++i) {
1415 l = unloadListeners[i];
1416 if (l) {
1417 var scope = window;
1418 if (l[EU.ADJ_SCOPE]) {
1419 if (l[EU.ADJ_SCOPE] === true) {
1420 scope = l[EU.OBJ];
1421 } else {
1422 scope = l[EU.ADJ_SCOPE];
1425 l[EU.FN].call(scope, EU.getEvent(e), l[EU.OBJ] );
1426 unloadListeners[i] = null;
1427 l=null;
1428 scope=null;
1432 unloadListeners = null;
1434 if (listeners && listeners.length > 0) {
1435 j = listeners.length;
1436 while (j) {
1437 index = j-1;
1438 l = listeners[index];
1439 if (l) {
1440 EU.removeListener(l[EU.EL], l[EU.TYPE],
1441 l[EU.FN], index);
1443 j = j - 1;
1445 l=null;
1447 EU.clearCache();
1450 for (i=0,len=legacyEvents.length; i<len; ++i) {
1451 // dereference the element
1452 //delete legacyEvents[i][0];
1453 legacyEvents[i][0] = null;
1455 // delete the array item
1456 //delete legacyEvents[i];
1457 legacyEvents[i] = null;
1460 legacyEvents = null;
1462 EU._simpleRemove(window, "unload", EU._unload);
1467 * Returns scrollLeft
1468 * @method _getScrollLeft
1469 * @static
1470 * @private
1472 _getScrollLeft: function() {
1473 return this._getScroll()[1];
1477 * Returns scrollTop
1478 * @method _getScrollTop
1479 * @static
1480 * @private
1482 _getScrollTop: function() {
1483 return this._getScroll()[0];
1487 * Returns the scrollTop and scrollLeft. Used to calculate the
1488 * pageX and pageY in Internet Explorer
1489 * @method _getScroll
1490 * @static
1491 * @private
1493 _getScroll: function() {
1494 var dd = document.documentElement, db = document.body;
1495 if (dd && (dd.scrollTop || dd.scrollLeft)) {
1496 return [dd.scrollTop, dd.scrollLeft];
1497 } else if (db) {
1498 return [db.scrollTop, db.scrollLeft];
1499 } else {
1500 return [0, 0];
1505 * Adds a DOM event directly without the caching, cleanup, scope adj, etc
1507 * @method _simpleAdd
1508 * @param {HTMLElement} el the element to bind the handler to
1509 * @param {string} sType the type of event handler
1510 * @param {function} fn the callback to invoke
1511 * @param {boolen} capture capture or bubble phase
1512 * @static
1513 * @private
1515 _simpleAdd: function () {
1516 if (window.addEventListener) {
1517 return function(el, sType, fn, capture) {
1518 el.addEventListener(sType, fn, (capture));
1520 } else if (window.attachEvent) {
1521 return function(el, sType, fn, capture) {
1522 el.attachEvent("on" + sType, fn);
1524 } else {
1525 return function(){};
1527 }(),
1530 * Basic remove listener
1532 * @method _simpleRemove
1533 * @param {HTMLElement} el the element to bind the handler to
1534 * @param {string} sType the type of event handler
1535 * @param {function} fn the callback to invoke
1536 * @param {boolen} capture capture or bubble phase
1537 * @static
1538 * @private
1540 _simpleRemove: function() {
1541 if (window.removeEventListener) {
1542 return function (el, sType, fn, capture) {
1543 el.removeEventListener(sType, fn, (capture));
1545 } else if (window.detachEvent) {
1546 return function (el, sType, fn) {
1547 el.detachEvent("on" + sType, fn);
1549 } else {
1550 return function(){};
1555 }();
1557 (function() {
1558 var EU = YAHOO.util.Event;
1561 * YAHOO.util.Event.on is an alias for addListener
1562 * @method on
1563 * @see addListener
1564 * @static
1566 EU.on = EU.addListener;
1568 // YAHOO.mix(EU, YAHOO.util.EventProvider.prototype);
1569 // EU.createEvent("DOMContentReady");
1570 // EU.subscribe("DOMContentReady", EU._load);
1572 if (document && document.body) {
1573 EU._load();
1574 } else {
1575 // EU._simpleAdd(document, "DOMContentLoaded", EU._load);
1576 EU._simpleAdd(window, "load", EU._load);
1578 EU._simpleAdd(window, "unload", EU._unload);
1579 EU._tryPreloadAttach();
1580 })();
1584 * EventProvider is designed to be used with YAHOO.augment to wrap
1585 * CustomEvents in an interface that allows events to be subscribed to
1586 * and fired by name. This makes it possible for implementing code to
1587 * subscribe to an event that either has not been created yet, or will
1588 * not be created at all.
1590 * @Class EventProvider
1592 YAHOO.util.EventProvider = function() { };
1594 YAHOO.util.EventProvider.prototype = {
1597 * Private storage of custom events
1598 * @property __yui_events
1599 * @type Object[]
1600 * @private
1602 __yui_events: null,
1605 * Private storage of custom event subscribers
1606 * @property __yui_subscribers
1607 * @type Object[]
1608 * @private
1610 __yui_subscribers: null,
1613 * Subscribe to a CustomEvent by event type
1615 * @method subscribe
1616 * @param p_type {string} the type, or name of the event
1617 * @param p_fn {function} the function to exectute when the event fires
1618 * @param p_obj
1619 * @param p_obj {Object} An object to be passed along when the event
1620 * fires
1621 * @param p_override {boolean} If true, the obj passed in becomes the
1622 * execution scope of the listener
1624 subscribe: function(p_type, p_fn, p_obj, p_override) {
1626 this.__yui_events = this.__yui_events || {};
1627 var ce = this.__yui_events[p_type];
1629 if (ce) {
1630 ce.subscribe(p_fn, p_obj, p_override);
1631 } else {
1632 this.__yui_subscribers = this.__yui_subscribers || {};
1633 var subs = this.__yui_subscribers;
1634 if (!subs[p_type]) {
1635 subs[p_type] = [];
1637 subs[p_type].push(
1638 { fn: p_fn, obj: p_obj, override: p_override } );
1643 * Unsubscribes the from the specified event
1644 * @method unsubscribe
1645 * @param p_type {string} The type, or name of the event
1646 * @param p_fn {Function} The function to execute
1647 * @param p_obj {Object} The custom object passed to subscribe (optional)
1648 * @return {boolean} true if the subscriber was found and detached.
1650 unsubscribe: function(p_type, p_fn, p_obj) {
1651 this.__yui_events = this.__yui_events || {};
1652 var ce = this.__yui_events[p_type];
1653 if (ce) {
1654 return ce.unsubscribe(p_fn, p_obj);
1655 } else {
1656 return false;
1661 * Creates a new custom event of the specified type. If a custom event
1662 * by that name already exists, it will not be re-created. In either
1663 * case the custom event is returned.
1665 * @method createEvent
1667 * @param p_type {string} the type, or name of the event
1668 * @param p_config {object} optional config params. Valid properties are:
1670 * <ul>
1671 * <li>
1672 * scope: defines the default execution scope. If not defined
1673 * the default scope will be this instance.
1674 * </li>
1675 * <li>
1676 * silent: if true, the custom event will not generate log messages.
1677 * This is false by default.
1678 * </li>
1679 * <li>
1680 * onSubscribeCallback: specifies a callback to execute when the
1681 * event has a new subscriber. This will fire immediately for
1682 * each queued subscriber if any exist prior to the creation of
1683 * the event.
1684 * </li>
1685 * </ul>
1687 * @return {CustomEvent} the custom event
1690 createEvent: function(p_type, p_config) {
1692 this.__yui_events = this.__yui_events || {};
1693 var opts = p_config || {};
1694 var events = this.__yui_events;
1696 if (events[p_type]) {
1697 } else {
1699 var scope = opts.scope || this;
1700 var silent = opts.silent || null;
1702 var ce = new YAHOO.util.CustomEvent(p_type, scope, silent,
1703 YAHOO.util.CustomEvent.FLAT);
1704 events[p_type] = ce;
1706 if (opts.onSubscribeCallback) {
1707 ce.subscribeEvent.subscribe(opts.onSubscribeCallback);
1710 this.__yui_subscribers = this.__yui_subscribers || {};
1711 var qs = this.__yui_subscribers[p_type];
1713 if (qs) {
1714 for (var i=0; i<qs.length; ++i) {
1715 ce.subscribe(qs[i].fn, qs[i].obj, qs[i].override);
1720 return events[p_type];
1724 * Fire a custom event by name. The callback functions will be executed
1725 * from the scope specified when the event was created, and with the
1726 * following parameters:
1727 * <ul>
1728 * <li>The first argument fire() was executed with</li>
1729 * <li>The custom object (if any) that was passed into the subscribe()
1730 * method</li>
1731 * </ul>
1732 * @method fireEvent
1733 * @param p_type {string} the type, or name of the event
1734 * @param arguments {Object*} an arbitrary set of parameters to pass to
1735 * the handler.
1736 * @return {boolean} the return value from CustomEvent.fire, or null if
1737 * the custom event does not exist.
1739 fireEvent: function(p_type, arg1, arg2, etc) {
1741 this.__yui_events = this.__yui_events || {};
1742 var ce = this.__yui_events[p_type];
1744 if (ce) {
1745 var args = [];
1746 for (var i=1; i<arguments.length; ++i) {
1747 args.push(arguments[i]);
1749 return ce.fire.apply(ce, args);
1750 } else {
1751 return null;
1756 * Returns true if the custom event of the provided type has been created
1757 * with createEvent.
1758 * @method hasEvent
1759 * @param type {string} the type, or name of the event
1761 hasEvent: function(type) {
1762 if (this.__yui_events) {
1763 if (this.__yui_events[type]) {
1764 return true;
1767 return false;