Macros in jQuery

Creating a plugin for jQuery is incredibly simple and is a very useful way to abstract complex behaviours so that they can be used repeatedly as part of your jQuery “chains”.

But, when the time comes, and you’re faced with the decision to either create a jQuery plugin or to simply create a regular function, everything suddenly becomes quite complicated. First, you’ll wonder whether the piece of behaviour you want to abstract is best kept under the jQuery namespace, and then you’ll doubt its applicability to the DOM-centred jQuery chain, and then sometimes you’ll recede to something you’re much more comfortable with, a regular ol’ JavaScript function.

If we forget about the plugins available online, and we simply focus on your plugins, made and used within a specific project, then the question of whether a plugin is really the right route becomes all the more difficult to answer. Are you going to benefit from extending jQuery’s API? Will the readability of your code benefit?

For example:

function applyColors(elems) {
    $(elems).css({
        color: config.color,
        backgroundColor: config.bgColor,
        borderColor: config.bdColor
    });
}
 
// Call it:
var myElems = $('div.something');
applyColors(myElems);

applyColors encapsulates some behaviour that is needed frequently, and that’s why it’s been abstracted into a function. To some, this approach is lacking in that it doesn’t harness the full power of jQuery, and more specifically, jQuery’s plugin mechanism. How about this:

jQuery.fn.applyColors = function( {
    return this.css({
        color: config.color,
        backgroundColor: config.bgColor,
        borderColor: config.bdColor
    });
};
 
// Call it:
$('div.something').applyColors();

Cleaner? More readable? I think so.

Many developers are not prepared to extend jQuery’s API with their own simple abstractions. I don’t know why. But, I hope, that jQuery macros can help in lowering the barrier to extending jQuery.

The humble macro

(the following examples require jQuery.macro, which you can get at Github!)

If we take the example from above, we can create a macro with the same functionality:

jQuery.macro('applyColors').css({
    color: config.color,
    backgroundColor: config.bgColor,
    borderColor: config.bdColor
});
 
// Call it:
$('div.something').applyColors();

jQuery.macro allows you to record a set of jQuery method calls, and it will create a regular jQuery plugin that will play back the macro. A more verbose example:

var myMacro = jQuery.macro('myMacro');
myMacro.css('color', 'red').scrollTop(0).addClass('foo');
 
jQuery('div').myMacro(); // All that stuff happens! css()->scrollTop()->addClass()
 
myMacro.removeClass('bar'); // Record another action
 
jQuery('div').myMacro(); // css()->scrollTop()->addClass()->removeClass()

Calling jQuery.macro will give you a macro object, which you can think of as a blank disc that will record anything you do.

This is obviously not a replacement for regular jQuery plugins; it’s simply an easier way to abstract multiple simple behaviours, without having to get caught up in the plugin dilemma.

// Record a macro:
jQuery.macro('foo').css({
    color:'red',
    border: '1px solid #000'
});
 
// Create a plugin:
jQuery.fn.foo = function() {
    return this.css({
        color:'red',
        border: '1px solid #000'
    });
}
 
//... Both have, effectively, the same result ...

jQuery.macro aims to lower the barrier to extending jQuery’s API — to rid your global namespace of misplaced functions — to spare you the superfluous function notation when creating a plugin that only calls a bunch of jQuery methods.

Like I said, this is certainly not a replacement for regular plugin creation. When recording a macro you are without many luxuries, like a nice closure to work within, plugin arguments/options, using jQuery getters etc.

A jQuery plugin is something that allows you to deeply embed your abstracted behaviour within the jQuery API, a macro is essentially just a list of method calls — even though each macro disguises itself as a plugin, it is meant for a wholly different purpose.

Please download and experiment with jQuery.macro.

To round off this post, a last ditch example:

jQuery.macro('cousins').parent().siblings().children();
jQuery('#elem').cousins(); // YEH!

Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!