source: Dev/trunk/src/client/dojo/aspect.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 7.9 KB
RevLine 
[483]1define([], function(){
2
3        // module:
4        //              dojo/aspect
5
6        "use strict";
7        var undefined, nextId = 0;
8        function advise(dispatcher, type, advice, receiveArguments){
9                var previous = dispatcher[type];
10                var around = type == "around";
11                var signal;
12                if(around){
13                        var advised = advice(function(){
14                                return previous.advice(this, arguments);
15                        });
16                        signal = {
17                                remove: function(){
18                                        if(advised){
19                                                advised = dispatcher = advice = null;
20                                        }
21                                },
22                                advice: function(target, args){
23                                        return advised ?
24                                                advised.apply(target, args) :  // called the advised function
25                                                previous.advice(target, args); // cancelled, skip to next one
26                                }
27                        };
28                }else{
29                        // create the remove handler
30                        signal = {
31                                remove: function(){
32                                        if(signal.advice){
33                                                var previous = signal.previous;
34                                                var next = signal.next;
35                                                if(!next && !previous){
36                                                        delete dispatcher[type];
37                                                }else{
38                                                        if(previous){
39                                                                previous.next = next;
40                                                        }else{
41                                                                dispatcher[type] = next;
42                                                        }
43                                                        if(next){
44                                                                next.previous = previous;
45                                                        }
46                                                }
47
48                                                // remove the advice to signal that this signal has been removed
49                                                dispatcher = advice = signal.advice = null;
50                                        }
51                                },
52                                id: nextId++,
53                                advice: advice,
54                                receiveArguments: receiveArguments
55                        };
56                }
57                if(previous && !around){
58                        if(type == "after"){
59                                // add the listener to the end of the list
60                                // note that we had to change this loop a little bit to workaround a bizarre IE10 JIT bug
61                                while(previous.next && (previous = previous.next)){}
62                                previous.next = signal;
63                                signal.previous = previous;
64                        }else if(type == "before"){
65                                // add to beginning
66                                dispatcher[type] = signal;
67                                signal.next = previous;
68                                previous.previous = signal;
69                        }
70                }else{
71                        // around or first one just replaces
72                        dispatcher[type] = signal;
73                }
74                return signal;
75        }
76        function aspect(type){
77                return function(target, methodName, advice, receiveArguments){
78                        var existing = target[methodName], dispatcher;
79                        if(!existing || existing.target != target){
80                                // no dispatcher in place
81                                target[methodName] = dispatcher = function(){
82                                        var executionId = nextId;
83                                        // before advice
84                                        var args = arguments;
85                                        var before = dispatcher.before;
86                                        while(before){
87                                                args = before.advice.apply(this, args) || args;
88                                                before = before.next;
89                                        }
90                                        // around advice
91                                        if(dispatcher.around){
92                                                var results = dispatcher.around.advice(this, args);
93                                        }
94                                        // after advice
95                                        var after = dispatcher.after;
96                                        while(after && after.id < executionId){
97                                                if(after.receiveArguments){
98                                                        var newResults = after.advice.apply(this, args);
99                                                        // change the return value only if a new value was returned
100                                                        results = newResults === undefined ? results : newResults;
101                                                }else{
102                                                        results = after.advice.call(this, results, args);
103                                                }
104                                                after = after.next;
105                                        }
106                                        return results;
107                                };
108                                if(existing){
109                                        dispatcher.around = {advice: function(target, args){
110                                                return existing.apply(target, args);
111                                        }};
112                                }
113                                dispatcher.target = target;
114                        }
115                        var results = advise((dispatcher || existing), type, advice, receiveArguments);
116                        advice = null;
117                        return results;
118                };
119        }
120
121        // TODOC: after/before/around return object
122
123        var after = aspect("after");
124        /*=====
125        after = function(target, methodName, advice, receiveArguments){
126                // summary:
127                //              The "after" export of the aspect module is a function that can be used to attach
128                //              "after" advice to a method. This function will be executed after the original method
129                //              is executed. By default the function will be called with a single argument, the return
130                //              value of the original method, or the the return value of the last executed advice (if a previous one exists).
131                //              The fourth (optional) argument can be set to true to so the function receives the original
132                //              arguments (from when the original method was called) rather than the return value.
133                //              If there are multiple "after" advisors, they are executed in the order they were registered.
134                // target: Object
135                //              This is the target object
136                // methodName: String
137                //              This is the name of the method to attach to.
138                // advice: Function
139                //              This is function to be called after the original method
140                // receiveArguments: Boolean?
141                //              If this is set to true, the advice function receives the original arguments (from when the original mehtod
142                //              was called) rather than the return value of the original/previous method.
143                // returns:
144                //              A signal object that can be used to cancel the advice. If remove() is called on this signal object, it will
145                //              stop the advice function from being executed.
146        };
147        =====*/
148
149        var before = aspect("before");
150        /*=====
151        before = function(target, methodName, advice){
152                // summary:
153                //              The "before" export of the aspect module is a function that can be used to attach
154                //              "before" advice to a method. This function will be executed before the original method
155                //              is executed. This function will be called with the arguments used to call the method.
156                //              This function may optionally return an array as the new arguments to use to call
157                //              the original method (or the previous, next-to-execute before advice, if one exists).
158                //              If the before method doesn't return anything (returns undefined) the original arguments
159                //              will be preserved.
160                //              If there are multiple "before" advisors, they are executed in the reverse order they were registered.
161                // target: Object
162                //              This is the target object
163                // methodName: String
164                //              This is the name of the method to attach to.
165                // advice: Function
166                //              This is function to be called before the original method
167        };
168        =====*/
169
170        var around = aspect("around");
171        /*=====
172         around = function(target, methodName, advice){
173                // summary:
174                //              The "around" export of the aspect module is a function that can be used to attach
175                //              "around" advice to a method. The advisor function is immediately executed when
176                //              the around() is called, is passed a single argument that is a function that can be
177                //              called to continue execution of the original method (or the next around advisor).
178                //              The advisor function should return a function, and this function will be called whenever
179                //              the method is called. It will be called with the arguments used to call the method.
180                //              Whatever this function returns will be returned as the result of the method call (unless after advise changes it).
181                // example:
182                //              If there are multiple "around" advisors, the most recent one is executed first,
183                //              which can then delegate to the next one and so on. For example:
184                //              |       around(obj, "foo", function(originalFoo){
185                //              |               return function(){
186                //              |                       var start = new Date().getTime();
187                //              |                       var results = originalFoo.apply(this, arguments); // call the original
188                //              |                       var end = new Date().getTime();
189                //              |                       console.log("foo execution took " + (end - start) + " ms");
190                //              |                       return results;
191                //              |               };
192                //              |       });
193                // target: Object
194                //              This is the target object
195                // methodName: String
196                //              This is the name of the method to attach to.
197                // advice: Function
198                //              This is function to be called around the original method
199        };
200        =====*/
201
202        return {
203                // summary:
204                //              provides aspect oriented programming functionality, allowing for
205                //              one to add before, around, or after advice on existing methods.
206                // example:
207                //      |       define(["dojo/aspect"], function(aspect){
208                //      |               var signal = aspect.after(targetObject, "methodName", function(someArgument){
209                //      |                       this will be called when targetObject.methodName() is called, after the original function is called
210                //      |               });
211                //
212                // example:
213                //      The returned signal object can be used to cancel the advice.
214                //      |       signal.remove(); // this will stop the advice from being executed anymore
215                //      |       aspect.before(targetObject, "methodName", function(someArgument){
216                //      |               // this will be called when targetObject.methodName() is called, before the original function is called
217                //      |        });
218
219                before: before,
220                around: around,
221                after: after
222        };
223});
Note: See TracBrowser for help on using the repository browser.