2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 var GLOBAL_ENV = YUI.Env;
10 if (!GLOBAL_ENV._ready) {
11 GLOBAL_ENV._ready = function() {
12 GLOBAL_ENV.DOMReady = true;
13 GLOBAL_ENV.remove(YUI.config.doc, 'DOMContentLoaded', GLOBAL_ENV._ready);
17 GLOBAL_ENV.add(YUI.config.doc, 'DOMContentLoaded', GLOBAL_ENV._ready);
21 YUI.add('event-base', function(Y) {
24 * DOM event listener abstraction layer
26 * @submodule event-base
30 * The domready event fires at the moment the browser's DOM is
31 * usable. In most cases, this is before images are fully
32 * downloaded, allowing you to provide a more responsive user
35 * In YUI 3, domready subscribers will be notified immediately if
36 * that moment has already passed when the subscription is created.
38 * One exception is if the yui.js file is dynamically injected into
39 * the page. If this is done, you must tell the YUI instance that
40 * you did this in order for DOMReady (and window load events) to
41 * fire normally. That configuration option is 'injected' -- set
42 * it to true if the yui.js script is not included inline.
44 * This method is part of the 'event-ready' module, which is a
45 * submodule of 'event'.
50 Y.publish('domready', {
55 if (GLOBAL_ENV.DOMReady) {
58 Y.Do.before(function() { Y.fire('domready'); }, YUI.Env, '_ready');
62 * Custom event engine, DOM event listener abstraction layer, synthetic DOM
65 * @submodule event-base
69 * Wraps a DOM event, properties requiring browser abstraction are
70 * fixed here. Provids a security layer when required.
71 * @class DOMEventFacade
72 * @param ev {Event} the DOM event
73 * @param currentTarget {HTMLElement} the element the listener was attached to
74 * @param wrapper {Event.Custom} the custom event wrapper for this DOM event
82 * webkit key remapping required for Safari < 3.1
83 * @property webkitKeymap
92 63277: 34, // page down
93 25: 9, // SHIFT-TAB (Safari provides a different key code in
94 // this case, even though the shiftKey modifier is set)
101 * Returns a wrapped node. Intended to be used on event targets,
102 * so it will return the node's parent if the target is a text
105 * If accessing a property of the node throws an error, this is
106 * probably the anonymous div wrapper Gecko adds inside text
107 * nodes. This likely will only occur when attempting to access
108 * the relatedTarget. In this case, we now return null because
109 * the anonymous div is completely useless and we do not know
110 * what the related target was because we can't even get to
111 * the element's parent node.
116 resolve = function(n) {
121 if (n && 3 == n.nodeType) {
131 DOMEventFacade = function(ev, currentTarget, wrapper) {
133 this._currentTarget = currentTarget;
134 this._wrapper = wrapper || EMPTY;
140 Y.extend(DOMEventFacade, Object, {
145 overrides = this._wrapper.overrides,
149 currentTarget = this._currentTarget;
151 this.altKey = e.altKey;
152 this.ctrlKey = e.ctrlKey;
153 this.metaKey = e.metaKey;
154 this.shiftKey = e.shiftKey;
155 this.type = (overrides && overrides.type) || e.type;
156 this.clientX = e.clientX;
157 this.clientY = e.clientY;
162 c = e.keyCode || e.charCode;
164 if (ua.webkit && (c in webkitKeymap)) {
170 this.which = e.which || e.charCode || c;
171 // this.button = e.button;
172 this.button = this.which;
174 this.target = resolve(e.target);
175 this.currentTarget = resolve(currentTarget);
176 this.relatedTarget = resolve(e.relatedTarget);
178 if (e.type == "mousewheel" || e.type == "DOMMouseScroll") {
179 this.wheelDelta = (e.detail) ? (e.detail * -1) : Math.round(e.wheelDelta / 80) || ((e.wheelDelta < 0) ? -1 : 1);
183 this._touch(e, currentTarget, this._wrapper);
187 stopPropagation: function() {
188 this._event.stopPropagation();
189 this._wrapper.stopped = 1;
193 stopImmediatePropagation: function() {
195 if (e.stopImmediatePropagation) {
196 e.stopImmediatePropagation();
198 this.stopPropagation();
200 this._wrapper.stopped = 2;
204 preventDefault: function(returnValue) {
207 e.returnValue = returnValue || false;
208 this._wrapper.prevented = 1;
212 halt: function(immediate) {
214 this.stopImmediatePropagation();
216 this.stopPropagation();
219 this.preventDefault();
224 DOMEventFacade.resolve = resolve;
225 Y.DOM2EventFacade = DOMEventFacade;
226 Y.DOMEventFacade = DOMEventFacade;
234 * The X location of the event on the page (including scroll)
240 * The Y location of the event on the page (including scroll)
246 * The keyCode for key events. Uses charCode if keyCode is not available
252 * The charCode for key events. Same as keyCode
258 * The button that was pushed.
264 * The button that was pushed. Same as button.
270 * Node reference for the targeted element
276 * Node reference for the element that the listener was attached to.
277 * @propery currentTarget
282 * Node reference to the relatedTarget
283 * @propery relatedTarget
288 * Number representing the direction and velocity of the movement of the mousewheel.
289 * Negative is down, the higher the number, the faster. Applies to the mousewheel event.
290 * @property wheelDelta
295 * Stops the propagation to the next bubble target
296 * @method stopPropagation
300 * Stops the propagation to the next bubble target and
301 * prevents any additional listeners from being exectued
302 * on the current target.
303 * @method stopImmediatePropagation
307 * Prevents the event's default behavior
308 * @method preventDefault
309 * @param returnValue {string} sets the returnValue of the event to this value
310 * (rather than the default false value). This can be used to add a customized
311 * confirmation query to the beforeunload event).
315 * Stops the event propagation and prevents the default
318 * @param immediate {boolean} if true additional listeners
319 * on the current target will not be executed
323 * DOM event listener abstraction layer
325 * @submodule event-base
329 * The event utility provides functions to add and remove event listeners,
330 * event cleansing. It also tries to automatically remove listeners it
331 * registers during the unload event.
337 Y.Env.evt.dom_wrappers = {};
338 Y.Env.evt.dom_map = {};
340 var _eventenv = Y.Env.evt,
344 remove = YUI.Env.remove,
346 onLoad = function() {
347 YUI.Env.windowLoaded = true;
349 remove(win, "load", onLoad);
352 onUnload = function() {
356 EVENT_READY = 'domready',
358 COMPAT_ARG = '~yui|2|compat~',
360 shouldIterate = function(o) {
362 return (o && typeof o !== "string" && Y.Lang.isNumber(o.length) &&
363 !o.tagName && !o.alert);
373 * True after the onload event has fired
374 * @property _loadComplete
379 var _loadComplete = false,
382 * The number of times to poll after window.onload. This number is
383 * increased if additional late-bound handlers are requested after
385 * @property _retryCount
392 * onAvailable listeners
400 * Custom event wrappers for DOM events. Key is
401 * 'event:' + Element uid stamp + event type
402 * @property _wrappers
403 * @type Y.Event.Custom
407 _wrappers = _eventenv.dom_wrappers,
409 _windowLoadKey = null,
412 * Custom event wrapper map DOM events. Key is
413 * Element uid stamp. Each item is a hash of custom event
414 * wrappers as provided in the _wrappers collection. This
415 * provides the infrastructure for getListeners.
416 * @property _el_events
420 _el_events = _eventenv.dom_map;
425 * The number of times we should look for elements that are not
426 * in the DOM at the time the event is requested after the document
427 * has been loaded. The default is 1000@amp;40 ms, so it will poll
428 * for 40 seconds or until all outstanding handlers are bound
429 * (whichever comes first).
430 * @property POLL_RETRYS
438 * The poll interval in milliseconds
439 * @property POLL_INTERVAL
447 * addListener/removeListener can throw errors in unexpected scenarios.
448 * These errors are suppressed, the method returns false, and this property
450 * @property lastError
459 * @property _interval
466 * document readystate poll handle
474 * True when the document is initially usable
482 * @method startInterval
486 startInterval: function() {
487 if (!Event._interval) {
488 Event._interval = setInterval(Event._poll, Event.POLL_INTERVAL);
493 * Executes the supplied callback when the item with the supplied
494 * id is found. This is meant to be used to execute behavior as
495 * soon as possible as the page loads. If you use this after the
496 * initial page load it will poll for a fixed time for the element.
497 * The number of times it will poll and the frequency are
498 * configurable. By default it will poll for 10 seconds.
500 * <p>The callback is executed with a single parameter:
501 * the custom object parameter, if provided.</p>
503 * @method onAvailable
505 * @param {string||string[]} id the id of the element, or an array
506 * of ids to look for.
507 * @param {function} fn what to execute when the element is found.
508 * @param {object} p_obj an optional object to be passed back as
510 * @param {boolean|object} p_override If set to true, fn will execute
511 * in the context of p_obj, if set to an object it
512 * will execute in the context of that object
513 * @param checkContent {boolean} check child node readiness (onContentReady)
515 * @deprecated Use Y.on("available")
517 // @TODO fix arguments
518 onAvailable: function(id, fn, p_obj, p_override, checkContent, compat) {
520 var a = Y.Array(id), i, availHandle;
523 for (i=0; i<a.length; i=i+1) {
528 override: p_override,
529 checkReady: checkContent,
533 _retryCount = this.POLL_RETRYS;
535 // We want the first test to be immediate, but async
536 setTimeout(Event._poll, 0);
538 availHandle = new Y.EventHandle({
540 _delete: function() {
541 // set by the event system for lazy DOM listeners
542 if (availHandle.handle) {
543 availHandle.handle.detach();
549 // otherwise try to remove the onAvailable listener(s)
550 for (i = 0; i < a.length; i++) {
551 for (j = 0; j < _avail.length; j++) {
552 if (a[i] === _avail[j].id) {
565 * Works the same way as onAvailable, but additionally checks the
566 * state of sibling elements to determine if the content of the
567 * available element is safe to modify.
569 * <p>The callback is executed with a single parameter:
570 * the custom object parameter, if provided.</p>
572 * @method onContentReady
574 * @param {string} id the id of the element to look for.
575 * @param {function} fn what to execute when the element is ready.
576 * @param {object} obj an optional object to be passed back as
578 * @param {boolean|object} override If set to true, fn will execute
579 * in the context of p_obj. If an object, fn will
580 * exectute in the context of that object
583 * @deprecated Use Y.on("contentready")
585 // @TODO fix arguments
586 onContentReady: function(id, fn, obj, override, compat) {
587 return Event.onAvailable(id, fn, obj, override, true, compat);
591 * Adds an event listener
595 * @param {String} type The type of event to append
596 * @param {Function} fn The method the event invokes
597 * @param {String|HTMLElement|Array|NodeList} el An id, an element
598 * reference, or a collection of ids and/or elements to assign the
600 * @param {Object} context optional context object
601 * @param {Boolean|object} args 0..n arguments to pass to the callback
602 * @return {EventHandle} an object to that can be used to detach the listener
607 attach: function(type, fn, el, context) {
608 return Event._attach(Y.Array(arguments, 0, true));
611 _createWrapper: function (el, type, capture, compat, facade) {
615 key = 'event:' + ek + type;
617 if (false === facade) {
625 cewrapper = _wrappers[key];
630 cewrapper = Y.publish(key, {
633 contextFn: function() {
637 cewrapper.nodeRef = cewrapper.nodeRef || Y.one(cewrapper.el);
638 return cewrapper.nodeRef;
643 cewrapper.overrides = {};
645 // for later removeListener calls
648 cewrapper.domkey = ek;
649 cewrapper.type = type;
650 cewrapper.fn = function(e) {
651 cewrapper.fire(Event.getEvent(e, el, (compat || (false === facade))));
653 cewrapper.capture = capture;
655 if (el == win && type == "load") {
656 // window load happens once
657 cewrapper.fireOnce = true;
658 _windowLoadKey = key;
661 _wrappers[key] = cewrapper;
662 _el_events[ek] = _el_events[ek] || {};
663 _el_events[ek][key] = cewrapper;
665 add(el, type, cewrapper.fn, capture);
672 _attach: function(args, conf) {
675 handles, oEl, cewrapper, context,
676 fireNow = false, ret,
680 facade = conf && conf.facade,
681 capture = conf && conf.capture,
682 overrides = conf && conf.overrides;
684 if (args[args.length-1] === COMPAT_ARG) {
686 // trimmedArgs.pop();
689 if (!fn || !fn.call) {
690 // throw new TypeError(type + " attach call failed, callback undefined");
694 // The el argument can be an array of elements or element ids.
695 if (shouldIterate(el)) {
699 Y.each(el, function(v, k) {
701 handles.push(Event._attach(args, conf));
704 // return (handles.length === 1) ? handles[0] : handles;
705 return new Y.EventHandle(handles);
707 // If the el argument is a string, we assume it is
708 // actually the id of the element. If the page is loaded
709 // we convert el to the actual element, otherwise we
710 // defer attaching the event until the element is
712 } else if (Y.Lang.isString(el)) {
714 // oEl = (compat) ? Y.DOM.byId(el) : Y.Selector.query(el);
717 oEl = Y.DOM.byId(el);
720 oEl = Y.Selector.query(el);
722 switch (oEl.length) {
731 return Event._attach(args, conf);
739 // Not found = defer adding the event until the element is available
742 ret = Event.onAvailable(el, function() {
744 ret.handle = Event._attach(args, conf);
746 }, Event, true, false, compat);
753 // Element should be an html element or node
758 if (Y.Node && Y.instanceOf(el, Y.Node)) {
759 el = Y.Node.getDOMNode(el);
762 cewrapper = Event._createWrapper(el, type, capture, compat, facade);
764 Y.mix(cewrapper.overrides, overrides);
767 if (el == win && type == "load") {
769 // if the load is complete, fire immediately.
770 // all subscribers, including the current one
772 if (YUI.Env.windowLoaded) {
783 // set context to the Node if not specified
784 // ret = cewrapper.on.apply(cewrapper, trimmedArgs);
785 ret = cewrapper._on(fn, context, (args.length > 4) ? args.slice(4) : null);
796 * Removes an event listener. Supports the signature the event was bound
797 * with, but the preferred way to remove listeners is using the handle
798 * that is returned when using Y.on
802 * @param {String} type the type of event to remove.
803 * @param {Function} fn the method the event invokes. If fn is
804 * undefined, then all event handlers for the type of event are
806 * @param {String|HTMLElement|Array|NodeList|EventHandle} el An
807 * event handle, an id, an element reference, or a collection
808 * of ids and/or elements to remove the listener from.
809 * @return {boolean} true if the unbind was successful, false otherwise.
812 detach: function(type, fn, el, obj) {
814 var args=Y.Array(arguments, 0, true), compat, l, ok, i,
817 if (args[args.length-1] === COMPAT_ARG) {
822 if (type && type.detach) {
823 return type.detach();
826 // The el argument can be a string
827 if (typeof el == "string") {
829 // el = (compat) ? Y.DOM.byId(el) : Y.all(el);
833 el = Y.Selector.query(el);
841 // return Event.detach.apply(Event, args);
850 return el.detach.apply(el, args);
851 // The el argument can be an array of elements or element ids.
852 } else if (shouldIterate(el)) {
854 for (i=0, l=el.length; i<l; ++i) {
856 ok = ( Y.Event.detach.apply(Y.Event, args) && ok );
862 if (!type || !fn || !fn.call) {
863 return Event.purgeElement(el, false, type);
866 id = 'event:' + Y.stamp(el) + type;
870 return ce.detach(fn);
878 * Finds the event in the window object, the caller's arguments, or
879 * in the arguments of another method in the callstack. This is
880 * executed automatically for events registered through the event
881 * manager, so the implementer should not normally need to execute
882 * this function at all.
884 * @param {Event} e the event parameter from the handler
885 * @param {HTMLElement} el the element the listener was attached to
886 * @return {Event} the event
889 getEvent: function(e, el, noFacade) {
890 var ev = e || win.event;
892 return (noFacade) ? ev :
893 new Y.DOMEventFacade(ev, el, _wrappers['event:' + Y.stamp(el) + e.type]);
897 * Generates an unique ID for the element if it does not already
900 * @param el the element to create the id for
901 * @return {string} the resulting id of the element
904 generateId: function(el) {
905 return Y.DOM.generateID(el);
909 * We want to be able to use getElementsByTagName as a collection
910 * to attach a group of events to. Unfortunately, different
911 * browsers return different types of collections. This function
912 * tests to determine if the object is array-like. It will also
913 * fail if the object is an array, but is empty.
914 * @method _isValidCollection
915 * @param o the object to test
916 * @return {boolean} true if the object is array-like and populated
917 * @deprecated was not meant to be used directly
921 _isValidCollection: shouldIterate,
924 * hook up any deferred listeners
930 if (!_loadComplete) {
931 _loadComplete = true;
933 // Just in case DOMReady did not go off for some reason
939 // Available elements may not have been detected before the
940 // window load event fires. Try to find them now so that the
941 // the user is more likely to get the onAvailable notifications
942 // before the window load notification
948 * Polling function that runs before the onload event fires,
949 * attempting to attach to DOM Nodes as soon as they are
960 if (Y.UA.ie && !YUI.Env.DOMReady) {
961 // Hold off if DOMReady has not fired and check current
962 // readyState to protect against the IE operation aborted
964 Event.startInterval();
970 // keep trying until after the page is loaded. We need to
971 // check the page load state prior to trying to bind the
972 // elements so that we can be certain all elements have been
973 // tested appropriately
974 var i, len, item, el, notAvail, executeItem,
975 tryAgain = !_loadComplete;
978 tryAgain = (_retryCount > 0);
984 executeItem = function (el, item) {
985 var context, ov = item.override;
996 item.fn.call(context, item.obj);
998 context = item.obj || Y.one(el);
999 item.fn.apply(context, (Y.Lang.isArray(ov)) ? ov : []);
1004 for (i=0,len=_avail.length; i<len; ++i) {
1006 if (item && !item.checkReady) {
1008 // el = (item.compat) ? Y.DOM.byId(item.id) : Y.one(item.id);
1009 el = (item.compat) ? Y.DOM.byId(item.id) : Y.Selector.query(item.id, null, true);
1012 executeItem(el, item);
1015 notAvail.push(item);
1021 for (i=0,len=_avail.length; i<len; ++i) {
1023 if (item && item.checkReady) {
1025 // el = (item.compat) ? Y.DOM.byId(item.id) : Y.one(item.id);
1026 el = (item.compat) ? Y.DOM.byId(item.id) : Y.Selector.query(item.id, null, true);
1029 // The element is available, but not necessarily ready
1030 // @todo should we test parentNode.nextSibling?
1031 if (_loadComplete || (el.get && el.get('nextSibling')) || el.nextSibling) {
1032 executeItem(el, item);
1036 notAvail.push(item);
1041 _retryCount = (notAvail.length === 0) ? 0 : _retryCount - 1;
1044 // we may need to strip the nulled out items here
1045 Event.startInterval();
1047 clearInterval(Event._interval);
1048 Event._interval = null;
1051 Event.locked = false;
1058 * Removes all listeners attached to the given element via addListener.
1059 * Optionally, the node's children can also be purged.
1060 * Optionally, you can specify a specific type of event to remove.
1061 * @method purgeElement
1062 * @param {HTMLElement} el the element to purge
1063 * @param {boolean} recurse recursively purge this element's children
1064 * as well. Use with caution.
1065 * @param {string} type optional type of listener to purge. If
1066 * left out, all listeners will be removed
1069 purgeElement: function(el, recurse, type) {
1070 // var oEl = (Y.Lang.isString(el)) ? Y.one(el) : el,
1071 var oEl = (Y.Lang.isString(el)) ? Y.Selector.query(el, null, true) : el,
1072 lis = Event.getListeners(oEl, type), i, len, props, children, child;
1074 if (recurse && oEl) {
1076 children = Y.Selector.query('*', oEl);
1078 len = children.length;
1079 for (; i < len; ++i) {
1080 child = Event.getListeners(children[i], type);
1082 lis = lis.concat(child);
1090 for (; i < len; ++i) {
1093 remove(props.el, props.type, props.fn, props.capture);
1094 delete _wrappers[props.key];
1095 delete _el_events[props.domkey][props.key];
1103 * Returns all listeners attached to the given element via addListener.
1104 * Optionally, you can specify a specific type of event to return.
1105 * @method getListeners
1106 * @param el {HTMLElement|string} the element or element id to inspect
1107 * @param type {string} optional type of listener to return. If
1108 * left out, all listeners will be returned
1109 * @return {Y.Custom.Event} the custom event wrapper for the DOM event(s)
1112 getListeners: function(el, type) {
1113 var ek = Y.stamp(el, true), evts = _el_events[ek],
1114 results=[] , key = (type) ? 'event:' + ek + type : null,
1115 adapters = _eventenv.plugins;
1122 // look for synthetic events
1123 if (adapters[type] && adapters[type].eventDef) {
1128 results.push(evts[key]);
1131 // get native events as well
1134 results.push(evts[key]);
1138 Y.each(evts, function(v, k) {
1143 return (results.length) ? results : null;
1147 * Removes all listeners registered by pe.event. Called
1148 * automatically during the unload event.
1153 _unload: function(e) {
1154 Y.each(_wrappers, function(v, k) {
1156 remove(v.el, v.type, v.fn, v.capture);
1157 delete _wrappers[k];
1158 delete _el_events[v.domkey][k];
1160 remove(win, "unload", onUnload);
1164 * Adds a DOM event directly without the caching, cleanup, context adj, etc
1167 * @param {HTMLElement} el the element to bind the handler to
1168 * @param {string} type the type of event handler
1169 * @param {function} fn the callback to invoke
1170 * @param {boolen} capture capture or bubble phase
1177 * Basic remove listener
1179 * @method nativeRemove
1180 * @param {HTMLElement} el the element to bind the handler to
1181 * @param {string} type the type of event handler
1182 * @param {function} fn the callback to invoke
1183 * @param {boolen} capture capture or bubble phase
1187 nativeRemove: remove
1194 if (config.injected || YUI.Env.windowLoaded) {
1197 add(win, "load", onLoad);
1200 // Process onAvailable/onContentReady items when when the DOM is ready in IE
1202 Y.on(EVENT_READY, Event._poll);
1205 add(win, "unload", onUnload);
1207 Event.Custom = Y.CustomEvent;
1208 Event.Subscriber = Y.Subscriber;
1209 Event.Target = Y.EventTarget;
1210 Event.Handle = Y.EventHandle;
1211 Event.Facade = Y.EventFacade;
1218 * DOM event listener abstraction layer
1220 * @submodule event-base
1224 * Executes the callback as soon as the specified element
1225 * is detected in the DOM. This function expects a selector
1226 * string for the element(s) to detect. If you already have
1227 * an element reference, you don't need this event.
1229 * @param type {string} 'available'
1230 * @param fn {function} the callback function to execute.
1231 * @param el {string} an selector for the element(s) to attach
1232 * @param context optional argument that specifies what 'this' refers to.
1233 * @param args* 0..n additional arguments to pass on to the callback function.
1234 * These arguments will be added after the event object.
1235 * @return {EventHandle} the detach handle
1238 Y.Env.evt.plugins.available = {
1239 on: function(type, fn, id, o) {
1240 var a = arguments.length > 4 ? Y.Array(arguments, 4, true) : null;
1241 return Y.Event.onAvailable.call(Y.Event, id, fn, o, a);
1246 * Executes the callback as soon as the specified element
1247 * is detected in the DOM with a nextSibling property
1248 * (indicating that the element's children are available).
1249 * This function expects a selector
1250 * string for the element(s) to detect. If you already have
1251 * an element reference, you don't need this event.
1252 * @event contentready
1253 * @param type {string} 'contentready'
1254 * @param fn {function} the callback function to execute.
1255 * @param el {string} an selector for the element(s) to attach.
1256 * @param context optional argument that specifies what 'this' refers to.
1257 * @param args* 0..n additional arguments to pass on to the callback function.
1258 * These arguments will be added after the event object.
1259 * @return {EventHandle} the detach handle
1262 Y.Env.evt.plugins.contentready = {
1263 on: function(type, fn, id, o) {
1264 var a = arguments.length > 4 ? Y.Array(arguments, 4, true) : null;
1265 return Y.Event.onContentReady.call(Y.Event, id, fn, o, a);
1270 }, '3.3.0' ,{requires:['event-custom-base']});
1271 YUI.add('event-delegate', function(Y) {
1274 * Adds event delegation support to the library.
1277 * @submodule event-delegate
1280 var toArray = Y.Array,
1282 isString = YLang.isString,
1283 isObject = YLang.isObject,
1284 isArray = YLang.isArray,
1285 selectorTest = Y.Selector.test,
1286 detachCategories = Y.Env.evt.handles;
1289 * <p>Sets up event delegation on a container element. The delegated event
1290 * will use a supplied selector or filtering function to test if the event
1291 * references at least one node that should trigger the subscription
1294 * <p>Selector string filters will trigger the callback if the event originated
1295 * from a node that matches it or is contained in a node that matches it.
1296 * Function filters are called for each Node up the parent axis to the
1297 * subscribing container node, and receive at each level the Node and the event
1298 * object. The function should return true (or a truthy value) if that Node
1299 * should trigger the subscription callback. Note, it is possible for filters
1300 * to match multiple Nodes for a single event. In this case, the delegate
1301 * callback will be executed for each matching Node.</p>
1303 * <p>For each matching Node, the callback will be executed with its 'this'
1304 * object set to the Node matched by the filter (unless a specific context was
1305 * provided during subscription), and the provided event's
1306 * <code>currentTarget</code> will also be set to the matching Node. The
1307 * containing Node from which the subscription was originally made can be
1308 * referenced as <code>e.container</code>.
1311 * @param type {String} the event type to delegate
1312 * @param fn {Function} the callback function to execute. This function
1313 * will be provided the event object for the delegated event.
1314 * @param el {String|node} the element that is the delegation container
1315 * @param spec {string|Function} a selector that must match the target of the
1316 * event or a function to test target and its parents for a match
1317 * @param context optional argument that specifies what 'this' refers to.
1318 * @param args* 0..n additional arguments to pass on to the callback function.
1319 * These arguments will be added after the event object.
1320 * @return {EventHandle} the detach handle
1323 function delegate(type, fn, el, filter) {
1324 var args = toArray(arguments, 0, true),
1325 query = isString(el) ? el : null,
1326 typeBits, synth, container, categories, cat, i, len, handles, handle;
1328 // Support Y.delegate({ click: fnA, key: fnB }, context, filter, ...);
1329 // and Y.delegate(['click', 'key'], fn, context, filter, ...);
1330 if (isObject(type)) {
1333 if (isArray(type)) {
1334 for (i = 0, len = type.length; i < len; ++i) {
1336 handles.push(Y.delegate.apply(Y, args));
1339 // Y.delegate({'click', fn}, context, filter) =>
1340 // Y.delegate('click', fn, context, filter)
1341 args.unshift(null); // one arg becomes two; need to make space
1344 if (type.hasOwnProperty(i)) {
1347 handles.push(Y.delegate.apply(Y, args));
1352 return new Y.EventHandle(handles);
1355 typeBits = type.split(/\|/);
1357 if (typeBits.length > 1) {
1358 cat = typeBits.shift();
1359 type = typeBits.shift();
1362 synth = Y.Node.DOM_EVENTS[type];
1364 if (isObject(synth) && synth.delegate) {
1365 handle = synth.delegate.apply(synth, arguments);
1369 if (!type || !fn || !el || !filter) {
1373 container = (query) ? Y.Selector.query(query, null, true) : el;
1375 if (!container && isString(el)) {
1376 handle = Y.on('available', function () {
1377 Y.mix(handle, Y.delegate.apply(Y, args), true);
1381 if (!handle && container) {
1382 args.splice(2, 2, container); // remove the filter
1384 handle = Y.Event._attach(args, { facade: false });
1385 handle.sub.filter = filter;
1386 handle.sub._notify = delegate.notifySub;
1390 if (handle && cat) {
1391 categories = detachCategories[cat] || (detachCategories[cat] = {});
1392 categories = categories[type] || (categories[type] = []);
1393 categories.push(handle);
1400 * Overrides the <code>_notify</code> method on the normal DOM subscription to
1401 * inject the filtering logic and only proceed in the case of a match.
1403 * @method delegate.notifySub
1404 * @param thisObj {Object} default 'this' object for the callback
1405 * @param args {Array} arguments passed to the event's <code>fire()</code>
1406 * @param ce {CustomEvent} the custom event managing the DOM subscriptions for
1407 * the subscribed event on the subscribing node.
1408 * @return {Boolean} false if the event was stopped
1413 delegate.notifySub = function (thisObj, args, ce) {
1414 // Preserve args for other subscribers
1415 args = args.slice();
1417 args.push.apply(args, this.args);
1420 // Only notify subs if the event occurred on a targeted element
1421 var currentTarget = delegate._applyFilter(this.filter, args, ce),
1422 //container = e.currentTarget,
1425 if (currentTarget) {
1426 // Support multiple matches up the the container subtree
1427 currentTarget = toArray(currentTarget);
1429 // The second arg is the currentTarget, but we'll be reusing this
1430 // facade, replacing the currentTarget for each use, so it doesn't
1431 // matter what element we seed it with.
1432 e = args[0] = new Y.DOMEventFacade(args[0], ce.el, ce);
1434 e.container = Y.one(ce.el);
1436 for (i = 0, len = currentTarget.length; i < len && !e.stopped; ++i) {
1437 e.currentTarget = Y.one(currentTarget[i]);
1439 ret = this.fn.apply(this.context || e.currentTarget, args);
1441 if (ret === false) { // stop further notifications
1451 * <p>Compiles a selector string into a filter function to identify whether
1452 * Nodes along the parent axis of an event's target should trigger event
1455 * <p>This function is memoized, so previously compiled filter functions are
1456 * returned if the same selector string is provided.</p>
1458 * <p>This function may be useful when defining synthetic events for delegate
1461 * @method delegate.compileFilter
1462 * @param selector {String} the selector string to base the filtration on
1463 * @return {Function}
1467 delegate.compileFilter = Y.cached(function (selector) {
1468 return function (target, e) {
1469 return selectorTest(target._node, selector, e.currentTarget._node);
1474 * Walks up the parent axis of an event's target, and tests each element
1475 * against a supplied filter function. If any Nodes, including the container,
1476 * satisfy the filter, the delegated callback will be triggered for each.
1478 * @method delegate._applyFilter
1479 * @param filter {Function} boolean function to test for inclusion in event
1481 * @param args {Array} the arguments that would be passed to subscribers
1482 * @param ce {CustomEvent} the DOM event wrapper
1483 * @return {Node|Node[]|undefined} The Node or Nodes that satisfy the filter
1486 delegate._applyFilter = function (filter, args, ce) {
1488 container = ce.el, // facadeless events in IE, have no e.currentTarget
1489 target = e.target || e.srcElement,
1491 isContainer = false;
1493 // Resolve text nodes to their containing element
1494 if (target.nodeType === 3) {
1495 target = target.parentNode;
1498 // passing target as the first arg rather than leaving well enough alone
1499 // making 'this' in the filter function refer to the target. This is to
1500 // support bound filter functions.
1501 args.unshift(target);
1503 if (isString(filter)) {
1505 isContainer = (target === container);
1506 if (selectorTest(target, filter, (isContainer ?null: container))) {
1514 target = target.parentNode;
1517 // filter functions are implementer code and should receive wrappers
1518 args[0] = Y.one(target);
1519 args[1] = new Y.DOMEventFacade(e, container, ce);
1522 // filter(target, e, extra args...) - this === target
1523 if (filter.apply(args[0], args)) {
1527 if (target === container) {
1531 target = target.parentNode;
1532 args[0] = Y.one(target);
1534 args[1] = e; // restore the raw DOM event
1537 if (match.length <= 1) {
1538 match = match[0]; // single match or undefined
1541 // remove the target
1548 * Sets up event delegation on a container element. The delegated event
1549 * will use a supplied filter to test if the callback should be executed.
1550 * This filter can be either a selector string or a function that returns
1551 * a Node to use as the currentTarget for the event.
1553 * The event object for the delegated event is supplied to the callback
1554 * function. It is modified slightly in order to support all properties
1555 * that may be needed for event delegation. 'currentTarget' is set to
1556 * the element that matched the selector string filter or the Node returned
1557 * from the filter function. 'container' is set to the element that the
1558 * listener is delegated from (this normally would be the 'currentTarget').
1560 * Filter functions will be called with the arguments that would be passed to
1561 * the callback function, including the event object as the first parameter.
1562 * The function should return false (or a falsey value) if the success criteria
1563 * aren't met, and the Node to use as the event's currentTarget and 'this'
1564 * object if they are.
1567 * @param type {string} the event type to delegate
1568 * @param fn {function} the callback function to execute. This function
1569 * will be provided the event object for the delegated event.
1570 * @param el {string|node} the element that is the delegation container
1571 * @param filter {string|function} a selector that must match the target of the
1572 * event or a function that returns a Node or false.
1573 * @param context optional argument that specifies what 'this' refers to.
1574 * @param args* 0..n additional arguments to pass on to the callback function.
1575 * These arguments will be added after the event object.
1576 * @return {EventHandle} the detach handle
1579 Y.delegate = Y.Event.delegate = delegate;
1582 }, '3.3.0' ,{requires:['node-base']});
1583 YUI.add('event-synthetic', function(Y) {
1586 * Define new DOM events that can be subscribed to from Nodes.
1589 * @submodule event-synthetic
1591 var DOMMap = Y.Env.evt.dom_map,
1594 isObject = YLang.isObject,
1595 isString = YLang.isString,
1596 query = Y.Selector.query,
1597 noop = function () {};
1600 * <p>The triggering mechanism used by SyntheticEvents.</p>
1602 * <p>Implementers should not instantiate these directly. Use the Notifier
1603 * provided to the event's implemented <code>on(node, sub, notifier)</code> or
1604 * <code>delegate(node, sub, notifier, filter)</code> methods.</p>
1606 * @class SyntheticEvent.Notifier
1608 * @param handle {EventHandle} the detach handle for the subscription to an
1609 * internal custom event used to execute the callback passed to
1610 * on(..) or delegate(..)
1611 * @param emitFacade {Boolean} take steps to ensure the first arg received by
1612 * the subscription callback is an event facade
1616 function Notifier(handle, emitFacade) {
1617 this.handle = handle;
1618 this.emitFacade = emitFacade;
1622 * <p>Executes the subscription callback, passing the firing arguments as the
1623 * first parameters to that callback. For events that are configured with
1624 * emitFacade=true, it is common practice to pass the triggering DOMEventFacade
1625 * as the first parameter. Barring a proper DOMEventFacade or EventFacade
1626 * (from a CustomEvent), a new EventFacade will be generated. In that case, if
1627 * fire() is called with a simple object, it will be mixed into the facade.
1628 * Otherwise, the facade will be prepended to the callback parameters.</p>
1630 * <p>For notifiers provided to delegate logic, the first argument should be an
1631 * object with a "currentTarget" property to identify what object to
1632 * default as 'this' in the callback. Typically this is gleaned from the
1633 * DOMEventFacade or EventFacade, but if configured with emitFacade=false, an
1634 * object must be provided. In that case, the object will be removed from the
1635 * callback parameters.</p>
1637 * <p>Additional arguments passed during event subscription will be
1638 * automatically added after those passed to fire().</p>
1641 * @param e {EventFacade|DOMEventFacade|Object|any} (see description)
1642 * @param arg* {any} additional arguments received by all subscriptions
1645 Notifier.prototype.fire = function (e) {
1646 // first arg to delegate notifier should be an object with currentTarget
1647 var args = toArray(arguments, 0, true),
1648 handle = this.handle,
1651 thisObj = sub.context,
1652 delegate = sub.filter,
1655 if (this.emitFacade) {
1656 if (!e || !e.preventDefault) {
1657 event = ce._getFacade();
1659 if (isObject(e) && !e.preventDefault) {
1660 Y.mix(event, e, true);
1663 args.unshift(event);
1667 event.type = ce.type;
1668 event.details = args.slice();
1671 event.container = ce.host;
1673 } else if (delegate && isObject(e) && e.currentTarget) {
1677 sub.context = thisObj || event.currentTarget || ce.host;
1678 ce.fire.apply(ce, args);
1679 sub.context = thisObj; // reset for future firing
1684 * <p>Wrapper class for the integration of new events into the YUI event
1685 * infrastructure. Don't instantiate this object directly, use
1686 * <code>Y.Event.define(type, config)</code>. See that method for details.</p>
1688 * <p>Properties that MAY or SHOULD be specified in the configuration are noted
1689 * below and in the description of <code>Y.Event.define</code>.</p>
1691 * @class SyntheticEvent
1693 * @param cfg {Object} Implementation pieces and configuration
1695 * @in event-synthetic
1697 function SyntheticEvent() {
1698 this._init.apply(this, arguments);
1701 Y.mix(SyntheticEvent, {
1705 * Returns the array of subscription handles for a node for the given event
1706 * type. Passing true as the third argument will create a registry entry
1707 * in the event system's DOM map to host the array if one doesn't yet exist.
1709 * @method getRegistry
1710 * @param node {Node} the node
1711 * @param type {String} the event
1712 * @param create {Boolean} create a registration entry to host a new array
1713 * if one doesn't exist.
1719 getRegistry: function (node, type, create) {
1720 var el = node._node,
1722 key = 'event:' + yuid + type + '_synth',
1723 events = DOMMap[yuid] || (DOMMap[yuid] = {});
1725 if (!events[key] && create) {
1735 detachAll : function () {
1736 var notifiers = this.notifiers,
1737 i = notifiers.length;
1740 notifiers[i].detach();
1746 return (events[key]) ? events[key].notifiers : null;
1750 * Alternate <code>_delete()</code> method for the CustomEvent object
1751 * created to manage SyntheticEvent subscriptions.
1753 * @method _deleteSub
1754 * @param sub {Subscription} the subscription to clean up
1758 _deleteSub: function (sub) {
1759 if (sub && sub.fn) {
1760 var synth = this.eventDef,
1761 method = (sub.filter) ? 'detachDelegate' : 'detach';
1763 this.subscribers = {};
1766 synth[method](sub.node, sub, this.notifier, sub.filter);
1767 synth._unregisterSub(sub);
1776 constructor: SyntheticEvent,
1779 * Construction logic for the event.
1784 _init: function () {
1785 var config = this.publishConfig || (this.publishConfig = {});
1787 // The notification mechanism handles facade creation
1788 this.emitFacade = ('emitFacade' in config) ?
1791 config.emitFacade = false;
1795 * <p>Implementers MAY provide this method definition.</p>
1797 * <p>Implement this function if the event supports a different
1798 * subscription signature. This function is used by both
1799 * <code>on()</code> and <code>delegate()</code>. The second parameter
1800 * indicates that the event is being subscribed via
1801 * <code>delegate()</code>.</p>
1803 * <p>Implementations must remove extra arguments from the args list
1804 * before returning. The required args for <code>on()</code>
1805 * subscriptions are</p>
1806 * <pre><code>[type, callback, target, context, argN...]</code></pre>
1808 * <p>The required args for <code>delegate()</code>
1809 * subscriptions are</p>
1811 * <pre><code>[type, callback, target, filter, context, argN...]</code></pre>
1813 * <p>The return value from this function will be stored on the
1814 * subscription in the '_extra' property for reference elsewhere.</p>
1816 * @method processArgs
1817 * @param args {Array} parmeters passed to Y.on(..) or Y.delegate(..)
1818 * @param delegate {Boolean} true if the subscription is from Y.delegate
1824 * <p>Implementers MAY override this property.</p>
1826 * <p>Whether to prevent multiple subscriptions to this event that are
1827 * classified as being the same. By default, this means the subscribed
1828 * callback is the same function. See the <code>subMatch</code>
1829 * method. Setting this to true will impact performance for high volume
1832 * @property preventDups
1836 //preventDups : false,
1839 * <p>Implementers SHOULD provide this method definition.</p>
1841 * Implementation logic for subscriptions done via <code>node.on(type,
1842 * fn)</code> or <code>Y.on(type, fn, target)</code>. This
1843 * function should set up the monitor(s) that will eventually fire the
1844 * event. Typically this involves subscribing to at least one DOM
1845 * event. It is recommended to store detach handles from any DOM
1846 * subscriptions to make for easy cleanup in the <code>detach</code>
1847 * method. Typically these handles are added to the <code>sub</code>
1848 * object. Also for SyntheticEvents that leverage a single DOM
1849 * subscription under the hood, it is recommended to pass the DOM event
1850 * object to <code>notifier.fire(e)</code>. (The event name on the
1851 * object will be updated).
1854 * @param node {Node} the node the subscription is being applied to
1855 * @param sub {Subscription} the object to track this subscription
1856 * @param notifier {SyntheticEvent.Notifier} call notifier.fire(..) to
1857 * trigger the execution of the subscribers
1862 * <p>Implementers SHOULD provide this method definition.</p>
1864 * <p>Implementation logic for detaching subscriptions done via
1865 * <code>node.on(type, fn)</code>. This function should clean up any
1866 * subscriptions made in the <code>on()</code> phase.</p>
1869 * @param node {Node} the node the subscription was applied to
1870 * @param sub {Subscription} the object tracking this subscription
1871 * @param notifier {SyntheticEvent.Notifier} the Notifier used to
1872 * trigger the execution of the subscribers
1877 * <p>Implementers SHOULD provide this method definition.</p>
1879 * <p>Implementation logic for subscriptions done via
1880 * <code>node.delegate(type, fn, filter)</code> or
1881 * <code>Y.delegate(type, fn, container, filter)</code>. Like with
1882 * <code>on()</code> above, this function should monitor the environment
1883 * for the event being fired, and trigger subscription execution by
1884 * calling <code>notifier.fire(e)</code>.</p>
1886 * <p>This function receives a fourth argument, which is the filter
1887 * used to identify which Node's are of interest to the subscription.
1888 * The filter will be either a boolean function that accepts a target
1889 * Node for each hierarchy level as the event bubbles, or a selector
1890 * string. To translate selector strings into filter functions, use
1891 * <code>Y.delegate.compileFilter(filter)</code>.</p>
1894 * @param node {Node} the node the subscription is being applied to
1895 * @param sub {Subscription} the object to track this subscription
1896 * @param notifier {SyntheticEvent.Notifier} call notifier.fire(..) to
1897 * trigger the execution of the subscribers
1898 * @param filter {String|Function} Selector string or function that
1899 * accepts an event object and returns null, a Node, or an
1900 * array of Nodes matching the criteria for processing.
1906 * <p>Implementers SHOULD provide this method definition.</p>
1908 * <p>Implementation logic for detaching subscriptions done via
1909 * <code>node.delegate(type, fn, filter)</code> or
1910 * <code>Y.delegate(type, fn, container, filter)</code>. This function
1911 * should clean up any subscriptions made in the
1912 * <code>delegate()</code> phase.</p>
1914 * @method detachDelegate
1915 * @param node {Node} the node the subscription was applied to
1916 * @param sub {Subscription} the object tracking this subscription
1917 * @param notifier {SyntheticEvent.Notifier} the Notifier used to
1918 * trigger the execution of the subscribers
1919 * @param filter {String|Function} Selector string or function that
1920 * accepts an event object and returns null, a Node, or an
1921 * array of Nodes matching the criteria for processing.
1924 detachDelegate : noop,
1927 * Sets up the boilerplate for detaching the event and facilitating the
1928 * execution of subscriber callbacks.
1931 * @param args {Array} array of arguments passed to
1932 * <code>Y.on(...)</code> or <code>Y.delegate(...)</code>
1933 * @param delegate {Boolean} true if called from
1934 * <code>Y.delegate(...)</code>
1935 * @return {EventHandle} the detach handle for this subscription
1939 _on: function (args, delegate) {
1941 extra = this.processArgs(args, delegate),
1943 method = delegate ? 'delegate' : 'on',
1946 // Can't just use Y.all because it doesn't support window (yet?)
1947 nodes = (isString(selector)) ? query(selector) : toArray(selector);
1949 if (!nodes.length && isString(selector)) {
1950 handle = Y.on('available', function () {
1951 Y.mix(handle, Y[method].apply(Y, args), true);
1957 Y.Array.each(nodes, function (node) {
1958 var subArgs = args.slice(),
1965 filter = subArgs.splice(3, 1)[0];
1968 // (type, fn, el, thisObj, ...) => (fn, thisObj, ...)
1969 subArgs.splice(0, 4, subArgs[1], subArgs[3]);
1971 if (!this.preventDups || !this.getSubs(node, args,null,true)) {
1972 handle = this._getNotifier(node, subArgs, extra,filter);
1974 this[method](node, handle.sub, handle.notifier, filter);
1976 handles.push(handle);
1981 return (handles.length === 1) ?
1983 new Y.EventHandle(handles);
1987 * Creates a new Notifier object for use by this event's
1988 * <code>on(...)</code> or <code>delegate(...)</code> implementation.
1990 * @method _getNotifier
1991 * @param node {Node} the Node hosting the event
1992 * @param args {Array} the subscription arguments passed to either
1993 * <code>Y.on(...)</code> or <code>Y.delegate(...)</code>
1994 * after running through <code>processArgs(args)</code> to
1995 * normalize the argument signature
1996 * @param extra {any} Extra data parsed from
1997 * <code>processArgs(args)</code>
1998 * @param filter {String|Function} the selector string or function
1999 * filter passed to <code>Y.delegate(...)</code> (not
2000 * present when called from <code>Y.on(...)</code>)
2001 * @return {SyntheticEvent.Notifier}
2005 _getNotifier: function (node, args, extra, filter) {
2006 var dispatcher = new Y.CustomEvent(this.type, this.publishConfig),
2007 handle = dispatcher.on.apply(dispatcher, args),
2008 notifier = new Notifier(handle, this.emitFacade),
2009 registry = SyntheticEvent.getRegistry(node, this.type, true),
2012 handle.notifier = notifier;
2015 sub.filter = filter;
2017 this.applyArgExtras(extra, sub);
2022 notifier : notifier,
2023 host : node, // I forget what this is for
2024 currentTarget: node, // for generating facades
2025 target : node, // for generating facades
2026 el : node._node, // For category detach
2028 _delete : SyntheticEvent._deleteSub
2031 registry.push(handle);
2037 * <p>Implementers MAY provide this method definition.</p>
2039 * <p>Implement this function if you want extra data extracted during
2040 * processArgs to be propagated to subscriptions on a per-node basis.
2041 * That is to say, if you call <code>Y.on('xyz', fn, xtra, 'div')</code>
2042 * the data returned from processArgs will be shared
2043 * across the subscription objects for all the divs. If you want each
2044 * subscription to receive unique information, do that processing
2047 * <p>The default implementation adds the data extracted by processArgs
2048 * to the subscription object as <code>sub._extra</code>.</p>
2050 * @method applyArgExtras
2051 * @param extra {any} Any extra data extracted from processArgs
2052 * @param sub {Subscription} the individual subscription
2054 applyArgExtras: function (extra, sub) {
2059 * Removes the subscription from the Notifier registry.
2061 * @method _unregisterSub
2062 * @param sub {Subscription} the subscription
2066 _unregisterSub: function (sub) {
2067 var notifiers = SyntheticEvent.getRegistry(sub.node, this.type),
2071 for (i = notifiers.length - 1; i >= 0; --i) {
2072 if (notifiers[i].sub === sub) {
2073 notifiers.splice(i, 1);
2081 * Removes the subscription(s) from the internal subscription dispatch
2082 * mechanism. See <code>SyntheticEvent._deleteSub</code>.
2085 * @param args {Array} The arguments passed to
2086 * <code>node.detach(...)</code>
2090 _detach: function (args) {
2091 // Can't use Y.all because it doesn't support window (yet?)
2092 // TODO: Does Y.all support window now?
2093 var target = args[2],
2094 els = (isString(target)) ?
2095 query(target) : toArray(target),
2096 node, i, len, handles, j;
2098 // (type, fn, el, context, filter?) => (type, fn, context, filter?)
2101 for (i = 0, len = els.length; i < len; ++i) {
2102 node = Y.one(els[i]);
2105 handles = this.getSubs(node, args);
2108 for (j = handles.length - 1; j >= 0; --j) {
2109 handles[j].detach();
2117 * Returns the detach handles of subscriptions on a node that satisfy a
2118 * search/filter function. By default, the filter used is the
2119 * <code>subMatch</code> method.
2122 * @param node {Node} the node hosting the event
2123 * @param args {Array} the array of original subscription args passed
2124 * to <code>Y.on(...)</code> (before
2125 * <code>processArgs</code>
2126 * @param filter {Function} function used to identify a subscription
2127 * for inclusion in the returned array
2128 * @param first {Boolean} stop after the first match (used to check for
2129 * duplicate subscriptions)
2130 * @return {Array} detach handles for the matching subscriptions
2132 getSubs: function (node, args, filter, first) {
2133 var notifiers = SyntheticEvent.getRegistry(node, this.type),
2139 filter = this.subMatch;
2142 for (i = 0, len = notifiers.length; i < len; ++i) {
2143 handle = notifiers[i];
2144 if (filter.call(this, handle.sub, args)) {
2148 handles.push(notifiers[i]);
2154 return handles.length && handles;
2158 * <p>Implementers MAY override this to define what constitutes a
2159 * "same" subscription. Override implementations should
2160 * consider the lack of a comparator as a match, so calling
2161 * <code>getSubs()</code> with no arguments will return all subs.</p>
2163 * <p>Compares a set of subscription arguments against a Subscription
2164 * object to determine if they match. The default implementation
2165 * compares the callback function against the second argument passed to
2166 * <code>Y.on(...)</code> or <code>node.detach(...)</code> etc.</p>
2169 * @param sub {Subscription} the existing subscription
2170 * @param args {Array} the calling arguments passed to
2171 * <code>Y.on(...)</code> etc.
2172 * @return {Boolean} true if the sub can be described by the args
2176 subMatch: function (sub, args) {
2177 // Default detach cares only about the callback matching
2178 return !args[1] || sub.fn === args[1];
2183 Y.SyntheticEvent = SyntheticEvent;
2186 * <p>Defines a new event in the DOM event system. Implementers are
2187 * responsible for monitoring for a scenario whereby the event is fired. A
2188 * notifier object is provided to the functions identified below. When the
2189 * criteria defining the event are met, call notifier.fire( [args] ); to
2190 * execute event subscribers.</p>
2192 * <p>The first parameter is the name of the event. The second parameter is a
2193 * configuration object which define the behavior of the event system when the
2194 * new event is subscribed to or detached from. The methods that should be
2195 * defined in this configuration object are <code>on</code>,
2196 * <code>detach</code>, <code>delegate</code>, and <code>detachDelegate</code>.
2197 * You are free to define any other methods or properties needed to define your
2198 * event. Be aware, however, that since the object is used to subclass
2199 * SyntheticEvent, you should avoid method names used by SyntheticEvent unless
2200 * your intention is to override the default behavior.</p>
2202 * <p>This is a list of properties and methods that you can or should specify
2203 * in the configuration object:</p>
2206 * <dt><code>on</code></dt>
2207 * <dd><code>function (node, subscription, notifier)</code> The
2208 * implementation logic for subscription. Any special setup you need to
2209 * do to create the environment for the event being fired--E.g. native
2210 * DOM event subscriptions. Store subscription related objects and
2211 * state on the <code>subscription</code> object. When the
2212 * criteria have been met to fire the synthetic event, call
2213 * <code>notifier.fire(e)</code>. See Notifier's <code>fire()</code>
2214 * method for details about what to pass as parameters.</dd>
2216 * <dt><code>detach</code></dt>
2217 * <dd><code>function (node, subscription, notifier)</code> The
2218 * implementation logic for cleaning up a detached subscription. E.g.
2219 * detach any DOM subscriptions added in <code>on</code>.</dd>
2221 * <dt><code>delegate</code></dt>
2222 * <dd><code>function (node, subscription, notifier, filter)</code> The
2223 * implementation logic for subscription via <code>Y.delegate</code> or
2224 * <code>node.delegate</code>. The filter is typically either a selector
2225 * string or a function. You can use
2226 * <code>Y.delegate.compileFilter(selectorString)</code> to create a
2227 * filter function from a selector string if needed. The filter function
2228 * expects an event object as input and should output either null, a
2229 * matching Node, or an array of matching Nodes. Otherwise, this acts
2230 * like <code>on</code> DOM event subscriptions. Store subscription
2231 * related objects and information on the <code>subscription</code>
2232 * object. When the criteria have been met to fire the synthetic event,
2233 * call <code>notifier.fire(e)</code> as noted above.</dd>
2235 * <dt><code>detachDelegate</code></dt>
2236 * <dd><code>function (node, subscription, notifier)</code> The
2237 * implementation logic for cleaning up a detached delegate subscription.
2238 * E.g. detach any DOM delegate subscriptions added in
2239 * <code>delegate</code>.</dd>
2241 * <dt><code>publishConfig</code></dt>
2242 * <dd>(Object) The configuration object that will be used to instantiate
2243 * the underlying CustomEvent. See Notifier's <code>fire</code> method
2246 * <dt><code>processArgs</code></dt
2248 * <p><code>function (argArray, fromDelegate)</code> Optional method
2249 * to extract any additional arguments from the subscription
2250 * signature. Using this allows <code>on</code> or
2251 * <code>delegate</code> signatures like
2252 * <code>node.on("hover", overCallback,
2253 * outCallback)</code>.</p>
2254 * <p>When processing an atypical argument signature, make sure the
2255 * args array is returned to the normal signature before returning
2256 * from the function. For example, in the "hover" example
2257 * above, the <code>outCallback</code> needs to be <code>splice</code>d
2258 * out of the array. The expected signature of the args array for
2259 * <code>on()</code> subscriptions is:</p>
2261 * <code>[type, callback, target, contextOverride, argN...]</code>
2263 * <p>And for <code>delegate()</code>:</p>
2265 * <code>[type, callback, target, filter, contextOverride, argN...]</code>
2267 * <p>where <code>target</code> is the node the event is being
2268 * subscribed for. You can see these signatures documented for
2269 * <code>Y.on()</code> and <code>Y.delegate()</code> respectively.</p>
2270 * <p>Whatever gets returned from the function will be stored on the
2271 * <code>subscription</code> object under
2272 * <code>subscription._extra</code>.</p></dd>
2273 * <dt><code>subMatch</code></dt>
2275 * <p><code>function (sub, args)</code> Compares a set of
2276 * subscription arguments against a Subscription object to determine
2277 * if they match. The default implementation compares the callback
2278 * function against the second argument passed to
2279 * <code>Y.on(...)</code> or <code>node.detach(...)</code> etc.</p>
2283 * @method Event.define
2284 * @param type {String} the name of the event
2285 * @param config {Object} the prototype definition for the new event (see above)
2286 * @param force {Boolean} override an existing event (use with caution)
2288 * @return {SyntheticEvent} the subclass implementation instance created to
2289 * handle event subscriptions of this type
2292 * @in event-synthetic
2294 Y.Event.define = function (type, config, force) {
2299 var eventDef = (isObject(type)) ? type : Y.merge({ type: type }, config),
2302 if (force || !Y.Node.DOM_EVENTS[eventDef.type]) {
2303 Impl = function () {
2304 SyntheticEvent.apply(this, arguments);
2306 Y.extend(Impl, SyntheticEvent, eventDef);
2311 Y.Node.DOM_EVENTS[type] = Y.Env.evt.plugins[type] = {
2315 return synth._on(toArray(arguments));
2318 delegate: function () {
2319 return synth._on(toArray(arguments), true);
2322 detach: function () {
2323 return synth._detach(toArray(arguments));
2333 }, '3.3.0' ,{requires:['node-base', 'event-custom']});
2334 YUI.add('event-mousewheel', function(Y) {
2337 * Adds mousewheel event support
2339 * @submodule event-mousewheel
2341 var DOM_MOUSE_SCROLL = 'DOMMouseScroll',
2342 fixArgs = function(args) {
2343 var a = Y.Array(args, 0, true), target;
2345 a[0] = DOM_MOUSE_SCROLL;
2346 target = Y.config.win;
2348 target = Y.config.doc;
2354 a.splice(2, 0, target);
2361 * Mousewheel event. This listener is automatically attached to the
2362 * correct target, so one should not be supplied. Mouse wheel
2363 * direction and velocity is stored in the 'mouseDelta' field.
2365 * @param type {string} 'mousewheel'
2366 * @param fn {function} the callback to execute
2367 * @param context optional context object
2368 * @param args 0..n additional arguments to provide to the listener.
2369 * @return {EventHandle} the detach handle
2372 Y.Env.evt.plugins.mousewheel = {
2374 return Y.Event._attach(fixArgs(arguments));
2377 detach: function() {
2378 return Y.Event.detach.apply(Y.Event, fixArgs(arguments));
2383 }, '3.3.0' ,{requires:['node-base']});
2384 YUI.add('event-mouseenter', function(Y) {
2387 * <p>Adds subscription and delegation support for mouseenter and mouseleave
2388 * events. Unlike mouseover and mouseout, these events aren't fired from child
2389 * elements of a subscribed node.</p>
2391 * <p>This avoids receiving three mouseover notifications from a setup like</p>
2393 * <pre><code>div#container > p > a[href]</code></pre>
2397 * <pre><code>Y.one('#container').on('mouseover', callback)</code></pre>
2399 * <p>When the mouse moves over the link, one mouseover event is fired from
2400 * #container, then when the mouse moves over the p, another mouseover event is
2401 * fired and bubbles to #container, causing a second notification, and finally
2402 * when the mouse moves over the link, a third mouseover event is fired and
2403 * bubbles to #container for a third notification.</p>
2405 * <p>By contrast, using mouseenter instead of mouseover, the callback would be
2406 * executed only once when the mouse moves over #container.</p>
2409 * @submodule event-mouseenter
2411 function notify(e, notifier) {
2412 var current = e.currentTarget,
2413 related = e.relatedTarget;
2415 if (current !== related && !current.contains(related)) {
2421 proxyType: "mouseover",
2423 on: function (node, sub, notifier) {
2424 sub.onHandle = node.on(this.proxyType, notify, null, notifier);
2427 detach: function (node, sub) {
2428 sub.onHandle.detach();
2431 delegate: function (node, sub, notifier, filter) {
2432 sub.delegateHandle =
2433 Y.delegate(this.proxyType, notify, node, filter, null, notifier);
2436 detachDelegate: function (node, sub) {
2437 sub.delegateHandle.detach();
2441 Y.Event.define("mouseenter", config, true);
2442 Y.Event.define("mouseleave", Y.merge(config, { proxyType: "mouseout" }), true);
2445 }, '3.3.0' ,{requires:['event-synthetic']});
2446 YUI.add('event-key', function(Y) {
2449 * Functionality to listen for one or more specific key combinations.
2451 * @submodule event-key
2455 * Add a key listener. The listener will only be notified if the
2456 * keystroke detected meets the supplied specification. The
2457 * spec consists of the key event type, followed by a colon,
2458 * followed by zero or more comma separated key codes, followed
2459 * by zero or more modifiers delimited by a plus sign. Ex:
2460 * press:12,65+shift+ctrl
2463 * @param type {string} 'key'
2464 * @param fn {function} the function to execute
2465 * @param id {string|HTMLElement|collection} the element(s) to bind
2466 * @param spec {string} the keyCode and modifier specification
2467 * @param o optional context object
2468 * @param args 0..n additional arguments to provide to the listener.
2469 * @return {Event.Handle} the detach handle
2471 Y.Env.evt.plugins.key = {
2473 on: function(type, fn, id, spec, o) {
2474 var a = Y.Array(arguments, 0, true), parsed, etype, criteria, ename;
2476 parsed = spec && spec.split(':');
2478 if (!spec || spec.indexOf(':') == -1 || !parsed[1]) {
2479 a[0] = 'key' + ((parsed && parsed[0]) || 'press');
2480 return Y.on.apply(Y, a);
2483 // key event type: 'down', 'up', or 'press'
2486 // list of key codes optionally followed by modifiers
2487 criteria = (parsed[1]) ? parsed[1].split(/,|\+/) : null;
2489 // the name of the custom event that will be created for the spec
2490 ename = (Y.Lang.isString(id) ? id : Y.stamp(id)) + spec;
2492 ename = ename.replace(/,/g, '_');
2494 if (!Y.getEvent(ename)) {
2496 // subscribe spec validator to the DOM event
2497 Y.on(type + etype, function(e) {
2500 var passed = false, failed = false, i, crit, critInt;
2502 for (i=0; i<criteria.length; i=i+1) {
2504 critInt = parseInt(crit, 10);
2506 // pass this section if any supplied keyCode
2508 if (Y.Lang.isNumber(critInt)) {
2510 if (e.charCode === critInt) {
2516 // only check modifier if no keyCode was specified
2517 // or the keyCode check was successful. pass only
2518 // if every modifier passes
2519 } else if (passed || !failed) {
2520 passed = (e[crit + 'Key']);
2525 // fire spec custom event if spec if met
2534 // subscribe supplied listener to custom event for spec validator
2535 // remove element and spec.
2539 return Y.on.apply(Y, a);
2544 }, '3.3.0' ,{requires:['node-base']});
2545 YUI.add('event-focus', function(Y) {
2548 * Adds bubbling and delegation support to DOM events focus and blur.
2551 * @submodule event-focus
2553 var Event = Y.Event,
2555 isString = YLang.isString,
2556 useActivate = YLang.isFunction(
2557 Y.DOM.create('<p onbeforeactivate=";"/>').onbeforeactivate);
2559 function define(type, proxy, directEvent) {
2560 var nodeDataKey = '_' + type + 'Notifiers';
2562 Y.Event.define(type, {
2564 _attach: function (el, notifier, delegate) {
2565 if (Y.DOM.isWindow(el)) {
2566 return Event._attach([type, function (e) {
2570 return Event._attach(
2571 [proxy, this._proxy, el, this, notifier, delegate],
2576 _proxy: function (e, notifier, delegate) {
2577 var node = e.target,
2578 notifiers = node.getData(nodeDataKey),
2579 yuid = Y.stamp(e.currentTarget._node),
2580 defer = (useActivate || e.target !== e.currentTarget),
2581 sub = notifier.handle.sub,
2582 filterArgs = [node, e].concat(sub.args || []),
2585 notifier.currentTarget = (delegate) ? node : e.currentTarget;
2586 notifier.container = (delegate) ? e.currentTarget : null;
2588 if (!sub.filter || sub.filter.apply(node, filterArgs)) {
2589 // Maintain a list to handle subscriptions from nested
2590 // containers div#a>div#b>input #a.on(focus..) #b.on(focus..),
2591 // use one focus or blur subscription that fires notifiers from
2592 // #b then #a to emulate bubble sequence.
2595 node.setData(nodeDataKey, notifiers);
2597 // only subscribe to the element's focus if the target is
2598 // not the current target (
2600 directSub = Event._attach(
2601 [directEvent, this._notify, node._node]).sub;
2602 directSub.once = true;
2606 if (!notifiers[yuid]) {
2607 notifiers[yuid] = [];
2610 notifiers[yuid].push(notifier);
2618 _notify: function (e, container) {
2619 var node = e.currentTarget,
2620 notifiers = node.getData(nodeDataKey),
2621 // document.get('ownerDocument') returns null
2622 doc = node.get('ownerDocument') || node,
2628 // Walk up the parent axis until the origin node,
2629 while (target && target !== doc) {
2630 nots.push.apply(nots, notifiers[Y.stamp(target)] || []);
2631 target = target.get('parentNode');
2633 nots.push.apply(nots, notifiers[Y.stamp(doc)] || []);
2635 for (i = 0, len = nots.length; i < len; ++i) {
2637 e.currentTarget = nots[i].currentTarget;
2639 if (notifier.container) {
2640 e.container = notifier.container;
2648 // clear the notifications list (mainly for delegation)
2649 node.clearData(nodeDataKey);
2653 on: function (node, sub, notifier) {
2654 sub.onHandle = this._attach(node._node, notifier);
2657 detach: function (node, sub) {
2658 sub.onHandle.detach();
2661 delegate: function (node, sub, notifier, filter) {
2662 if (isString(filter)) {
2663 sub.filter = Y.delegate.compileFilter(filter);
2666 sub.delegateHandle = this._attach(node._node, notifier, true);
2669 detachDelegate: function (node, sub) {
2670 sub.delegateHandle.detach();
2675 // For IE, we need to defer to focusin rather than focus because
2676 // `el.focus(); doSomething();` executes el.onbeforeactivate, el.onactivate,
2677 // el.onfocusin, doSomething, then el.onfocus. All others support capture
2678 // phase focus, which executes before doSomething. To guarantee consistent
2679 // behavior for this use case, IE's direct subscriptions are made against
2680 // focusin so subscribers will be notified before js following el.focus() is
2683 // name capture phase direct subscription
2684 define("focus", "beforeactivate", "focusin");
2685 define("blur", "beforedeactivate", "focusout");
2687 define("focus", "focus", "focus");
2688 define("blur", "blur", "blur");
2692 }, '3.3.0' ,{requires:['event-synthetic']});
2693 YUI.add('event-resize', function(Y) {
2696 * Adds a window resize event that has its behavior normalized to fire at the
2697 * end of the resize rather than constantly during the resize.
2699 * @submodule event-resize
2707 CE_NAME = 'window:resize',
2709 handler = function(e) {
2718 timerHandle.cancel();
2721 timerHandle = Y.later(Y.config.windowResizeDelay || 40, Y, function() {
2730 * Firefox fires the window resize event once when the resize action
2731 * finishes, other browsers fire the event periodically during the
2732 * resize. This code uses timeout logic to simulate the Firefox
2733 * behavior in other browsers.
2734 * @event windowresize
2737 Y.Env.evt.plugins.windowresize = {
2739 on: function(type, fn) {
2741 // check for single window listener and add if needed
2742 if (!detachHandle) {
2743 detachHandle = Y.Event._attach(['resize', handler]);
2746 var a = Y.Array(arguments, 0, true);
2749 return Y.on.apply(Y, a);
2756 }, '3.3.0' ,{requires:['node-base']});
2757 YUI.add('event-hover', function(Y) {
2760 * Adds support for a "hover" event. The event provides a convenience wrapper
2761 * for subscribing separately to mouseenter and mouseleave. The signature for
2762 * subscribing to the event is</p>
2764 * <pre><code>node.on("hover", overFn, outFn);
2765 * node.delegate("hover", overFn, outFn, ".filterSelector");
2766 * Y.on("hover", overFn, outFn, ".targetSelector");
2767 * Y.delegate("hover", overFn, outFn, "#container", ".filterSelector");
2770 * <p>Additionally, for compatibility with a more typical subscription
2771 * signature, the following are also supported:</p>
2773 * <pre><code>Y.on("hover", overFn, ".targetSelector", outFn);
2774 * Y.delegate("hover", overFn, "#container", outFn, ".filterSelector");
2778 * @submodule event-hover
2780 var isFunction = Y.Lang.isFunction,
2781 noop = function () {},
2783 processArgs: function (args) {
2784 // Y.delegate('hover', over, out, '#container', '.filter')
2785 // comes in as ['hover', over, out, '#container', '.filter'], but
2786 // node.delegate('hover', over, out, '.filter')
2787 // comes in as ['hover', over, containerEl, out, '.filter']
2788 var i = isFunction(args[2]) ? 2 : 3;
2790 return (isFunction(args[i])) ? args.splice(i,1)[0] : noop;
2793 on: function (node, sub, notifier, filter) {
2794 sub._detach = node[(filter) ? "delegate" : "on"]({
2795 mouseenter: Y.bind(notifier.fire, notifier),
2796 mouseleave: sub._extra
2800 detach: function (node, sub, notifier) {
2801 sub._detacher.detach();
2805 conf.delegate = conf.on;
2806 conf.detachDelegate = conf.detach;
2808 Y.Event.define("hover", conf);
2811 }, '3.3.0' ,{requires:['event-mouseenter']});
2814 YUI.add('event', function(Y){}, '3.3.0' ,{use:['event-base', 'event-delegate', 'event-synthetic', 'event-mousewheel', 'event-mouseenter', 'event-key', 'event-focus', 'event-resize', 'event-hover']});