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>

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:


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

Unknown 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.

Unknown 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.

Unknown 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.

Unknown 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: So if you must, use .attr() for this rare case, but definitely don't use it in place of .data() in general.

Unknown 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.

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 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.

Unknown said...

This is one of those "rare" circumstances when you might expect jQuery not to modify your data:

Dorian said...

The idea you're missing is that data-key="1.4000" is not meant to store a string in the key property. The string is just the "container" for your data, which is 1.4 (a float). When you retrieve it using .data it is being deserialized into a float. When you call .attr it returns the attribute of your tag, which is a string. The important point is that you're operating at different levels of abstraction. The .data method is at a higher level of abstraction than .attr, so you get different results.

Unknown said...

@Dorian ... and my point is... that 'higher level of abstraction' is confusing and stupid... It completely fails the principle of least astonishment.

Brent Clark said...

I am very happy that you've posted this as I did not know that attr() returned a string.

I, personally, would expect this exact behavior from data(). JavaScript routinely produces 1.4 anywhere that I use 1.400 without taking an additional step to convince the parser I really want a string.

Given that this is how the language works everywhere else I would argue that it is consistent with the PLA and that the attr() behavior is not.

I have to imagine your real issue is that the two methods do different things; I agree with that statement.

Unknown said...

@Unknown. JavaScript doesn't 'produce' 1.4, it is 1.4. If you have a String, which is '1.400' you will always get a String which is '1.400'. If you cast that String to a Float, you get 1.4 (note the lack of single quotes).

The incorrect and broken part here is that .data() is doing that casting from something which is stored as an html attribute as part of an element, which can ONLY ever be a String because there is no type associated with html attributes. Telling people to always use attr() is just broken because the behavior of attr() and data() is totally different.

Brent Clark said...

Not sure why my name came through as Unknown; I gave the appropriate information to your login. My Name is Brent Clark.

I understand your point that data-key="1.400" appears to be a string; however, it's not. In that state it simply the value of a HTML attribute. As soon as you extract it for use with JavaScript you'll get a float. That's part of the languages loosely typed nature.

Perhaps I am still misunderstanding your point? I am open-minded to opinion but so far you've shown me nothing to convince me that data() should return a string. It would seem jQuery and the majority of responders to this post feel the same.

Avorin said...
This comment has been removed by a blog administrator.
Unknown said...

If I write the following html:

<div id="foo" data-key="1400"></div>

There is no way to know if the value of data-key is a float or a string. Period.

Now, let's say one day, my developer decides to change my 'keys' to have a period character in them...

<div id="foo" data-key="1.400"></div>

If I'm calling .data(), now all of my code breaks because I'm expecting my keys to have two 00 after the 4 and be a String.

Writing HTML with the data- attributes is different than using .data() to set and then get data. If I use .data() to set/get data, then the value I put in should be the value I get out. If I set a String, I get a String. If I set a Float, I get a Float.

.data() is always assuming that I want to cast my String value and sure, the argument could be made that ok, don't ever use data()...

The issue here is that it isn't obvious that data() is doing the casting (principle of least surprises) and that the internal behavior within the jquery code is different between attr() and data().

If all of this isn't an issue, then how come this 6 month old blog post still gets comments, has had ~5k views and a total of 19 comments? There is also several issues within the issue tracker and mailing lists complaining or being confused about it.

Brent Clark said...

I was finally able to sit down and work on this a little bit with the base JavaScript and DOM functions. They are document.getElementById('myEl').dataset.key and document.getElementById('myElt').getAttribute('data-key')respectively. These two functions WILL treat data-key="1.400" as a string which is consistent with your expectations. I find this incredibly odd because in all other situations I am aware of I would expect a float of 1.4.

Whether or not this is "obvious" is debatable and since you've presented this post as an opinion there's not much reason to debate. jQuery, the majority of posters here so far and I feel you should get a float in this scenario, you do not. However, I think your your opinion is the correct one if we look at this solely through the lens of the aforementioned functions.

You rightfully removed the prior post; the expletives were completely uncalled for. It did, however, contain a bit of useful information that showed the documentation for jQuery's .data() function. The documentation states:

"Every attempt is made to convert the string to a JavaScript value (this includes booleans, numbers, objects, arrays, and null) otherwise it is left as a string. To retrieve the value's attribute as a string without any attempt to convert it, use the attr() method. 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. 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)."

This is how I would expect the function to behave as well but I can't deny that it is inconsistent with the native JavaScript and DOM functions. I thank you for taking the time to create this post and make your concern known.

Unknown said...

Yes, of course those DOM functions treat things as Strings because that is all that they are. That is the sane approach here. JavaScript is an untyped language, there is no way to know what something is without trying to convert it.

" jQuery, the majority of posters here so far and I feel you should get a float in this scenario, you do not."

I'm saying that HTML attributes should *always* be treated as Strings because there is absolutely no way to know what type of value it is AND anything SET with .data() should come out the exact same way that it went in.

If you put a float into .data(), you should get a float back. If you put a String into .data() give me back a String. If you try to read an attribute from an html element, don't touch it because there is *no way* that it could be anything other than a String.

That would be a sane and predictable api. The current approach is totally broken and crazy. It depends on a single buried paragraph (that tons of people miss) which explains this casting behavior. That paragraph should be the first thing at the top of the page and in bright red.

If we go back to the title of my post, then I'm still correct. Don't use .data().... because you can't be certain of what you'll get back.

I deleted that post, not because of the language, but because it was stupid and ignorant of everything that has already been said. Any more posts here that don't add value to the conversation will also be deleted.

Chab said...

Even trickier with strings like "1e23": here returns number 1e+23. If you're using that as a string you get "1e+23" :-/ I kinda agree on both point of views but imho i think we should have the option to cast or not. Otherwise i'll just give the same advice as yours : use attr() to avoid surprises or silly html (data-id="'1e23'")

pte said...

+1 Jon - is horribly broken. All the haters clearly don't understand the problem.

Get this "01010" comes back as a string, "10101" is an int! WTFBBQ indeed, its not even consistently broken.

Unknown said...

I just encountered another great example of this brokenness.

Unknown said...

i just know that function, and i still use attr().

But for performance reason i will use data().

Thanks Jon.

Unknown said...

emaniacs, you prefer speed over valid data? wow.

Unknown said...

Personally I'm happy with the way data works in this regard, to me it feels consistent with the language, e.g.

var foo = 1.400; // foo is 1.4, not 1.400

if you wanted foo to have a value of '1.400' you declare it as such:

var foo = '1.400';

IMO data-foo="1.400" is analogous to the first example above - because what is in the quotes is 1.400 and not "1.400", i.e. it is a number, not a string

I don't see why data-foo="'1.400'" is absurd, especially since the documentation states that coercion will be attempted, but to each their own.

Unknown said...

var foo = 1.400; is not 1.4. It is printed out as 1.4, but the reality is that it is stored as a 64-bit float, which is neither 1.400 or 1.4.

The only way from JS to get data-foo="'1.400'" would be to write:

$(elem).data('foo', "'1.400'")

but that is ridiculous, who does that? So, of course, you end up having to use attr(), so now we are back where we started, which is what this blog posting is all about.

Unknown said...

Fair point, but it's still a number, not a string which was what I was getting at.

Maybe this is my background, but why would you be setting a data- attribute on an html element using javascript in that way?

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

Unknown said...

Peter, before I discovered that I was supposed to be using attr(), I was using data() because that is what I thought was the right thing to do. Pretend for a moment, you are like myself and a ton of other people, that you didn't read the tiny print about data() casting in the JQuery documentation.

In my case, I'm generating HTML via HandlebarsJS and dynamically inserting attributes into elements. The code looks something like this:

<div data-key="{{key}}"></div>

Now, when we changed our 'keys' from being just straight numbers (1234) to a dotted notation (12.34), all the sudden my application started breaking in strange ways.

From my perspective, this is a terrible way to design an API. You *always* want the same data to come out as what you put in.

When you are generating elements, like I am, you lose all of the type information. There is no way to know if something is a string or a number. The *best* option here is to assume it is a string because that is the *safest* possible denominator.

Unknown said...

I had a issue like this, i was putting a integer "10150793650258471" into a data attribute and fetching it with data().

The out put was 10150793650258472 so
data() added 1 to my number :|

fetching the attr property fixed my issue..

So thanks

Unknown said...

I rarely comment, but I want to applaud your patience with the other comments. This is indeed a problem. jQuery should return the unchanged string value, and let the developer type cast if needed.

soulBit said...

Very insightful blog post, thanks for sharing - it's such a shame that this is currently a taboo topic to the jQuery devs..

Unknown said...

Hi Jon,
Do you try with $.type($('#foo').data('key'))? It returns string type.
So, it seems console.log fxn here makes confusion, isn't it?

Unknown said...

1.8.x of jquery 'fixes' the issue specifically with floats... in that it converts them all to strings... which is also kind of broken because now it is just a confusing mess...


Here is the comment I added to the issue...

I've been thinking a lot about this for the last couple of days and have come to the conclusion that this fix is a terrible idea that only creates even more confusion in the long term. It really is a hack on top of a bad design decision in the first place. Now, the message is 'use .data() instead of .attr() because it is kinda safe to use in certain circumstances.'

If you generate a div such as <div data-foo="4.000"></div>, you get back a String... $('div').data('foo') despite the fact that the documentation says it tries to coerce numbers. If I do $('div').data('foo', 4.000) and then look at it with .data('foo'), it is a number. If I do data-foo="4000", it is a number. I'm sorry, but this is very inconsistent behavior.

Unknown said...


I agree. It's pretty confusing. Most of the time, data-* attributes behaves differently from data fxns. I always use them separately. Probably the best idea is to make a distinction between them. Maybe something similar to Mathias' plugin:

See you around

Unknown said...

There's no need to make a distinction by using a single line of code since they all start with data-. That is distinction enough.

Unknown said...

Actually, this plugin is a bad example. The point is:

The best way to overcome this is to use data() fxns or data-* (and .attr()) separately. Just don't mix them and everything will be OK.


jag said...

Why would you ever use $.data() to retrieve an html attribute?

Why would you ever want to set a $.data() value via html?

I don't understand why you're making an issue out of setting a data element via html. If you use it as it appears to be intended (setting typed/complex data values within javascript) It works fine:

a fiddle

Unknown said...

Jamie, your fiddle has changed behavior since this article was first written (jquery >1.8 'fixed' things). See my comment above.

jag said...

That still doesn't explain why you would have used $.data() in the first place...

I think I've figured out how jsfiddle works now. Here's a new fiddle that will work with all the versions of jquery that it supports: new fiddle

Unknown said...

Sigh, the reason why I used .data in the first place is well explained in the article and comments. I'm sorry, but I just don't have the energy right now to go over it again with you. Please do some reading first.

jag said...

I read your article and all the comments before posting my question.

If what you meant to say in the article was "I made a mistake and used $.data() to retrieve html attributes instead of $.attr() and here's why that's a bad thing." I'd understand, but implying that $.data() is/was broken seems like the wrong way to go about it.

Unknown said...

It is added as a data- attribute of an element. I'd expect to read it with $.data(). End of story.

jag said...

So, you assumed that you had to read an attribute with an id like 'data-*' with $.data() instead of $.attr()

We've all heard that story before about what happens when you assume things. Good ending. I like the classics.

Unknown said...

Wow, you're a real pain in the ass. I like it.

I didn't assume anything. Using .data to retrieve data- attributes via .data() is documented as part of jquery since Oct 2010 (the release date of 1.4.3)...

The issue here is that the conversion happen(s)(ed) on float's in an unexpected way. jquery 1.8.x 'fixed' this.

jag said...

Ah! See? Now that you've actually read my questions/comments and have explained WHY, I can understand where your article is coming from. All the sighs and homework assignments were just a distraction.

Unknown said...

Ok, you got me. I admit that I have a secret fetish for repeating myself to random strangers on the internets who think that because they know how to write code in this shitty language called javascript that they are entitled gods... or something.


jag said...

Well, you're the one that made the public blog entry that showed as the most recent posting on the subject during a public google search done by someone considering whether to use the $.data() functions of jquery for the first time...

Your fetish isn't much of a secret


Unknown said...

This really destroyed my day, debugging a complex application that utilizes the "data-" technique of outputting and fetching values between back- and front-end.

This was the error:

But as you can see if you change the jQuery version to 1.8.2, this has been fixed for this particular use case.

Joe said...

Wow... thanks Jon Stevens for the useful post.

I am stunned by the negative responses you received (along with the ironic and annoying smiley face). I know this is the internet and all, but come on... even the people who work on jquery seem to agree with you (and have done the best that they can).

Basically, html attributes are strings (even if they look like numbers), jquery automatically reads data-* attributes, and stores them. If that attribute looks like a number, it sometimes converts it into a number (instead of leaving it as a string, which is what I would have expected).

Good to know.

Thanks for the article. Helped me.

Kevin Nelson said...

From the comments I read, it seems that everyone is misunderstanding the purpose and value of data(). Please understand that doing data-attr='"1.4000"' is not a hack or a work-around for a bug, it's JSON notation. So, it is NOT a bad practice, it's just weird to people thinking of data attributes as HTML instead of as JSON.

Today, I added some keypress filters to some HTML and added the attribute: data-allowed-chars='[37,45]'. The fact that I know I can get data('allowedChars') and it will be an array and not a string is something I greatly appreciate and EXPECT. If I had wanted it to be a string, I would have used JSON notation to make it such and done data-allowed-chars='"[37,45]"'.

If you know something should be a string because its potential value is volatile, then you plan for that and put quotes around it. This is NOT a bug. You have to do the same thing in actual JavaScript markup. It only seems like a bug when you are thinking of it as an HTML attribute instead of as JSON.

Thinking of the field as JSON, I have never encountered any unexpected bugs with the data attributes and came to this page more out of curiosity as to why anyone would avoid data() since it is such an awesome tool.

If you want your attributes to be treated like HTML strings instead of code, then you're stuck with attr().

Anyway, on the side of all programmers who utilize the power of JSON notation in their data attributes, I am very glad that the JQuery team is not changing the way that the data() behaves.

Erik Bongers said...

1) data attributes are strings.
W3C states that data attributes are stored in a DOMStringMap.
So, therefor, all data attributes only contain strings.
2) jQuery conveniently converts these strings to objects or numbers.
This is the reason the previous commentator assumes that data attributes contain JSON data, which is not the case at HTML level.
3) It is fair to assume that if you first set data and then get the data again, you get exactly the same thing. (I hadn't heard of the the Principle of Least Astonishment before)
4) This is indeed where jQuery's attempt to be convenient has an unfortunate side-effect: setData() != getData() !!!
5) I can understand that jQuery can't revoke this automatic type conversion as this would break many websites.
6) Best way for jQuery to proceed might be to clearly mention this type conversion in the initial description of the data-function. Or perhaps add a setting to the jQuery object $.dataConvert(true/false) with a default of true?
7) I will now proceed with my James Bond website: <div data-agent-id="007">...

Test said...

I applaud your patience in the comments, sir. It boggles my mind to see that so many people are okay with jQuery silently converting your data to whatever suits its fancy, just for the sake of occasionally avoiding a trivial conversion to the data type the string is actually meant to represent.

Test said...

Kevin Nelson: But what if it **was** a string? You see the problem? What if I want to store the string `"[35,42.0000]"` as some sort of secret code? You can get your array from that string if you want, if that's what the string represents to you, but data is irretrievably lost to me if it is silently converted to the array `[35,42]`.

Tymek said...

One-line solution:
$.fn.dta = function(sel, val){if(typeof val === "undefined"){return this.attr("data-"+sel);} else {this.attr("data-"+sel, val);}};

Unknown said...

@Tymoteusz, sorry, that isn't a solution. That is you totally not getting the point of the posting.

Tymek said...

I'm sorry that my comment was so short. That .dta("attr'[, 'val']) plugin returns string in my case. Tested in Chrome DevTool.

> $("#l").data("value")
> $("#l").dta("value")

Unknown said...

You guys shouldn't focus on stuff like this, let's see the big advantage of data() method: then call $('plugin').each(function(){var el = $(this); el.plugin(;}); This kind of html markup called bootstrap, very flexible, and also very fast to have only one function listening on the whole document, map function to specific element that matches a selector. For me, if you have few properties, should use attr instead.

Isochronous said...

Sorry, but I have to agree with jQuery on this one.

The problem arises from the feature where jQuery includes any data attributes on the HTML tag as part of the return value for .data(). The HTML spec requires that any attribute values be wrapped in quotes. data-foo=bar is not valid, but data-foo="bar" is. However, what you have to remember is that those quotes are part of the HTML, and not part of the value. The value is whatever is INSIDE of the quotation marks - those simply mark the "border" between HTML land and arbitrary value land.

That means that when you get into types that can be coerced, you have to be more specific. What if you had "true" or "false"? You wouldn't want to get back the string "true" or the string "false," you'd want to get back true and false. So you want data-key="1.4000" to be a string. That means that once you've added the quotes that the HTML spec requires, you're now basically in JavaScript land (for the purposes of this discussion, at least). And in JavaScript, if you define a variable to be equal to 1.400, JavaScript is going to go "oh, that's a float," and just store/represent it as 1.4. However, if you want that number to be a string in JS, then you have to wrap it in quotes - '1.400'.

If you're using .data to store values within DOM elements, you have to expect those values to be treated like everything else in JS, and that includes the behavior of primitives and variables. If you need arbitrary levels of precision, including trailing zeros, then you've got to do the same things with .data that you have to do with any other bit of JavaScript code.

Unknown said...

@isochronous. Thanks for making a well worded comment. However, you still have done nothing to convince me.

Requiring people to single quote their attribute values in order to get a string is an absurd idea. "attributes on HTML elements may have any string value, including the empty string" - There is nothing in the spec about storing data as anything other than as a string.

There is also this... in other words, jquery isn't properly coercing my intended string into a string. It is returning some value with single quotes around it. What happens if my output has single quotes in it too? Then I have to setup some sort of escaping mechanism.

If I'm going to store values within HTML attributes as strings, I expect them to remain strings. Period. Using .data() creates a potential for not getting the intended value back.

The jquery documentation has long been updated to better reflect the fact that .data() attempts to coerce your data into whatever it feels like. That has made this blog post relatively moot. That said, just the other day, I watched a very smart developer get burnt by using .data().

Kevin said...
This comment has been removed by a blog administrator.
nmclean said...

Kevin Nelson writes:
"If you know something should be a string because its potential value is volatile, then you plan for that and put quotes around it. This is NOT a bug. You have to do the same thing in actual JavaScript markup. It only seems like a bug when you are thinking of it as an HTML attribute instead of as JSON."

Yes, technically it is not a "bug" because jQuery was designed to behave that way. But that's not the point.

You seem to be under the impression that "data-" exists solely in the realm of jQuery. No, it is an HTML attribute. It is not documented as JSON in the spec, because it's not. If I call "el.dataset.allowedChars" on your page, I will receive a string, not an array.

It is jQuery that is thinking of it as differently than what it truly is, and this is exactly the reason that it fails the "principle of least astonishment".

Felix said...
This comment has been removed by a blog administrator.
Felix said...
This comment has been removed by a blog administrator.