Adding custom callbacks to existing JavaScript functions

Categories: jQuery, JavaScript

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.

  1. As written, this would only work if the original sayHello() function was defined in another <script> tag because of function hoisting.
  2. We're polluting the global name space.

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.

Dave's Original Solution

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;
};

Improved Solution

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:

  1. We're not polluting the global namespace
  2. We can now attach custom callbacks to each jQuery object separately

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?

Comments

mark's Gravatar I'm trying to do this:
jQuery.fn.tmpl.on('onAfterTmpl', function() {
    alert("after!");
});

I want the onAfterTmpl event to fire on ALL templates when done? what needs done.
Dan G. Switzer, II's Gravatar @Mark:

Follow Dave's original example. It's a global callback.
Piotr Kula's Gravatar Duck Punching? lol, any way had to monkey patch __doPostBack on ASP Classic project. Your simple closure solution worked for me. I had to do it in document ready of jQuery because the function I wanted to patch was loaded just after this, which I could not change. But for some reason the solution by Dave did NOT work? Odd. Well thanks again! I definitely learned something new in the land of run-time codding languages :) "Shaking the bag!"

Add Comment

Leave this field empty


If you subscribe, any new posts to this thread will be sent to your email address.