source: Dev/branches/rest-dojo-ui/client/dojo/aspect.js @ 274

Last change on this file since 274 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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