This was a tough but intriguing one... After thinking about it, I decided the most elegant and flexible solution would be to modify the constructor and allow callbacks on any method. So I added an associative array of functions to the parameter list and immediately built a loop with a callback in it... Oops. After making my own function and setting 3 different types of callbacks, I ended up with a pretty large function. Then I decided this is a script in its own right and put my modifications to the constructor in another function.
So here are the functions' documentation, PHP style:
var failed = setCallbacks(object, callbacks)
Wraps specified methods on an object in callbacks. Returns a numeric array of method names which don't exist on object
, or false
to indicate an invalid argument.
object - must be non-null, and have a typeof value of "object" or "function". Boolean, Number, and String objects may work.
callbacks - an associative array (i.e. object) of functions. Each value must be either:- a function, or
- an object with properties 'func' or '0', the function, and 'when' or '1', the corresponding when value (see documentation for chain() below).
var success = chain(object, funcName, callback, [when = 1])
Wraps an original method in a user-supplied function. All state is transferred to the new function and may be modified as desired to affect the original, the caller, or both, or to discard the original completely. If funcName
does not indicate a method of object
or the arguments are otherwise invalid, false is returned; otherwise, true is returned.
object - must be non-null, and have a typeof value of "object" or "function". Boolean, Number, and String objects may work. Defaults to the global object (i.e. window
) according to the ECMAScript specification.
funcName - the name used by the object to refer to the target function - NOT the actual function.
callback - your function. In all cases, this
will be the passed object in both functions.
when - optional; determines when your function will be executed in relation to the original; defaults to 1 for all unrecognized values. Values are interpreted as follows:
-1 - before. Your function will be passed the arguments meant for the original. If your function returns an array, the contents will be passed to the original as the ordinary arguments.
0 - around. Your function will be passed the original function and the arguments, and will be responsible for calling the original. Your return value will be seen as the original's to outside code. (See Function.call and Function.apply.)
1 - after. Your function will be passed the original's return value and arguments. If you return a value (other than undefined
), it will override the original's.
example code
(Note that I duplicate the foo function so the callbacks don't stack on top of each other.)
Code:
<html>
<head>
<title>Chain Script</title>
<script type="text/javascript" src="chain.js"></script>
</head>
<body>
<script type="text/javascript">
function foo(bar){
return --bar;
}
function foo1(bar){
return --bar;
}
function foo2(bar){
return --bar;
}
function foo3(bar){
return --bar;
}
function baz1(bar){
return [bar + 100];
}
function baz2(old, bar){
bar += 100;
var result = old.call(this, bar);
document.writeln('2 = ' + result + '<br>');
}
function baz3(result, bar){
document.writeln('3 = ' + result + '<br>');
}
/* These lines are effectively identical to the single setCallbacks() call below:
chain(window, 'foo1', baz1, -1);
chain(window, 'foo2', baz2, 0);
chain(window, 'foo3', baz3, 1);*/
setCallbacks(window, {foo1: [baz1, -1], foo2: [baz2, 0], foo3: [baz3, 1]});
document.writeln(foo(1) + '<br>');
document.writeln('1 = ' + foo1(1) + '<br>');
foo2(1);
foo3(1);
</script>
</body>
</html>
source code
Code:
function chain(object, funcName, neu, when){
if((typeof object != "object" && typeof object != "function") || object == null)
return false;
if(typeof funcName != 'string' || typeof neu != 'function')
return false;
if(typeof when != 'number' && (when % 1 != 0 || when < -1 || when > 1))
when = 1;
oldFunc = object[funcName];
if(typeof oldFunc != 'function')
return false;
object[funcName] = function(){
var result1, result2, args = [];
for(var i = 0; i < arguments.length; i++)
args[i] = arguments[i];
if(when == 1){
result1 = oldFunc.apply(object, args);
args.unshift(result1);
}
if(when == 0)
args.unshift(oldFunc);
result2 = neu.apply(object, args);
if(when == -1){
if(typeof result2 == 'object' && result2.constructor == Array)
args = result2;
return oldFunc.apply(object, args);
}
if(typeof result2 != 'undefined')
return result2;
else
return result1;
};
return true;
}
function setCallbacks(object, callbacks){
if((typeof object != "object" && typeof object != "function") || object == null)
return false;
if((typeof callbacks != "array" && typeof callbacks != "object") || callbacks == null)
return false;
var results = [];
if(typeof callbacks != 'undefined'){
for(i in callbacks){
var neu = callbacks[i], when;
if(typeof neu == 'object' && neu.constructor != Function){
when = neu.when || neu[1];
neu = neu.func || neu[0];
}
if(!chain(object, i, neu, when)){
results.push(i);
}
}
}
return results;
}
Bookmarks