Distinguishing real events from simulated events
Simulated events are one of the real joys of jQuery. Geniusly, to simulate an event in jQuery you simply call the method relating to the event in question (or use the trigger() method) without any arguments.
1//set up the event
2$('#tabs').on(''click', 'li', function() {
3 alert('Hello!');
4});
5
6//simulate the event immediately on the first LI
7$('#tabs li:first').click();
This is a common situation. Here, I have some tabs which, when clicked, show a corresponding tab content area, which are all hidden to start with. By simulating the event on the first tab, it is automatically turned on as though we had clicked it ourselves.
(Yes, I could avoid this by having the first tab area showing by default via CSS, but let's imagine our event handler does more than simply show and hide DIVs, but also some more complex code).
So far, so easy, and so common. But how do you differentiate a simulated event from a real one?
I had a situation the other day where I had a carousel-like widget on the page, and a bigger version of the same carousel, hidden, to appear in a lightbox should the user wish. Traversing one of the carousels (i.e. changing slide) should update the other one, too.
Consider the following.
1var carousels = $('.carousel');
2
3//listen for clicks to carousel left & right buttons
4carousels.on('click', 'button', function(evt) {
5
6 //get this carousel, other carousel and click direction
7 var this_carousel = $(this).closest('.carousel'),
8 other_carousel = carousels[$(this).is('#carousel1') ? 'first' : 'last'](),
9 direction = $(this).is('.left') ? 'left' : 'right';
10
11 //simulate the same click on the other carousel
12 other_carousel.find('button.'+click_direction).click();
13
14 //more code here...
15
16});
There, we listen for clicks to the left/right buttons of each carousel and then simulate the same click on the other carousel.
But there's an obvious problem; the simulated click uses the same event handler, so it too will invoke a simulated click on the first carousel, and so on and so on, without end.
What we actually want, of course, is for a simulated click to occur only in response to a real click. In other words, how do we spot the difference between a real and simulated event with jQuery?
Simple: with a simulated event, the originalEvent property of the event object will be undefined, whereas with a real event it will be the native event object.
1$('#tabs').on('click', 'li', function(evt) {
2 if (evt.originalEvent) {
3 //real event
4 } else {
5 //simulated event
6 }
7});
Obviously you don't have to simulate events - you could assign their callbacks to a named function and call the function manually. But then you'd have no event object passed, so this often means filling your code with lots of conditions - to work both as an event callback and as a direct function call. Simulated event calls save the need for this reengineering.
post a commentNon-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 newNumberfy: add line numbers to your textareas
Ever wished textareas had native support for line numbers?
I, and doubtless the developers behind the many JSFiddle-esq sites out there these days, have.
So I've built a jQuery plugin that does precisely that. Just target the textarea(s) you want to add line number support to via a jQuery selector, call the numberfy method, and that's it.
Head over here to download, get usage info or view a demo.
$('#myTextarea').numberfy();
This tuned out to be an interesting little thing to build. The most challenging part, obviously, was getting the numbers to appear at the right positions vertically, to take into account line wrapping.
The solution here is simple but effective; on every keypress in the textarea, the textarea's current value is split into lines, then one by one each line is added to the clone and its offset height measured.
That tells us how tall the line is, including wrapping, and tells us therefore where to position the line number.
It's worth pointing out that the clone is not actually a clone of the textarea - it's a<p> tag; the reason is the clone needs to have fluid height, so we can read its offset height. A textarea's dimensions are never fluid.
IE, sod off
It doesn't currently work correctly in IE. This is because I discovered a little-known 'bug' in IE whereby text within textareas wraps differently from text in other areas - even where they have the same word-wrap / white-space CSS properties.
I moaned about it on Stack Overflow, and the consensus so far seems to be there's nothing that can be done about it. I'll keep looking at this.
Head over here to download, get usage info or view a demo.
Enjoy.
post a commentUnderstanding Javascript self executing functions
A colleague of mine asked yesterday what the point of self-executing functions (SEF) in Javascript was. This is a common question; surely a function, by definition, contains code that you wish to execute at some point later, not right now, no?
Yes, generally. But SEFs do have their uses - if you can get over the theoretical heressy that is a function executing itself.
The most common use of these in my experience is to 'capture' current values when assigning event handlers. Consider the following problem:
1var animal = 'dog';
2$('#someLink').click(function() { alert(animal); });
3animal = 'cat';
When you click the link the alert will say 'cat', not 'dog'. This is because the event callback is evaluated when the event fires - not when it is bound. In other words, our alert will say the current value of animal - not the value as it was when the event was bound.
You could get round this by using a SEF. First a demo, then the science:
1var animal = 'dog';
2var evtCallback = (function(animal) {
3 return function() {
4 alert(animal);
5 };
6})(animal);
7$('#someLink').click(evtCallback);
8animal = 'cat';
The crucial difference here is that our event callback is generated by a SEF. We do this to take advantage of the local scope it provides. When we pass it our animal variable, it takes its own, local copy of it - entirely unconnected with the variable of the same name in the outer scope.
So even though the outer scope animal changes value a few lines later, the copy of this variable in our SEF is fixed to the value it had at the time it was captured: 'dog'.
Obviously if real-world code was as simple as the above, we could just hard-code the word 'dog' in our alert, but you get the idea.
Incidentally, jQuery provides its own shortcut to achieve the above effect, using the bind() method. (bind() is the daddy of event methods; all others, such as click(), effectively get re-routed to this behind the scenes).
It allows you to pass in event data that, like our second example above, will be used in the event callback - protected from anything that happens in the outer scope between now and the time the event fires. Event data is passed as the second argument:
1$('#someLink').bind('click', {animal: 'dog'}, function() {
2 alert(animal);
3});