jQuery .data () ne fonctionne pas, mais .attr () fonctionne

107

Pardonnez-moi de ne pas être plus précis à ce sujet. J'ai un bug tellement étrange. Après le chargement de la documentation, je boucle certains éléments qui ont à l'origine data-itemname=""et je définis ces valeurs à l'aide de .attr("data-itemname", "someValue").

Problème: plus tard, lorsque je boucle sur ces éléments, si je le fais elem.data().itemname, j'obtiens "", mais si je le fais elem.attr("data-itemname"), j'obtiens "someValue". C'est comme si le .data()getter de jQuery n'obtient que les éléments qui sont initialement définis pour contenir une valeur, mais s'ils sont initialement vides, puis définis ultérieurement, ils .data()n'obtiennent pas la valeur plus tard.

J'ai essayé de recréer ce bogue mais je n'ai pas pu.

Éditer

J'ai recréé le bogue! http://jsbin.com/ihuhep/edit#javascript,html,live

Aussi, extraits du lien ci-dessus ...

JS:

var theaters = [
    { name: "theater A", theaterId: 5 },
    { name: "theater B", theaterId: 17 }
];

$("#theaters").html(
    $("#theaterTmpl").render(theaters)
);

// DOES NOT WORK - .data("name", "val") does NOT set the val
var theaterA = $("[data-theaterid='5']");
theaterA.find(".someLink").data("tofilllater", "theater5link"); // this does NOT set data-tofilllater
$(".someLink[data-tofilllater='theater5link']").html("changed link text"); // never gets changed

// WORKS - .attr("data-name", "val") DOES set val
var theaterB = $("[data-theaterid='17']");
theaterB.find(".someLink").attr("data-tofilllater", "theater17link"); // this does set data-tofilllater
$(".someLink[data-tofilllater='theater17link']").html("changed link text");

HTML:

<body>
    <div id="theaters"></div>
</body>

<script id="theaterTmpl" type="text/x-jquery-tmpl">
    <div class="theater" data-theaterid="{{=theaterId}}">
        <h2>{{=name}}</h2>
        <a href="#" class="someLink" data-tofilllater="">need to change this text</a>
    </div>
</script>
Ian Davis
la source
4
Essayez plus fort de recréer le bogue :-)
dkamins
1
N'est-ce elem.data("itemname")pas elem.data().itemname?
Hogan
tandis que elem.data (). itemname vous permettra de lire la valeur, il ne vous laissera pas définir la valeur. (Cela elem.data().itemname = somevalue;ne change donc pas les données sous-jacentes.)
Hogan
@dkamins - J'ai recréé le bogue, veuillez consulter la version modifiée.
Ian Davis

Réponses:

210

J'ai rencontré un "bug" similaire il y a quelques jours en travaillant avec .data()et .attr('data-name')pour les attributs de données HTML5.

Le comportement que vous décrivez n'est pas un bogue, mais est intentionnel.

L' .data()appel est spécial - non seulement il récupère les attributs de données HTML5, mais il tente également d'évaluer / analyser les attributs. Donc, avec un attribut comme data-myjson='{"hello":"world"}'lors de la récupération via .data()retournera un Objectwhile la récupération via .attr()retournera une chaîne. Voir l'exemple jsfiddle.

Comme .data()un traitement supplémentaire, jQuery stocke les résultats de l'évaluation des attributs dans $.cache- après tout, une fois qu'un attribut de données a été évalué, il serait inutile de le réévaluer à chaque .data()appel - d'autant plus que les variables de données peuvent contenir des chaînes JSON complexes.

J'ai dit tout cela pour dire ce qui suit: après avoir récupéré un attribut via .data()les modifications apportées par .attr('data-myvar', '')ne sera pas vu par les .data()appels suivants . Testez ceci sur jsfiddle.

Pour éviter ce problème , ne mélangez pas .dataet n'appelez pas .attr(). Utilise l'un ou l'autre.

leepowers
la source
marquer ceci comme réponse. s'il vous plaît voir ce test pour tous les scénarios possibles avec .data () vs .attr (), qui comprend l'obtention et la configuration en utilisant chacun, et la sortie de la longueur des sélecteurs après qu'ils ont été définis, jsbin.com/acegef/edit# javascript, html, live
Ian Davis
9
Au moins, cela explique le comportement apparemment non intuitif ... bien que cela puisse causer quelques petits maux de tête lorsque l'on utilise des bibliothèques tierces, certaines n'utilisant que data (), et d'autres modifiant l'attribut réel.
Haroldo_OK
1
@leepowers, "Après avoir récupéré un attribut via .data (), toutes les modifications apportées par .attr ('data-myvar', '') ne seront pas vues par les appels .data () ultérieurs", si c'est vraiment à cause du cache ( $ .cache), alors ça ne semble pas bon! au lieu de s'appuyer aveuglément sur le cache, les appels ultérieurs à .data () pourraient effectuer des vérifications de base () comme si la valeur est vide maintenant ou si la longueur de la chaîne a changé (correspondant au cache avec la valeur actuelle)
minhajul
1
Il convient de noter que la tentative de définition des données ('id', val) échoue également si la valeur n'a pas été récupérée plus tôt ... excellente conception de la fonction data () par Jquery.
andreszs
3
Donc, c'est le cache data () qui me retient pendant des heures. Pas étonnant que peu importe combien je change sa valeur, il renvoie toujours la même valeur. Merci pour cela.
Lynnell Emmanuel Neri
17

Ceci est le résultat d'un malentendu: datan'est PAS un accesseur pour les data-*attributs . C'est à la fois plus et moins que cela.

dataest un accesseur pour le cache de données de jQuery sur l'élément. Ce cache est initialisé à partir des data-*attributs s'il y en a, mais n'écritdata jamais dans les attributs, et la modification de l'attribut ne change pas le cache de données après l'initialisation:

const div = $("[data-example]");
console.log('div.data("example"):', div.data("example"));
console.log('div.attr("data-example"):', div.attr("data-example"));
console.log('Using div.data("example", "updated")');
div.data("example", "updated");
console.log('div.data("example"):', div.data("example"));
console.log('div.attr("data-example"):', div.attr("data-example"));
<div data-example="initial value"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

datamasse également ce qu'il trouve de différentes manières, en devinant les types de données, en créant data("answer")un élément avec data-answer="42"un nombre, pas une chaîne, ou même en analysant les choses en JSON si elles ressemblent à JSON:

console.log(typeof $("[data-answer]").data("answer"));
console.log(typeof $("[data-json]").data("json"));
console.log(typeof $("[data-str]").data("str"));
<div data-answer="42"></div>
<div data-json='{"answer":42}'></div>
<div data-str="example"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Si vous souhaitez utiliser les attributs (à la fois les lire et les définir), utilisez attr, pas data. attr est un accesseur pour les attributs.

TJ Crowder
la source
6

C'est parce que le nom de l'attribut est data-itemname. Vous ne pouvez pas utiliser -dans la obj.attributenotation abrégée (obj.data-itemname serait interprété comme "obj.data moins itemname").

Marc B
la source
dit qui? pouvez-vous fournir un lien?
jahrichie
6

.attr("data-itemname", "someValue") modifie le DOM.

.data("itemname", "someValue") modifie le cache jQuery.

Pour que cela fonctionne en suivant Javascript et en plus en CSS, vous devez définir les deux.

theaterA.find(".someLink").attr("data-itemname", "someValue");
theaterA.find(".someLink").data("itemname", "someValue");
Elmar Höfinghoff
la source
4

Pourquoi n'utilisez-vous pas .data()partout?

Vous pouvez également déclarer les valeurs par défaut en ligne sur le HTML, ce qui convient également.

<span data-code="pony">text</span>

et

$("span").data("code") == "pony" // true

si tu veux le changer tu fais juste

$("span").data("code", "not-a-pony");

et pour le supprimer complètement, vous pouvez invoquer

$("span").removeData("code");

vous devriez vraiment essayer d'éviter d'utiliser .attr("data-*"), je ne sais pas pourquoi vous voudriez le faire de toute façon.

bevacqua
la source
s / should / must, l'utilisation .attr('data-*', ...)ne rendra pas les données visibles pour.data()
ThiefMaster
Mais n'est-ce pas le faire avec attr () comme vous le feriez sans jQuery? (avec getAttribute tho)
powerbuoy
Si vous n'avez pas jQuery que vous utilisez getAttribute()et setAttribute()- les deux méthodes accéderont aux attributs réels et cela fonctionnera à nouveau. Ou vous utiliseriez simplement la dataSetpropriété fournie par les navigateurs modernes.
ThiefMaster
1
Ne sélectionnez pas d'éléments comme ça. C'est horriblement inefficace car il devra parcourir chaque élément du document. Utilisez classplutôt un car les navigateurs ont des fonctions natives pour obtenir des éléments avec une certaine classe.
ThiefMaster
1
mais, "555" est des données, donc je devrais utiliser la logique data (). mettre ces données dans le nom de la classe, c'est mélanger les données avec la présentation. manière différente de le faire je suppose.
Ian Davis
1

Vous devez définir les données à l'aide de .data('itemname', 'someValue');. L'utilisation .attr()pour définir les attributs de données ne fonctionnera pas: http://jsfiddle.net/ThiefMaster/YHsKx/

Cependant, vous pouvez fournir des valeurs en ligne en utilisant par exemple <div data-key="value">dans le balisage.

ThiefMaster
la source
dans le jsfiddle, le get fonctionne après avoir fait les deux ensembles. ainsi, faire attr ("data-itemname", "value") fonctionne. J'utilise Chrome.
Ian Davis
@Ian euh, non. l' .data()appel définit l'attribut, tandis que l' .attr()appel ne fait rien.
bevacqua
@IanDavis: Cliquez sur "set attr" et "get" vous donnera un objet vide. Seules les "données définies" fonctionnent.
ThiefMaster
@Nico - c'est exactement ce que j'ai fait: (1) cliquez sur le bouton "set attr", puis (2) cliquez sur le bouton "get". c'est ce qui s'est passé: il m'a alerté, "{" test ":" meow "}". ainsi, le .attr () définit l'attribut. sinon, l'alerte aurait été vide. Si vous suivez ces étapes exactes, obtenez-vous des résultats différents? Merci.
Ian Davis
1
@ThiefMaster - dans theatreA dans mon code fourni, je n'utilise pas .attr()du tout, seulement .data(), et, la longueur du sélecteur $(".someLink[data-tofilllater='theater5link']"), est égale à zéro. donc c'est comme si je devais utiliser .attr(): /
Ian Davis
0

Je peux voir que cela a soulevé une certaine division sur la façon d'aborder le paramètre d'attribut de données.

Moi aussi, je rencontre ce problème et j'ai trouvé que le problème semblait être simplement le formatage du nom d'attribut de données .

D'après mon expérience, vous devriez éviter d'utiliser des traits d'union dans la variable data (le nom de la variable qui vient après " data- ").

Cela n'a pas fonctionné pour moi:

[Balisage]

<div class="list" id="myElement1" data-item-order="some-value"> .... </div>

[jQuery]

jQuery("#myElement1").data("item-order", "my-new-value");

Mais ce qui suit a très bien fonctionné! :):

(J'utilise un trait de soulignement au lieu d'un trait d'union si nécessaire)

[Balisage]

<div class="list" id="myElement1" data-item_order="some-value"> .... </div>

[jQuery]

jQuery("#myElement1").data("item_order", "my-new-value");

J'espère que cela aide. Salut tout le monde!

mmmdearte
la source