]>
Commit | Line | Data |
---|---|---|
1354d172 AD |
1 | define("dojo/aspect", [], 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 | var nextId = 0; | |
99 | function advise(dispatcher, type, advice, receiveArguments){ | |
100 | var previous = dispatcher[type]; | |
101 | var around = type == "around"; | |
102 | var signal; | |
103 | if(around){ | |
104 | var advised = advice(function(){ | |
105 | return previous.advice(this, arguments); | |
106 | }); | |
107 | signal = { | |
108 | remove: function(){ | |
109 | signal.cancelled = true; | |
110 | }, | |
111 | advice: function(target, args){ | |
112 | return signal.cancelled ? | |
113 | previous.advice(target, args) : // cancelled, skip to next one | |
114 | advised.apply(target, args); // called the advised function | |
115 | } | |
116 | }; | |
117 | }else{ | |
118 | // create the remove handler | |
119 | signal = { | |
120 | remove: function(){ | |
121 | var previous = signal.previous; | |
122 | var next = signal.next; | |
123 | if(!next && !previous){ | |
124 | delete dispatcher[type]; | |
125 | }else{ | |
126 | if(previous){ | |
127 | previous.next = next; | |
128 | }else{ | |
129 | dispatcher[type] = next; | |
130 | } | |
131 | if(next){ | |
132 | next.previous = previous; | |
133 | } | |
134 | } | |
135 | }, | |
136 | id: nextId++, | |
137 | advice: advice, | |
138 | receiveArguments: receiveArguments | |
139 | }; | |
140 | } | |
141 | if(previous && !around){ | |
142 | if(type == "after"){ | |
143 | // add the listener to the end of the list | |
144 | var next = previous; | |
145 | while(next){ | |
146 | previous = next; | |
147 | next = next.next; | |
148 | } | |
149 | previous.next = signal; | |
150 | signal.previous = previous; | |
151 | }else if(type == "before"){ | |
152 | // add to beginning | |
153 | dispatcher[type] = signal; | |
154 | signal.next = previous; | |
155 | previous.previous = signal; | |
156 | } | |
157 | }else{ | |
158 | // around or first one just replaces | |
159 | dispatcher[type] = signal; | |
160 | } | |
161 | return signal; | |
162 | } | |
163 | function aspect(type){ | |
164 | return function(target, methodName, advice, receiveArguments){ | |
165 | var existing = target[methodName], dispatcher; | |
166 | if(!existing || existing.target != target){ | |
167 | // no dispatcher in place | |
168 | target[methodName] = dispatcher = function(){ | |
169 | var executionId = nextId; | |
170 | // before advice | |
171 | var args = arguments; | |
172 | var before = dispatcher.before; | |
173 | while(before){ | |
174 | args = before.advice.apply(this, args) || args; | |
175 | before = before.next; | |
176 | } | |
177 | // around advice | |
178 | if(dispatcher.around){ | |
179 | var results = dispatcher.around.advice(this, args); | |
180 | } | |
181 | // after advice | |
182 | var after = dispatcher.after; | |
183 | while(after && after.id < executionId){ | |
184 | results = after.receiveArguments ? after.advice.apply(this, args) || results : | |
185 | after.advice.call(this, results); | |
186 | after = after.next; | |
187 | } | |
188 | return results; | |
189 | }; | |
190 | if(existing){ | |
191 | dispatcher.around = {advice: function(target, args){ | |
192 | return existing.apply(target, args); | |
193 | }}; | |
194 | } | |
195 | dispatcher.target = target; | |
196 | } | |
197 | var results = advise((dispatcher || existing), type, advice, receiveArguments); | |
198 | advice = null; | |
199 | return results; | |
200 | }; | |
201 | } | |
202 | return { | |
203 | before: aspect("before"), | |
204 | around: aspect("around"), | |
205 | after: aspect("after") | |
206 | }; | |
207 | }); |