This morning I was reading Adding your own callbacks to existing JavaScript functions by Dave Ward—which covers how to overwrite an existing function so you can add some additional functionality (in this case, adding callbacks.) While the article is informative, a couple of improvements can dramatically improve his suggestion.
If you don't want to take the time to read Dave's article, in a nutshell he describes how we can overwrite a JavaScript function by storing a reference to the original function in a variable. So, we can take the following function:
function sayHello(name){ alert("Hello, " + name + "!"); }
And we can now overwrite that function by storing a reference to the original function in a variable:
var sayHelloOld = sayHello; function sayHello(){ var name = prompt("Enter your name"); sayHelloOld.apply(this, [name]); }
Now there's a couple of problems with the above code.
We can solve both those problems by using a closure around our code:
// define a closure and pass in a reference to the global window object (function (w){ var sayHelloOld = w.sayHello; w.sayHello = function (){ var name = prompt("Enter your name"); sayHelloOld.apply(this, [name]); } })(window || {});
(NOTE: You can see a working copy on JSFiddle.)
The other topic Dave discusses is how to add callback hooks to run before and after a the original function code runs. His suggestion is built around using the global name space to declare some function names. Since the example is based around jQuery, I'd suggest a much better method would be to add in custom events to your function. This gives you a way to bind callbacks to run, but your neither cluttering the global namespace nor running into issues if the callbacks aren't needed.
Here's what Dave's original solution looks like:
var oldTmpl = jQuery.fn.tmpl; // Note: the parameters don't need to be named the same as in the // original. This could just as well be function(a, b, c). jQuery.fn.tmpl = function() { if (typeof onBeforeTmpl === 'function') onBeforeTmpl.apply(this, arguments); // Make a call to the old tmpl() function, maintaining the value // of "this" and its expected function arguments. var tmplResult = oldTmpl.apply(this, arguments); if (typeof onAfterTmpl === 'function') onAfterTmpl.apply(this, arguments); // Returning the result of tmpl() back so that it's actually // useful, but also to preserve jQuery's chaining. return tmplResult; };
Using the two previously mentioned techniques combined, here's how I'd change that code:
(function ($){ var oldTmpl = $.fn.tmpl; // Note: the parameters don't need to be named the same as in the // original. This could just as well be function(a, b, c). $.fn.tmpl = function(){ // trigger the before callback // to attach a callback, we just bind() this custom event to our jQuery object this.trigger("onBeforeTmpl", arguments); // Make a call to the old tmpl() function, maintaining the value // of "this" and its expected function arguments. var tmplResult = oldTmpl.apply(this, arguments); // trigger the after callback // to attach a callback, we just bind() this custom event to our jQuery object this.trigger("onAfterTmpl", arguments); // Returning the result of tmpl() back so that it's actually // useful, but also to preserve jQuery's chaining. return tmplResult; }; })(jQuery || {});
This gives use a few benefits over Dave's original solution:
To use our code, we can do:
$("#id") .bind("onBeforeTmpl", function (){ alert("before!"); }) .bind("onAfterTmpl", function (){ alert("after!"); }) .tmpl(data, options, parentItem);
NOTE:If your prefer to run the same callbacks for all $.tmpl() calls, you could attach the custom events globally.
Any comments on how to make this solution even better?
3 Comments
Comments for this entry have been disabled.