As things currently stand, the vast majority of event handling between JavaScript and the DOM is somewhat primative; only involving the element being affected by the event. An example of this is a simple rollover effect:
$(elem).hover(function(){ $(this).addClass('over'); }, function(){ $(this).removeClass('over'); }); |
The only effect induced by the event is on the element itself. Even with something as simple as the above example there’s a lot of uneccessary bloat, all of which could be avoided by a simple abstraction; consider this plugin:
$.fn.hoverClass = function(className){ return this.hover(function(){ $(this).addClass(className); }, function(){ $(this).removeClass(className); }); }; // Usage: $(elem).hoverClass('over'); |
While that hugely simplifies the process it doesn’t provide much scope as a plugin; it can only be used for adding and removing a class when an element is hovered over. These kind of abstractions, as far as I’m concerned, are perfectly welcome; anything that makes the end product more readable is a bonus!
On the topic of readability, a thread came up a while ago on jQuery’s development group within which the benefits of “ultra-chaining” were discussed; a term first highlighted by John Resig in a blog post back in ’08. It’s definitely worth a read!
The resulting proposals were all of the same basic idea; provide methods to enable more seamless chaining in jQuery. Here’s an example:
// From the Dev thread: jQuery("div").hide("slow") .wait() .addClass("done") .find("span") .addClass("done") .end() .show("slow") .wait() .removeClass("done") |
As John stated in his post, this style, if adopted, would transform jQuery into a domain-specific-language, one with little or no resemblance to JavaScript as it normally exists.
Take what you will from John’s post and the enthralling discussion that followed. I for one am not too fussed either way; the syntax itself is seamless and somewhat more graceful than current techniques but, if adopted, it would fall much more hardly on the developer to ensure proper indentation, without which the readability of the code would suffer massively.
I was only really interested in one aspect of this new chaining technique; event handling. Throughout the Dev thread there were a number of similar suggestions for easier event handling in jQuery; here’s one example (proposed by Ariel Flesler):
$('div') .when('click') .addClass('active') .text('Hey') .done() // Also possible with on() .on('mouseout') .removeClass('active') .text('Ho') .done(); |
As Ariel stated, this would not be a replacement for the conventional callback technique; it’s just a convenient shortcut for the primitive event handling I mentioned before (an event triggers on an element; something happens to that element). While unsuitable for many scenarios, the “ultra-chaining” technique does offer a refreshing simplicity.
My only issue is with the fact that code could quite quickly become a nightmare to maintain. I’d be uncomfortable using it because of its dependence on indentation. I much prefer having the code I write represent the logical outline of the actions performed through the code; if a piece of code is not executed until later (e.g. a callback) then it should be logically contained in such a way that it infurs when and if it will execute; unfortunately “ultra-chaining” does not provide this explicit way of containing functionality (in fact, it does the opposite).
After reading through the discussion surrounding ultra-chaining and other similar endeavors I thought I may as well have a crack at it. Like I said, the only part that really interested me at this point was the event handling. So, in my efforts to create a simple and readable alternative to callback functions I eventually came to the conclusion that method chaining (or “ultra chaining”) was not the way to go; like I said before, it’s not well contained and the readability of it relies too heavily on indentation.
Function’less callbacks
// Regular callback functions: $(elem).hover(function(){ $(this).addClass('over'); }, function(){ $(this).removeClass('over'); }); // Callback objects: $(elem).hover({addClass: 'over'}, {removeClass: 'over'}); |
I’m not just musing here; you can check out the source which makes the above notation (“callback objects”) possible.
Instead of passing a function you can pass a regular object with key-value pairs. Each property name of this object is executed as a jQuery method with arguments specified in the corresponding property value. So, in the above example, jQuery’s addClass
method would be executed with ‘over’ as its only argument.
I know what some of you are thinking; abstracting away the functional side of event handling is dangerous and relinquishes necessary control. While I agree with you (mostly) it’s important that you note the following:
- No control is relinquished; you can still pass regular functions as handlers and they will be executed just as before. If a function and a data object is passed then the data object will be treated as before.
- Much of event handling involves simply changing something on the corresponding element (E.g. Hover over a table row and it changes colour). This extension acknowledges this common requirement.
Other examples:
Here are some examples of the ways in which you can use the extension:
// E.g. #1 $('a').bind('click.namespace', { addClass: 'clicked', animate: [{fontSize: '+=2px'}, 300] }); // E.g. #2 $('div').click({ css: { color: 'black', background: 'orange' }, attr: { id: 'something' } }); // E.g. #3 var over = { css: ['color','red'], unbind: ['mouseover', over] }; $('h1').mouseover(over); |
Note: jQuery’s bind
method accepts three parameters by default: an event type, a data object and a callback function. This extension will only activate when you pass an object without a function as the third parameter (as shown in #1 above). If you pass all three parameters then the second (object) will be treated as a regular data object.
I hope this has provided you with some perspective on ultra-chaining and its alternatives. Please feel free to make use of the extension (download), I’ve been using it without a problem so far…
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
I’ve only been using jQuery for about 4 months, and mostly only on my twitter project, tweetlens.com. So I’m not really a JS pro (yet) 🙂
Anyways, I looked through your JS code a bit, this is really just short cut on top regular jQuery and passing anonymous functions.
So this:
instead of:
I don’t really see the advantages, readability isn’t even really that improved. IMHO.
This is great. Added to tutlist.com
Very interesting post. I hadn’t seen the “ultra chaining” discussion and I agree with the issues you’ve pointed out. I think you’re alternative method is pretty darn sweet. I like the fact that if you want to pass the typical callback function you can. Your method certainly cleans up the code a bit and makes it more structured, which is awesome.
Really nice concept. There are many cases which this would be useful. I’d like to see this implemented along with the current event functions.
My only reservation is that it feels like it breaks some of the basic concepts of jQuery. On of the big rules is that you don’t pass functions to be called in as strings. But since this rule is broken for event type, maybe this would just work along side it.
Keep up the great work!
I Hope this will be implemented in some kind of way in future release of JQuery. Good work James!
I just can’t keep starring your posts lately, this is really great!
After a bit digging I’ve found the original discussion where this thing came up, there were some other interesting solutions for this:
http://groups.google.com/group/jquery-dev/msg/727536a1f11c5921
http://groups.google.com/group/jquery-dev/msg/6611968e3dfde574
http://groups.google.com/group/jquery-dev/msg/c0a13ce5067ab784
But I think yours pretty much beats beats them, the only problem I think is that you can’t specify the order of execution of the commands, so you can’t use `find` or anything that relies on it. A simple solution would be if you could (optionally) wrap the objects in an array, though it might be a bit overkill.
And watch out for the next release as it looks like there will be some important changes in the event handling code: http://dev.jquery.com/changeset/6344
This smells on Perl.
I am in Perl last few days and main problem is its dirtiness.
Anyway this is “nice to know”, but I would not recommend anyone to use this.
@Benson Wong, yes, this is a shortcut; no extra functionality is available. The point in the extension is to simplify the process of adding primitive event handlers. Like everything, the effectiveness of the extension is subjective – I for one do see the benefits and I think it improves readability and rids the code of unnecessary bloat.
@Paul, I know what you mean and I feel the same way while using it; I like having low-level control, especially when it comes to callbacks. The extension is just additional layer of simplicity for those who want it.
@Balazs, Thanks for your comment. I think adding any more functionality (like specifying execution order with arrays) would pretty much dispel the need for the extension in the first place; if someone needs to use ‘find’ or other methods of that type then they should definitely stick to using a callback function. Btw, that upcoming change looks promising; definitely a useful addition!
@Marko Simic, maybe you could expand your argument?