2 Copyright (c) 2006, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
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
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
26 YAHOO.util.CustomEvent = function(type, oScope, silent, signature) {
29 * The type of event, returned to subscribers when the event fires
36 * The scope the the event will fire from by default. Defaults to the window
41 this.scope = oScope || window;
44 * By default all custom events are logged in the debug build, set silent
45 * to true to disable debug outpu for this event.
52 * Custom events support two styles of arguments provided to the event
55 * <li>YAHOO.util.CustomEvent.LIST:
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>
62 * <li>YAHOO.util.CustomEvent.FLAT
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>
73 this.signature = signature || YAHOO.util.CustomEvent.LIST;
76 * The subscribers to this event
77 * @property subscribers
80 this.subscribers = [];
85 var onsubscribeType = "_YUICEOnSubscribe";
87 // Only add subscribe events for events that are not generated by
89 if (type !== onsubscribeType) {
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
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
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
127 * @property YAHOO.util.CustomEvent.FLAT
131 YAHOO.util.CustomEvent.FLAT = 1;
133 YAHOO.util.CustomEvent.prototype = {
136 * Subscribes the caller to this event
138 * @param {Function} fn The function to execute
139 * @param {Object} obj An object to be passed along when the event
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) {
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)) {
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:
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()
185 * @param {Object*} arguments an arbitrary set of parameters to pass to
187 * @return {boolean} false if one of the subscribers returned false,
191 var len=this.subscribers.length;
192 if (!len && this.silent) {
196 var args=[], ret=true, i;
198 for (i=0; i<arguments.length; ++i) {
199 args.push(arguments[i]);
202 var argslength = args.length;
207 for (i=0; i<len; ++i) {
208 var s = this.subscribers[i];
213 var scope = s.getScope(this.scope);
215 if (this.signature == YAHOO.util.CustomEvent.FLAT) {
217 if (args.length > 0) {
220 ret = s.fn.call(scope, param, s.obj);
222 ret = s.fn.call(scope, this.type, args, s.obj);
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);
251 _delete: function(index) {
252 var s = this.subscribers[index];
258 // delete this.subscribers[index];
259 this.subscribers.splice(index, 1);
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
283 YAHOO.util.Subscriber = function(fn, obj, override) {
286 * The callback that will be execute when the event fires
293 * An optional custom object that will passed to the callback when
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.
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.
318 * @param {Object} defaultScope the scope to use if this listener does not
321 YAHOO.util.Subscriber.prototype.getScope = function(defaultScope) {
323 if (this.override === true) {
326 return this.override;
333 * Returns true if the fn and obj match this objects properties.
334 * Used by the unsubscribe method to match the right subscriber.
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) {
344 return (this.fn == fn && this.obj == obj);
346 return (this.fn == fn);
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
363 * @title Event Utility
364 * @namespace YAHOO.util
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.
379 YAHOO.util.Event = function() {
382 * True after the onload event has fired
383 * @property loadComplete
388 var loadComplete = false;
391 * Cache of wrapped listeners
392 * @property listeners
400 * User-defined unload function that will be fired before all events
402 * @property unloadListeners
407 var unloadListeners = [];
410 * Cache of DOM0 event handlers to work around issues with DOM2 events
412 * @property legacyEvents
416 var legacyEvents = [];
419 * Listener stack for DOM0 events
420 * @property legacyHandlers
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
430 * @property retryCount
437 * onAvailable listeners
438 * @property onAvailStack
442 var onAvailStack = [];
445 * Lookup table for legacy events
446 * @property legacyMap
453 * Counter for auto id generation
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
476 * The poll interval in milliseconds
477 * @property POLL_INTERVAL
485 * Element to bind, int constant
494 * Type of event, int constant
503 * Function to execute, int constant
512 * Function wrapped for scope correction and cleanup, int constant
521 * Object passed in by the user that will be returned as a
522 * parameter to the callback, int constant
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
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.
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.
559 isIE: (!this.isSafari && !navigator.userAgent.match(/opera/gi) &&
560 navigator.userAgent.match(/msie/gi)),
564 * @property _interval
570 * @method startInterval
574 startInterval: function() {
575 if (!this._interval) {
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
602 onAvailable: function(p_id, p_fn, p_obj, p_override) {
603 onAvailStack.push( { id: p_id,
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
629 onContentReady: function(p_id, p_fn, p_obj, p_override) {
630 onAvailStack.push( { id: p_id,
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
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.
659 addListener: function(el, sType, fn, obj, override) {
661 if (!fn || !fn.call) {
665 // The el argument can be an array of elements or element ids.
666 if ( this._isValidCollection(el)) {
668 for (var i=0,len=el.length; i<len; ++i) {
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.
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);
698 // Element should be an html element or an array if we get
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];
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
720 if (override === true) {
727 // wrap the function so we can return the obj object when
729 var wrappedFn = function(e) {
730 return fn.call(scope, YAHOO.util.Event.getEvent(e),
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
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] = [];
758 YAHOO.util.Event.fireLegacyEvent(
759 YAHOO.util.Event.getEvent(e), legacyIndex);
763 // add a reference to the wrapped listener to our custom
765 //legacyHandlers[legacyIndex].push(index);
766 legacyHandlers[legacyIndex].push(li);
770 this._simpleAdd(el, sType, wrappedFn, false);
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);
784 * When using legacy events, the handler is routed to this object
785 * so we can fire our custom listener stack.
786 * @method fireLegacyEvent
790 fireLegacyEvent: function(e, legacyIndex) {
793 var le = legacyHandlers[legacyIndex];
794 for (var i=0,len=le.length; i<len; ++i) {
796 if ( li && li[this.WFN] ) {
797 var scope = li[this.ADJ_SCOPE];
798 var ret = li[this.WFN].call(scope, e);
807 * Returns the legacy event index that matches the supplied
809 * @method getLegacyIndex
813 getLegacyIndex: function(el, sType) {
814 var key = this.generateId(el) + sType;
815 if (typeof legacyMap[key] == "undefined") {
818 return legacyMap[key];
823 * Logic that determines when we should automatically use legacy
824 * events instead of DOM2 events.
825 * @method useLegacyEvent
829 useLegacyEvent: function(el, sType) {
830 if (!el.addEventListener && !el.attachEvent) {
832 } else if (this.isSafari) {
833 if ("click" == sType || "dblclick" == sType) {
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
851 * @return {boolean} true if the unbind was successful, false
855 removeListener: function(el, sType, fn) {
858 // The el argument can be a string
859 if (typeof el == "string") {
861 // The el argument can be an array of elements or element ids.
862 } else if ( this._isValidCollection(el)) {
864 for (i=0,len=el.length; i<len; ++i) {
865 ok = ( this.removeListener(el[i], sType, fn) && ok );
870 if (!fn || !fn.call) {
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];
883 unloadListeners.splice(i, 1);
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);
903 cacheItem = listeners[index];
906 if (!el || !cacheItem) {
911 if (this.useLegacyEvent(el, sType)) {
912 var legacyIndex = this.getLegacyIndex(el, sType);
913 var llist = legacyHandlers[legacyIndex];
915 for (i=0, len=llist.length; i<len; ++i) {
919 li[this.TYPE] == sType &&
929 this._simpleRemove(el, sType, cacheItem[this.WFN], false);
935 // removed the wrapped handler
936 delete listeners[index][this.WFN];
937 delete listeners[index][this.FN];
938 listeners.splice(index, 1);
945 * Returns the event's target element
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
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
969 resolveTextNode: function(node) {
970 // if (node && node.nodeName &&
971 // "#TEXT" == node.nodeName.toUpperCase()) {
972 if (node && 3 == node.nodeType) {
973 return node.parentNode;
980 * Returns the event's pageX
982 * @param {Event} ev the event
983 * @return {int} the event's pageX
986 getPageX: function(ev) {
992 x += this._getScrollLeft();
1000 * Returns the event's pageY
1002 * @param {Event} ev the event
1003 * @return {int} the event's pageY
1006 getPageY: function(ev) {
1008 if (!y && 0 !== y) {
1009 y = ev.clientY || 0;
1012 y += this._getScrollTop();
1020 * Returns the pageX and pageY properties as an indexed array.
1022 * @param {Event} ev the event
1023 * @return {[x, y]} the pageX and pageY properties of the event
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
1037 getRelatedTarget: function(ev) {
1038 var t = ev.relatedTarget;
1040 if (ev.type == "mouseout") {
1042 } else if (ev.type == "mouseover") {
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.
1054 * @param {Event} ev the event
1055 * @return {Date} the time of the event
1058 getTime: function(ev) {
1060 var t = new Date().getTime();
1072 * Convenience method for stopPropagation + preventDefault
1074 * @param {Event} ev the event
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
1088 stopPropagation: function(ev) {
1089 if (ev.stopPropagation) {
1090 ev.stopPropagation();
1092 ev.cancelBubble = true;
1097 * Prevents the default behavior of the event
1098 * @method preventDefault
1099 * @param {Event} ev the event
1102 preventDefault: function(ev) {
1103 if (ev.preventDefault) {
1104 ev.preventDefault();
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.
1117 * @param {Event} e the event parameter from the handler
1118 * @return {Event} the event
1121 getEvent: function(e) {
1122 var ev = e || window.event;
1125 var c = this.getEvent.caller;
1127 ev = c.arguments[0];
1128 if (ev && Event == ev.constructor) {
1139 * Returns the charcode for an event
1140 * @method getCharCode
1141 * @param {Event} ev the event
1142 * @return {int} the event's charCode
1145 getCharCode: function(ev) {
1146 return ev.charCode || ev.keyCode || 0;
1150 * Locating the saved event handler data by function ref
1152 * @method _getCacheIndex
1156 _getCacheIndex: function(el, sType, fn) {
1157 for (var i=0,len=listeners.length; i<len; ++i) {
1158 var li = listeners[i];
1160 li[this.FN] == fn &&
1161 li[this.EL] == el &&
1162 li[this.TYPE] == sType ) {
1171 * Generates an unique ID for the element if it does not already
1173 * @method generateId
1174 * @param el the element to create the id for
1175 * @return {string} the resulting id of the element
1178 generateId: function(el) {
1182 id = "yuievtautoid-" + counter;
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
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" );
1224 * We cache elements bound by id because when the unload event
1225 * fires, we can no longer use document.getElementById
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
1241 clearCache: function() { },
1244 * hook up any deferred listeners
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.
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
1263 * @method _tryPreloadAttach
1267 _tryPreloadAttach: function() {
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;
1282 tryAgain = (retryCount > 0);
1287 for (var i=0,len=onAvailStack.length; i<len ; ++i) {
1288 var item = onAvailStack[i];
1290 var el = this.getEl(item.id);
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 ||
1300 (document && document.body) ) {
1303 if (item.override) {
1304 if (item.override === true) {
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;
1316 notAvail.push(item);
1321 retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;
1324 // we may need to strip the nulled out items here
1325 this.startInterval();
1327 clearInterval(this._interval);
1328 this._interval = null;
1331 this.locked = false;
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
1349 purgeElement: function(el, recurse, sType) {
1350 var elListeners = this.getListeners(el, sType);
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 * type: (string) the type of event
1376 * fn: (function) the callback supplied to addListener
1377 * obj: (object) the custom object supplied to addListener
1378 * adjust: (boolean) whether or not to adjust the default scope
1379 * index: (int) its position in the Event util listener cache
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]) ) {
1393 adjust: l[this.ADJ_SCOPE],
1400 return (elListeners.length) ? elListeners : null;
1404 * Removes all listeners registered by pe.event. Called
1405 * automatically during the unload event.
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];
1418 if (l[EU.ADJ_SCOPE]) {
1419 if (l[EU.ADJ_SCOPE] === true) {
1422 scope = l[EU.ADJ_SCOPE];
1425 l[EU.FN].call(scope, EU.getEvent(e), l[EU.OBJ] );
1426 unloadListeners[i] = null;
1432 unloadListeners = null;
1434 if (listeners && listeners.length > 0) {
1435 j = listeners.length;
1438 l = listeners[index];
1440 EU.removeListener(l[EU.EL], l[EU.TYPE],
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
1472 _getScrollLeft: function() {
1473 return this._getScroll()[1];
1478 * @method _getScrollTop
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
1493 _getScroll: function() {
1494 var dd = document.documentElement, db = document.body;
1495 if (dd && (dd.scrollTop || dd.scrollLeft)) {
1496 return [dd.scrollTop, dd.scrollLeft];
1498 return [db.scrollTop, db.scrollLeft];
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
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);
1525 return function(){};
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
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);
1550 return function(){};
1558 var EU = YAHOO.util.Event;
1561 * YAHOO.util.Event.on is an alias for addListener
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) {
1575 // EU._simpleAdd(document, "DOMContentLoaded", EU._load);
1576 EU._simpleAdd(window, "load", EU._load);
1578 EU._simpleAdd(window, "unload", EU._unload);
1579 EU._tryPreloadAttach();
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
1605 * Private storage of custom event subscribers
1606 * @property __yui_subscribers
1610 __yui_subscribers: null,
1613 * Subscribe to a CustomEvent by event type
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
1619 * @param p_obj {Object} An object to be passed along when the event
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];
1630 ce.subscribe(p_fn, p_obj, p_override);
1632 this.__yui_subscribers = this.__yui_subscribers || {};
1633 var subs = this.__yui_subscribers;
1634 if (!subs[p_type]) {
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];
1654 return ce.unsubscribe(p_fn, p_obj);
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:
1672 * scope: defines the default execution scope. If not defined
1673 * the default scope will be this instance.
1676 * silent: if true, the custom event will not generate log messages.
1677 * This is false by default.
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
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]) {
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];
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:
1728 * <li>The first argument fire() was executed with</li>
1729 * <li>The custom object (if any) that was passed into the subscribe()
1733 * @param p_type {string} the type, or name of the event
1734 * @param arguments {Object*} an arbitrary set of parameters to pass to
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];
1746 for (var i=1; i<arguments.length; ++i) {
1747 args.push(arguments[i]);
1749 return ce.fire.apply(ce, args);
1756 * Returns true if the custom event of the provided type has been created
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]) {