The Transparent Hook Pattern
February 4th, 2007The title is a bit misleading. This isn’t really a new pattern but a specific implementation of one. I just think it’s a neat use of javascript by dynamically adding/updating object methods. For those of you who are new to the hook pattern, it’s a design where functions and code blocks can be attached during run-time to other functions or code blocks and run before,at or after the original function call. For more info on a php implementation check out this site. I’ve been using a modified version of this for some of my php scripts. This implementation is a ported version of my php version. The Transparent Hook Pattern takes the hook pattern and makes it transparent so you don’t have to rely on another function to call the hookable functions. For example..
MyHookedObject.call( "func", "arg1", "arg2" );
//Can be called like this.
MyHookedObject.func("arg1","arg2");
The short of it, I used javascript’s prototyping ability to rewrite all the functions in the hookable object to call the appropriate hook execution function. Not anything new but I though using it in this setting was interesting. Using my Transparent Hook Pattern installation function you can make an object hookable with one line of code in the bottom of the constructor. Another neat use of JavaScript is applying external, arbitrary functions to objects using their context.
InstallTransparentHookPattern.apply(this);
This installs the basic hook functions needed to create and run hooks and also rewrites all the object methods to call hook function. Here’s the complete code.
function InstallTransparentHookPattern(){
this.HOOK_PARAMS = 'HOOK_PARAMS'; this.HOOK_BREAK = 'HOOK_BREAK';
this.createHooks = function(){
this._hooks = new Array();
//foreach function, rename to _hook_func()// for ( func in this ){ if ( typeof(this[func]) == 'function' && !/^(_hook_|initalize|runHook|hook|createHooks)/.test(func) ){ this['_hook_' + func] = this[func]; this[func] = new Function('return this.runHook("'+func+'",arguments);'); this.hook(func,this['_hook_'+func]); } } };
this.runHook = function(){ var hook = arguments[0]; var arguments = arguments[1]; var seq = ['before','at','after']; var rtn = null;
if ( typeof(this._hooks[hook]) == 'object' ){ for ( var s=0; s<3; s++ ){ var size = this._hooks[hook][seq[s]].length; for ( var cnt=0; cnt < size; cnt++ ){ var r = this._hooks[hook][seq[s]][cnt].apply(this,arguments); rtn = (r != undefined && r != null ? r : rtn ); if ( rtn && typeof(rtn) == 'object' ){ switch ( rtn[0] ){ case this.HOOK_BREAK: return rtn[1]; break; case this.HOOK_PARAMS: arguments = rtn[1]; break; default: break; } } } } }
return rtn; };
this.hook = function(){
var args = arguments;
switch(args.length){ case 2: if ( !this._hooks[args[0]] ){ this._hooks[args[0]] = {'before':[],'at':[],'after':[]}; }
this._hooks[args[0]]['at'][this._hooks[args[0]]['at'].length] = args[1]; break;
case 3: if ( !this._hooks[args[1]] ){ this._hooks[args[1]] = {'before':[],'at':[],'after':[]}; }
if ( args[0] == 'around' ){ this._hooks[args[1]]['before'][this._hooks[args[1]]['before'].length] = args[2]; this._hooks[args[1]]['after'][this._hooks[args[1]]['after'].length] = args[2]; } else { this._hooks[args[1]][args[0]][this._hooks[args[1]][args[0]].length] = args[2]; } break;
default: return null; break; } };
this.createHooks();
}
After installing the Transparent Hook, you hook functions using the below syntax.
//this will add 1 to myObj's value before any calls the myMethod. myObj.hook('before','myMethod',function(){ this.value++; }); //this will add 1 to myObj's value after any calls the myMethod. myObj.hook('after','myMethod',function(){ this.value++; }); //this will add 1 to myObj's value immediately after any calls the myMethod. myObj.hook('at','myMethod',function(){ this.value++; });
I also created some special case global constants to control how a chain of hooked functions are executed. I’m sure there’s a better way of doing, such as a command pattern, this but this is my hack for now. The two global constants are this.HOOK_PARAMS ( which takes what is returned in an array to pass to the next function in the hook chain ) and this.HOOK_BREAK ( which kills the chain of execution and returns to the caller ). To use these in a hooked function, you have to return the constant as the first element in an array. In the case of HOOK_PARAMS, the second array element is another array which constitutes the parameters to pass to the next function in line… Hacky…i know… but it works. And thats my amazing, super awesome transparent hook pattern thing! -bryan bryan AT bluelinecity DOT com


