I want to use a .filter
function against a large number of elements where I'm checking for a data-*
attribute, however they are set by the .data
method.
The problem is that the jQuery selector always to use the original value. If I set a data value using $('whatever').data('key', newValue)
and then try to use a selector $('[data-key=newValue]')
then nothing is found.
Here's my test HTML:
<div id="test" data-dummy="foo"></div>
<div id="result"</div>
Then:
$('#test').data('dummy', 'bar');
$('#result').
append('<div>Where dummy=foo: ' + $('[data-dummy="foo"]').length + '</div>').
append('<div>Where dummy=bar: ' + $('[data-dummy="bar"]').length + '</div>');
Outputs:
Where dummy=foo :1
Where dummy=bar :0
It looks like, for the selector engine, only the original value is used in selectors.
Update - cheers to initial answers, this is actually on purpose. .data
starts with data-*
values, but sets its own copy. I don't want to replace every call to .data(
with a call to .attr('data-' +
.
To work around this I'm using .filter
with a function, but as this filter will run for large numbers of elements I don't want to create a new jQuery object for each match. For this sort of circumstance jQuery provides some global functions.
Basically I should be able to use $.data(element,
instead of $(element).data(
, but this doesn't work either: $.data(element,
returns undefined
.
So my code ends up something like .filter(function(){return $.data(this, key) === value;})
However that appears to only be before a jQuery object is initialised:
var domEle = document.getElementById('test');
var globalBefore = $.data(domEle, 'dummy');
var first = $(domEle).data('dummy');
var globalAfter = $.data(domEle, 'dummy');
$('#result').
append('<div>$.data before: ' + globalBefore + '</div>').
append('<div>new $(): ' + first + '</div>').
append('<div>$.data after: ' + globalAfter + '</div>');
Outputs:
$.data before: undefined
new $(): foo
$.data after: foo
Weird. Never mind, the question is can I work around it? Is there any way to initialise whatever's happening for a new $
object without creating one for every tested node?
JS Fiddle demo: http://jsfiddle.net/kU4th/
It's not a bug, it's just really confusingly-designed behavior.
The problem is that the jQuery selector seems to use the original value. If I set a data value using $('whatever').data('key', newValue) and then try to use a selector $('[data-key=newValue]') then nothing is found.
That's because data
never sets data-*
attributes, it just initializes itself from them. It's assymetrical (reads from them but doesn't write to them).
To actually update the attribute, use attr
. E.g., instead of:
$('whatever').data('key', newValue);
you need
$('whatever').attr('data-key', newValue);
If you want to both set the data and the attribute:
$('whatever').data('key', newValue).attr('data-key', newValue);
data
associates key/value pairs of data with elements using a jQuery-managed cache of objects. If you ask data
for a key that doesn't already exist in the element's data cache, it looks to see if that key matches a data-*
attribute and, if so, initializes the data cache with that value. At that point, there is no further connection between data
and the data-*
attribute.
The reason data
doesn't write back to the attributes is probably at least partially down to the fact that you can store anything via data
, whereas of course attribute values are always strings. So for instance, if you do:
$("whatever").data('key', {
nice: "complex",
obj: "here",
answer: 42,
when: new Date()
});
...then later $("whatever").data('key')
will give you back that object. That object cannot be stored in an attribute.
You're not, by far, the first person to be burned by this surprising design. :-)
Basically I should be able to use
$.data(element
, instead of$(element).data(
, but this doesn't work either:$.data(element
, returnsundefined
.
Right. The documentation for $.data
tells you why:
Note: This is a low-level method; a more convenient
.data()
is also available.Regarding HTML5 data-* attributes: This low-level method does NOT retrieve the data-* attributes unless the more convenient
.data()
method has already retrieved them.
You've said you don't want to replace calls to data
with calls to attr
, but I have to say that seems like the best situation, so you can actually use those data-*
attributes in selectors.
Alternately, you can do this:
matchingElements.filter(function(){
return ($.data(this, key) || this.getAttribute('data-' + key)) === value;
});
...which first looks at the data
data and, if it gets back a falsey value (undefined
, null
, ""
, false
, NaN
, or 0
), goes and gets the attribute from the element instead. If you want to limit that to just undefined
:
matchingElements.filter(function(){
var value = $.data(this, key);
return typeof value === "undefined" ? this.getAttribute('data-' + key) : value;
});