ajax
November 9th, 2013

How to sprinkle magic fairy dust in your AJAX

Tom Boutell
Chief Software Architect

This code is terrible!

Do it and you'll wonder why users of different browsers get different results. For some, clicking any button element  does exactly what you want it to. For others, nothing happens. That's because the document might not be ready for JavaScript to "see" all of its elements yet.

$('button').click(function() { alert('Do amazing things'); return false; });

So this is a little better:

$(document).ready(function() { $('button').click(function() { alert('Do amazing things'); return false; }); });

This way, we wait for jQuery to tell us that the document is ready before we add our event handlers.

And this is... no better or worse, but more concise:

$(function() { $('button').click(function() { alert('Do amazing things'); return false; }); });

See what I did there? I skipped $(document).ready and just passed a function directly to jQuery. This is such a common and important feature that the jQuery developers saw fit to bake in this shortcut. Handy, yes?

But... it's still pretty terrible!

Why? Because of this bit:

$('button').click(...);

What's wrong with this? Nothing... if you can swear to me on a stack of graphic novels that you will never add another button to your page "on the fly." For instance, as part of an AJAX response from your server, like this:

 

$.get('/go-get-more-content', function(html) { $('body').append(html); });

This works great until you try to click on the buttons and they do nothing. That's because when you wrote:

 

$('button')

You grabbed all the buttons that existed in that page at that moment. But you said nothing about buttons that are born later.

This is why the jQuery developers saw fit to bless us with this feature:

 

$('body').on('click', 'button', function() { alert('Aha!'); return false; });

Wait, what just happened? I bound a click handler to the body element... and then filtered out all the clicks except those on my buttons.

Since your page always has a body, this works even if a button is added to the page after the event handler is created. And this is why it is so effective when you wish to mix AJAX magic with jQuery fairy dust. Or just think someone else using your code might want to.

"But what if I care which button was clicked?" Good question. It would be a bummer if we couldn't tell. But hey, we can:

 

$('body').on('click', 'button', function() { $(this).closest('article').css('background-color', 'red'); return false; });

When you filter events with "on," jQuery makes sure "this" refers to the element that matched the filter. So it'll always be the button... not the body. Here I use "closest" to find the "article" tag that contains my button and change its background color.

"You keep using 'return false.' Isn't that terrible too?"

You can have "return false" when you pry it from my cold dead... ahem, that's not a constructive response! Let me try again.

Here's my code without "return false":

In a jQuery event handler "return false" is equivalent to...

 

$('body').on('click', 'button', function(e) { // Do whatever I wanted to do, then... e.stopPropagation(); e.preventDefault(); });

stopPropagation means "don't bubble up and fire this event on other elements that enclose it," and preventDefault means "don't do whatever this element naturally does when you click on it." A simple "return false" does both. This happens to be what I want, in a jQuery event handler, almost all the time. But if you want to be more specific, you can.

Tom Boutell
Chief Software Architect