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