Unable to set data attribute using jQuery Data() API
Asked Answered
A

7

131

I've got the following field on an MVC view:

@Html.TextBoxFor(model => model.Course.Title, new { data_helptext = "Old Text" })</span>

In a seperate js file, I want to set the data-helptext attribute to a string value. Here's my code:

alert($(targetField).data("helptext"));

$(targetField).data("helptext", "Testing 123");

The alert() call works fine, it shows the text "Old Text" in an alert dialog. However, the call to set the data-helptext attribute to "Testing 123" does not work. "Old Text" is still the attribute's current value.

Am I using the call to data() incorrectly? I've looked this up on the web, and I can't see what I'm doing wrong.

Here's the HTML markup:

<input data-helptext="Old Text" id="Course_Title" name="Course.Title" type="text" value="" />
Appoggiatura answered 26/7, 2011 at 9:15 Comment(5)
The code looks OK. No problem in this demo. Which version of jQuery are you using?Throve
I'm using 1.5.1 which came with the ASP NET MVC project template. Could it be that I need to update jQuery?Appoggiatura
OK, it's not the version of jQuery then. I was thinking it might be a really old version. The data() API that you are using was added in v1.2.3Throve
Could you add the markup please? Are you using a custom HTML5 data- attribute?Throve
How are you observing the value? jQuery does not persist the value back to the DOM, although it does correctly update it. See my answer below for a test and explanationThrove
T
239

It is mentioned in the .data() documentation

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 was also covered on Why don't changes to jQuery $.fn.data() update the corresponding html 5 data-* attributes?

The demo on my original answer below doesn't seem to work any more.

Updated answer

Again, from the .data() documentation

The treatment of attributes with embedded dashes was changed in jQuery 1.6 to conform to the W3C HTML5 specification.

So for <div data-role="page"></div> the following is true $('div').data('role') === 'page'

I'm fairly sure that $('div').data('data-role') worked in the past but that doesn't seem to be the case any more. I've created a better showcase which logs to HTML rather than having to open up the Console and added an additional example of the multi-hyphen to camelCase data- attributes conversion.

Updated demo (2015-07-25)

Also see jQuery Data vs Attr?

HTML

<div id="changeMe" data-key="luke" data-another-key="vader"></div>
<a href="#" id="changeData"></a>
<table id="log">
    <tr><th>Setter</th><th>Getter</th><th>Result of calling getter</th><th>Notes</th></tr>
</table>

JavaScript (jQuery 1.6.2+)

var $changeMe = $('#changeMe');
var $log = $('#log');

var logger;
(logger = function(setter, getter, note) {
    note = note || '';
    eval('$changeMe' + setter);
    var result = eval('$changeMe' + getter);
    $log.append('<tr><td><code>' + setter + '</code></td><td><code>' + getter + '</code></td><td>' + result + '</td><td>' + note + '</td></tr>');
})('', ".data('key')", "Initial value");

$('#changeData').click(function() {
    // set data-key to new value
    logger(".data('key', 'leia')", ".data('key')", "expect leia on jQuery node object but DOM stays as luke");
    // try and set data-key via .attr and get via some methods
    logger(".attr('data-key', 'yoda')", ".data('key')", "expect leia (still) on jQuery object but DOM now yoda");
    logger("", ".attr('key')", "expect undefined (no attr <code>key</code>)");
    logger("", ".attr('data-key')", "expect yoda in DOM and on jQuery object");

    // bonus points
    logger('', ".data('data-key')", "expect undefined (cannot get via this method)");
    logger(".data('anotherKey')", ".data('anotherKey')", "jQuery 1.6+ get multi hyphen <code>data-another-key</code>");
    logger(".data('another-key')", ".data('another-key')", "jQuery < 1.6 get multi hyphen <code>data-another-key</code> (also supported in jQuery 1.6+)");

    return false;
});

$('#changeData').click();

Older demo


Original answer

For this HTML:

<div id="foo" data-helptext="bar"></div>
<a href="#" id="changeData">change data value</a>

and this JavaScript (with jQuery 1.6.2)

console.log($('#foo').data('helptext'));

$('#changeData').click(function() {
    $('#foo').data('helptext', 'Testing 123');
//  $('#foo').attr('data-helptext', 'Testing 123');
    console.log($('#foo').data('data-helptext'));
    return false;
});

See demo

Using the Chrome DevTools Console to inspect the DOM, the $('#foo').data('helptext', 'Testing 123'); does not update the value as seen in the Console but $('#foo').attr('data-helptext', 'Testing 123'); does.

Throve answered 26/7, 2011 at 9:47 Comment(17)
Not sure what has changed, but your fiddle demo returns undefined in chrome consoleMarlinmarline
So then what is the point of jQuery allowing you to put in a second argument? To update the value stored in the js variable cache?Anglonorman
@gwho I am not sure I completely understand your question, but I assume you are referring to the original answer from 2011 using jQuery 1.6.2. If so, then the .data('key', 'value') method does update the value in the jQuery cache but for performance reasons (I guess DOM mutation) the DOM itself is not updated.Throve
so if you wanted to update the DOM, you'd need to do .attr('key','value') regardless of whether you did .data('key', 'value') or not, right? That seems redundant to me, and I'm having trouble imagining a scenario where you would want to write to the cached DOM, but not the real DOM. Maybe I'm not understand the jQuery cache; So would a visitor see all the things that .data() modifies on their screen, or would they not?Anglonorman
Correct, if you really want to update the DOM you need something like .attr('key', 'value') or just simply element.attributeName = value. It is jQuery's decision to pull once from the DOM but that doesn't stop you using other methods if you really need the DOM data- attributes updating. Don't forget this is about HTML5 custom attributes which are not intended to compete with microformats. You still have access to the updated value in JavaScript where I would expect it to be used.Throve
So it's not just a matter of performance; they can't be compared. They have completely different purposes, and they change different "versions" of the DOM. So back to the question: what is the point of using .data() if you have to do .attr() to change the ACUTAL DOM? seems redundant.Anglonorman
I have always believed it to be solely for performance reasons. this test linked from MDN Using data-* attributes shows a huge performance penalty using setAttributeThrove
there seems to be a bug: console.log($('#foo').data('data-helptext')); should probably be: console.log($('#foo').data('helptext'));Caylor
@Frankforte that syntax was for the old version of jQuery. I left my original answer since there are multiple ways to get data-* attributes using different jQuery versions.Throve
Does this also affect newly-added elements? I cloned a table row that looks like this: <tr data-id="5"></tr>, retrieved the id, incremented it, and then used $clone.data('id',new_index), and then $tbody.append(clone) to append the new TR. But the new TR still has data-id="5". Why wouldn't this work on a newly created element?Unbolted
This is very frustrating because jQuery's handling of this AND using CSS3's attr(data-customdata) breaks the expected HTML5 spec, right? If the only valid custom attributes are data- ones, then this valid use case content: attr(data-xxx); will break when using jQuery best practices of setting with jQuery.data() because the DOM is OOS.Hogue
It's completely weird. I have exactly opposite situation. $('article#story').data('current', marker_data.id); works while $('article#story').attr('data-current', marker_data.id); doesn't behave correctly. :/Meader
@slick which version of jQuery are you using? Maybe something changed since I wrote the answerThrove
@andyb, jQuery v2.1.1Meader
@slick I'll update my demo soon to be a bit smarter but I cannot see any problems with newer jQuery. It's not clear from your comment what is working or not for you. Setting via .attr will not update the jQuery object.Throve
@Hogue so what do you do if your css depends on the Dom changing?Carreno
@Carreno This was a while ago so my context is limited now, but I think the answer to that you have to know when that is the case, and then in addition to using jQuery.data() (which doesn't modify DOM) you have to also update the DOM element attribute so that the content: attr(data-xxx); CSS picks it up.Hogue
A
35

I was having serious problems with

.data('property', value);

It was not setting the data-property attribute.

Started using jQuery's .attr():

Get the value of an attribute for the first element in the set of matched elements or set one or more attributes for every matched element.

.attr('property', value)

to set the value and

.attr('property')

to retrieve the value.

Now it just works!

Arrivederci answered 23/9, 2014 at 21:6 Comment(1)
For me I was able to change the data property with data() but I did notice in developer tools it did not show the change, so for that reason I went with attr()Woodrowwoodruff
C
9

@andyb's accepted answer has a small bug. Further to my comment on his post above...

For this HTML:

<div id="foo" data-helptext="bar"></div>
<a href="#" id="changeData">change data value</a>

You need to access the attribute like this:

$('#foo').attr('data-helptext', 'Testing 123');

but the data method like this:

$('#foo').data('helptext', 'Testing 123');

The fix above for the .data() method will prevent "undefined" and the data value will be updated (while the HTML will not)

The point of the "data" attribute is to bind (or "link") a value with the element. Very similar to the onclick="alert('do_something')" attribute, which binds an action to the element... the text is useless you just want the action to work when they click the element.

Once the data or action is bound to the element, there is usually* no need to update the HTML, only the data or method, since that is what your application (JavaScript) would use. Performance wise, I don't see why you would want to also update the HTML anyway, no one sees the html attribute (except in Firebug or other consoles).

One way you might want to think about it: The HTML (along with attributes) are just text. The data, functions, objects, etc that are used by JavaScript exist on a separate plane. Only when JavaScript is instructed to do so, it will read or update the HTML text, but all the data and functionality you create with JavaScript are acting completely separate from the HTML text/attributes you see in your Firebug (or other) console.

*I put emphasis on usually because if you have a case where you need to preserve and export HTML (e.g. some kind of micro format/data aware text editor) where the HTML will load fresh on another page, then maybe you need the HTML updated too.

Caylor answered 13/9, 2014 at 3:15 Comment(1)
Thanks. This helped, among all other answers with incorrect examples! The data in attr('data-helptext' makes the difference, where the accepted answer and the ones with many votes are not working. +1Marlborough
M
6

Happened the same to me. It turns out that

var data = $("#myObject").data();

gives you a non-writable object. I solved it using:

var data = $.extend({}, $("#myObject").data());

And from then on, data was a standard, writable JS object.

Ml answered 2/12, 2013 at 16:15 Comment(2)
How do you then write to it?Sutlej
Sorry Thom, I don't know what do you mean... After doing $.extend..., you can use data as you please: data.name = 'Nico'; data.questionSolved = true;, and console.log(data) will display this newly added propertiesMl
R
3

To quote a quote:

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

.data() - jQuery Documentiation

Note that this (Frankly odd) limitation is only withheld to the use of .data().

The solution? Use .attr instead.

Of course, several of you may feel uncomfortable with not using it's dedicated method. Consider the following scenario:

  • The 'standard' is updated so that the data- portion of custom attributes is no longer required/is replaced

Common sense - Why would they change an already established attribute like that? Just imagine class begin renamed to group and id to identifier. The Internet would break.

And even then, Javascript itself has the ability to fix this - And of course, despite it's infamous incompatibility with HTML, REGEX (And a variety of similar methods) could rapidly rename your attributes to this new-mythical 'standard'.

TL;DR

alert($(targetField).attr("data-helptext"));
Rebak answered 25/1, 2015 at 3:56 Comment(0)
H
1

As mentioned, the .data() method won't actually set the value of the data- attribute, nor will it read updated values if the data- attribute changes.

My solution was to extend jQuery with a .realData() method that actually corresponds to the current value of the attribute:

// Alternative to .data() that updates data- attributes, and reads their current value.
(function($){
  $.fn.realData = function(name,value) {
      if (value === undefined) {
        return $(this).attr('data-'+name);
      } else {
        $(this).attr('data-'+name,value);
      }
  };
})(jQuery);

NOTE: Sure you could just use .attr(), but from my experience, most developers (aka me) make the mistake of viewing .attr() and .data() as interchangeable, and often substitute one for the other without thinking. It might work most of the time, but it's a great way to introduce bugs, especially when dealing with any sort of dynamic data binding. So by using .realData(), I can be more explicit about the intended behavior.

Halibut answered 14/10, 2018 at 22:13 Comment(0)
P
0

Had the same problem. Since you can still get data using the .data() method, you only have to figure out a way to write to the elements. This is the helper method I use. Like most people have said, you will have to use .attr. I have it replacing any _ with - as I know it does that. I'm not aware of any other characters it replaces...however I have not researched that.

function ExtendElementData(element, object){
    //element is what you want to set data on
    //object is a hash/js-object
    var keys = Object.keys(object);
    for (var i = 0; i < keys.length; i++){
        var key = keys[i];
        $(element).attr('data-'+key.replace("_", "-"), object[key]);
    }
}

EDIT: 5/1/2017

I found there were still instances where you could not get the correct data using built in methods so what I use now is as follows:

function setDomData(element, object){
    //object is a hash

    var keys = Object.keys(object);
    for (var i = 0; i < keys.length; i++){
        var key = keys[i];
        $(element).attr('data-'+key.replace("_", "-"), object[key]);
    }
};

function getDomData(element, key){
    var domObject = $(element).get(0);
    var attKeys = Object.keys(domObject.attributes);

    var values = null;
    if (key != null){
        values = $(element).attr('data-' + key);
    } else {
        values = {};

        var keys = [];
        for (var i = 0; i < attKeys.length; i++) {
            keys.push(domObject.attributes[attKeys[i]]);
        }

        for (var i = 0; i < keys.length; i++){
            if(!keys[i].match(/data-.*/)){
                values[keys[i]] = $(element).attr(keys[i]);
            }
        }
    }
    return values;
};
Putter answered 24/11, 2015 at 20:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.