JavaScript for C# programmers: wrapping an existing function to add extra functionality

I was answering a JavaScript question on stackoverflow when a common usage scenario presented itself, one with a subtle gotcha that could catch you out. Perfect for a quick blog post. (Note: you could also view this post as an adjunct to my popular JavaScript callback posts (I, II, III).)

My solution to the problem involved adding some extra precondition code to an existing event handler. The event was the plotclick event of the jQuery Flot charting library, so the original event handler code looked like this:

  $("#chart").bind("plotclick", function (event, pos, item) {
    // respond to the click 
  });

Pretty easy stuff. However in my solution I wanted to enhance the event handler so that some preconditions were checked and, if they passed, the original code could be executed. Simple enough: just add the preconditions to the code – which is what I did for the answer.

  $("#chart").bind("plotclick", function (event, pos, item) {
    if (item) {
      var dataPoint = item.series.data[item.dataIndex];
      if (dataPoint[2]) {
        // respond to the click 
      }
    }
  });

For one chart, meh, not a problem. Make the change and move on. For several, it gets a little clunky; all those complicated bits of cut-n-paste. What would be better would be to wrap the original function – somehow – and then call the wrapper. In other words, in my mind I was thinking of something like this:

  $("#chart").bind("plotclick", wrap(function (event, pos, item) {
    // respond to the click 
  }));

So that the minimal amount of change would be required. This would also work if the event handler were not an anonymous function.

What would this wrap function look like? Well, it takes a function with the required signature (that is, takes three parameters) and returns another function, the event handler, that is also of the required signature and that calls the original function. (Call these the wrapper and the wrapped.)

  var wrap = function (originalHandler) {
    return function (event, pos, item) {
      // extra stuff
      originalHandler(event, pos, item);
    };
  };

There is, however, one small problem with this code. I will admit that the first time I wrote it I missed the issue, and it was only through testing that I discovered the bug, so it’s not glaringly obvious.

I’ll stop a moment for you to think about it.

Found it? Give yourself a bonus point if you did. The problem is the this variable, the context of the wrapper, is not passed on to the wrapped. As written, the call to originalHandler is a function invocation, and the this variable inside the function will be the global object, not the context passed in for the wrapper function. With jQuery, the context will be the DOM element for which the event was triggered, and my code just blithely throws that away.

Let’s rectify that right now:

  var wrap = function (originalHandler) {
    return function (event, pos, item) {
      // extra stuff 
      originalHandler.call(this, event, pos, item);
    };
  };

In other words, we use the Function call method to pass on the this variable on to the called function.

So, going back to my particular case, I’d have:

  var wrap = function (originalHandler) {
    return function (event, pos, item) {
      if (item) {
        var dataPoint = item.series.data[item.dataIndex];
        if (dataPoint[2]) {
          originalHandler.call(this, event, pos, item);
        }
      }
    };
  };

So, I’d say the takeaway from this blog post is that—yet again—the fundamental concept that functions are objects (that is, you can pass them around, and create functions that return functions), and that you should be careful if you do so that you take care of the this variable, if needed.

Album cover for Pieces In A Modern Style 2Now playing:
Orbit, William - Lark (alex metric remix)
(from Pieces In A Modern Style 2)

Loading similar posts...   Loading links to posts on similar topics...

2 Responses

 avatar
#1 configurator said...
08-Jul-11 9:11 AM

Use originalHandler.apply(this, arguments); - this way if you have extra arguments (other than event, pos, item) they're still forwarded to the originalHandler.

julian m bucknall avatar
#2 julian m bucknall said...
08-Jul-11 10:39 AM

Configurator: Excellent point. I agree.

Cheers, Julian

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response