define("dojo/aspect", [], function(){ // module: // dojo/aspect "use strict"; var undefined, nextId = 0; function advise(dispatcher, type, advice, receiveArguments){ var previous = dispatcher[type]; var around = type == "around"; var signal; if(around){ var advised = advice(function(){ return previous.advice(this, arguments); }); signal = { remove: function(){ signal.cancelled = true; }, advice: function(target, args){ return signal.cancelled ? previous.advice(target, args) : // cancelled, skip to next one advised.apply(target, args); // called the advised function } }; }else{ // create the remove handler signal = { remove: function(){ var previous = signal.previous; var next = signal.next; if(!next && !previous){ delete dispatcher[type]; }else{ if(previous){ previous.next = next; }else{ dispatcher[type] = next; } if(next){ next.previous = previous; } } }, id: nextId++, advice: advice, receiveArguments: receiveArguments }; } if(previous && !around){ if(type == "after"){ // add the listener to the end of the list // note that we had to change this loop a little bit to workaround a bizarre IE10 JIT bug while(previous.next && (previous = previous.next)){} previous.next = signal; signal.previous = previous; }else if(type == "before"){ // add to beginning dispatcher[type] = signal; signal.next = previous; previous.previous = signal; } }else{ // around or first one just replaces dispatcher[type] = signal; } return signal; } function aspect(type){ return function(target, methodName, advice, receiveArguments){ var existing = target[methodName], dispatcher; if(!existing || existing.target != target){ // no dispatcher in place target[methodName] = dispatcher = function(){ var executionId = nextId; // before advice var args = arguments; var before = dispatcher.before; while(before){ args = before.advice.apply(this, args) || args; before = before.next; } // around advice if(dispatcher.around){ var results = dispatcher.around.advice(this, args); } // after advice var after = dispatcher.after; while(after && after.id < executionId){ if(after.receiveArguments){ var newResults = after.advice.apply(this, args); // change the return value only if a new value was returned results = newResults === undefined ? results : newResults; }else{ results = after.advice.call(this, results, args); } after = after.next; } return results; }; if(existing){ dispatcher.around = {advice: function(target, args){ return existing.apply(target, args); }}; } dispatcher.target = target; } var results = advise((dispatcher || existing), type, advice, receiveArguments); advice = null; return results; }; } // TODOC: after/before/around return object var after = aspect("after"); /*===== after = function(target, methodName, advice, receiveArguments){ // summary: // The "after" export of the aspect module is a function that can be used to attach // "after" advice to a method. This function will be executed after the original method // is executed. By default the function will be called with a single argument, the return // value of the original method, or the the return value of the last executed advice (if a previous one exists). // The fourth (optional) argument can be set to true to so the function receives the original // arguments (from when the original method was called) rather than the return value. // If there are multiple "after" advisors, they are executed in the order they were registered. // target: Object // This is the target object // methodName: String // This is the name of the method to attach to. // advice: Function // This is function to be called after the original method // receiveArguments: Boolean? // If this is set to true, the advice function receives the original arguments (from when the original mehtod // was called) rather than the return value of the original/previous method. // returns: // A signal object that can be used to cancel the advice. If remove() is called on this signal object, it will // stop the advice function from being executed. }; =====*/ var before = aspect("before"); /*===== before = function(target, methodName, advice){ // summary: // The "before" export of the aspect module is a function that can be used to attach // "before" advice to a method. This function will be executed before the original method // is executed. This function will be called with the arguments used to call the method. // This function may optionally return an array as the new arguments to use to call // the original method (or the previous, next-to-execute before advice, if one exists). // If the before method doesn't return anything (returns undefined) the original arguments // will be preserved. // If there are multiple "before" advisors, they are executed in the reverse order they were registered. // target: Object // This is the target object // methodName: String // This is the name of the method to attach to. // advice: Function // This is function to be called before the original method }; =====*/ var around = aspect("around"); /*===== around = function(target, methodName, advice){ // summary: // The "around" export of the aspect module is a function that can be used to attach // "around" advice to a method. The advisor function is immediately executed when // the around() is called, is passed a single argument that is a function that can be // called to continue execution of the original method (or the next around advisor). // The advisor function should return a function, and this function will be called whenever // the method is called. It will be called with the arguments used to call the method. // Whatever this function returns will be returned as the result of the method call (unless after advise changes it). // example: // If there are multiple "around" advisors, the most recent one is executed first, // which can then delegate to the next one and so on. For example: // | around(obj, "foo", function(originalFoo){ // | return function(){ // | var start = new Date().getTime(); // | var results = originalFoo.apply(this, arguments); // call the original // | var end = new Date().getTime(); // | console.log("foo execution took " + (end - start) + " ms"); // | return results; // | }; // | }); // target: Object // This is the target object // methodName: String // This is the name of the method to attach to. // advice: Function // This is function to be called around the original method }; =====*/ return { // summary: // provides aspect oriented programming functionality, allowing for // one to add before, around, or after advice on existing methods. // example: // | define(["dojo/aspect"], function(aspect){ // | var signal = aspect.after(targetObject, "methodName", function(someArgument){ // | this will be called when targetObject.methodName() is called, after the original function is called // | }); // // example: // The returned signal object can be used to cancel the advice. // | signal.remove(); // this will stop the advice from being executed anymore // | aspect.before(targetObject, "methodName", function(someArgument){ // | // this will be called when targetObject.methodName() is called, before the original function is called // | }); before: before, around: around, after: after }; });