Callback Functions: Which Instance is Calling?

In support of multiple cropping widgets on the same page, I’ve had a couple requests for Jcrop to return an identifier of the image being cropped, when callback functions are called. On this point, I’d like to suggest a small but flexible workaround. I think it may illustrate an interesting pattern of Javascript at the same time.

I’ve had a couple of requests that Jcrop return an identifier of the image being cropped, when callback functions are called. This is understandable, but I’m not sure if it should do this or not. (Especially now, that it started out not doing that.) On this point, I’d like to suggest a little workaround. I think it may illustrate an interesting pattern of Javascript at the same time.

One common way using an inline callback handler might look like this:

$('#target').Jcrop({
    onSelect: function(c) { myhandler('#target',c); }
});

This is possible because Javascript supports first-class functions. In other words, the function can be expressed inline because it’s an entity value, like any scalar, array, object, and whatnot (you might think “functions are like variables”).

With that in mind, getting back to the issue at hand, we can create a pretty fair solution to the original problem by creating a function that returns a function.

function jcrop_target(my_id) {
    return function(c) { myhandler(my_id,c); };
};

It’s exactly the same as the inline example above, but instead we’re using a variable. When called, the function returns a new (anonymous, first-class) handler function, based on an arbitrary argument value of my_id.

Because my_id is in scope when the function is defined, we can wrap the id (or any value or values) and the object Jcrop returns when the callback is called (here labeled c) into another function call, just as the inline example does above.

Now the invocation can be simplified like this:

$(function() {
    $('#img1').Jcrop({ onSelect: jcrop_target('#img1') });
    $('#img2').Jcrop({ onSelect: jcrop_target('#img2') });
});

I think this is much cleaner and easier to read. If you haven’t thought much about functions as first-class objects, the jQuery DOM-ready wrapper above is a perfect example of a first-class function you may already be using every day. (In fact, jQuery is rife with them, as all Javascript ought.)

There’s also no more overhead than the original inline example, because you effectively end up with the same result: the callback handler is holding a callable function, or at least a reference to one.

What if you don’t like to type the selector twice? Here’s another idea:

$(function() {
    $('#img1, #img2, .autocrop').each(function(){
        $(this).Jcrop({ onSelect: jcrop_target(this.id) });
    });
});

This is slightly different in that it will pass the actual id (not #id which is really a selector). So the point is, this way you can choose what happens with the values Jcrop returns.

If Jcrop did return a value, should it return the id, an id selector, the DOM object itself, a jQuery object? It’s hard to know what would be best, if any. With this solution, you can decide!

I believe this workaround is sufficient and a fairly elegant approach to acheiving the end result—and potentially within the scope of one’s duties as a web developer trying to implement an advanced interface implementing multiple web-based cropping widgets.

Your input is useful. Does this solve the problem?

Tags: ,

Comments are closed.