Article by me in next issue of .NET
You might recall I did a tutorial last year for .NET Magazine, an introduction to regular expressions. Well, the next issue (?) will feature another one from me - this time, a jQuery catch-up piece.
The premise is this: many developers got comfy with earlier versions of jQuery. A scout around some big traffic sites proves this; many are still on 1.4 or 1.5.
But jQuery hasn't sat still; it has developed, and at some pace. The article will look in turn at jQuery 1.5, 1.6 and 1.7, covering some of the main features in each release:
Deferred objects
I'm a particular fan of the deferred objects concept, and jQuery's implementation of it is a good one. They offer a wealth of flexibility with regards to how you approach callbacks to asynchronous - or even synchronous - operations. These arrived in jQuery 1.5 as part of a major overhaul to jQuery's AJAX module, and are therefore probably the most notable feature of jQuery in recent years.
In the article I'll show not only how they can be used with AJAX (their most common haunt) but also for situations other than AJAX. I demonstrated this here a few months ago when I showed how deferreds could be used to make an interactive word game.
Attributes vs. properties
1.6 was a quieter affair; no new major features, but a handy tidy-up of how jQuery handles attributes vs. properties. Until 1.6, both had been dealt with via the attr() method. Whilst helpful, this unhelpfully gave the impression that attributes and properties were one and the same.
If you're not sure of the difference, I go into it fully in the article.
on() / off()
Of course the big arrival in 1.7 was an encapsulated approach to events. By 1.7 the events API had got pretty messy, with all sorts of methods kicking around. 1.7 introduced on() and off(). The former can be used for both direct and delegated events, such that no other method is required (previously, if you wanted to delegate, you had to look at live() or delegate()).
XMLPlayground
While we're talking about .NET, I was pleasantly alerted on Twitter today to the fact that they featured my very own XMLPlayground.com in their list of top recent tools. Happy days!
1 comments | post newNon-AJAX use for jQuery's deferred objects
I'm currently writing another article for .NET magazine on the newer features of jQuery, for developers who perhaps got comfy with jQuery 1.3 or 1.4 and didn't keep up.
One of the obvious candidates for the article is deferred objects, which landed in jQuery 1.5 as part of the overhaul to jQuery's AJAX module.
AJAX is the obvious use-case for deferred objects, and it's simple to come up with examples for that. But I was also trying to show a non-AJAX example.
I'm talking about cases where you would manually make your own deferred objects and apply subjective logic as to whether, when and how it is resolved or rejected. So other forms of asynchronous code execution.
This is quite a different beast from using deferreds with AJAX, since, at least usually, jQuery's AJAX methods automatically create, return and resolve/reject deferred objects for you. In other words, you can use deferreds in an AJAX context without ever going near methods like $.Deferred(), $.when() or deferred.resolve().
Click the vowels
I eventually came up with a slightly contrived game for children where they have to identify and click the vowels in a sequence of words. Each vowel would constitute a deferred object. When clicked, the vowel fades out and its deferred object is set to resolved. When all deferreds are resolved (i.e. all vowels have been clicked), we give feedback and move on. I think it's quite a nice pattern.
You can see a demo of the game here.
First, some simple HTML:
1<h1>Click all the vowel letters</h1>
2<div id='words'></div>
And CSS:
1#words { height: 100px; }
2#words div { display: inline-block; width: 100px; height: 100%; text-align: center; font-size: 50px; line-height: 90px; margin-right: 10px; background: #e50; color: #fff; cursor: default; }
3#words div:last-child { margin: 0; }
Now on to the JS (all inside a DRH, of course, as we're dealing with the DOM).
1//prep
2var
3words = ['square', 'hosepipe', 'canine', 'flower'],
4container = $('#words'),
5vowels = 'aeiou',
6next_word_index = 0;
All rather self-explanatory. Now for the bulk of the code:
1function build_word(word) {
2
3 //increment the next-word index
4 next_word_index++;
5
6 //remove the previous word, if any
7 container.empty();
8
9 //an array to store our deferreds (one for each vowel)
10 var deferreds = [];
11
12 //loop over the word's letters
13 for (var i=0, len=word.length; i<len; i++) {
14
15 var
16 letter = word.substr(i, 1),
17 isVowel = vowels.indexOf(letter) != -1,
18 letter_div = $('<div />', {text: letter}).appendTo(container);
19
20 //if this letter is a vowel...
21 if (isVowel) {
22
23 //set up a deferred object for it and log it in the array
24 var deferred = $.Deferred();
25 deferreds.push(deferred);
26
27 //on click, fade it out then resolve its deferred
28 letter_div.click({deferred: deferred}, function(evt) {
29 $(this).animate({opacity: .2}, evt.data.deferred.resolve).unbind('click');
30 });
31 }
32 }
33
34 //when all deferreds are resolved, do feedback and move on
35 $.when.apply(null, deferreds).done(function() {
36 var msg = 'Well done - you got all the vowels! ';
37 if (words[next_word_index]) {
38 alert(msg+"Let's try the next word...");
39 build_word(words[next_word_index]);
40 } else
41 alert(msg+"That's the end of the game!");
42 });
43}
44
45//on page entry, do the first word
46build_word(words[next_word_index]);
A few points
Hopefully the comments make it possible to follow what's going on there, but here's some points of particular note.
Firstly, I invoke $.when not directly but via the native apply(). This is because $.when() does not presently allow you to pass multiple objects as an array, which is necessary for my example. apply(), as you may know, allows you to stipulate arguments to a function as an array, so problem solved.
(If you're new to $.when(), I'll be covering that in a separate post, as it has a lot to offer your patterns.)
Secondly, in a production environment it would be prudent to expose not the deferreds themselves but their inherant promise objects (via the promise() method) instead. This allows environmental code to bind callbacks to them but not interfere with their state or progress. See the jQuery API page on promise objects for more detail.
Summary
I reiterate that this is a slightly contrived example, to highlight the use of deferreds independently of AJAX, but I think it's quite a nice pattern.
Of course, the same effect could be achieved several other ways without deferreds; one could continually check, in the fadeout callback, whether there were any vowels still remaining at full opacity. If no, the user has clicked all the vowels. That would require a more complex fadeout callback, but it would of course work.
5 comments | post new