Monday, January 30, 2012

Find the jQuery Bug #3: Give Me Truth

Introduction


In this open-ended series I'll be showcasing a snippet of buggy jQuery code that you might encounter, explain what the problem is, and then identify how you can easily resolve the issue.

You can view other posts in this series...

The Desired Feature


We want to take the following HTML div and build a simple popover module that uses the jQuery UI Dialog widget.


The Buggy Code


The intent of the following code snippet is to verify that the div being used in the modal dialog exists in the DOM and that it has not been already initialized. We have an init method that should initialize the widget and then an open method that should launch the dialog. The code snippet has some problems. Do you see them?


You can view, run, and edit the above code sample from JSBin.

When we run the code, things seem pretty much like what we expected. The widget seems to have been initialized and then opens as expected. We don't actually see the error visually, but there is a problem hidden in the init() code. If you debug through the code you'll notice something odd when calling init() again. We put in checks to verify for the existence of the DOM element and that it hasn't been initialized yet, right? Well, that is where the problem lies. Do you see the problem now?

The Underlying Problem


At the root of the problem is how we are testing for truth! Both the ways we are checking for existence and initial state are flawed.

"jQuery( selector [, context] ) Returns: jQuery
Description: Accepts a string containing a CSS selector which is then used to match a set of elements." --http://api.jquery.com/jquery

The check for existence is incorrect, because just checking the result of $( "dialog-modal" ) doesn't get you anywhere. Where does it get you? It always evaluates as true because a call to $( selector ) returns the jQuery object and based on our truthy/falsey rules an object is always truthy! What does this mean? Well, it means the code to initialize the DOM element would execute even if it didn't exist at all!

".not( selector ) Returns: jQuery
Description: Remove elements from the set of matched elements." --http://api.jquery.com/not

In a similar fashion the check we were making for the initial state of the DOM element is incorrect. The intent was to check if the element did not have a particular class (.ui-dialog-content), which is added by jQuery UI when it initializes the dialog widget. If we take a look at the documentation for the .not() method we'll see that it too returns the jQuery object! The purpose of the method is to filter down the matches by removing elements that match the selector provided. So, we run into the same problem as above where we are always evaluating as truthy!

A Solution


The solution to fix these problems are really simple and straightforward. All you really need to do is to check how many items where matched by a jQuery selection and to use the .is() method instead of the .not() method.


You can view, run, and edit the above code sample from JSBin.

If you run the above code again you'll notice that the console.log message only shows up the first time the .init() method is called.

Conclusion


The key concept to remember here is to realize that testing against the jQuery object will always evaluate as true. As shown above it is easy to adjust your code accordingly.

There are other more advanced techniques that can be used for initialization as well. I'd encourage you to look into Doug Neiner's (@dougneiner) Contextual jQuery series (Part 1 and Part 2) that he gave at the jQuery Boston 2010 and 2011 Conferences. In particular he gave some really interesting just-in-time initialization techniques in the 2nd talk. I highly encourage you viewing these if you haven't already.

Until next time...

No comments:

Post a Comment