/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 3.3.0 build: 3167 */ YUI.add('event-custom-base', function(Y) { /** * Custom event engine, DOM event listener abstraction layer, synthetic DOM * events. * @module event-custom */ Y.Env.evt = { handles: {}, plugins: {} }; /** * Custom event engine, DOM event listener abstraction layer, synthetic DOM * events. * @module event-custom * @submodule event-custom-base */ /** * Allows for the insertion of methods that are executed before or after * a specified method * @class Do * @static */ var DO_BEFORE = 0, DO_AFTER = 1, DO = { /** * Cache of objects touched by the utility * @property objs * @static */ objs: {}, /** * Execute the supplied method before the specified function * @method before * @param fn {Function} the function to execute * @param obj the object hosting the method to displace * @param sFn {string} the name of the method to displace * @param c The execution context for fn * @param arg* {mixed} 0..n additional arguments to supply to the subscriber * when the event fires. * @return {string} handle for the subscription * @static */ before: function(fn, obj, sFn, c) { var f = fn, a; if (c) { a = [fn, c].concat(Y.Array(arguments, 4, true)); f = Y.rbind.apply(Y, a); } return this._inject(DO_BEFORE, f, obj, sFn); }, /** * Execute the supplied method after the specified function * @method after * @param fn {Function} the function to execute * @param obj the object hosting the method to displace * @param sFn {string} the name of the method to displace * @param c The execution context for fn * @param arg* {mixed} 0..n additional arguments to supply to the subscriber * @return {string} handle for the subscription * @static */ after: function(fn, obj, sFn, c) { var f = fn, a; if (c) { a = [fn, c].concat(Y.Array(arguments, 4, true)); f = Y.rbind.apply(Y, a); } return this._inject(DO_AFTER, f, obj, sFn); }, /** * Execute the supplied method after the specified function * @method _inject * @param when {string} before or after * @param fn {Function} the function to execute * @param obj the object hosting the method to displace * @param sFn {string} the name of the method to displace * @param c The execution context for fn * @return {string} handle for the subscription * @private * @static */ _inject: function(when, fn, obj, sFn) { // object id var id = Y.stamp(obj), o, sid; if (! this.objs[id]) { // create a map entry for the obj if it doesn't exist this.objs[id] = {}; } o = this.objs[id]; if (! o[sFn]) { // create a map entry for the method if it doesn't exist o[sFn] = new Y.Do.Method(obj, sFn); // re-route the method to our wrapper obj[sFn] = function() { return o[sFn].exec.apply(o[sFn], arguments); }; } // subscriber id sid = id + Y.stamp(fn) + sFn; // register the callback o[sFn].register(sid, fn, when); return new Y.EventHandle(o[sFn], sid); }, /** * Detach a before or after subscription * @method detach * @param handle {string} the subscription handle */ detach: function(handle) { if (handle.detach) { handle.detach(); } }, _unload: function(e, me) { } }; Y.Do = DO; ////////////////////////////////////////////////////////////////////////// /** * Contains the return value from the wrapped method, accessible * by 'after' event listeners. * * @property Do.originalRetVal * @static * @since 2.3.0 */ /** * Contains the current state of the return value, consumable by * 'after' event listeners, and updated if an after subscriber * changes the return value generated by the wrapped function. * * @property Do.currentRetVal * @static * @since 2.3.0 */ ////////////////////////////////////////////////////////////////////////// /** * Wrapper for a displaced method with aop enabled * @class Do.Method * @constructor * @param obj The object to operate on * @param sFn The name of the method to displace */ DO.Method = function(obj, sFn) { this.obj = obj; this.methodName = sFn; this.method = obj[sFn]; this.before = {}; this.after = {}; }; /** * Register a aop subscriber * @method register * @param sid {string} the subscriber id * @param fn {Function} the function to execute * @param when {string} when to execute the function */ DO.Method.prototype.register = function (sid, fn, when) { if (when) { this.after[sid] = fn; } else { this.before[sid] = fn; } }; /** * Unregister a aop subscriber * @method delete * @param sid {string} the subscriber id * @param fn {Function} the function to execute * @param when {string} when to execute the function */ DO.Method.prototype._delete = function (sid) { delete this.before[sid]; delete this.after[sid]; }; /** * Execute the wrapped method * @method exec */ DO.Method.prototype.exec = function () { var args = Y.Array(arguments, 0, true), i, ret, newRet, bf = this.before, af = this.after, prevented = false; // execute before for (i in bf) { if (bf.hasOwnProperty(i)) { ret = bf[i].apply(this.obj, args); if (ret) { switch (ret.constructor) { case DO.Halt: return ret.retVal; case DO.AlterArgs: args = ret.newArgs; break; case DO.Prevent: prevented = true; break; default: } } } } // execute method if (!prevented) { ret = this.method.apply(this.obj, args); } DO.originalRetVal = ret; DO.currentRetVal = ret; // execute after methods. for (i in af) { if (af.hasOwnProperty(i)) { newRet = af[i].apply(this.obj, args); // Stop processing if a Halt object is returned if (newRet && newRet.constructor == DO.Halt) { return newRet.retVal; // Check for a new return value } else if (newRet && newRet.constructor == DO.AlterReturn) { ret = newRet.newRetVal; // Update the static retval state DO.currentRetVal = ret; } } } return ret; }; ////////////////////////////////////////////////////////////////////////// /** * Return an AlterArgs object when you want to change the arguments that * were passed into the function. An example would be a service that scrubs * out illegal characters prior to executing the core business logic. * @class Do.AlterArgs */ DO.AlterArgs = function(msg, newArgs) { this.msg = msg; this.newArgs = newArgs; }; /** * Return an AlterReturn object when you want to change the result returned * from the core method to the caller * @class Do.AlterReturn */ DO.AlterReturn = function(msg, newRetVal) { this.msg = msg; this.newRetVal = newRetVal; }; /** * Return a Halt object when you want to terminate the execution * of all subsequent subscribers as well as the wrapped method * if it has not exectued yet. * @class Do.Halt */ DO.Halt = function(msg, retVal) { this.msg = msg; this.retVal = retVal; }; /** * Return a Prevent object when you want to prevent the wrapped function * from executing, but want the remaining listeners to execute * @class Do.Prevent */ DO.Prevent = function(msg) { this.msg = msg; }; /** * Return an Error object when you want to terminate the execution * of all subsequent method calls. * @class Do.Error * @deprecated use Y.Do.Halt or Y.Do.Prevent */ DO.Error = DO.Halt; ////////////////////////////////////////////////////////////////////////// // Y["Event"] && Y.Event.addListener(window, "unload", Y.Do._unload, Y.Do); /** * Custom event engine, DOM event listener abstraction layer, synthetic DOM * events. * @module event-custom * @submodule event-custom-base */ // var onsubscribeType = "_event:onsub", var AFTER = 'after', CONFIGS = [ 'broadcast', 'monitored', 'bubbles', 'context', 'contextFn', 'currentTarget', 'defaultFn', 'defaultTargetOnly', 'details', 'emitFacade', 'fireOnce', 'async', 'host', 'preventable', 'preventedFn', 'queuable', 'silent', 'stoppedFn', 'target', 'type' ], YUI3_SIGNATURE = 9, YUI_LOG = 'yui:log'; /** * Return value from all subscribe operations * @class EventHandle * @constructor * @param {CustomEvent} evt the custom event. * @param {Subscriber} sub the subscriber. */ Y.EventHandle = function(evt, sub) { /** * The custom event * @type CustomEvent */ this.evt = evt; /** * The subscriber object * @type Subscriber */ this.sub = sub; }; Y.EventHandle.prototype = { batch: function(f, c) { f.call(c || this, this); if (Y.Lang.isArray(this.evt)) { Y.Array.each(this.evt, function(h) { h.batch.call(c || h, f); }); } }, /** * Detaches this subscriber * @method detach * @return {int} the number of detached listeners */ detach: function() { var evt = this.evt, detached = 0, i; if (evt) { if (Y.Lang.isArray(evt)) { for (i = 0; i < evt.length; i++) { detached += evt[i].detach(); } } else { evt._delete(this.sub); detached = 1; } } return detached; }, /** * Monitor the event state for the subscribed event. The first parameter * is what should be monitored, the rest are the normal parameters when * subscribing to an event. * @method monitor * @param what {string} what to monitor ('attach', 'detach', 'publish'). * @return {EventHandle} return value from the monitor event subscription. */ monitor: function(what) { return this.evt.monitor.apply(this.evt, arguments); } }; /** * The CustomEvent class lets you define events for your application * that can be subscribed to by one or more independent component. * * @param {String} type The type of event, which is passed to the callback * when the event fires. * @param {object} o configuration object. * @class CustomEvent * @constructor */ Y.CustomEvent = function(type, o) { // if (arguments.length > 2) { // this.log('CustomEvent context and silent are now in the config', 'warn', 'Event'); // } o = o || {}; this.id = Y.stamp(this); /** * The type of event, returned to subscribers when the event fires * @property type * @type string */ this.type = type; /** * The context the the event will fire from by default. Defaults to the YUI * instance. * @property context * @type object */ this.context = Y; /** * Monitor when an event is attached or detached. * * @property monitored * @type boolean */ // this.monitored = false; this.logSystem = (type == YUI_LOG); /** * If 0, this event does not broadcast. If 1, the YUI instance is notified * every time this event fires. If 2, the YUI instance and the YUI global * (if event is enabled on the global) are notified every time this event * fires. * @property broadcast * @type int */ // this.broadcast = 0; /** * By default all custom events are logged in the debug build, set silent * to true to disable debug outpu for this event. * @property silent * @type boolean */ this.silent = this.logSystem; /** * Specifies whether this event should be queued when the host is actively * processing an event. This will effect exectution order of the callbacks * for the various events. * @property queuable * @type boolean * @default false */ // this.queuable = false; /** * The subscribers to this event * @property subscribers * @type Subscriber {} */ this.subscribers = {}; /** * 'After' subscribers * @property afters * @type Subscriber {} */ this.afters = {}; /** * This event has fired if true * * @property fired * @type boolean * @default false; */ // this.fired = false; /** * An array containing the arguments the custom event * was last fired with. * @property firedWith * @type Array */ // this.firedWith; /** * This event should only fire one time if true, and if * it has fired, any new subscribers should be notified * immediately. * * @property fireOnce * @type boolean * @default false; */ // this.fireOnce = false; /** * fireOnce listeners will fire syncronously unless async * is set to true * @property async * @type boolean * @default false */ //this.async = false; /** * Flag for stopPropagation that is modified during fire() * 1 means to stop propagation to bubble targets. 2 means * to also stop additional subscribers on this target. * @property stopped * @type int */ // this.stopped = 0; /** * Flag for preventDefault that is modified during fire(). * if it is not 0, the default behavior for this event * @property prevented * @type int */ // this.prevented = 0; /** * Specifies the host for this custom event. This is used * to enable event bubbling * @property host * @type EventTarget */ // this.host = null; /** * The default function to execute after event listeners * have fire, but only if the default action was not * prevented. * @property defaultFn * @type Function */ // this.defaultFn = null; /** * The function to execute if a subscriber calls * stopPropagation or stopImmediatePropagation * @property stoppedFn * @type Function */ // this.stoppedFn = null; /** * The function to execute if a subscriber calls * preventDefault * @property preventedFn * @type Function */ // this.preventedFn = null; /** * Specifies whether or not this event's default function * can be cancelled by a subscriber by executing preventDefault() * on the event facade * @property preventable * @type boolean * @default true */ this.preventable = true; /** * Specifies whether or not a subscriber can stop the event propagation * via stopPropagation(), stopImmediatePropagation(), or halt() * * Events can only bubble if emitFacade is true. * * @property bubbles * @type boolean * @default true */ this.bubbles = true; /** * Supports multiple options for listener signatures in order to * port YUI 2 apps. * @property signature * @type int * @default 9 */ this.signature = YUI3_SIGNATURE; this.subCount = 0; this.afterCount = 0; // this.hasSubscribers = false; // this.hasAfters = false; /** * If set to true, the custom event will deliver an EventFacade object * that is similar to a DOM event object. * @property emitFacade * @type boolean * @default false */ // this.emitFacade = false; this.applyConfig(o, true); // this.log("Creating " + this.type); }; Y.CustomEvent.prototype = { hasSubs: function(when) { var s = this.subCount, a = this.afterCount, sib = this.sibling; if (sib) { s += sib.subCount; a += sib.afterCount; } if (when) { return (when == 'after') ? a : s; } return (s + a); }, /** * Monitor the event state for the subscribed event. The first parameter * is what should be monitored, the rest are the normal parameters when * subscribing to an event. * @method monitor * @param what {string} what to monitor ('detach', 'attach', 'publish'). * @return {EventHandle} return value from the monitor event subscription. */ monitor: function(what) { this.monitored = true; var type = this.id + '|' + this.type + '_' + what, args = Y.Array(arguments, 0, true); args[0] = type; return this.host.on.apply(this.host, args); }, /** * Get all of the subscribers to this event and any sibling event * @method getSubs * @return {Array} first item is the on subscribers, second the after. */ getSubs: function() { var s = Y.merge(this.subscribers), a = Y.merge(this.afters), sib = this.sibling; if (sib) { Y.mix(s, sib.subscribers); Y.mix(a, sib.afters); } return [s, a]; }, /** * Apply configuration properties. Only applies the CONFIG whitelist * @method applyConfig * @param o hash of properties to apply. * @param force {boolean} if true, properties that exist on the event * will be overwritten. */ applyConfig: function(o, force) { if (o) { Y.mix(this, o, force, CONFIGS); } }, _on: function(fn, context, args, when) { if (!fn) { this.log('Invalid callback for CE: ' + this.type); } var s = new Y.Subscriber(fn, context, args, when); if (this.fireOnce && this.fired) { if (this.async) { setTimeout(Y.bind(this._notify, this, s, this.firedWith), 0); } else { this._notify(s, this.firedWith); } } if (when == AFTER) { this.afters[s.id] = s; this.afterCount++; } else { this.subscribers[s.id] = s; this.subCount++; } return new Y.EventHandle(this, s); }, /** * Listen for this event * @method subscribe * @param {Function} fn The function to execute. * @return {EventHandle} Unsubscribe handle. * @deprecated use on. */ subscribe: function(fn, context) { var a = (arguments.length > 2) ? Y.Array(arguments, 2, true) : null; return this._on(fn, context, a, true); }, /** * Listen for this event * @method on * @param {Function} fn The function to execute. * @param {object} context optional execution context. * @param {mixed} arg* 0..n additional arguments to supply to the subscriber * when the event fires. * @return {EventHandle} An object with a detach method to detch the handler(s). */ on: function(fn, context) { var a = (arguments.length > 2) ? Y.Array(arguments, 2, true) : null; if (this.host) { this.host._monitor('attach', this.type, { args: arguments }); } return this._on(fn, context, a, true); }, /** * Listen for this event after the normal subscribers have been notified and * the default behavior has been applied. If a normal subscriber prevents the * default behavior, it also prevents after listeners from firing. * @method after * @param {Function} fn The function to execute. * @param {object} context optional execution context. * @param {mixed} arg* 0..n additional arguments to supply to the subscriber * when the event fires. * @return {EventHandle} handle Unsubscribe handle. */ after: function(fn, context) { var a = (arguments.length > 2) ? Y.Array(arguments, 2, true) : null; return this._on(fn, context, a, AFTER); }, /** * Detach listeners. * @method detach * @param {Function} fn The subscribed function to remove, if not supplied * all will be removed. * @param {Object} context The context object passed to subscribe. * @return {int} returns the number of subscribers unsubscribed. */ detach: function(fn, context) { // unsubscribe handle if (fn && fn.detach) { return fn.detach(); } var i, s, found = 0, subs = Y.merge(this.subscribers, this.afters); for (i in subs) { if (subs.hasOwnProperty(i)) { s = subs[i]; if (s && (!fn || fn === s.fn)) { this._delete(s); found++; } } } return found; }, /** * Detach listeners. * @method unsubscribe * @param {Function} fn The subscribed function to remove, if not supplied * all will be removed. * @param {Object} context The context object passed to subscribe. * @return {int|undefined} returns the number of subscribers unsubscribed. * @deprecated use detach. */ unsubscribe: function() { return this.detach.apply(this, arguments); }, /** * Notify a single subscriber * @method _notify * @param {Subscriber} s the subscriber. * @param {Array} args the arguments array to apply to the listener. * @private */ _notify: function(s, args, ef) { this.log(this.type + '->' + 'sub: ' + s.id); var ret; ret = s.notify(args, this); if (false === ret || this.stopped > 1) { this.log(this.type + ' cancelled by subscriber'); return false; } return true; }, /** * Logger abstraction to centralize the application of the silent flag * @method log * @param {string} msg message to log. * @param {string} cat log category. */ log: function(msg, cat) { if (!this.silent) { } }, /** * Notifies the subscribers. The callback functions will be executed * from the context specified when the event was created, and with the * following parameters: *
on
except the
* listener is immediatelly detached when it is executed.
* @method once
* @param type {string} The type of the event
* @param fn {Function} The callback
* @param context {object} optional execution context.
* @param arg* {mixed} 0..n additional arguments to supply to the subscriber
* @return the event target or a detach handle per 'chain' config
*/
once: function() {
var handle = this.on.apply(this, arguments);
handle.batch(function(hand) {
if (hand.sub) {
hand.sub.once = true;
}
});
return handle;
},
/**
* Takes the type parameter passed to 'on' and parses out the
* various pieces that could be included in the type. If the
* event type is passed without a prefix, it will be expanded
* to include the prefix one is supplied or the event target
* is configured with a default prefix.
* @method parseType
* @param {string} type the type
* @param {string} [pre=this._yuievt.config.prefix] the prefix
* @since 3.3.0
* @return {Array} an array containing:
* * the detach category, if supplied,
* * the prefixed event type,
* * whether or not this is an after listener,
* * the supplied event type
*/
parseType: function(type, pre) {
return _parseType(type, pre || this._yuievt.config.prefix);
},
/**
* Subscribe to a custom event hosted by this object
* @method on
* @param type {string} The type of the event
* @param fn {Function} The callback
* @param context {object} optional execution context.
* @param arg* {mixed} 0..n additional arguments to supply to the subscriber
* @return the event target or a detach handle per 'chain' config
*/
on: function(type, fn, context) {
var parts = _parseType(type, this._yuievt.config.prefix), f, c, args, ret, ce,
detachcategory, handle, store = Y.Env.evt.handles, after, adapt, shorttype,
Node = Y.Node, n, domevent, isArr;
// full name, args, detachcategory, after
this._monitor('attach', parts[1], {
args: arguments,
category: parts[0],
after: parts[2]
});
if (L.isObject(type)) {
if (L.isFunction(type)) {
return Y.Do.before.apply(Y.Do, arguments);
}
f = fn;
c = context;
args = YArray(arguments, 0, true);
ret = [];
if (L.isArray(type)) {
isArr = true;
}
after = type._after;
delete type._after;
Y.each(type, function(v, k) {
if (L.isObject(v)) {
f = v.fn || ((L.isFunction(v)) ? v : f);
c = v.context || c;
}
var nv = (after) ? AFTER_PREFIX : '';
args[0] = nv + ((isArr) ? v : k);
args[1] = f;
args[2] = c;
ret.push(this.on.apply(this, args));
}, this);
return (this._yuievt.chain) ? this : new Y.EventHandle(ret);
}
detachcategory = parts[0];
after = parts[2];
shorttype = parts[3];
// extra redirection so we catch adaptor events too. take a look at this.
if (Node && Y.instanceOf(this, Node) && (shorttype in Node.DOM_EVENTS)) {
args = YArray(arguments, 0, true);
args.splice(2, 0, Node.getDOMNode(this));
return Y.on.apply(Y, args);
}
type = parts[1];
if (Y.instanceOf(this, YUI)) {
adapt = Y.Env.evt.plugins[type];
args = YArray(arguments, 0, true);
args[0] = shorttype;
if (Node) {
n = args[2];
if (Y.instanceOf(n, Y.NodeList)) {
n = Y.NodeList.getDOMNodes(n);
} else if (Y.instanceOf(n, Node)) {
n = Node.getDOMNode(n);
}
domevent = (shorttype in Node.DOM_EVENTS);
// Captures both DOM events and event plugins.
if (domevent) {
args[2] = n;
}
}
// check for the existance of an event adaptor
if (adapt) {
handle = adapt.on.apply(Y, args);
} else if ((!type) || domevent) {
handle = Y.Event._attach(args);
}
}
if (!handle) {
ce = this._yuievt.events[type] || this.publish(type);
handle = ce._on(fn, context, (arguments.length > 3) ? YArray(arguments, 3, true) : null, (after) ? 'after' : true);
}
if (detachcategory) {
store[detachcategory] = store[detachcategory] || {};
store[detachcategory][type] = store[detachcategory][type] || [];
store[detachcategory][type].push(handle);
}
return (this._yuievt.chain) ? this : handle;
},
/**
* subscribe to an event
* @method subscribe
* @deprecated use on
*/
subscribe: function() {
return this.on.apply(this, arguments);
},
/**
* Detach one or more listeners the from the specified event
* @method detach
* @param type {string|Object} Either the handle to the subscriber or the
* type of event. If the type
* is not specified, it will attempt to remove
* the listener from all hosted events.
* @param fn {Function} The subscribed function to unsubscribe, if not
* supplied, all subscribers will be removed.
* @param context {Object} The custom object passed to subscribe. This is
* optional, but if supplied will be used to
* disambiguate multiple listeners that are the same
* (e.g., you subscribe many object using a function
* that lives on the prototype)
* @return {EventTarget} the host
*/
detach: function(type, fn, context) {
var evts = this._yuievt.events, i,
Node = Y.Node, isNode = Node && (Y.instanceOf(this, Node));
// detachAll disabled on the Y instance.
if (!type && (this !== Y)) {
for (i in evts) {
if (evts.hasOwnProperty(i)) {
evts[i].detach(fn, context);
}
}
if (isNode) {
Y.Event.purgeElement(Node.getDOMNode(this));
}
return this;
}
var parts = _parseType(type, this._yuievt.config.prefix),
detachcategory = L.isArray(parts) ? parts[0] : null,
shorttype = (parts) ? parts[3] : null,
adapt, store = Y.Env.evt.handles, detachhost, cat, args,
ce,
keyDetacher = function(lcat, ltype, host) {
var handles = lcat[ltype], ce, i;
if (handles) {
for (i = handles.length - 1; i >= 0; --i) {
ce = handles[i].evt;
if (ce.host === host || ce.el === host) {
handles[i].detach();
}
}
}
};
if (detachcategory) {
cat = store[detachcategory];
type = parts[1];
detachhost = (isNode) ? Y.Node.getDOMNode(this) : this;
if (cat) {
if (type) {
keyDetacher(cat, type, detachhost);
} else {
for (i in cat) {
if (cat.hasOwnProperty(i)) {
keyDetacher(cat, i, detachhost);
}
}
}
return this;
}
// If this is an event handle, use it to detach
} else if (L.isObject(type) && type.detach) {
type.detach();
return this;
// extra redirection so we catch adaptor events too. take a look at this.
} else if (isNode && ((!shorttype) || (shorttype in Node.DOM_EVENTS))) {
args = YArray(arguments, 0, true);
args[2] = Node.getDOMNode(this);
Y.detach.apply(Y, args);
return this;
}
adapt = Y.Env.evt.plugins[shorttype];
// The YUI instance handles DOM events and adaptors
if (Y.instanceOf(this, YUI)) {
args = YArray(arguments, 0, true);
// use the adaptor specific detach code if
if (adapt && adapt.detach) {
adapt.detach.apply(Y, args);
return this;
// DOM event fork
} else if (!type || (!adapt && Node && (type in Node.DOM_EVENTS))) {
args[0] = type;
Y.Event.detach.apply(Y.Event, args);
return this;
}
}
// ce = evts[type];
ce = evts[parts[1]];
if (ce) {
ce.detach(fn, context);
}
return this;
},
/**
* detach a listener
* @method unsubscribe
* @deprecated use detach
*/
unsubscribe: function() {
return this.detach.apply(this, arguments);
},
/**
* Removes all listeners from the specified event. If the event type
* is not specified, all listeners from all hosted custom events will
* be removed.
* @method detachAll
* @param type {string} The type, or name of the event
*/
detachAll: function(type) {
return this.detach(type);
},
/**
* Removes all listeners from the specified event. If the event type
* is not specified, all listeners from all hosted custom events will
* be removed.
* @method unsubscribeAll
* @param type {string} The type, or name of the event
* @deprecated use detachAll
*/
unsubscribeAll: function() {
return this.detachAll.apply(this, arguments);
},
/**
* Creates a new custom event of the specified type. If a custom event
* by that name already exists, it will not be re-created. In either
* case the custom event is returned.
*
* @method publish
*
* @param type {string} the type, or name of the event
* @param opts {object} optional config params. Valid properties are:
*
* YUI
's on
method is a unified interface for subscribing to
* most events exposed by YUI. This includes custom events, DOM events, and
* function events. detach
is also provided to remove listeners
* serviced by this function.
*
* The signature that on
accepts varies depending on the type
* of event being consumed. Refer to the specific methods that will
* service a specific request for additional information about subscribing
* to that type of event.
*
* EventTarget
's on
method.
* Y.on('drag:drophit', function() { // start work });
* Event
's
* attach
method.
* Y.on('click', function(e) { // something was clicked }, '#someelement');
* Event.Do
's
* before
method.
* Y.on(function(arg1, arg2, etc) { // obj.methodname was executed }, obj 'methodname');
* on
corresponds to the moment before any default behavior of
* the event. after
works the same way, but these listeners
* execute after the event's default behavior. before
is an
* alias for on
.
*
* @method on
* @param type event type (this parameter does not apply for function events)
* @param fn the callback
* @param context optionally change the value of 'this' in the callback
* @param args* 0..n additional arguments to pass to the callback.
* @return the event target or a detach handle per 'chain' config
* @for YUI
*/
/**
* Listen for an event one time. Equivalent to on
, except that
* the listener is immediately detached when executed.
* @see on
* @method once
* @param type event type (this parameter does not apply for function events)
* @param fn the callback
* @param context optionally change the value of 'this' in the callback
* @param args* 0..n additional arguments to pass to the callback.
* @return the event target or a detach handle per 'chain' config
* @for YUI
*/
/**
* after() is a unified interface for subscribing to
* most events exposed by YUI. This includes custom events,
* DOM events, and AOP events. This works the same way as
* the on() function, only it operates after any default
* behavior for the event has executed. @see on
for more
* information.
* @method after
* @param type event type (this parameter does not apply for function events)
* @param fn the callback
* @param context optionally change the value of 'this' in the callback
* @param args* 0..n additional arguments to pass to the callback.
* @return the event target or a detach handle per 'chain' config
* @for YUI
*/
}, '3.3.0' ,{requires:['oop']});
YUI.add('event-custom-complex', function(Y) {
/**
* Adds event facades, preventable default behavior, and bubbling.
* events.
* @module event-custom
* @submodule event-custom-complex
*/
var FACADE,
FACADE_KEYS,
EMPTY = {},
CEProto = Y.CustomEvent.prototype,
ETProto = Y.EventTarget.prototype;
/**
* Wraps and protects a custom event for use when emitFacade is set to true.
* Requires the event-custom-complex module
* @class EventFacade
* @param e {Event} the custom event
* @param currentTarget {HTMLElement} the element the listener was attached to
*/
Y.EventFacade = function(e, currentTarget) {
e = e || EMPTY;
this._event = e;
/**
* The arguments passed to fire
* @property details
* @type Array
*/
this.details = e.details;
/**
* The event type, this can be overridden by the fire() payload
* @property type
* @type string
*/
this.type = e.type;
/**
* The real event type
* @property type
* @type string
*/
this._type = e.type;
//////////////////////////////////////////////////////
/**
* Node reference for the targeted eventtarget
* @propery target
* @type Node
*/
this.target = e.target;
/**
* Node reference for the element that the listener was attached to.
* @propery currentTarget
* @type Node
*/
this.currentTarget = currentTarget;
/**
* Node reference to the relatedTarget
* @propery relatedTarget
* @type Node
*/
this.relatedTarget = e.relatedTarget;
};
Y.extend(Y.EventFacade, Object, {
/**
* Stops the propagation to the next bubble target
* @method stopPropagation
*/
stopPropagation: function() {
this._event.stopPropagation();
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
*/
stopImmediatePropagation: function() {
this._event.stopImmediatePropagation();
this.stopped = 2;
},
/**
* Prevents the event's default behavior
* @method preventDefault
*/
preventDefault: function() {
this._event.preventDefault();
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
*/
halt: function(immediate) {
this._event.halt(immediate);
this.prevented = 1;
this.stopped = (immediate) ? 2 : 1;
}
});
CEProto.fireComplex = function(args) {
var es, ef, q, queue, ce, ret, events, subs, postponed,
self = this, host = self.host || self, next, oldbubble;
if (self.stack) {
// queue this event if the current item in the queue bubbles
if (self.queuable && self.type != self.stack.next.type) {
self.log('queue ' + self.type);
self.stack.queue.push([self, args]);
return true;
}
}
es = self.stack || {
// id of the first event in the stack
id: self.id,
next: self,
silent: self.silent,
stopped: 0,
prevented: 0,
bubbling: null,
type: self.type,
// defaultFnQueue: new Y.Queue(),
afterQueue: new Y.Queue(),
defaultTargetOnly: self.defaultTargetOnly,
queue: []
};
subs = self.getSubs();
self.stopped = (self.type !== es.type) ? 0 : es.stopped;
self.prevented = (self.type !== es.type) ? 0 : es.prevented;
self.target = self.target || host;
events = new Y.EventTarget({
fireOnce: true,
context: host
});
self.events = events;
if (self.preventedFn) {
events.on('prevented', self.preventedFn);
}
if (self.stoppedFn) {
events.on('stopped', self.stoppedFn);
}
self.currentTarget = host;
self.details = args.slice(); // original arguments in the details
// self.log("Firing " + self + ", " + "args: " + args);
self.log("Firing " + self.type);
self._facade = null; // kill facade to eliminate stale properties
ef = self._getFacade(args);
if (Y.Lang.isObject(args[0])) {
args[0] = ef;
} else {
args.unshift(ef);
}
// if (subCount) {
if (subs[0]) {
// self._procSubs(Y.merge(self.subscribers), args, ef);
self._procSubs(subs[0], args, ef);
}
// bubble if this is hosted in an event target and propagation has not been stopped
if (self.bubbles && host.bubble && !self.stopped) {
oldbubble = es.bubbling;
// self.bubbling = true;
es.bubbling = self.type;
// if (host !== ef.target || es.type != self.type) {
if (es.type != self.type) {
es.stopped = 0;
es.prevented = 0;
}
ret = host.bubble(self, args, null, es);
self.stopped = Math.max(self.stopped, es.stopped);
self.prevented = Math.max(self.prevented, es.prevented);
// self.bubbling = false;
es.bubbling = oldbubble;
}
if (self.defaultFn &&
!self.prevented &&
((!self.defaultTargetOnly && !es.defaultTargetOnly) || host === ef.target)) {
self.defaultFn.apply(host, args);
}
// broadcast listeners are fired as discreet events on the
// YUI instance and potentially the YUI global.
self._broadcast(args);
// Queue the after
if (subs[1] && !self.prevented && self.stopped < 2) {
if (es.id === self.id || self.type != host._yuievt.bubbling) {
self._procSubs(subs[1], args, ef);
while ((next = es.afterQueue.last())) {
next();
}
} else {
postponed = subs[1];
if (es.execDefaultCnt) {
postponed = Y.merge(postponed);
Y.each(postponed, function(s) {
s.postponed = true;
});
}
es.afterQueue.add(function() {
self._procSubs(postponed, args, ef);
});
}
}
self.target = null;
if (es.id === self.id) {
queue = es.queue;
while (queue.length) {
q = queue.pop();
ce = q[0];
// set up stack to allow the next item to be processed
es.next = ce;
ce.fire.apply(ce, q[1]);
}
self.stack = null;
}
ret = !(self.stopped);
if (self.type != host._yuievt.bubbling) {
es.stopped = 0;
es.prevented = 0;
self.stopped = 0;
self.prevented = 0;
}
return ret;
};
CEProto._getFacade = function() {
var ef = this._facade, o, o2,
args = this.details;
if (!ef) {
ef = new Y.EventFacade(this, this.currentTarget);
}
// if the first argument is an object literal, apply the
// properties to the event facade
o = args && args[0];
if (Y.Lang.isObject(o, true)) {
o2 = {};
// protect the event facade properties
Y.mix(o2, ef, true, FACADE_KEYS);
// mix the data
Y.mix(ef, o, true);
// restore ef
Y.mix(ef, o2, true, FACADE_KEYS);
// Allow the event type to be faked
// http://yuilibrary.com/projects/yui3/ticket/2528376
ef.type = o.type || ef.type;
}
// update the details field with the arguments
// ef.type = this.type;
ef.details = this.details;
// use the original target when the event bubbled to this target
ef.target = this.originalTarget || this.target;
ef.currentTarget = this.currentTarget;
ef.stopped = 0;
ef.prevented = 0;
this._facade = ef;
return this._facade;
};
/**
* Stop propagation to bubble targets
* @for CustomEvent
* @method stopPropagation
*/
CEProto.stopPropagation = function() {
this.stopped = 1;
if (this.stack) {
this.stack.stopped = 1;
}
this.events.fire('stopped', this);
};
/**
* Stops propagation to bubble targets, and prevents any remaining
* subscribers on the current target from executing.
* @method stopImmediatePropagation
*/
CEProto.stopImmediatePropagation = function() {
this.stopped = 2;
if (this.stack) {
this.stack.stopped = 2;
}
this.events.fire('stopped', this);
};
/**
* Prevents the execution of this event's defaultFn
* @method preventDefault
*/
CEProto.preventDefault = function() {
if (this.preventable) {
this.prevented = 1;
if (this.stack) {
this.stack.prevented = 1;
}
this.events.fire('prevented', this);
}
};
/**
* 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
*/
CEProto.halt = function(immediate) {
if (immediate) {
this.stopImmediatePropagation();
} else {
this.stopPropagation();
}
this.preventDefault();
};
/**
* Registers another EventTarget as a bubble target. Bubble order
* is determined by the order registered. Multiple targets can
* be specified.
*
* Events can only bubble if emitFacade is true.
*
* Included in the event-custom-complex submodule.
*
* @method addTarget
* @param o {EventTarget} the target to add
* @for EventTarget
*/
ETProto.addTarget = function(o) {
this._yuievt.targets[Y.stamp(o)] = o;
this._yuievt.hasTargets = true;
};
/**
* Returns an array of bubble targets for this object.
* @method getTargets
* @return EventTarget[]
*/
ETProto.getTargets = function() {
return Y.Object.values(this._yuievt.targets);
};
/**
* Removes a bubble target
* @method removeTarget
* @param o {EventTarget} the target to remove
* @for EventTarget
*/
ETProto.removeTarget = function(o) {
delete this._yuievt.targets[Y.stamp(o)];
};
/**
* Propagate an event. Requires the event-custom-complex module.
* @method bubble
* @param evt {CustomEvent} the custom event to propagate
* @return {boolean} the aggregated return value from Event.Custom.fire
* @for EventTarget
*/
ETProto.bubble = function(evt, args, target, es) {
var targs = this._yuievt.targets, ret = true,
t, type = evt && evt.type, ce, i, bc, ce2,
originalTarget = target || (evt && evt.target) || this,
oldbubble;
if (!evt || ((!evt.stopped) && targs)) {
for (i in targs) {
if (targs.hasOwnProperty(i)) {
t = targs[i];
ce = t.getEvent(type, true);
ce2 = t.getSibling(type, ce);
if (ce2 && !ce) {
ce = t.publish(type);
}
oldbubble = t._yuievt.bubbling;
t._yuievt.bubbling = type;
// if this event was not published on the bubble target,
// continue propagating the event.
if (!ce) {
if (t._yuievt.hasTargets) {
t.bubble(evt, args, originalTarget, es);
}
} else {
ce.sibling = ce2;
// set the original target to that the target payload on the
// facade is correct.
ce.target = originalTarget;
ce.originalTarget = originalTarget;
ce.currentTarget = t;
bc = ce.broadcast;
ce.broadcast = false;
// default publish may not have emitFacade true -- that
// shouldn't be what the implementer meant to do
ce.emitFacade = true;
ce.stack = es;
ret = ret && ce.fire.apply(ce, args || evt.details || []);
ce.broadcast = bc;
ce.originalTarget = null;
// stopPropagation() was called
if (ce.stopped) {
break;
}
}
t._yuievt.bubbling = oldbubble;
}
}
}
return ret;
};
FACADE = new Y.EventFacade();
FACADE_KEYS = Y.Object.keys(FACADE);
}, '3.3.0' ,{requires:['event-custom-base']});
YUI.add('event-custom', function(Y){}, '3.3.0' ,{use:['event-custom-base', 'event-custom-complex']});