33

Here's a simple example to illustrate the behavior:

Given this html markup:

<div data-company="Microsoft"></div>

and this jQuery code (using jQuery 1.5.1):

// read the data
alert($("div").data("company"));
// returns Microsoft <<< OK!

// set the data
$("div").data("company","Apple");
alert($("div").data("company"));
// returns Apple <<< OK!

// attribute selector
alert($("div[data-company='Apple']").length);
// returns 0  <<< WHY???

// attribute selector again
alert($("div[data-company='Microsoft']").length);
// returns 1  <<< WHY???

// set the attribute directly
$("div").attr("data-company","Apple");
alert($("div[data-company='Apple']").length);
// now returns 1 <<< OK!

Since jQuery automatically imports the HTML5 data-* into jQuery's data object, shouldn't the attributes be updated as well when the data changes?

James H
  • 2,401
  • 3
  • 22
  • 20

3 Answers3

51

Normally, there's not a need for roundtripping .data()'s if you're consistent in using .data() to access/set/modify data on DOM elements. For that reason, it makes sense to avoid the performance overhead of accessing the DOM for every .data() set/modify operation (.data() stores its values in jQuery.cache internally).

If you want to force the roundtrip behavior yourself, you could subscribe to the "setData" or "changeData" events and then push the .data() update in those events through to the corresponding DOM element via .attr().

Dave Ward
  • 59,815
  • 13
  • 117
  • 134
  • 2
    That makes sense - what would then be the preferred method for using .data() in the context of a selector? Say I wanted to access all elements where data-company='Microsoft' that were set/modified with .data()? – James H Apr 01 '11 at 00:12
  • 1
    I don't think there's an easy method for querying against the `.data()` cache. If you wanted to use a selector like that, you'd need to implement the roundtripping on "changeData" event that I mentioned. Then, you could keep your data- attributes in sync with `.data()` changes (and you'd also be able to do that only selectively, on the elements you intend to query later). – Dave Ward Apr 01 '11 at 00:19
  • Thank you, Dave! Btw, I've enjoyed your series on tekpub. – James H Apr 01 '11 at 00:38
  • Thanks, glad to hear it. If you haven't made it all the way to the end yet, we talk about how to handle the "changeData" event in episode 10 or 11, which would be helpful here. – Dave Ward Apr 01 '11 at 00:44
  • Biggest gotcha in jQuery: use `.data()` to set the data on a new element, then add it to the DOM, then retrieve it from the DOM, then find that there is no data. The documentation doesn't draw _nearly_ enough attention that you must use .attr('data-...') in this scenario. – Roman Starkov Mar 19 '16 at 18:59
17

This is the correct behavior according to the docs:

The data- attributes are pulled in the first time the data property is accessed and then are no longer accessed or mutated (all data values are then stored internally in jQuery).

(from: http://api.jquery.com/data)

Craig
  • 7,471
  • 4
  • 29
  • 46
  • Has this always been the case or was it changed in one of the versions? – John Magnolia Nov 19 '12 at 14:41
  • 1
    It looks like this has always been the case. The above quote from the docs is also in the release notes of 1.4.3 when this feature was first released: http://blog.jquery.com/2010/10/16/jquery-143-released/ – Craig Nov 25 '13 at 19:16
0

The query selector of [data-company] checks against the attributes, while .data does not update them.

You can change code to use only .attr('data-, avoid .data completely.

You can set your own function that updates both data and attr:

// update both data and corresponding attribute 'data-x'
$.fn.attrdata = function (a, b)
{
    if (arguments.length > 1)
        this.attr('data-' + a, b);
    else if (typeof a === 'object')
        this.attr(Object.keys(a).reduce(function (obj, key)
        {
            obj['data-' + key] = a[key];
            return obj;
        }, {}));
    return this.data.apply(this, arguments);
};

Use like this:

$("div").attrdata("company", "Apple");
$("div").attrdata({company: "Apple"}); // also possible
console.log($("div").data("company")); // Apple
console.log($("div").attr("data-company")); // Apple
console.log($("div[data-company='Apple']").length); // 1

If you do not use CSS selectors you can create your own jQuery selector:

$.expr[':'].data = function(elem, index, match) {
  var split = match[3].split('=');
  return $(elem).data(split[0]) == split[1];
};

Use like this:

$("div").attr("data-company", "Microsoft");
$("div").data("company", "Apple");
console.log($('div:data(company=Apple)').length); // 1
console.log($('div[data-company="Apple"]').length); // 0

https://jsfiddle.net/oriadam/a14jvqcw/

oriadam
  • 7,747
  • 2
  • 50
  • 48