SyntaxHighlighter

Sunday, December 18, 2011

Don't use the jQuery .data() method. Use .attr() instead.

I just discovered the hard way that the jQuery .data() method is horribly broken. By design, it attempts to convert whatever you put into it into a native type.

I've got a template where I'm generating a button with a data-key element:

<button id="fooButton" data-key="1.4000">Click me to edit</button>

http://jsfiddle.net/KwjvA/

It looks like a float where one could assume that 1.4000 === 1.4, but what I really want here is the string 1.4000. I certainly don't want my input to be modified. One suggestion I found in the issue tracker is to put single quotes around the field (ie: data-key="'1.4000'"). That seems rather absurd as well.

The only reason why I'm warning about this here is that I've seen a bunch of libraries using .data() to store stuff in elements in the DOM. I think it is a really bad idea to have a method called .data() where you expect to be able to store something in it and be able to get back out exactly what you put into it.

The recommended alternative is to use .attr(). The problem with this is that while it achieves the same effect, it is actually much different functionality from .data(). .data() stores information within an internal cache within jQuery, while .attr() calls element.setAttribute().

I read through several bug reports on the jQuery website, where people are also confused by this behavior and all of them get closed with a wontfix. I see this as a terrible choice. Yuck.

Update: Here is a bug I just filed, hopefully that explains things better to the people who seem to be having a hard time understanding what I'm talking about: http://bugs.jquery.com/ticket/11060

12 comments:

Brent Traut said...

I'm kind of at a loss as to your point. You're passing a float into a function, and you're complaining that you don't get a string out? If you want to get a string out, pass a string in.

Better yet, when you use .attr, you get the same result! $('div').attr('key', 1.400) results in <div key="1.4">.

Jon Stevens said...

Please go back and read the posting again. I'm not passing a float into a function. I've got a data attribute in an element and I'm expecting to get it out the same way it went in. It is lame that jquery is doing that conversion.

rad_g said...

$.data(document.body, "meh", 1.400);
$.data(document.body, "meh2", 1.400+"");
$(document.body).attr("meh3", 1.400+"");
alert( $.data(document.body, "meh") );
alert( $.data(document.body, "meh2") );
alert( $(document.body).attr("meh3") );

In all cases it returns 1.4.

Jon Stevens said...

Of course it does. Your point?

rad_g said...

Don't use the jQuery .data() method. Use .attr() instead.
What's the point of this argument if both return the same and none of them gives what you ask it for? Explain.

Jon Stevens said...

Please click the jsfiddle above and read the blog posting.

If have data stored in an element (not put there by a method call, but actually stored in the element), then you can't count on .data() to return the original value because what I'm storing is a string with a period in it, not a float.

What you have done is just simply showed that storing a float gets returned as a float.

Xwero said...

It is behaviour a programmer should expect.

I can't think of a language that will not return 1.400 as 1.4.

In java they use a postfix character to set literal type, maybe you can adopt that to identify a float as string.

http://jsfiddle.net/cj5mn/

Jon Stevens said...

People, don't get held up on using $.data('key', 'value'), that is NOT what I'm talking about here.

What is happening with what I'm talking about is that It is a string being auto converted to a float.

This means that *any* time you have unstructured data within a data-foo attribute, you need to always use attr() to get it out because you can never depend on using data(). That is broken.

What if you are writing this:

<button data-id="1"></button>

Then, one day, you decide to make all of your id values look like this string: data-id="1.100". That means that now, you have to go and change ALL of your code to use attr() instead of data().

That is a terribly broken design.

There should either be another argument on data which says "don't try to convert it to a native type" or data shouldn't do any conversion at all. Whatever goes in should go in and whatever comes out should come out.

Drake said...

I see your point. Guess it would be nice if .data() let you specify that it should return a string, without trying to parse it as something else. I don't think I'd call it " terribly broken" though. I would argue that having a value in a data-* attribute that looks like a float, but is actually a string is a pretty rare case.

Also, your post mentions that this same issue applies when using .data() to write values, but that's not the case. You can call .data("foo", "1.4000") and .data("foo") will return the string, not the float. The case your talking about is only an issue when the attributes are read directly from the DOM, and as you said, .data() doesn't write to the DOM.

And since .data() doesn't write to the DOM it is much faster: http://jsperf.com/jquery-data-vs-attr/9. So if you must, use .attr() for this rare case, but definitely don't use it in place of .data() in general.

Jon Stevens said...

'rare case'... that is *exactly* when you want a system to behave as you would expect it to. It is these 'rare cases' that are the cause very hard to debug issues. That is the major failure here.

Also, this isn't a 'rare case'... look in the jQuery issue tracker. I found quite a few confused people just from a query on float + data.

http://bugs.jquery.com/search?q=.data+float&go=&ticket=on

All of the responses are use attr(), but that isn't right. I should be able to use .data() here.

Eames said...

as the api clearly states http://api.jquery.com/data/ when it's an html5 data element, then it attempts to coerce. But when it's data you store yourself using data() then it's stored as the type you insert.

.data() is just a convenience method to read html5 data attributes and it includes type coersion. they're also read only through the data() function.

So yes, in the case that you want the string value, you would use attr() instead of data(). Whether or not the type coersion is a bad thing or not is debatable. It would depend on your use.

But you said that you want to retrieve it in the same manner you put it in, well, you can't put it in using data(). you can only store it using attr() or when writing out the html. So it's not really accurate to say that you can't get it out the way you put it in, since you can't put it in using data().

Unknown said...

It is true that you need to use attr() if you want to avoid automatic type conversion on hard-coded data-X attributes. Good tip.

However it is UNTRUE that something should automatically be the default just because there are _some_ instances where it is useful. In the majority of use-cases,user expect 'numbers' to be returned as numbers, rather than needing to add their own logic.

It seems that the ideal scenario would be adding a qualifier that could be used to specify the desired datatype in the data=X field. For example:

"=string:1.4000" == "1.400"
"=bool:9" == true

Such an enhancement would not be difficult to add to the data method. But then again, it is also not hard to just use .attr() in those _minority_ of situations where it is an issue.