Using jQuery’s Data APIs

read 14 comments

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:
  1. <div id="novel" data-novelist='{"firstname": "Jose", "lastname": "Saramago"}'>Blindness</div>
  2. <div id="poem" data-poet="Edna St. Vincent Millay">Sonnet 18</div>
  3. <div id="story" data-story-writer="Raymond Carver">A Small, Good Thing</div>

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.

    JavaScript:
    1. console.log( $.data( document.getElementById('poem'), 'poet' ) );
    2. //>> undefined
    3.  
    4. console.log( $('#poem').data('poet') );
    5. //>> "Edna St. Vincent Millay"
    6.  
    7. // Change the HTML5 data-poet attribute
    8. $('#poem').attr('data-poet', 'Edmund Spenser');
    9.  
    10. console.log( $('#poem').data('poet') );
    11. //>> "Edna St. Vincent Millay"

  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.

    JavaScript:
    1. console.log( $('#novel').data('novelist') );
    2. //>> Object> {"firstname": "Jose", "lastname": "Saramago"}
    3.  
    4. console.log( $('#novel').attr('data-novelist') );
    5. //>> '{"firstname": "Jose", "lastname": "Saramago"}'

  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.

    JavaScript:
    1. // Before reading with .data()
    2. console.log( $.data( document.getElementById('poem'), 'poet' ) );
    3. //>> undefined
    4.  
    5. console.log( $('#poem').data('poet') );
    6. //>> "Edna St. Vincent Millay"
    7.  
    8. // After reading with .data()
    9. console.log( $.data( document.getElementById('poem'), 'poet' ) );
    10. //>> "Edna St. Vincent Millay"

  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.

    JavaScript:
    1. console.log( $('#poem').data('poet') );
    2. //>> "Edna St. Vincent Millay"
    3.  
    4. console.log( $('#poem').attr('data-poet') );
    5. //>> "Edna St. Vincent Millay"
    6.  
    7. // Change the HTML5 data-* attribute
    8. $('#poem').attr('data-poet', 'William Shakespeare');
    9.  
    10. console.log( $('#poem').data('poet') );
    11. //>> "Edna St. Vincent Millay"
    12.  
    13. console.log( $('#poem').attr('data-poet') );
    14. //>> "William Shakespeare"
    15.  
    16. // Change .data('poet')
    17. $('#poem').data('poet', 'Edmund Spenser');
    18.  
    19. console.log( $('#poem').data('poet') );
    20. //>> "Edmund Spenser"
    21.  
    22. console.log( $('#poem').attr('data-poet') );
    23. //>> "William Shakespeare"

  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.

    JavaScript:
    1. // Not recommended:
    2. console.log( $('#story').data('STORY-writer') );
    3. //>> "Raymond Carver"
    4.  
    5. // Better:
    6. console.log( $('#story').data('storyWriter') );
    7. //>> "Raymond Carver"
    8.  
    9. // Broken:
    10. console.log( $('#story').attr('dataStoryWriter') );
    11. //>> undefined
    12.  
    13. // Better:
    14. console.log( $('#story').attr('data-STORY-writer') );
    15. //>> "Raymond Carver"

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.

comment feed

14 comments

  1. I had a rough idea of how this worked from reading jQuery's source a while back, but it's nice to see the details here as well as the rational behind it. I think you guys made a good tradeoff in how the data API works.

  2. Manuel

    Nice!
    Nice choice of authors too.

  3. FYI, long before .data() started supporting data-* attributes, I needed to control JS via HTML. So I wrote my own jQuery plug-in .defineDatasets() and submitted it to plugins.jquery.com. I consulted with Ian Hickson on it via e-mail. There wasn't any camel case conversion requirement at that time that I recall. It was intended to allow writing code to the HTML5 spec in advance of browser support, so it attached a .dataset object property to the DOM element.

    Like .data(), changes to my plugin-defined .dataset don't get propagated back to the attribute. That would require getters, setters and believe-it-or-not deleters. There's no good cross-browser way to do getters and setters, so I gave up on that. Hixie remarked that he didn't think it could be done with present-day JavaScript and he was right. But propagation back to the attribute is part of the HTML5 standard. That's a long way off.

  4. In #5, you say data-caMEL-case becomes camelCase, but this is not true in IE. It will become caMELCase.
    Example: http://jsfiddle.net/XcD8m/

  5. Rodolfo

    Very nice article... i use the .data() method to build a very complete "build yourself a form with fields", and is work very good!
    jQuery indeed is a powerfull library... hope the library, keep improve this way!

  6. Thanks Dave, This post was very useful for me today. Was getting quite confused with .data attributes until I read through this. Cheers.

  7. VT

    This script is not working on iphone. can you please help.

  8. PaulKD

    Is this a gotcha or have I not RTFM?

    objects work -

    <div id="novel" data-novelist='{"firstname": "Jose", "lastname": "Saramago"}'>Blindness</div>

    objects undefined or appear as string

    <div id="novel" data-novelist="{'firstname': 'Jose', 'lastname': 'Saramago'}">Blindness</div>

    • Hi Paul,

      From the documentation: "When the data attribute is an object (starts with '{') or array (starts with '[') then jQuery.parseJSON is used to parse the string; it must follow valid JSON syntax including quoted property names."

      Valid JSON requires double quotes for property names and string values.

  9. eBuildy

    Hmmm, I dont understand the complexity of src/data.js, why they don't use attr methods instead ?

    jQuery.data = function(key) { return jQuery.attr('data-' + key); } works fine for me.

    also, see this fiddle : http://jsfiddle.net/h7UZ9/1/ get data method is a bit buggy isnit ?

    • Lorenzo

      html 5 data and JQ data are not the same as explain previously :

      // 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"

  10. Nice and simple explanation of Data APIs, I like it better than jQuery's docs. Cheers!

6 Pings

  1. [...] Understanding jQuery’s Data API’s by Dave Methvin at http://www.learningjquery.com [...]

  2. [...] Moar data Everything you need to know about the jQuery data API and data- attributes. [...]

  3. [...] Using jQuery’s Data APIs » Learning jQuery Bra info om förhållandet mellan jQuerys .data() och HTML5s data-*-attribut. [...]

  4. [...] Methvin, membru in jQuery Core Team, are un articol inedit pe LearningjQuery.com numit “Using jQuery’s Data API“. In articol este vorba despre ce vroia la inceput sa fie .data() API, un mod de stocare [...]

Sorry, but comments for this entry are now closed.