File: event-custom/js/event-do.js
/**
* 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
* @deprecated Since 3.6.0. The `_yuiaop` property on the AOP'd object
* replaces the role of this property, but is considered to be private, and
* is only mentioned to provide a migration path.
*
* If you have a use case which warrants migration to the _yuiaop property,
* please file a ticket to let us know what it's used for and we can see if
* we need to expose hooks for that functionality more formally.
*/
objs: null,
/**
* <p>Execute the supplied method before the specified function. Wrapping
* function may optionally return an instance of the following classes to
* further alter runtime behavior:</p>
* <dl>
* <dt></code>Y.Do.Halt(message, returnValue)</code></dt>
* <dd>Immediatly stop execution and return
* <code>returnValue</code>. No other wrapping functions will be
* executed.</dd>
* <dt></code>Y.Do.AlterArgs(message, newArgArray)</code></dt>
* <dd>Replace the arguments that the original function will be
* called with.</dd>
* <dt></code>Y.Do.Prevent(message)</code></dt>
* <dd>Don't execute the wrapped function. Other before phase
* wrappers will be executed.</dd>
* </dl>
*
* @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 {EventHandle} handle for the subscription
* @static
*/
before: function(fn, obj, sFn, c) {
// Y.log('Do before: ' + sFn, 'info', 'event');
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);
},
/**
* <p>Execute the supplied method after the specified function. Wrapping
* function may optionally return an instance of the following classes to
* further alter runtime behavior:</p>
* <dl>
* <dt></code>Y.Do.Halt(message, returnValue)</code></dt>
* <dd>Immediatly stop execution and return
* <code>returnValue</code>. No other wrapping functions will be
* executed.</dd>
* <dt></code>Y.Do.AlterReturn(message, returnValue)</code></dt>
* <dd>Return <code>returnValue</code> instead of the wrapped
* method's original return value. This can be further altered by
* other after phase wrappers.</dd>
* </dl>
*
* <p>The static properties <code>Y.Do.originalRetVal</code> and
* <code>Y.Do.currentRetVal</code> will be populated for reference.</p>
*
* @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 {EventHandle} 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 before or after the specified function.
* Used by <code>before</code> and <code>after</code>.
*
* @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 {EventHandle} handle for the subscription
* @private
* @static
*/
_inject: function(when, fn, obj, sFn) {
// object id
var id = Y.stamp(obj), o, sid;
if (!obj._yuiaop) {
// create a map entry for the obj if it doesn't exist, to hold overridden methods
obj._yuiaop = {};
}
o = obj._yuiaop;
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 {EventHandle} the subscription handle
* @static
*/
detach: function(handle) {
if (handle.detach) {
handle.detach();
}
}
};
Y.Do = DO;
//////////////////////////////////////////////////////////////////////////
/**
* Contains the return value from the wrapped method, accessible
* by 'after' event listeners.
*
* @property originalRetVal
* @static
* @since 3.2.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 currentRetVal
* @static
* @since 3.2.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) {
// Y.log('Y.Do._delete: ' + sid, 'info', 'Event');
delete this.before[sid];
delete this.after[sid];
};
/**
* <p>Execute the wrapped method. All arguments are passed into the wrapping
* functions. If any of the before wrappers return an instance of
* <code>Y.Do.Halt</code> or <code>Y.Do.Prevent</code>, neither the wrapped
* function nor any after phase subscribers will be executed.</p>
*
* <p>The return value will be the return value of the wrapped function or one
* provided by a wrapper function via an instance of <code>Y.Do.Halt</code> or
* <code>Y.Do.AlterReturn</code>.
*
* @method exec
* @param arg* {any} Arguments are passed to the wrapping and wrapped functions
* @return {any} Return value of wrapped function unless overwritten (see above)
*/
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. Useful for Do.before subscribers. An
* example would be a service that scrubs out illegal characters prior to
* executing the core business logic.
* @class Do.AlterArgs
* @constructor
* @param msg {String} (optional) Explanation of the altered return value
* @param newArgs {Array} Call parameters to be used for the original method
* instead of the arguments originally passed in.
*/
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. Useful for Do.after subscribers.
* @class Do.AlterReturn
* @constructor
* @param msg {String} (optional) Explanation of the altered return value
* @param newRetVal {any} Return value passed to code that invoked the wrapped
* function.
*/
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. Useful for Do.before subscribers.
* @class Do.Halt
* @constructor
* @param msg {String} (optional) Explanation of why the termination was done
* @param retVal {any} Return value passed to code that invoked the wrapped
* function.
*/
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. Useful
* for Do.before subscribers.
* @class Do.Prevent
* @constructor
* @param msg {String} (optional) Explanation of why the termination was done
*/
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
* @constructor
* @param msg {String} (optional) Explanation of the altered return value
* @param retVal {any} Return value passed to code that invoked the wrapped
* function.
* @deprecated use Y.Do.Halt or Y.Do.Prevent
*/
DO.Error = DO.Halt;
//////////////////////////////////////////////////////////////////////////