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!
Hi James,
I like your article – it made me discover the macro plug-in (I will have a look at it) and also made me want to write my point of view of your thoughts:
Extending the way that you can work with a javascript framework like jQuery can in theori only be good. In practic keeping your code unified and simpel has many advantages. If people share code it can be a good idea to use common programming patterns, like agreeing on using plug-ins. That is one way of doing things, and people will spend time learing to optimize their code – based on an agreement on using plug-ins. If the same thing can be accomplised using macros, fine – but you might see it as a problem if one day use use plug-ins, and another day use macros.
All the people sharing code then have to understand macros, the macro plug-in should then (sometimes) be included and that will force the programmers to have to consider when to use macroes and when to use plug-ins. My experience as a programmer is that standards are good. When you after 3 months have to look back at a soloution, or have to look at code by collegue, it is much easier if you have some commen programming patterns like: We use plug-ins.
I am not saying that the macro plug-in is not usefull, actually if it turns out that many people use it and it shows up as a very powerfull and cool plug-in, why not go for a target like making it a build-in feature of jQuery? I am sure that making it even easier to use jQuery will just make it stronger!
Happy new year!
Hi Sten, thanks for your comment.
I agree with you on many of your points. I don’t really think having another addon mechanism is a good thing… I just like writing about different ideas and paradigms. Macros are far from perfect, and are certainly not something I would want to see out there in the real world, but I still think its fascinating to explore such territories.
I hope people will take this article and the macro mechanism with a pinch of salt. I only created it to see if it was possible. jQuery’s highly abstracted API offers a lot of possibilities, much more so than other libraries.
The real crux of this article was in the introduction:
After thinking about different ways to tackle this, I came up with this simple macro solution. Creating it and writing this article has made me think about the merits of such an approach; and I’m probably more against it than I am for it…
But, then, I wonder what direction jQuery will go in. Every day it’s getting adopted by new people — many of whom don’t know squat about JavaScript. I am both excited and worried about this kind of uptake. Few of the beginners (and even the “professionals”) seem to be harnessing the full power and flexibility of jQuery.
Cool, I hadn’t heard of this project before. I am going to have to take a look inside the hood at the macro plugin’s code.
As an aside (sort of):I think one of the problems with talking about “macros” is that there are so many types of macros, the Holy Grail of them being Lisp macros.
I used some very simple regex pre-processors to extend the syntax of JS here: http://fitzgeraldnick.com/weblog/3/ Note that it is just a proof-of-concept and not meant for use beyond that page.
The response to that post I made was overwhelmingly that if you want real Lispy macros in JS, you have to use ParenScript.
Setting up ParenScript can be a little daunting for some. You have to get a Common Lisp implementation up and runing, then figure out asdf and install ParenScript. But even once that is done the time from writing code to debugging is too long because there is no REPL or interactive shell for ParenScript.
Well, until now.
You may or may not have heard of CoffeeScript (http://jashkenas.github.com/coffee-script/), but after it was released, Tom Robinson (of 280 North, people who started Narwhal JS (http://narwhaljs.org/)) integrated CoffeeScript in to Narwhal and provided a REPL for it. Very cool! CoffeeScript is awesome.
He posted on the mailing list for Narwhal if anyone wanted to give a shot at doing the same for ParenScript, and I stepped up to the challenge. As of right now, the REPL is working great for ParenScript, but the module as a whole needs a little more polish.
Check it out here: http://github.com/fitzgen/parenscript-narwhal
Right now it assumes you use SBCL for your common lisp implementation and that you have ParenScript installed already. I am working on fixing it up a little and providing first a set of steps for installing SBCL and ParenScript, then I want to write a script to do it automatically, and lastly I want to lose the SBCL dependency, if I can. We will see.
Here is an example of the REPL:
And here is a very simple example of using macros to extend the language’s syntax.
Source:
Compiles to:
Hey. I like this solution for a problem which I have come across many times; when to choose a repeated function, when to choose a plugin.
The only thing that I’m missing is to use this in an element. An example of what I mean:
You could extend this then also with an end macro for more usefulness:
gr J
Pretty interesting article here… I’ve never worked with macros before but it seems like an interesting area I could get into. Thanks for sharing!
Very interesting stuff. I especially like the way you register the existing FN-scoped methods; will work seamlessly with new plugins as they are added to the jQuery FN name space.
jQuery.macro is great and useful plugin. Thanks for simple and cool post!
Very interesting. One thing that I want to add to Ben Nadel’s comment is that this will work with existing plugins if, and only if, this plugin is loaded *after* those plugins have registered themselves.
Something else to consider: If the goal is not to extend the jQuery API, then as you know, this macro plugin does break that goal. What if, instead, the macros were kept to the macro object. For example
This way, you can leave the jQuery object a little less cluttered. Naturally, the downside to this is if that macro ever needs to become a full fledge plugin, you would have to do more refactoring to make the conversion.
Hi James, thanks for this useful article.
But I saw the first time macro plug-in. This plugin look like very useful. good works.
I don’t understand. jQuery natively can link multiple effects on one line.
What is different between:
and simply:
@jerone, I did consider something similar to
endMacro()
but ended up scrapping it — it was getting a bit too complicated IMO.@K.Adam, good point! I did consider this too… But I thought the plugin-like syntax would be a little cleaner.
@Dss, there is no difference — that’s the point!
Macros are useful in the way that plugins are useful… You don’t need to write out all that code every time you want to use it.
@Nick, some interesting stuff there! I’ve looked at CoffeeScript, but I can’t say I’m very enthused. Using CS would be the same as using GWT and compiling to typically less-than-good JavaScript. It’s JS for people that don’t like JS…
ah ok.. now I get it
James, I don’t think it’s quite fair to lump CoffeeScript (and ParenScript) in to the same group as GWT (or Pyjamas, etc…).
Both CS and PS expressions compile one-to-one with JS, keep the same variable names, and even indent properly so that the compiled JS looks “handwritten” or pretty printed. This makes a world of difference when debugging; perhaps the most common complaint of X-to-javascript compilers.
GWT, on the other hand, is a full blown Java framework and GUI toolkit. Who knows what kind of code ends up being generated?
I think in most cases its language geeks having fun. Personally, I love a lot about JS and dislike some too (Crockford has pointed out the beauties and flaws of JS so completely that it is almost cliche to reference or quote him any more). I think there is nothing wrong with exploring an alternate syntax; Because that is really all CS and PS are (with the exception of ParenScript’s macros, but I digress).
I will risk being labeled cliche: I believe it was Crockford that has the somewhat famous quote about JS being Lisp in C’s clothing or whatnot; why not see what it looks like when we strip the C syntax from it?
PS: I like the new design on your site, its snazzy 🙂
@Nick, I probably was a bit quick to judge. In a flame war, I’d be one those people blindly defending JS until the cows come home… To me, there’s no alternative, it’s great!
CS does look pretty cool, and kudos to the creator, but, for me, its yet another abstraction that fuzzes the details. I know I can’t really talk, being involved with one the highest abstracted programming stacks on the planet (the “web” stack).
Anyway, thanks for the info. I can see why CS (and other similar things) would be appealing to some, but for me, having being brought up with C-like languages, I feel quite at home with JS. I have been looking at Perl recently though, which is pretty cool.
What is the purpose of the second argument of
register
function? Not used.@sedat:ah i see… now I get it.
Thanks.