Sunday, 15. April 2007, 07:15:57
Many people are irritated by the fact that IE's DOM functions (which are really COM objects) don't support the "call" and "apply" methods.
Transfering those methods from a function object to a COM object throws errors. People invented a weird hack involving the Function() constructor to overcome this issue, but I think I know better. I don't know if I'm the first one to come up with it (probably not), but I found a "standard" way to make an arbitrary DOM function into a real Ecmascript function.
As a very easy test case, type this javascript url into IE:
javascript:alert(Function.prototype.call.call(document.createElement,document,'p'))
This will produce a new paragraph (
) Element. You can change the string to any valid value.
And now the annotated minimised version of the wrapDOMf function:
function wrapDOMf (scope,func)
{
if(!(scope && typeof func == "string" && scope[func])) return; //ensure proper usage; no type checking
var storage = arguments.callee; //using wrapDOMf as temporary storage
storage.orig = scope[func];
scope[func] = function WRAP()
{
return Function.prototype.apply.call(arguments.callee.$original,this,arguments);
};
scope[func].$original = storage.orig;
delete storage.orig; //get rid of any unnecessary references, optional
}
It shouldn't leak memory. If it should turn out to do that, I'll have to sort that out.
The minimised version I intend to use is the following (but I don't know about leak-status):
function wrapDOMf (s,f) //scope, function
{
if(!(s && typeof f == "string" && s[f])) return;
var m = s[f]; //using local var instead of property
s[f] = function WRAP()
{
return Function.prototype.apply.call(arguments.callee.$original,this,arguments);
};
s[f].$original = m;
}
Why I delete the variable at the end [long version only] ? Because that's not where my imagination stopped. I wanted to use a closure to add functionality. We'll want to manipulate the input and output of the function, don't we? I assume we want to change the output most of the cases. Thus, my primary function will do that and only that. The change in the name reflects the new usage: It could be useful for all functions now, not just DOM ones.
function wrapFunc (scope,func,hijack)
{
if(!(scope && typeof func == "string" && scope[func])) return; //ensure proper usage
if(!(hijack && hijack instanceof Function)) hijack = function(x){return x}; //if no hijack exists, f(x)=x
var storage = arguments.callee;
storage.orig = scope[func];
scope[func] = function WRAP()
{
var obj = Function.prototype.apply.call(arguments.callee.$original,this,arguments)
return hijack.call(this,obj,arguments); // 'hijack' is passed the output object and all the arguments
};
scope[func].$original = storage.orig;
delete storage.orig; //now mandatory, since we retain closure
}
The one for input manipulation:
function wrapFuncBefore (scope,func,hijack)
{
if(!(scope && typeof func == "string" && scope[func])) return; //ensure proper usage
if(!(hijack && hijack instanceof Function)) hijack = function(){return [this,arguments]};
var storage = arguments.callee;
storage.orig = scope[func];
scope[func] = function WRAP()
{
var temp = hijack.apply(this,arguments); //I want destructuring assignment
var that = temp[0];
var args = temp[1];
return Function.prototype.apply.call(arguments.callee.$original,that,args);
};
scope[func].$original = storage.orig;
delete storage.orig; //now mandatory, since we retain closure
}