]> git.wh0rd.org - tt-rss.git/blob - lib/dojo/aspect.js.uncompressed.js
update dojo to 1.7.3
[tt-rss.git] / lib / dojo / aspect.js.uncompressed.js
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 });