jQuery Events: Stop (Mis)Using Return False

Probably one of the first topics covered when you get started learning about jQuery events is the concept of canceling the browser’s default behavior. For instance, a beginner click tutorial may include this:

$("a.toggle").click(function () {
    $("#mydiv").toggle();
    return false; // Prevent browser from visiting `#`
});

This function toggles the hiding and displaying of #mydiv, then cancels the browser’s default behavior of visiting the href of the anchor tag.

It is in these very first examples that bad habits are formed as users continue to use return false; whenever they want to cancel the default browser action. I am going to cover two very important topics in this article relating to the canceling of browser events:

  • Use the right method for the job: return false vs. preventDefault, stopPropagation, and stopImmediatePropagation
  • Top, bottom or somewhere in the middle: where in the event callback should you cancel default behavior?

Note: in this article when I refer to event bubbling, I am talking about how most events will fire on the original DOM element, and then on each parent element in the DOM tree. Events do not bubble to siblings or children (When events “bubble” downward, it is called event capturing). Learn more about bubbling and capturing.

Use the Right Method for the Job

The main reason return false is so widely misused is because it appears to be doing what we want. Link callbacks no longer redirect the browser, form submit callbacks no longer submit the form, etc. So why is it so bad?

What return false is really doing

First off, return false is actually doing three very separate things when you call it:

  1. event.preventDefault();
  2. event.stopPropagation();
  3. Stops callback execution and returns immediately when called.

“Wait a minute,” you cry! I only needed to stop the default behavior! I don’t need these other two items… I think.

The only one of those three actions needed to cancel the default behavior is preventDefault(). Unless you mean to actually stop event propagation (bubbling), using return false will greatly increase the brittleness of your code. Lets see how this misuse plays out in a real world scenario:

Here is our HTML for the example:

<div class="post">
    <h2><a href="/path/to/page">My Page</a></h2>
    <div class="content">
        Teaser text...
    </div>
</div>
<div class="post">
    <h2><a href="/path/to/other_page">My Other Page</a></h2>
    <div class="content">
        Teaser text...
    </div>
</div>

Now lets say we want the actual article to load into the corresponding div.content when a user clicks on either title link:

jQuery(document).ready(function ($) {
   $("div.post h2 a").click(function () {
      var a    = $(this),
          href = a.attr('href'), // Let jQuery normalize `href`,
          content  = a.parent().next();
      content.load(href + " #content");
      return false; // "cancel" the default behavior of following the link
   });
});

All is well (currently) and our dynamic page is well under way. Down the road we decide another piece of functionality we want is to add the class "active" to any div.post that has been clicked (or when a child element has been clicked) most recently. So, we decide to add a click handler to them as well:

// Inside Document Ready:
var posts = $("div.post");
posts.click(function () {
    // Remove active from all div.post
    posts.removeClass("active");
    // Add it back to this one
    $(this).addClass("active");
});

Will this work when we click a title link? NO! The reason it won’t work is because we used return false in the link click event instead of using what we really meant! Because return false really means event.preventDefault(); event.stopPropagation(); the click event never bubbled up to the parent div.post and our new event has not been called.

This becomes even more of an issue when mixing normal events with live or delegate events:

$("a").click(function () {
    // do something
    return false;
});

$("a").live("click", function () {
    // THIS WON'T FIRE
});

So what DO you really want?

preventDefault()

In most situations where you would use return false what you really want is e.preventDefault(). Using preventDefault requires you allow for the event parameter to be accessed in your callback (In this example, I use e):

$("a").click(function (e) {
    // e == our event data
    e.preventDefault();
});

This does everything we want without prohibiting parent elements from receiving these events as well. The fewer restrictions you place on your code the more flexible it will be to maintain.

stopPropagation()

Sometimes you just want to stop the propagation. Take the following example:

<div class="post">
    Normal text and then a <a href="/path">link</a> and then more text.
</div>

Now, lets pretend we want one thing to happen when you click anywhere in the div except on the link, and you want the user to actually be able to follow the link if they click on it. (From a usability standpoint, this is a poor example. You probably don’t want something else to happen if a user slightly misses clicking on the link!)

$("div.post").click(function () {
   // Do the first thing;
});

$("div.post a").click(function (e) {
    // Don't cancel the browser's default action
    // and don't bubble this event!
    e.stopPropagation();
});

In this case if we had used return false the div’s click event would not have fired, but the user would also not have been directed to the correct destination either.

stopImmediatePropagation()

This method stops any further execution of an event, even to other handlers bound on the same object. All events bound to a particular item will fire in the order they were bound. Take the following example:

$("div a").click(function () {
   // Do something
});

$("div a").click(function (e) {
   // Do something else
   e.stopImmediatePropagation();
});

$("div a").click(function () {
   // THIS NEVER FIRES
});

$("div").click(function () {
   // THIS NEVER FIRES
});

If you think this example looks contrived, it really is. However, the situation is a very real one. As you build more abstracted code, different widgets and plugins may be adding events to the same code you are working with. This makes understanding and using stopImmediatePropagation worth while when you come across a situation that needs it!

return false

Only use return false when you want both preventDefault() and stopPropagation() and your code can support not canceling the default behavior until you reach the end of your callback execution. I strongly encourage against using this method in any examples you may write for new jQuery developers. It promotes a poor use of event cancellation and should only be used when you are consciously deciding you need what it provides..

Top, Bottom or Somewhere in the Middle

Before, when you were (mis)using return false, it always had to go at the end of your function, or at least at the end of a particular line of logic where no further execution was needed. With e.preventDefault we have more choices. It can be called at any time during a callback function to take effect. So where should you put it?

1. During development, it should (almost) always be the very first line. The last thing you want is for your form you are trying to AJAXify to actually submit to another page while you try to debug a JavaScript error in the callback.

2. In production, if you are following progressive enhancement, put it at the bottom of the callback, or at the end of execution flow. If you are progressively enhancing a normal page, then your link click event or form submit event has the proper server side fallbacks needed for browsers that don’t support JavaScript (or don’t have support enabled). The benefit here, however, is not related to browsers with JS turned off, but rather browsers with it turned on in the situation where your code throws an error! Take a look at the following code:

var data = {};
$("a").click(function (e) {
    e.preventDefault(); // cancel default behavior

    // Throws an error because `my` is undefined
    $("body").append(data.my.link); 

    // The original link doesn't work AND the "cool"
    // JavaScript has broken. The user is left with NOTHING!
});

Now, lets take a look at the same event with the preventDefault call being placed at the bottom:

var data = {};
$("a").click(function (e) {
    // Throws an error because `my` is undefined
    $("body").append(data.my.link); 

    // This line is never reached, and your website
    // falls back to using the `href` instead of this
    // "cool" broken JavaScript!

    e.preventDefault(); // cancel default behavior
});

The same applies for form submit events as well, provided you have the proper fallback information in place. Never count on your code always working. It is far better to plan for a nice fallback than assume errors will never occur!

3. In production, if the functionality is JavaScript only, keep the call on the first line. Remember, it doesn’t necessarily have to be the first line in the function, but it should come as early as fits with your program logic. The concept here is this: If this part of your functionality was added with JavaScript in the first place, then a fallback is not really as necessary. In this case, having it first will just ensure random # characters don’t appear in the URL or cause the page to jump around. Obviously, provide as much error handling as needed to ensure your users aren’t left with nothing for their efforts!

Conclusion

I hope this article presented enough information for you to make the right choice when you cancel events. Remember to only use return false when you really need it, and make sure you cancel the default behavior at the right location in your callback. Always work to make your code as flexible as possible, and stop using return false!

Doug Neiner is an Editor at Fuel Your Coding and an official member of the jQuery Team. He is addicted to new technology, and specifically loves spending time with WordPress, Ruby on Rails and jQuery. Learn more via twitter or his Google Profile.

 

If you liked this article, please help spread the news on the following sites:

  • Bump It
  • Blend It
  • Bookmark on Delicious
  • Stumble It
  • Float This
  • Reddit This
  • Share on FriendFeed
  • Clip to Evernote