jQuery .data () não funciona, mas .attr () funciona

107

Perdoe-me por não ser mais específico sobre isso. Eu tenho um bug tão estranho. Depois que o documento é carregado, faço um loop em alguns elementos que originalmente o continham data-itemname=""e defino esses valores usando .attr("data-itemname", "someValue").

Problema: Quando mais tarde faço um loop através desses elementos, se eu faço elem.data().itemname, eu entendo "", mas se eu faço elem.attr("data-itemname"), eu entendo "someValue". É como se o .data()getter do jQuery apenas obtivesse os elementos que são configurados inicialmente para conter algum valor, mas se eles estiverem originalmente vazios e posteriormente configurados, .data()não obterá o valor posteriormente.

Tenho tentado recriar esse bug, mas não consegui.

Editar

Eu recriei o bug! http://jsbin.com/ihuhep/edit#javascript,html,live

Além disso, trechos do link acima ...

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
fonte
4
Tente mais recriar o bug :-)
dkamins
1
Não é elem.data("itemname")não elem.data().itemname?
Hogan
enquanto elem.data (). itemname permitirá que você leia o valor e NÃO permitirá que você defina o valor. (Portanto elem.data().itemname = somevalue;, não altera os dados subjacentes.)
Hogan
@dkamins - Recriei o bug, consulte a versão editada.
Ian Davis

Respostas:

210

Encontrei um "bug" semelhante há alguns dias ao trabalhar com .data()e .attr('data-name')para atributos de dados HTML5.

O comportamento que você está descrevendo não é um bug, mas é intencional.

A .data()chamada é especial - ela não apenas recupera atributos de dados HTML5, mas também tenta avaliar / analisar os atributos. Portanto, com um atributo como data-myjson='{"hello":"world"}'when recuperado via .data()retornará um Objectwhile recuperação via .attr()retornará uma string. Veja o exemplo de jsfiddle.

Uma vez que .data()faz o processamento extra, o jQuery armazena os resultados da avaliação de atributos $.cache- afinal, uma vez que um atributo de dados foi avaliado, seria um desperdício reavaliar em cada .data()chamada - especialmente porque as variáveis ​​de dados podem conter strings JSON complexas.

Eu disse tudo isso para dizer o seguinte: Depois de recuperar um atributo por meio de .data()quaisquer alterações feitas por .attr('data-myvar', ''), não será visto pelas .data()chamadas subsequentes . Teste isso no jsfiddle.

Para evitar este problema , não misture .datae faça .attr()chamadas. Use um ou outro.

leepowers
fonte
marcando isso como resposta. por favor, veja este teste para todos os cenários possíveis com .data () vs. .attr (), que inclui obter e configurar usando cada um, e emitir o comprimento dos seletores após terem sido configurados, jsbin.com/acegef/edit# javascript, html, live
Ian Davis
9
Pelo menos isso explica o comportamento aparentemente não intuitivo ... embora isso possa causar algumas dores de cabeça menores quando alguém usa bibliotecas de terceiros, algumas delas usando apenas dados (), e outras alterando o atributo real.
Haroldo_OK
1
@leepowers, "Depois de recuperar um atributo via .data () quaisquer alterações feitas por .attr ('data-myvar', '') não serão vistas pelas chamadas .data () subsequentes", se isso for realmente por causa do cache ( $ .cache), então não parece bom! em vez de depender cegamente do cache, chamadas subsequentes de .data () podem fazer algumas verificações básicas () como se o valor está vazio agora ou o comprimento da string mudou (combinando o cache com o valor atual)
minhajul
1
Deve-se notar que tentar definir dados ('id', val) também falha se o valor não foi recuperado antes ... ótimo design da função data () por Jquery.
andreszs
3
Portanto, é o cache data () que está me segurando por horas. Não é à toa que, não importa o quanto eu mude seu valor, ele ainda retorna o mesmo valor. Obrigado por isso.
Lynnell Emmanuel Neri
17

Este é o resultado de um mal-entendido: dataNÃO é um acessador de data-*atributos . É mais e menos do que isso.

dataé um acessador para o cache de dados do jQuery no elemento. Esse cache é inicializado a partir de data-*atributos, se houver algum presente, mas datanunca grava nos atributos, nem a alteração do atributo altera o cache de dados após a inicialização:

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>

datatambém massageia o que encontra de várias maneiras, adivinhando os tipos de dados, criando data("answer")em um elemento com data-answer="42"um número, não uma string, ou até mesmo analisando coisas como JSON se forem semelhantes a 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>

Se você quiser usar os atributos (tanto lendo quanto configurando-os), use attr, não data. attr é um acessador para atributos.

TJ Crowder
fonte
6

Isso porque o nome do atributo é data-itemname. Você não pode usar -na obj.attributenotação abreviada (obj.data-itemname seria interpretado como "obj.data menos itemname").

Marc B
fonte
quem disse? Você pode fornecer um link?
jahrichie
6

.attr("data-itemname", "someValue") modifica o DOM.

.data("itemname", "someValue") modifica o cache jQuery.

Para fazer isso funcionar seguindo Javascript e, além disso, CSS, você deve definir ambos.

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

Por que você não usa em .data()qualquer lugar?

Você também pode declarar valores padrão embutidos no HTML, o que também é bom.

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

e

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

se você quiser mudar, basta fazer

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

e para removê-lo completamente, você pode invocar

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

você realmente deve tentar evitar o uso .attr("data-*"), não sei por que você gostaria de fazer isso de qualquer maneira.

bevacqua
fonte
s / deve / deve, usar .attr('data-*', ...)não tornará os dados visíveis para.data()
ThiefMaster
Mas não é fazer com attr () da mesma forma que você faria sem jQuery? (com getAttribute tho)
powerbuoy
Se você não tiver o jQuery, use getAttribute()e setAttribute()- então os dois métodos acessarão os atributos reais e funcionarão novamente. Ou você apenas usaria a dataSetpropriedade que os navegadores modernos fornecem.
ThiefMaster
1
Não selecione elementos como esse. É terrivelmente ineficiente, pois terá que iterar em cada elemento do documento. classEm vez disso, use um, pois os navegadores têm funções nativas para obter elementos com uma determinada classe.
ThiefMaster
1
mas, "555" são dados, então devo usar a lógica data (). colocar esses dados no nome da classe é misturar dados com apresentação. maneira diferente de fazer isso, suponho.
Ian Davis
1

Você deve definir os dados usando .data('itemname', 'someValue');. Usar .attr()para definir atributos de dados não funciona: http://jsfiddle.net/ThiefMaster/YHsKx/

No entanto, você pode fornecer valores embutidos usando, por exemplo, <div data-key="value">na marcação.

ThiefMaster
fonte
no jsfiddle, o get funciona depois de fazer os dois conjuntos. então, fazer attr ("data-itemname", "value") FUNCIONA. Estou usando o Chrome.
Ian Davis
@Ian uh, não. a .data()chamada define o atributo, enquanto a .attr()chamada não faz nada.
bevacqua
@IanDavis: Clique em "set attr" e "get" lhe dará um objeto vazio. Apenas "definir dados" funciona.
ThiefMaster
@Nico - isso é exatamente o que eu fiz: (1) clique no botão "definir atr" e (2) clique no botão "obter". isto é o que aconteceu: me alertou, "{" test ":" meow "}". então, o .attr () define o atributo. caso contrário, o alerta estaria vazio. Se você seguir essas etapas exatas, obterá resultados diferentes? obrigado.
Ian Davis
1
@ThiefMaster - no theaterA em meu código fornecido, não estou usando .attr()nada, apenas .data(), e, o comprimento do seletor ,, $(".someLink[data-tofilllater='theater5link']")é zero. então é como se eu tivesse que usar .attr(): /
Ian Davis
0

Posso ver que isso trouxe algumas divisões sobre como abordar a configuração do atributo de dados.

Eu também tive esse problema e descobri que o problema parecia ser simplesmente a formatação do nome do atributo de dados .

Na minha experiência, você deve evitar o uso de hifens na variável de dados (o nome da variável que vem depois de " data- ").

Isso não funcionou para mim:

[Markup]

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

[jQuery]

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

Mas o seguinte funcionou muito bem! :):

(Eu uso um sublinhado em vez de um hífen quando necessário)

[Markup]

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

[jQuery]

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

Espero que ajude. Felicidades a todos!

mmmdearte
fonte