Hey Dave, is there a way to create an event handler so when .tmpl() is done it will fire a function? I’m trying to make it global, so I was hoping I could say, when ANY template gets done, do this…

The question of how to retroactively add hooks before and/or after a pre-existing JavaScript function executes is one that comes up from time to time. Whether it’s a simple method like tmpl(), a server-generated script artifact, or a function in a third-party script, sometimes it’s desirable to alter a JavaScript function without access to change the original declaration of the function.

One of the handy things about JavaScript is that you can combine its functional and dynamic aspects to make surprisingly quick work of tasks like this one. Doing so is fairly straightforward, but it involves an approach you might not consider if you’re more familiar with languages that lack JavaScript’s unique features.

In this post, I’ll briefly cover a few examples of how to “patch” existing JavaScript functions with callbacks. We’ll begin with simple examples, then address an issue that stems from applying this approach to jQuery plugins, and finish with a more elegant way to handle the problem of patching functions that accept parameters.


A simple() example

Let’s start with the simplest possible example, a function that accepts no arguments and returns nothing:

function simple() {
  // I am a black box.
}

Maybe that function is pre-defined and emitted by server-side code. Maybe it’s part of a script you’re referencing from a public CDN. For whatever reason, assume that this function is part of your page, but that you don’t have the ability to directly change its code.

If you need to run some of your own code before and/or after any time the simple() function is called, you can use a technique called monkey patching to achieve that without access to the original function declaration.

// Get a reference to the previously defined simple() function.
var oldSimple = simple;
 
function simple() {
  // Code that runs before any call to simple() here.
 
  // Make a call to the old/previous simple() function.
  oldSimple();
 
  // Code that runs after any call to simple() here.
}

Since functions are just like any other variable in JavaScript, it’s that simple.

Without touching the original simple() function declaration, this snippet has effectively redefined it anyway. Calls to the original function will also execute our before and after code now, unaware that anything has changed.

Parameters and external “event” handlers

If you want to apply this technique to a function that accepts parameters, you’ll also need to capture and relay those parameters.

Take this more complex function, for example:

function complex(foo, bar, baz) {
  // An assortment of black boxes.
}

You could patch in a “before” event, parameters unimpeded, like this:

var oldComplex = complex;
 
// Capture the parameters that the original complex()
//  function expects.
function complex(foo, bar, baz) {
  // Call onBeforeComplex() if it's defined when this runs, and
  //  pass a copy of the parameters that complex() was called with.
  if (typeof onBeforeComplex === 'function')
    onBeforeComplex(foo, bar, baz);
 
  // Call the original function, parameters included.
  oldComplex(foo, bar, baz);
}

Using a separate onBeforeComplex() function instead of inline code isn’t necessary, but I prefer that approach. Now, you can drop this code into a script include anywhere after complex() is defined and then declare the “handler” function anywhere on the page (or not at all).

Passing foo, bar, and baz to onBeforeComplex() is optional, of course, but it’s usually helpful to know something about what sort of call triggered the event.

Applying this to jQuery plugins

Now let’s apply this technique to the real-world problem of updating tmpl(). Doing that isn’t much more difficult than the previous example, but we do need to identify the method’s signature first so that we can apply the patch.

Digging into the last version of jQuery.tmpl.js on GitHub, the function we’ll need to override can be found beginning on line 80:

So, the function we’ll need to patch is jQuery.fn.tmpl. Given the previous examples, you might thing it would be as easy as this:

var oldTmpl = jQuery.fn.tmpl;
 
jQuery.fn.tmpl = function(data, options, parentItem) {
  if (typeof onBeforeTmpl === 'function')
    onBeforeTmpl(data, options, parentItem);
 
  oldTmpl(data, options, parentItem);
}

Unfortunately, that will break tmpl() (in two separate ways, no less).

jQuery plugins make “this” more complicated

Applying the monkey patching approach to functions that operate on and return jQuery collection objects, such as the tmpl() plugin that led to this post, requires extra consideration. The crux of jQuery’s fluent API is that chainable jQuery methods both operate upon and return an instance of the jQuery object.

Now that we’re interjecting an additional level of abstraction, we need to be sure to maintain the scope of this down into the original jQuery plugin method and preserve the original function’s return value. Otherwise, our intermediary function will break jQuery’s chaining.

Using apply() to call the original function allows us to control its this value:

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(data, options, parentItem) {
  if (typeof onBeforeTmpl === 'function')
    onBeforeTmpl.apply(this, [data, options, parentItem]);
 
  // Make a call to the old tmpl() function, maintaining the value 
  //  of "this" and its expected function arguments.
  var tmplResult = oldTmpl.apply(this, [data, options, parentItem]);
 
  if (typeof onAfterTmpl === 'function')
    onAfterTmpl.apply(this, [data, options, parentItem]);
 
  // Returning the result of tmpl() back so that it's actually 
  //  useful, but also to preserve jQuery's chaining.
  return tmplResult;
};

By capturing the return value of the original tmpl() function and returning that back to the original caller, chaining is preserved at that previous level. That’s particularly important in this specific example because that return value is the rendered template.

Better argument handling

If you’re like me, explicitly capturing and passing each individual function parameter doesn’t feel great. Not only is it extra work to determine how many parameters a method accepts, but our patched version of tmpl() would stop working correctly if the original were updated to begin accepting additional parameters.

We can improve this situation easily enough by taking advantage of JavaScript’s built-in way to capture all of the parameters that were passed to a function. Aptly named arguments, this so-called array-like object is perfect for eliminating the explicit plumbing code to pass parameters between the patched function and the original.

Even better, arguments will capture all the parameters passed into a function even if that function’s declaration doesn’t accept parameters at all. So, now there’s no need to worry about matching the original’s function signature:

var oldTmpl = jQuery.fn.tmpl;
 
jQuery.fn.tmpl = function() {
  if (typeof onBeforeTmpl === 'function')
    onBeforeTmpl.apply(this, arguments);
 
  var tmplResult = oldTmpl.apply(this, arguments);
 
  if (typeof onAfterTmpl === 'function')
    onAfterTmpl.apply(this, arguments);
 
  return tmplResult;
};

Throw that code on a page, anywhere after the reference to jquery.tmpl.js, and then all existing usages of tmpl() will begin triggering the before and after “events”.

Conclusion

We took a circuitous route to get here, but I hope you agree that it was worthwhile to build the solution up step by step. Even if you don’t need to patch an event into tmpl(), you might be surprised how often this technique comes in handy once you’re keeping the possibility in mind.