/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 3.2.0 build: 2676 */ /* * DOM event listener abstraction layer * @module event * @submodule event-base */ (function() { // Unlike most of the library, this code has to be executed as soon as it is // introduced into the page -- and it should only be executed one time // regardless of the number of instances that use it. var stateChangeListener, GLOBAL_ENV = YUI.Env, config = YUI.config, doc = config.doc, docElement = doc && doc.documentElement, doScrollCap = docElement && docElement.doScroll, add = YUI.Env.add, remove = YUI.Env.remove, targetEvent = (doScrollCap) ? 'onreadystatechange' : 'DOMContentLoaded', pollInterval = config.pollInterval || 40, _ready = function(e) { GLOBAL_ENV._ready(); }; if (!GLOBAL_ENV._ready) { GLOBAL_ENV._ready = function() { if (!GLOBAL_ENV.DOMReady) { GLOBAL_ENV.DOMReady = true; remove(doc, targetEvent, _ready); // remove DOMContentLoaded listener } }; /*! DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller/Diego Perini */ // Internet Explorer: use the doScroll() method on the root element. This isolates what // appears to be a safe moment to manipulate the DOM prior to when the document's readyState // suggests it is safe to do so. if (doScrollCap) { if (self !== self.top) { stateChangeListener = function() { if (doc.readyState == 'complete') { remove(doc, targetEvent, stateChangeListener); // remove onreadystatechange listener _ready(); } }; add(doc, targetEvent, stateChangeListener); // add onreadystatechange listener } else { GLOBAL_ENV._dri = setInterval(function() { try { docElement.doScroll('left'); clearInterval(GLOBAL_ENV._dri); GLOBAL_ENV._dri = null; _ready(); } catch (domNotReady) { } }, pollInterval); } } else { // FireFox, Opera, Safari 3+ provide an event for this moment. add(doc, targetEvent, _ready); // add DOMContentLoaded listener } } })(); YUI.add('event-base', function(Y) { (function() { /* * DOM event listener abstraction layer * @module event * @submodule event-base */ var GLOBAL_ENV = YUI.Env, yready = function() { Y.fire('domready'); }; /** * The domready event fires at the moment the browser's DOM is * usable. In most cases, this is before images are fully * downloaded, allowing you to provide a more responsive user * interface. * * In YUI 3, domready subscribers will be notified immediately if * that moment has already passed when the subscription is created. * * One exception is if the yui.js file is dynamically injected into * the page. If this is done, you must tell the YUI instance that * you did this in order for DOMReady (and window load events) to * fire normally. That configuration option is 'injected' -- set * it to true if the yui.js script is not included inline. * * This method is part of the 'event-ready' module, which is a * submodule of 'event'. * * @event domready * @for YUI */ Y.publish('domready', { fireOnce: true, async: true }); if (GLOBAL_ENV.DOMReady) { // console.log('DOMReady already fired', 'info', 'event'); yready(); } else { // console.log('setting up before listener', 'info', 'event'); // console.log('env: ' + YUI.Env.windowLoaded, 'info', 'event'); Y.before(yready, GLOBAL_ENV, "_ready"); } })(); (function() { /** * Custom event engine, DOM event listener abstraction layer, synthetic DOM * events. * @module event * @submodule event-base */ /** * Wraps a DOM event, properties requiring browser abstraction are * fixed here. Provids a security layer when required. * @class DOMEventFacade * @param ev {Event} the DOM event * @param currentTarget {HTMLElement} the element the listener was attached to * @param wrapper {Event.Custom} the custom event wrapper for this DOM event */ /* * @TODO constants? LEFTBUTTON, MIDDLEBUTTON, RIGHTBUTTON, keys */ /* var whitelist = { altKey : 1, // "button" : 1, // we supply // "bubbles" : 1, // needed? // "cancelable" : 1, // needed? // "charCode" : 1, // we supply cancelBubble : 1, // "currentTarget" : 1, // we supply ctrlKey : 1, clientX : 1, // needed? clientY : 1, // needed? detail : 1, // not fully implemented // "fromElement" : 1, keyCode : 1, // "height" : 1, // needed? // "initEvent" : 1, // need the init events? // "initMouseEvent" : 1, // "initUIEvent" : 1, // "layerX" : 1, // needed? // "layerY" : 1, // needed? metaKey : 1, // "modifiers" : 1, // needed? // "offsetX" : 1, // needed? // "offsetY" : 1, // needed? // "preventDefault" : 1, // we supply // "reason" : 1, // IE proprietary // "relatedTarget" : 1, // "returnValue" : 1, // needed? shiftKey : 1, // "srcUrn" : 1, // IE proprietary // "srcElement" : 1, // "srcFilter" : 1, IE proprietary // "stopPropagation" : 1, // we supply // "target" : 1, // "timeStamp" : 1, // needed? // "toElement" : 1, type : 1, // "view" : 1, // "which" : 1, // we supply // "width" : 1, // needed? x : 1, y : 1 }, */ var ua = Y.UA, /** * webkit key remapping required for Safari < 3.1 * @property webkitKeymap * @private */ webkitKeymap = { 63232: 38, // up 63233: 40, // down 63234: 37, // left 63235: 39, // right 63276: 33, // page up 63277: 34, // page down 25: 9, // SHIFT-TAB (Safari provides a different key code in // this case, even though the shiftKey modifier is set) 63272: 46, // delete 63273: 36, // home 63275: 35 // end }, /** * Returns a wrapped node. Intended to be used on event targets, * so it will return the node's parent if the target is a text * node. * * If accessing a property of the node throws an error, this is * probably the anonymous div wrapper Gecko adds inside text * nodes. This likely will only occur when attempting to access * the relatedTarget. In this case, we now return null because * the anonymous div is completely useless and we do not know * what the related target was because we can't even get to * the element's parent node. * * @method resolve * @private */ resolve = function(n) { try { if (n && 3 == n.nodeType) { n = n.parentNode; } } catch(e) { return null; } return Y.one(n); }; // provide a single event with browser abstractions resolved // // include all properties for both browers? // include only DOM2 spec properties? // provide browser-specific facade? Y.DOMEventFacade = function(ev, currentTarget, wrapper) { wrapper = wrapper || {}; var e = ev, ot = currentTarget, d = Y.config.doc, b = d.body, x = e.pageX, y = e.pageY, c, t, de = d.documentElement, overrides = wrapper.overrides || {}; this.altKey = e.altKey; this.ctrlKey = e.ctrlKey; this.metaKey = e.metaKey; this.shiftKey = e.shiftKey; this.type = overrides.type || e.type; this.clientX = e.clientX; this.clientY = e.clientY; ////////////////////////////////////////////////////// if (('clientX' in e) && (!x) && (0 !== x)) { x = e.clientX; y = e.clientY; if (ua.ie) { x += (de.scrollLeft || b.scrollLeft || 0); y += (de.scrollTop || b.scrollTop || 0); } } this._yuifacade = true; /** * The native event * @property _event */ this._event = e; /** * The X location of the event on the page (including scroll) * @property pageX * @type int */ this.pageX = x; /** * The Y location of the event on the page (including scroll) * @property pageY * @type int */ this.pageY = y; ////////////////////////////////////////////////////// c = e.keyCode || e.charCode || 0; if (ua.webkit && (c in webkitKeymap)) { c = webkitKeymap[c]; } /** * The keyCode for key events. Uses charCode if keyCode is not available * @property keyCode * @type int */ this.keyCode = c; /** * The charCode for key events. Same as keyCode * @property charCode * @type int */ this.charCode = c; ////////////////////////////////////////////////////// /** * The button that was pushed. * @property button * @type int */ this.button = e.which || e.button; /** * The button that was pushed. Same as button. * @property which * @type int */ this.which = this.button; ////////////////////////////////////////////////////// /** * Node reference for the targeted element * @propery target * @type Node */ this.target = resolve(e.target || e.srcElement); /** * Node reference for the element that the listener was attached to. * @propery currentTarget * @type Node */ this.currentTarget = resolve(ot); t = e.relatedTarget; if (!t) { if (e.type == "mouseout") { t = e.toElement; } else if (e.type == "mouseover") { t = e.fromElement; } } /** * Node reference to the relatedTarget * @propery relatedTarget * @type Node */ this.relatedTarget = resolve(t); /** * Number representing the direction and velocity of the movement of the mousewheel. * Negative is down, the higher the number, the faster. Applies to the mousewheel event. * @property wheelDelta * @type int */ if (e.type == "mousewheel" || e.type == "DOMMouseScroll") { this.wheelDelta = (e.detail) ? (e.detail * -1) : Math.round(e.wheelDelta / 80) || ((e.wheelDelta < 0) ? -1 : 1); } ////////////////////////////////////////////////////// // methods /** * Stops the propagation to the next bubble target * @method stopPropagation */ this.stopPropagation = function() { if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } wrapper.stopped = 1; this.stopped = 1; }; /** * Stops the propagation to the next bubble target and * prevents any additional listeners from being exectued * on the current target. * @method stopImmediatePropagation */ this.stopImmediatePropagation = function() { if (e.stopImmediatePropagation) { e.stopImmediatePropagation(); } else { this.stopPropagation(); } wrapper.stopped = 2; this.stopped = 2; }; /** * Prevents the event's default behavior * @method preventDefault * @param returnValue {string} sets the returnValue of the event to this value * (rather than the default false value). This can be used to add a customized * confirmation query to the beforeunload event). */ this.preventDefault = function(returnValue) { if (e.preventDefault) { e.preventDefault(); } e.returnValue = returnValue || false; wrapper.prevented = 1; this.prevented = 1; }; /** * Stops the event propagation and prevents the default * event behavior. * @method halt * @param immediate {boolean} if true additional listeners * on the current target will not be executed */ this.halt = function(immediate) { if (immediate) { this.stopImmediatePropagation(); } else { this.stopPropagation(); } this.preventDefault(); }; if (this._touch) { this._touch(e, currentTarget, wrapper); } }; })(); (function() { /** * DOM event listener abstraction layer * @module event * @submodule event-base */ /** * The event utility provides functions to add and remove event listeners, * event cleansing. It also tries to automatically remove listeners it * registers during the unload event. * * @class Event * @static */ Y.Env.evt.dom_wrappers = {}; Y.Env.evt.dom_map = {}; var _eventenv = Y.Env.evt, config = Y.config, win = config.win, add = YUI.Env.add, remove = YUI.Env.remove, onLoad = function() { YUI.Env.windowLoaded = true; Y.Event._load(); remove(win, "load", onLoad); }, onUnload = function() { Y.Event._unload(); remove(win, "unload", onUnload); }, EVENT_READY = 'domready', COMPAT_ARG = '~yui|2|compat~', shouldIterate = function(o) { try { return (o && typeof o !== "string" && Y.Lang.isNumber(o.length) && !o.tagName && !o.alert); } catch(ex) { Y.log("collection check failure", "warn", "event"); return false; } }, Event = function() { /** * True after the onload event has fired * @property _loadComplete * @type boolean * @static * @private */ var _loadComplete = false, /** * The number of times to poll after window.onload. This number is * increased if additional late-bound handlers are requested after * the page load. * @property _retryCount * @static * @private */ _retryCount = 0, /** * onAvailable listeners * @property _avail * @static * @private */ _avail = [], /** * Custom event wrappers for DOM events. Key is * 'event:' + Element uid stamp + event type * @property _wrappers * @type Y.Event.Custom * @static * @private */ _wrappers = _eventenv.dom_wrappers, _windowLoadKey = null, /** * Custom event wrapper map DOM events. Key is * Element uid stamp. Each item is a hash of custom event * wrappers as provided in the _wrappers collection. This * provides the infrastructure for getListeners. * @property _el_events * @static * @private */ _el_events = _eventenv.dom_map; return { /** * The number of times we should look for elements that are not * in the DOM at the time the event is requested after the document * has been loaded. The default is 1000@amp;40 ms, so it will poll * for 40 seconds or until all outstanding handlers are bound * (whichever comes first). * @property POLL_RETRYS * @type int * @static * @final */ POLL_RETRYS: 1000, /** * The poll interval in milliseconds * @property POLL_INTERVAL * @type int * @static * @final */ POLL_INTERVAL: 40, /** * addListener/removeListener can throw errors in unexpected scenarios. * These errors are suppressed, the method returns false, and this property * is set * @property lastError * @static * @type Error */ lastError: null, /** * poll handle * @property _interval * @static * @private */ _interval: null, /** * document readystate poll handle * @property _dri * @static * @private */ _dri: null, /** * True when the document is initially usable * @property DOMReady * @type boolean * @static */ DOMReady: false, /** * @method startInterval * @static * @private */ startInterval: function() { if (!Event._interval) { Event._interval = setInterval(Y.bind(Event._poll, Event), Event.POLL_INTERVAL); } }, /** * Executes the supplied callback when the item with the supplied * id is found. This is meant to be used to execute behavior as * soon as possible as the page loads. If you use this after the * initial page load it will poll for a fixed time for the element. * The number of times it will poll and the frequency are * configurable. By default it will poll for 10 seconds. * *
The callback is executed with a single parameter: * the custom object parameter, if provided.
* * @method onAvailable * * @param {string||string[]} id the id of the element, or an array * of ids to look for. * @param {function} fn what to execute when the element is found. * @param {object} p_obj an optional object to be passed back as * a parameter to fn. * @param {boolean|object} p_override If set to true, fn will execute * in the context of p_obj, if set to an object it * will execute in the context of that object * @param checkContent {boolean} check child node readiness (onContentReady) * @static * @deprecated Use Y.on("available") */ // @TODO fix arguments onAvailable: function(id, fn, p_obj, p_override, checkContent, compat) { var a = Y.Array(id), i, availHandle; // Y.log('onAvailable registered for: ' + id); for (i=0; iSets up event delegation on a container element. The delegated event * will use a supplied selector or filtering function to test if the event * references at least one node that should trigger the subscription * callback.
* *Selector string filters will trigger the callback if the event originated * from a node that matches it or is contained in a node that matches it. * Function filters are called for each Node up the parent axis to the * subscribing container node, and receive at each level the Node and the event * object. The function should return true (or a truthy value) if that Node * should trigger the subscription callback. Note, it is possible for filters * to match multiple Nodes for a single event. In this case, the delegate * callback will be executed for each matching Node.
* *For each matching Node, the callback will be executed with its 'this'
* object set to the Node matched by the filter (unless a specific context was
* provided during subscription), and the provided event's
* currentTarget
will also be set to the matching Node. The
* containing Node from which the subscription was originally made can be
* referenced as e.container
.
*
* @method delegate
* @param type {String} the event type to delegate
* @param fn {Function} the callback function to execute. This function
* will be provided the event object for the delegated event.
* @param el {String|node} the element that is the delegation container
* @param spec {string|Function} a selector that must match the target of the
* event or a function to test target and its parents for a match
* @param context optional argument that specifies what 'this' refers to.
* @param args* 0..n additional arguments to pass on to the callback function.
* These arguments will be added after the event object.
* @return {EventHandle} the detach handle
* @for YUI
*/
function delegate(type, fn, el, filter) {
var args = toArray(arguments, 0, true),
query = isString(el) ? el : null,
typeBits = type.split(/\|/),
synth, container, categories, cat, handle;
if (typeBits.length > 1) {
cat = typeBits.shift();
type = typeBits.shift();
}
synth = Y.Node.DOM_EVENTS[type];
if (YLang.isObject(synth) && synth.delegate) {
handle = synth.delegate.apply(synth, arguments);
}
if (!handle) {
if (!type || !fn || !el || !filter) {
Y.log("delegate requires type, callback, parent, & filter", "warn");
return;
}
container = (query) ? Y.Selector.query(query, null, true) : el;
if (!container && isString(el)) {
handle = Y.on('available', function () {
Y.mix(handle, Y.delegate.apply(Y, args), true);
}, el);
}
if (!handle && container) {
args.splice(2, 2, container); // remove the filter
if (isString(filter)) {
filter = Y.delegate.compileFilter(filter);
}
handle = Y.on.apply(Y, args);
handle.sub.filter = filter;
handle.sub._notify = delegate.notifySub;
}
}
if (handle && cat) {
categories = detachCategories[cat] || (detachCategories[cat] = {});
categories = categories[type] || (categories[type] = []);
categories.push(handle);
}
return handle;
}
/**
* Overrides the _notify
method on the normal DOM subscription to inject the filtering logic and only proceed in the case of a match.
*
* @method delegate.notifySub
* @param thisObj {Object} default 'this' object for the callback
* @param args {Array} arguments passed to the event's fire()
* @param ce {CustomEvent} the custom event managing the DOM subscriptions for
* the subscribed event on the subscribing node.
* @return {Boolean} false if the event was stopped
* @private
* @static
* @since 3.2.0
*/
delegate.notifySub = function (thisObj, args, ce) {
// Preserve args for other subscribers
args = args.slice();
if (this.args) {
args.push.apply(args, this.args);
}
// Only notify subs if the event occurred on a targeted element
var e = args[0],
currentTarget = delegate._applyFilter(this.filter, args),
container = e.currentTarget,
i, ret, target;
if (currentTarget) {
// Support multiple matches up the the container subtree
currentTarget = toArray(currentTarget);
for (i = currentTarget.length - 1; i >= 0; --i) {
target = currentTarget[i];
// New facade to avoid corrupting facade sent to direct subs
args[0] = new Y.DOMEventFacade(e, target, ce);
args[0].container = container;
thisObj = this.context || target;
ret = this.fn.apply(thisObj, args);
if (ret === false) { // stop further notifications
break;
}
}
return ret;
}
};
/**
*
Compiles a selector string into a filter function to identify whether * Nodes along the parent axis of an event's target should trigger event * notification.
* *This function is memoized, so previously compiled filter functions are * returned if the same selector string is provided.
* *This function may be useful when defining synthetic events for delegate * handling.
* * @method delegate.compileFilter * @param selector {String} the selector string to base the filtration on * @return {Function} * @since 3.2.0 * @static */ delegate.compileFilter = Y.cached(function (selector) { return function (target, e) { return selectorTest(target._node, selector, e.currentTarget._node); }; }); /** * Walks up the parent axis of an event's target, and tests each element * against a supplied filter function. If any Nodes satisfy the filter, the * delegated callback will be triggered for each. * * @method delegate._applyFilter * @param filter {Function} boolean function to test for inclusion in event * notification * @param args {Array} the arguments that would be passed to subscribers * @return {Node|Node[]|undefined} The Node or Nodes that satisfy the filter * @protected */ delegate._applyFilter = function (filter, args) { var e = args[0], container = e.currentTarget, target = e.target, match = []; // passing target as the first arg rather than leaving well enough alone // making 'this' in the filter function refer to the target. This is to // support bound filter functions. args.unshift(target); while (target && target !== container) { // filter(target, e, extra args...) - this === target if (filter.apply(target, args)) { match.push(target); } args[0] = target = target.get('parentNode'); } if (match.length <= 1) { match = match[0]; // single match or undefined } // remove the target args.shift(); return match; }; /** * Sets up event delegation on a container element. The delegated event * will use a supplied filter to test if the callback should be executed. * This filter can be either a selector string or a function that returns * a Node to use as the currentTarget for the event. * * The event object for the delegated event is supplied to the callback * function. It is modified slightly in order to support all properties * that may be needed for event delegation. 'currentTarget' is set to * the element that matched the selector string filter or the Node returned * from the filter function. 'container' is set to the element that the * listener is delegated from (this normally would be the 'currentTarget'). * * Filter functions will be called with the arguments that would be passed to * the callback function, including the event object as the first parameter. * The function should return false (or a falsey value) if the success criteria * aren't met, and the Node to use as the event's currentTarget and 'this' * object if they are. * * @method delegate * @param type {string} the event type to delegate * @param fn {function} the callback function to execute. This function * will be provided the event object for the delegated event. * @param el {string|node} the element that is the delegation container * @param filter {string|function} a selector that must match the target of the * event or a function that returns a Node or false. * @param context optional argument that specifies what 'this' refers to. * @param args* 0..n additional arguments to pass on to the callback function. * These arguments will be added after the event object. * @return {EventHandle} the detach handle * @for YUI */ Y.delegate = Y.Event.delegate = delegate; }, '3.2.0' ,{requires:['node-base']}); YUI.add('event-synthetic', function(Y) { /** * Define new DOM events that can be subscribed to from Nodes. * * @module event * @submodule event-synthetic */ var DOMMap = Y.Env.evt.dom_map, toArray = Y.Array, YLang = Y.Lang, isObject = YLang.isObject, isString = YLang.isString, query = Y.Selector.query, noop = function () {}; /** *The triggering mechanism used by SyntheticEvents.
* *Implementers should not instantiate these directly. Use the Notifier
* provided to the event's implemented on(node, sub, notifier)
or
* delegate(node, sub, notifier, filter)
methods.
Executes the subscription callback, passing the firing arguments as the * first parameters to that callback. For events that are configured with * emitFacade=true, it is common practice to pass the triggering DOMEventFacade * as the first parameter. Barring a proper DOMEventFacade or EventFacade * (from a CustomEvent), a new EventFacade will be generated. In that case, if * fire() is called with a simple object, it will be mixed into the facade. * Otherwise, the facade will be prepended to the callback parameters.
* *For notifiers provided to delegate logic, the first argument should be an * object with a "currentTarget" property to identify what object to * default as 'this' in the callback. Typically this is gleaned from the * DOMEventFacade or EventFacade, but if configured with emitFacade=false, an * object must be provided. In that case, the object will be removed from the * callback parameters.
* *Additional arguments passed during event subscription will be * automatically added after those passed to fire().
* * @method fire * @param e {EventFacade|DOMEventFacade|Object|any} (see description) * @param arg* {any} additional arguments received by all subscriptions * @private */ Notifier.prototype.fire = function (e) { // first arg to delegate notifier should be an object with currentTarget var args = toArray(arguments, 0, true), handle = this.handle, ce = handle.evt, sub = handle.sub, thisObj = sub.context, delegate = sub.filter, event = e || {}; if (this.emitFacade) { if (!e || !e.preventDefault) { event = ce._getFacade(); if (isObject(e) && !e.preventDefault) { Y.mix(event, e, true); args[0] = event; } else { args.unshift(event); } } event.type = ce.type; event.details = args.slice(); if (delegate) { event.container = ce.host; } } else if (delegate && isObject(e) && e.currentTarget) { args.shift(); } sub.context = thisObj || event.currentTarget || ce.host; ce.fire.apply(ce, args); sub.context = thisObj; // reset for future firing }; /** *Wrapper class for the integration of new events into the YUI event
* infrastructure. Don't instantiate this object directly, use
* Y.Event.define(type, config)
. See that method for details.
Properties that MAY or SHOULD be specified in the configuration are noted
* below and in the description of Y.Event.define
.
_delete()
method for the CustomEvent object
* created to manage SyntheticEvent subscriptions.
*
* @method _deleteSub
* @param sub {Subscription} the subscription to clean up
* @private
* @since 3.2.0
*/
_deleteSub: function (sub) {
if (sub && sub.fn) {
var synth = this.eventDef,
method = (sub.filter) ? 'detachDelegate' : 'detach';
this.subscribers = {};
this.subCount = 0;
synth[method](sub.node, sub, this.notifier, sub.filter);
synth._unregisterSub(sub);
delete sub.fn;
delete sub.node;
delete sub.context;
}
},
prototype: {
constructor: SyntheticEvent,
/**
* Construction logic for the event.
*
* @method _init
* @protected
*/
_init: function () {
var config = this.publishConfig || (this.publishConfig = {});
// The notification mechanism handles facade creation
this.emitFacade = ('emitFacade' in config) ?
config.emitFacade :
true;
config.emitFacade = false;
},
/**
* Implementers MAY provide this method definition.
* *Implement this function if the event supports a different
* subscription signature. This function is used by both
* on()
and delegate()
. The second parameter
* indicates that the event is being subscribed via
* delegate()
.
Implementations must remove extra arguments from the args list
* before returning. The required args for on()
* subscriptions are
[type, callback, target, context, argN...]
*
* The required args for delegate()
* subscriptions are
[type, callback, target, filter, context, argN...]
*
* The return value from this function will be stored on the * subscription in the '_extra' property for reference elsewhere.
* * @method processArgs * @param args {Array} parmeters passed to Y.on(..) or Y.delegate(..) * @param delegate {Boolean} true if the subscription is from Y.delegate * @return {any} */ processArgs: noop, /** *Implementers MAY override this property.
* *Whether to prevent multiple subscriptions to this event that are
* classified as being the same. By default, this means the subscribed
* callback is the same function. See the subMatch
* method. Setting this to true will impact performance for high volume
* events.
Implementers SHOULD provide this method definition.
* * Implementation logic for subscriptions done vianode.on(type,
* fn)
or Y.on(type, fn, target)
. This
* function should set up the monitor(s) that will eventually fire the
* event. Typically this involves subscribing to at least one DOM
* event. It is recommended to store detach handles from any DOM
* subscriptions to make for easy cleanup in the detach
* method. Typically these handles are added to the sub
* object. Also for SyntheticEvents that leverage a single DOM
* subscription under the hood, it is recommended to pass the DOM event
* object to notifier.fire(e)
. (The event name on the
* object will be updated).
*
* @method on
* @param node {Node} the node the subscription is being applied to
* @param sub {Subscription} the object to track this subscription
* @param notifier {SyntheticEvent.Notifier} call notifier.fire(..) to
* trigger the execution of the subscribers
*/
on: noop,
/**
* Implementers SHOULD provide this method definition.
* *Implementation logic for detaching subscriptions done via
* node.on(type, fn)
. This function should clean up any
* subscriptions made in the on()
phase.
Implementers SHOULD provide this method definition.
* *Implementation logic for subscriptions done via
* node.delegate(type, fn, filter)
or
* Y.delegate(type, fn, container, filter)
. Like with
* on()
above, this function should monitor the environment
* for the event being fired, and trigger subscription execution by
* calling notifier.fire(e)
.
This function receives a fourth argument, which is the filter
* used to identify which Node's are of interest to the subscription.
* The filter will be either a boolean function that accepts a target
* Node for each hierarchy level as the event bubbles, or a selector
* string. To translate selector strings into filter functions, use
* Y.delegate.compileFilter(filter)
.
Implementers SHOULD provide this method definition.
* *Implementation logic for detaching subscriptions done via
* node.delegate(type, fn, filter)
or
* Y.delegate(type, fn, container, filter)
. This function
* should clean up any subscriptions made in the
* delegate()
phase.
Y.on(...)
or Y.delegate(...)
* @param delegate {Boolean} true if called from
* Y.delegate(...)
* @return {EventHandle} the detach handle for this subscription
* @private
* since 3.2.0
*/
_on: function (args, delegate) {
var handles = [],
selector = args[2],
method = delegate ? 'delegate' : 'on',
nodes, handle;
// Can't just use Y.all because it doesn't support window (yet?)
nodes = (isString(selector)) ? query(selector) : toArray(selector);
if (!nodes.length && isString(selector)) {
handle = Y.on('available', function () {
Y.mix(handle, Y[method].apply(Y, args), true);
}, selector);
return handle;
}
Y.each(nodes, function (node) {
var subArgs = args.slice(),
extra, filter;
node = Y.one(node);
if (node) {
extra = this.processArgs(subArgs, delegate);
if (delegate) {
filter = subArgs.splice(3, 1)[0];
}
// (type, fn, el, thisObj, ...) => (fn, thisObj, ...)
subArgs.splice(0, 4, subArgs[1], subArgs[3]);
if (!this.preventDups || !this.getSubs(node, args,null,true)) {
handle = this._getNotifier(node, subArgs, extra,filter);
this[method](node, handle.sub, handle.notifier, filter);
handles.push(handle);
}
}
}, this);
return (handles.length === 1) ?
handles[0] :
new Y.EventHandle(handles);
},
/**
* Creates a new Notifier object for use by this event's
* on(...)
or delegate(...)
implementation.
*
* @method _getNotifier
* @param node {Node} the Node hosting the event
* @param args {Array} the subscription arguments passed to either
* Y.on(...)
or Y.delegate(...)
* after running through processArgs(args)
to
* normalize the argument signature
* @param extra {any} Extra data parsed from
* processArgs(args)
* @param filter {String|Function} the selector string or function
* filter passed to Y.delegate(...)
(not
* present when called from Y.on(...)
)
* @return {SyntheticEvent.Notifier}
* @private
* @since 3.2.0
*/
_getNotifier: function (node, args, extra, filter) {
var dispatcher = new Y.CustomEvent(this.type, this.publishConfig),
handle = dispatcher.on.apply(dispatcher, args),
notifier = new Notifier(handle, this.emitFacade),
registry = SyntheticEvent.getRegistry(node, this.type, true),
sub = handle.sub;
handle.notifier = notifier;
sub.node = node;
sub.filter = filter;
sub._extra = extra;
Y.mix(dispatcher, {
eventDef : this,
notifier : notifier,
host : node, // I forget what this is for
currentTarget: node, // for generating facades
target : node, // for generating facades
el : node._node, // For category detach
_delete : SyntheticEvent._deleteSub
}, true);
registry.push(handle);
return handle;
},
/**
* Removes the subscription from the Notifier registry.
*
* @method _unregisterSub
* @param sub {Subscription} the subscription
* @private
* @since 3.2.0
*/
_unregisterSub: function (sub) {
var notifiers = SyntheticEvent.getRegistry(sub.node, this.type),
i;
if (notifiers) {
for (i = notifiers.length - 1; i >= 0; --i) {
if (notifiers[i].sub === sub) {
notifiers.splice(i, 1);
break;
}
}
}
},
/**
* Removes the subscription(s) from the internal subscription dispatch
* mechanism. See SyntheticEvent._deleteSub
.
*
* @method _detach
* @param args {Array} The arguments passed to
* node.detach(...)
* @private
* @since 3.2.0
*/
_detach: function (args) {
// Can't use Y.all because it doesn't support window (yet?)
// TODO: Does Y.all support window now?
var target = args[2],
els = (isString(target)) ?
query(target) : toArray(target),
node, i, len, handles, j;
// (type, fn, el, context, filter?) => (type, fn, context, filter?)
args.splice(2, 1);
for (i = 0, len = els.length; i < len; ++i) {
node = Y.one(els[i]);
if (node) {
handles = this.getSubs(node, args);
if (handles) {
for (j = handles.length - 1; j >= 0; --j) {
handles[j].detach();
}
}
}
}
},
/**
* Returns the detach handles of subscriptions on a node that satisfy a
* search/filter function. By default, the filter used is the
* subMatch
method.
*
* @method getSubs
* @param node {Node} the node hosting the event
* @param args {Array} the array of original subscription args passed
* to Y.on(...)
(before
* processArgs
* @param filter {Function} function used to identify a subscription
* for inclusion in the returned array
* @param first {Boolean} stop after the first match (used to check for
* duplicate subscriptions)
* @return {Array} detach handles for the matching subscriptions
*/
getSubs: function (node, args, filter, first) {
var notifiers = SyntheticEvent.getRegistry(node, this.type),
handles = [],
i, len, handle;
if (notifiers) {
if (!filter) {
filter = this.subMatch;
}
for (i = 0, len = notifiers.length; i < len; ++i) {
handle = notifiers[i];
if (filter.call(this, handle.sub, args)) {
if (first) {
return handle;
} else {
handles.push(notifiers[i]);
}
}
}
}
return handles.length && handles;
},
/**
* Implementers MAY override this to define what constitutes a
* "same" subscription. Override implementations should
* consider the lack of a comparator as a match, so calling
* getSubs()
with no arguments will return all subs.
Compares a set of subscription arguments against a Subscription
* object to determine if they match. The default implementation
* compares the callback function against the second argument passed to
* Y.on(...)
or node.detach(...)
etc.
Y.on(...)
etc.
* @return {Boolean} true if the sub can be described by the args
* present
* @since 3.2.0
*/
subMatch: function (sub, args) {
// Default detach cares only about the callback matching
return !args[1] || sub.fn === args[1];
}
}
}, true);
Y.SyntheticEvent = SyntheticEvent;
/**
* Defines a new event in the DOM event system. Implementers are * responsible for monitoring for a scenario whereby the event is fired. A * notifier object is provided to the functions identified below. When the * criteria defining the event are met, call notifier.fire( [args] ); to * execute event subscribers.
* *The first parameter is the name of the event. The second parameter is a
* configuration object which define the behavior of the event system when the
* new event is subscribed to or detached from. The methods that should be
* defined in this configuration object are on
,
* detach
, delegate
, and detachDelegate
.
* You are free to define any other methods or properties needed to define your
* event. Be aware, however, that since the object is used to subclass
* SyntheticEvent, you should avoid method names used by SyntheticEvent unless
* your intention is to override the default behavior.
This is a list of properties and methods that you can or should specify * in the configuration object:
* *on
function (node, subscription, notifier)
The
* implementation logic for subscription. Any special setup you need to
* do to create the environment for the event being fired--E.g. native
* DOM event subscriptions. Store subscription related objects and
* state on the subscription
object. When the
* criteria have been met to fire the synthetic event, call
* notifier.fire(e)
. See Notifier's fire()
* method for details about what to pass as parameters.detach
function (node, subscription, notifier)
The
* implementation logic for cleaning up a detached subscription. E.g.
* detach any DOM subscriptions added in on
.delegate
function (node, subscription, notifier, filter)
The
* implementation logic for subscription via Y.delegate
or
* node.delegate
. The filter is typically either a selector
* string or a function. You can use
* Y.delegate.compileFilter(selectorString)
to create a
* filter function from a selector string if needed. The filter function
* expects an event object as input and should output either null, a
* matching Node, or an array of matching Nodes. Otherwise, this acts
* like on
DOM event subscriptions. Store subscription
* related objects and information on the subscription
* object. When the criteria have been met to fire the synthetic event,
* call notifier.fire(e)
as noted above.detachDelegate
function (node, subscription, notifier)
The
* implementation logic for cleaning up a detached delegate subscription.
* E.g. detach any DOM delegate subscriptions added in
* delegate
.publishConfig
fire
method
* for details.processArgs
function (argArray, fromDelegate)
Optional method
* to extract any additional arguments from the subscription
* signature. Using this allows on
or
* delegate
signatures like
* node.on("hover", overCallback,
* outCallback)
.
When processing an atypical argument signature, make sure the
* args array is returned to the normal signature before returning
* from the function. For example, in the "hover" example
* above, the outCallback
needs to be splice
d
* out of the array. The expected signature of the args array for
* on()
subscriptions is:
* [type, callback, target, contextOverride, argN...]
*
* And for delegate()
:
* [type, callback, target, filter, contextOverride, argN...]
*
* where target
is the node the event is being
* subscribed for. You can see these signatures documented for
* Y.on()
and Y.delegate()
respectively.
Whatever gets returned from the function will be stored on the
* subscription
object under
* subscription._extra
.
subMatch
function (sub, args)
Compares a set of
* subscription arguments against a Subscription object to determine
* if they match. The default implementation compares the callback
* function against the second argument passed to
* Y.on(...)
or node.detach(...)
etc.
Adds subscription and delegation support for mouseenter and mouseleave * events. Unlike mouseover and mouseout, these events aren't fired from child * elements of a subscribed node.
* *This avoids receiving three mouseover notifications from a setup like
* *div#container > p > a[href]
*
* where
* *Y.one('#container').on('mouseover', callback)
*
* When the mouse moves over the link, one mouseover event is fired from * #container, then when the mouse moves over the p, another mouseover event is * fired and bubbles to #container, causing a second notification, and finally * when the mouse moves over the link, a third mouseover event is fired and * bubbles to #container for a third notification.
* *By contrast, using mouseenter instead of mouseover, the callback would be * executed only once when the mouse moves over #container.
* * @module event * @submodule event-mouseenter */ function notify(e, notifier) { var current = e.currentTarget, related = e.relatedTarget; if (current !== related && !current.contains(related)) { notifier.fire(e); } } var config = { proxyType: "mouseover", on: function (node, sub, notifier) { sub.onHandle = node.on(this.proxyType, notify, null, notifier); }, detach: function (node, sub) { sub.onHandle.detach(); }, delegate: function (node, sub, notifier, filter) { sub.delegateHandle = Y.delegate(this.proxyType, notify, node, filter, null, notifier); }, detachDelegate: function (node, sub) { sub.delegateHandle.detach(); } }; Y.Event.define("mouseenter", config, true); Y.Event.define("mouseleave", Y.merge(config, { proxyType: "mouseout" }), true); }, '3.2.0' ,{requires:['event-synthetic']}); YUI.add('event-key', function(Y) { /** * Functionality to listen for one or more specific key combinations. * @module event * @submodule event-key */ /** * Add a key listener. The listener will only be notified if the * keystroke detected meets the supplied specification. The * spec consists of the key event type, followed by a colon, * followed by zero or more comma separated key codes, followed * by zero or more modifiers delimited by a plus sign. Ex: * press:12,65+shift+ctrl * @event key * @for YUI * @param type {string} 'key' * @param fn {function} the function to execute * @param id {string|HTMLElement|collection} the element(s) to bind * @param spec {string} the keyCode and modifier specification * @param o optional context object * @param args 0..n additional arguments to provide to the listener. * @return {Event.Handle} the detach handle */ Y.Env.evt.plugins.key = { on: function(type, fn, id, spec, o) { var a = Y.Array(arguments, 0, true), parsed, etype, criteria, ename; parsed = spec && spec.split(':'); if (!spec || spec.indexOf(':') == -1 || !parsed[1]) { Y.log('Illegal key spec, creating a regular keypress listener instead.', 'info', 'event'); a[0] = 'key' + ((parsed && parsed[0]) || 'press'); return Y.on.apply(Y, a); } // key event type: 'down', 'up', or 'press' etype = parsed[0]; // list of key codes optionally followed by modifiers criteria = (parsed[1]) ? parsed[1].split(/,|\+/) : null; // the name of the custom event that will be created for the spec ename = (Y.Lang.isString(id) ? id : Y.stamp(id)) + spec; ename = ename.replace(/,/g, '_'); if (!Y.getEvent(ename)) { // subscribe spec validator to the DOM event Y.on(type + etype, function(e) { // Y.log('keylistener: ' + e.keyCode); var passed = false, failed = false, i, crit, critInt; for (i=0; i