Using jQuery’s Data APIs

In the beginning (well, beginning with jQuery 1.2.3 in early 2008) there was the jQuery.data() API. It offers a way to associate JavaScript data — strings, numbers, or any object — with a DOM element. As long as you manipulate the DOM element with jQuery, the library ensures that when the DOM element goes away, the associated data goes away as well. This is especially important for older versions of IE that tend to leak memory when JavaScript data is mixed with DOM data.

Most jQuery code sets data values using the higher-level .data() API; for example, $("div").data("imaDiv", true) sets a boolean value on every div in the document. This API, in turn, calls down to jQuery.data() with each element to set the value. For completeness, there are also jQuery.removeData() and .removeData() to remove data elements, and jQuery.hasData() to determine if any data is currently set for an element.

So to recap: At the inception of these APIs, they were only about getting and setting values associated with DOM elements in memory. Most importantly, the data was managed to ensure no memory would leak when the DOM elements were removed. Many internal jQuery features such as event handling and toggle state memory use these data APIs and their benefits.

Enter HTML5

A few years later, HTML5 became popular and associated another concept with the word "data" through its data-* attributes and the associated DOM .dataset property. This isn't quite the same as jQuery's original idea of data: It involves values being associated with HTML elements in markup and not DOM elements in memory. But they are logically close enough that we added the ability to read HTML5 data-* attributes into jQuery's data object starting with version 1.4.

It's not a perfect marriage, though. HTML5 data-* attribute names are more like CSS names; a name like data-shrivel-up is turned into shrivelUp when read in JavaScript-land. No such rules ever applied to jQuery data names in the past, which means we may have to try both shrivel-up and shrivelUp to find a match. We know it's not ideal, but it's a consequence of trying to fit two concepts with differing semantics into a single API.

Rules of the Road for Data APIs

With that history in mind, there are a few important things you should know in order to use the .data() and jQuery.data() APIs effectively. To give you a better sense of what's going on, the items are illustrated with some code. Assume that each code block runs independently of the others and that they all refer the following HTML:

[html]
Blindness
Sonnet 18
A Small, Good Thing
[/html]

Here are the rules of the road:

  1. Only the .data() API reads HTML5 data-* attributes, and it does so once.

    The in-memory data object for an element is initialized from those data-* attributes the first time you call .data() for the element. Any subsequent changes to the attributes are ignored, since jQuery has already cached the data.

    Rule: If HTML5 data-* attributes change during program execution, use jQuery's .attr() method to get the current values.

    [js] console.log( $.data( document.getElementById('poem'), 'poet' ) ); // >> undefined console.log( $('#poem').data('poet') ); // >> "Edna St. Vincent Millay" // Change the HTML5 data-poet attribute $('#poem').attr('data-poet', 'Edmund Spenser'); console.log( $('#poem').data('poet') ); // >> "Edna St. Vincent Millay" [/js]
  2. The .data() API converts HTML5 data-* values to Javascript types whenever possible.

    That means sequences of digits or exponential-looking values like "11E5" are translated to a Javascript Number type, the string "true" becomes Boolean true, and a valid JSON string becomes a JavaScript object.

    Rule: To get HTML5 data-* attributes as strings without data conversion, use jQuery's .attr() method.

    [js] console.log( $('#novel').data('novelist') ); // >> Object > {"firstname": "Jose", "lastname": "Saramago"} console.log( $('#novel').attr('data-novelist') ); // >> '{"firstname": "Jose", "lastname": "Saramago"}' [/js]
  3. The lower-level jQuery.data() API does not read HTML5 data-* attributes.

    However, if the .data() API has been called already on that DOM element, jQuery.data() will "see" the values that it has already read from the data-* attributes. Conversely, if jQuery.data() sets a value with the same name as an HTML5 data-* attribute and .data() later reads them, the HTML5 attribute is ignored.

    Rule: To prevent confusion, do not use similar names for HTML5 data-* attributes and strictly internal data stored using jQuery.data() or .data() on the same elements.

    [js] // Before reading with .data() console.log( $.data( document.getElementById('poem'), 'poet' ) ); // >> undefined console.log( $('#poem').data('poet') ); // >> "Edna St. Vincent Millay" // After reading with .data() console.log( $.data( document.getElementById('poem'), 'poet' ) ); // >> "Edna St. Vincent Millay" [/js]
  4. No jQuery data API ever changes HTML5 data-* attributes.

    Most uses of .data() and .removeData() are still for the original purpose of associating data with DOM elements in memory. Updating DOM attributes each time data was changed would slow things down for no good reason. Also, it's not even possible to serialize all data types that might be attached to a DOM element, such as functions, references to other DOM elements, or custom JavaScript objects.

    Rule: To update or remove HTML5 data-* attributes, use jQuery's .attr() or .removeAttr() methods.

    [js] console.log( $('#poem').data('poet') ); // >> "Edna St. Vincent Millay" console.log( $('#poem').attr('data-poet') ); // >> "Edna St. Vincent Millay" // Change the HTML5 data-* attribute $('#poem').attr('data-poet', 'William Shakespeare'); console.log( $('#poem').data('poet') ); // >> "Edna St. Vincent Millay" console.log( $('#poem').attr('data-poet') ); // >> "William Shakespeare" // Change .data('poet') $('#poem').data('poet', 'Edmund Spenser'); console.log( $('#poem').data('poet') ); // >> "Edmund Spenser" console.log( $('#poem').attr('data-poet') ); // >> "William Shakespeare" [/js]
  5. All data-* names are stored in camelCase in the jQuery data object, using W3C rules.

    So, data-caMEL-case becomes the camelCase property in the data object and should be accessed using .data("camelCase"). 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. If you get the entire data object using code like data = jQuery.data(elem), you must use data.camelCase to access the data item.

    Rule: When accessing data taken from data-* attributes, and especially when accessing the data object directly, use the W3C camelCasing conventions.

    [js] // Not recommended: console.log( $('#story').data('STORY-writer') ); // >> "Raymond Carver" // Better: console.log( $('#story').data('storyWriter') ); // >> "Raymond Carver" // Broken: console.log( $('#story').attr('dataStoryWriter') ); // >> undefined // Better: console.log( $('#story').attr('data-STORY-writer') ); // >> "Raymond Carver" [/js]

Pick What You Like

Over time, jQuery's .data() API has taken on more responsibilities than it originally had when it was just a way to associate in-memory data with DOM elements and prevent IE leakage. If you need only a simple way to read HTML5 data-* attributes as strings, then the .attr() method may be the best choice, even though the siren-song-name .data() may be telling you otherwise. Whether you use .attr() or .data(), they work consistently across browsers all the way back to IE6 — even if the browser doesn't support HTML5 — so just choose the API with the feature set that works best for your needs.



Responsive Menu
Add more content here...