Find the jQuery Bug #5: Defective Data

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 build a simple jQuery Plugin that will add a confirm message to a simple button or anchor.

The Buggy Code

The following code snippet is our first attempt at building the plugin, but there is a subtle error. Do you see it?

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

The developer initialized the buttons on the page and then wanted to update the prompt for the first button. He removed all the event handlers added by the plugin, updated the attribute, and then re-initialized the button with the plugin. Unfortunately, this technique didn't work as intended.

The Underlying Problem

At the root of the problem is an issue when trying to use the .attr() method after already using the .data() method.

"Only the .data() API reads HTML5 data-* attributes, and it does so once." -- Dave Methvin http://www.learningjquery.com/...

The jQuery plugin reads the HTML5 data- attribute first, and the developer tried to update the data- attribute later with the .attr() method. Once the jQuery plugin is re-initialized on the DOM element it will not read the data-* attribute again, but instead use the data it retrieved the first time.

A Solution

The solution to fix this problem is really simple and straightforward. All you really need to do is to change the code where the data-* attribute was updated to use the .data() method instead of the .attr() method.

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

If you test out the code again below you'll notice that it behaves as expected. When you click on the button it will use the updated text that was provided before the plugin was re-initialized.

You may notice that I also changed the key parameter to the .data() method to camelCase instead of the dashed version I had previously. As of jQuery 1.6 the library has changed direction in how they deal with HTML5 data-* attributes.

"The treatment of attributes with embedded dashes was changed in jQuery 1.6 to conform to the W3C HTML5 specification." -- http://api.jquery.com/data...

Now, you can still reference that HTML5 data-* attributes using their dashed keys, but jQuery will first attempt to use the key "as is" and only attempt to convert that to a camelCased version if it failed. See the following comment from the Dave's article I referenced above.

"Because many people will use .data( "camel-case" ) instead, we convert that to camelCase as well, but only if no data item named camel-case is found so it's faster to use the first form." -- Dave Methvin http://www.learningjquery.com/...

Other Things You Might Want to Know

Something that you also may not know about the $.data() method is that it will attempt to convert the contents of a HTML5 data-* attributes into the appropriate type.

"Every attempt is made to convert the string to a JavaScript value (this includes booleans, numbers, objects, arrays, and null) otherwise it is left as a string. To retrieve the value's attribute as a string without any attempt to convert it, use the .attr() method." -- http://api.jquery.com/data...

The following example shows a DOM element using various types of HTML5 data-* attributes, and then shows that when you call the .data() method to retrieve a value it will parse and convert it into a boolean, number, object, array, or null.

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

If for some reason you didn't want jQuery to convert the HTML data-* attributes when using the .data() method, you could use the .attr() method instead. The .attr() method will always return the string version of the attribute.

Conclusion

The key concept to remember here is that once you call the .data() getter method on a element then it will read the HTML5 data- atributes only once. If you want to update the data from there on, then you'll need to use the .data() setter method. Also, it is important to know that when you read these HTML5 data- attributes jQuery will convert those values into it's appropriate type.

I hope you found this helpful. I recommend you reading Dave Methvin's article entitled Using jQuery’s Data APIs.

Please provide any feedback in the comments below. Until next time...