/** * 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; //////////////////////////////////////////////////////////////////////////