Introduction
Let's imagine that you have some legacy or third party code, that has well-known, but awful API. In my case, it was js object that called my functions directly by function name, without support for callbacks.
So if I needed receive some notification from this object, I was required to directly assign appropriate function, e.g.:
api.OnSomeEvent = function (argument){
}
Sometimes, I needed to receive these callbacks in multiple places, so I was expected to waste my time creating proxy objects that receive notifications from API and transfer them to end destination. It may look like:
var proxy = {};
proxy.Callbacks = $.Callbacks();
proxy.OnSomeEventHandler = function(argument){
proxy.Callbacks.fire(argument);
}
api.OnSomeEvent = proxy.OnSomeEventHandler;
FYI, this is not real code, I know that my proxy object is miserable, and code is ugly, but this is the simplified version for quick understanding.
What is worse, sometimes API does not provide events that I needed, and I had no chance to change API. So I need to 'hook' required functions (thank goodness they were accessible), e.g.:
var proxy = {};
proxy.Callbacks = $.Callbacks();
proxy.OnStupidFunctionHook = function(argument){
proxy.Callbacks.fire(argument);
proxy.HookedStupidFunction();
}
proxy.HookedStupidFunction = api.StupidFunction;
api.StupidFunction = proxy.OnStupidFunctionHook;
Using the Code
Certainly, I was unhappy to write sheets of code for such a trivial task. But JavaScript has an ace in the hole - closures. With such a great instrument, I managed to solve each of the previously described problems with one line of code:
JsUtils.SetCallbackHook(api.StupidFunction, function(arguments){ });
JsUtils.SetCallbackHook(api.OnSomeCallback, function(arguments){ });
JsUtils.SetCallbackHook(api.OnSomeCallback, function(arguments){ });
But let's take a look at what lays under the bonnet of JsUtils.SetCallbackHook
:
var JsUtils = new function() {
var self = this;
self.SetCallbackHook = function (sourceFunction, targetCallback) {
var callBackClosure = function () {
sourceFunction.callbacks.fireWith(this, $.makeArray(arguments));
};
if (!sourceFunction) {
sourceFunction = callBackClosure;
}
if(sourceFunction.toString() != callBackClosure.toString()){
sourceFunction = self.SetCallbackHook(callBackClosure, sourceFunction);
}
if (!sourceFunction.callbacks) {
sourceFunction.callbacks = $.Callbacks();
}
sourceFunction.callbacks.add(targetCallback);
return sourceFunction;
};
};
As you understand from the comments, we replace native function with our closure, that stores also list of callbacks. And each time we assign new callback it is added to the list, and raised once closure is called.
I did not wrap JsUtils to namespace to avoid additional confusion that beginners may feel. You may do it in your own project if need be.
You may play with the working example in this fiddle.