14

EDIT: OK, I believe the following solutions are valid:

  1. Use the jQuery AOP plugin. It basically wraps the old function together with the hook into a function sandwich and reassigns it to the old function name. This causes nesting of functions with each new added hook.
    If jQuery is not usable for you, just pillage the source code, there did not seem to be any jQuery dependencies in the plugin, and the source is simple and very small.

  2. Have an object describing all hooks and their targets and one to store the initial unmodified function. When adding a new hook, the wrapping would be redone around the original function, instead of re-wrap the the previous wrapping function.
    You escape nested functions, and get two objects to handle instead. Potentially, this could also mean easier hook handling, if you add/remove hooks often and out of order.

I'll go with the first, since it's already done, and I don't have performance to worry about. And since the original functions are not affected, even if I switch hooking methods, I'll only need to redo the hook adding, which might be just some simple search&replace operations.


Hi,

Is it possible to create a mechanism, in which function A might have a set of hooks(functions that will execute before/after function A)?

Ideally, function A would not be aware of hooking functionality, so that I do not have to modify the source code of function A to call the hooks. Something like:

A = function(){
    alert("I'm a naive function");
};
B = function(){
    alert("I'm having a piggyback ride on function A!"+
          "And the fool doesn't even know it!");
};
addHook(B, A)//add hook B to function A
A()
//getting alerts "I'm a naive function"/"I'm having a 
//piggyback ride on function A! And the fool doesn't even know it!"

I've been trying to hack something up for a couple of hours, but so far no luck.

Seyen
  • 143
  • 1
  • 1
  • 6
  • this is a nice question, altho i doubt there will be a positive answer. You can certainly do it but with some sort of workaround. I'm bookmarking this. – Peter Perháč Apr 25 '09 at 20:07

7 Answers7

13

Might not be pretty but it seems to work...

<script>

function A(x) { alert(x); return x; }
function B() { alert(123); }

function addHook(functionB, functionA, parent)
{
    if (typeof parent == 'undefined')
        parent = window;

    for (var i in parent)
    {
        if (parent[i] === functionA)
        {
            parent[i] = function()
            {
                functionB();
                return functionA.apply(this, arguments)
            }

            break;
        }
    }
}

addHook(B, A);

A(2);

</script>
Greg
  • 316,276
  • 54
  • 369
  • 333
  • 1
    Thanks! I thought about this one, and it works, but you get nesting with this method, since each new hook wraps all previous hooks: hookC(hookB(hookA(functionA.apply(this,arguments)))) I'm not sure if this affects performance in any way, but it doesn't feel right/safe. Is `parent` used so that this from `apply(this, arguments)` is bound correctly? – Seyen Apr 25 '09 at 21:43
10

Take a look at jQuery's AOP plugin. In general, google "javascript aspect oriented programming".

Travis Jensen
  • 5,362
  • 3
  • 36
  • 40
  • 5
    jQuery != Javascript. This matters for instance, if you are programming on the Facebook platform, and probably other "sandboxed" environments as well. – Dexygen Apr 25 '09 at 21:12
  • @George: the techniques demonstrated still apply. So far, Greg is the only one to post usable code *here*. – Shog9 Apr 25 '09 at 21:16
  • Thank you for the link and mention of AOP, i wasn't familiar with it. I am able and do use jQuery, so this might work out. Trying to figure out the plugin right now. – Seyen Apr 25 '09 at 21:47
  • Could you provide a sample of how this might be implemented? – Jeroen Vannevel Mar 06 '14 at 14:36
4

Very simple answer:

function someFunction() { alert("Bar!") }
var placeholder=someFunction;
someFunction=function() {
  alert("Foo?");
  placeholder();
}
Asmor
  • 5,043
  • 6
  • 32
  • 42
2

This answer is not definitive, but rather demonstrative of a different technique than those offered thus far. This leverages the fact that a function in Javascript is a first-class object, and as such, a) you can pass it as a value to another function and b) you can add properties to it. Combine these traits with function's built-in "call" (or "apply") methods, and you have yourself a start toward a solution.

var function_itself = function() {
    alert('in function itself');
}
function_itself.PRE_PROCESS = function() {
    alert('in pre_process');
}
function_itself.POST_PROCESS = function() {
    alert('in post_process');
}

var function_processor = function(func) {
    if (func.PRE_PROCESS) {
        func.PRE_PROCESS.call();
    }
    func.call();
    if (func.POST_PROCESS) {
        func.POST_PROCESS.call();
    }        
}
Dexygen
  • 12,287
  • 13
  • 80
  • 147
  • This works, but then you have to use function_processor for calling function_itself in all places where it would be called, which somewhat defeats the purpose. The code might quickly devolve into a forest of function_processor calls. – Seyen Apr 25 '09 at 21:51
1

The following function will give you before and after hooks that can be stacked. So if you have a number of potential functions that need to run before the given function or after the given function then this would be a working solution. This solution does not require jQuery and uses native array methods (no shims required). It should also be context sensitive so if you are calling the original function with a context if should run each before and after function likewise.

// usage: 
/*
function test(x) {
    alert(x);
}

var htest = hookable(test);

htest.addHook("before", function (x) {
    alert("Before " + x);
})
htest.addHook("after", function (x) {
    alert("After " + x);
})

htest("test") // => Before test ... test ... After test
*/
function hookable(fn) {
    var ifn = fn,
        hooks = {
            before : [],
            after : []
        };

    function hookableFunction() {
        var args = [].slice.call(arguments, 0),
            i = 0,
            fn;
        for (i = 0; !!hooks.before[i]; i += 1) {
            fn = hooks.before[i];
            fn.apply(this, args);
        }
        ifn.apply(this, arguments);
        for (i = 0; !!hooks.after[i]; i++) {
            fn = hooks.after[i];
            fn.apply(this, args);
        }
    }

    hookableFunction.addHook = function (type, fn) {
        if (hooks[type] instanceof Array) {
            hooks[type].push(fn);
        } else {
            throw (function () {
                var e = new Error("Invalid hook type");
                e.expected = Object.keys(hooks);
                e.got = type;
                return e;
            }());
        }
    };

    return hookableFunction;
}
Gabriel
  • 18,322
  • 2
  • 37
  • 44
0

Here's what I did, might be useful in other applications like this:

//Setup a hooking object
a={
    hook:function(name,f){
        aion.hooks[name]=f;
    }
}a.hooks={
    //default hooks (also sets the object)
}; 

//Add a hook
a.hook('test',function(){
    alert('test');
});

//Apply each Hook (can be done with for)
$.each(a.hooks,function(index,f){
    f();
});
aubreypwd
  • 21,507
  • 2
  • 17
  • 14
0

I don't know if this will be useful. You do need to modify the original function but only once and you don't need to keep editing it for firing hooks

https://github.com/rcorp/hooker

Gaurav Ramanan
  • 3,655
  • 2
  • 21
  • 29