jQuery .data () no funciona, pero .attr () sí

107

Perdóname por no ser más específico en esto. Tengo un error tan extraño. Después de que se carga el documento, hago un bucle con algunos elementos que originalmente tenían data-itemname=""y configuro esos valores usando .attr("data-itemname", "someValue").

Problema: cuando luego recorro esos elementos, si lo hago elem.data().itemname, obtengo "", pero si lo hago elem.attr("data-itemname"), obtengo "someValue". Es como si el .data()captador de jQuery solo obtiene elementos que están configurados inicialmente para contener algún valor, pero si originalmente están vacíos y luego configurados, entonces .data()no obtiene el valor más adelante.

He intentado recrear este error, pero no he podido.

Editar

¡He recreado el error! http://jsbin.com/ihuhep/edit#javascript,html,live

Además, fragmentos del enlace anterior ...

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
fuente
4
Esfuércese
1
¿No es elem.data("itemname")así elem.data().itemname?
Hogan
mientras que elem.data (). itemname le permitirá leer el valor, NO le permitirá establecer el valor. (Por elem.data().itemname = somevalue;lo tanto , no cambia los datos subyacentes)
Hogan
@dkamins: he recreado el error, consulte la versión editada.
Ian Davis

Respuestas:

210

Me encontré con un "error" similar hace unos días cuando trabajaba con .data()y .attr('data-name')para atributos de datos HTML5.

El comportamiento que está describiendo no es un error, sino por diseño.

La .data()llamada es especial: no solo recupera atributos de datos HTML5, sino que también intenta evaluar / analizar los atributos. Entonces, con un atributo como data-myjson='{"hello":"world"}'cuando se recupera a través de .data(), devolverá un Objecttiempo de recuperación a través de .attr()devolverá una cadena. Vea el ejemplo de jsfiddle.

Dado .data()que el procesamiento adicional, jQuery almacena los resultados de la evaluación de atributos en $.cache, después de todo, una vez que se ha evaluado un atributo de datos, sería un desperdicio volver a evaluar en cada .data()llamada, especialmente porque las variables de datos pueden contener cadenas JSON complejas.

Dije todo eso para decir lo siguiente: después de recuperar un atributo a través de .data()cualquier cambio realizado por .attr('data-myvar', ''), no será visto por .data()llamadas posteriores . Pruebe esto en jsfiddle.

Para evitar este problema , no mezcle .datay .attr()llame. Usar uno u otro.

leepowers
fuente
marcando esto como respuesta. consulte esta prueba para todos los escenarios posibles con .data () frente a .attr (), que incluye obtener y configurar el uso de cada uno, y generar la longitud de los selectores después de que se hayan configurado, jsbin.com/acegef/edit# javascript, html, en vivo
Ian Davis
9
Al menos esto explica el comportamiento aparentemente no intuitivo ... aunque esto podría causar algunos dolores de cabeza más pequeños cuando se usan bibliotecas de terceros, algunas de ellas solo usan datos () y otras cambian el atributo real.
Haroldo_OK
1
@leepowers, "Después de recuperar un atributo a través de .data (), cualquier cambio realizado por .attr ('data-myvar', '') no será visto por las siguientes llamadas .data ()", si eso es realmente debido a la caché ( $ .cache), ¡entonces no parece nada bueno! en lugar de depender ciegamente del caché, las llamadas .data () posteriores podrían hacer algunas comprobaciones básicas () como si el valor está vacío ahora o la longitud de la cadena cambió (haciendo coincidir el caché con el valor actual)
minhajul
1
Cabe señalar que intentar establecer datos ('id', val) también falla si el valor no se recuperó antes ... gran diseño de la función data () por Jquery.
andreszs
3
Entonces, es la caché de datos () la que me retiene durante horas. No es de extrañar por qué no importa cuánto cambie su valor, todavía devuelve el mismo valor. Gracias por esto.
Lynnell Emmanuel Neri
17

Este es el resultado de un malentendido: dataNO es un descriptor de acceso para data-*atributos . Es más y menos que eso.

dataes un acceso para la caché de datos de jQuery en el elemento. Esa caché se inicializa a partir de los data-*atributos si hay alguno presente, pero datanunca escribe en los atributos, ni el cambio de atributo cambia la caché de datos después de la inicialización:

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>

datatambién masajea lo que encuentra de varias maneras, adivinando tipos de datos, haciendo data("answer")un elemento con data-answer="42"un número, no una cadena, o incluso analizando cosas como JSON si se ven como 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 desea usar los atributos (tanto leerlos como establecerlos), use attr, no data. attr es un descriptor de acceso para atributos.

TJ Crowder
fuente
6

Eso es porque el nombre del atributo es data-itemname. No se puede utilizar -en la obj.attributenotación abreviada (obj.data-itemname se interpretaría como "obj.data menos itemname").

Marc B
fuente
¿dice quién? ¿puede proporcionar un enlace?
jahrichie
6

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

.data("itemname", "someValue") modifica la caché de jQuery.

Para que esto funcione en el seguimiento de Javascript y, además, en CSS, debe configurar ambos.

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

¿Por qué no lo usas en .data()todas partes?

También puede declarar valores predeterminados en línea en el HTML, lo cual también está bien.

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

y

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

si quieres cambiarlo solo hazlo

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

y para eliminarlo por completo, podría invocar

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

realmente deberías intentar evitar el uso .attr("data-*"), no sé por qué querrías hacerlo de todos modos.

bevacqua
fuente
s / should / must, el uso .attr('data-*', ...)no hará que los datos sean visibles para.data()
ThiefMaster
¿Pero no lo está haciendo con attr () de la manera que tendría que hacerlo sin jQuery? (con getAttribute aunque)
powerbuoy
Si no tiene jQuery, usa getAttribute()y setAttribute(), por lo que ambos métodos accederán a los atributos reales y volverá a funcionar. O simplemente usaría la dataSetpropiedad que proporcionan los navegadores modernos.
ThiefMaster
1
No seleccione elementos como ese. Es terriblemente ineficiente ya que tendrá que iterar sobre cada elemento del documento. Utilice en su classlugar, ya que los navegadores tienen funciones nativas para obtener elementos con una determinada clase.
ThiefMaster
1
pero "555" son datos, así que debería usar la lógica de datos (). poner esos datos en el nombre de la clase es mezclar datos con presentación. diferente forma de hacerlo, supongo.
Ian Davis
1

Debe configurar los datos usando .data('itemname', 'someValue');. Usar .attr()para establecer atributos de datos no funcionará: http://jsfiddle.net/ThiefMaster/YHsKx/

Sin embargo, puede proporcionar valores en línea utilizando, por ejemplo, <div data-key="value">en el marcado.

ThiefMaster
fuente
en jsfiddle, get funciona después de hacer ambos conjuntos. entonces, hacer attr ("data-itemname", "value") FUNCIONA. Estoy usando Chrome.
Ian Davis
@Ian uh, no. la .data()llamada establece el atributo, mientras que la .attr()llamada no hace nada.
bevacqua
@IanDavis: Haga clic en "establecer atributo" y "obtener" le dará un objeto vacío. Sólo funciona "establecer datos".
ThiefMaster
@Nico - esto es exactamente lo que hice: (1) haga clic en el botón "establecer atributo", luego (2) haga clic en el botón "obtener". esto es lo que sucedió: me alertó, "{" test ":" miau "}". entonces, el .attr () establece el atributo. de lo contrario, la alerta estaría vacía. Si sigue esos pasos exactos, ¿obtiene resultados diferentes? Gracias.
Ian Davis
1
@ThiefMaster: en theaterA en mi código proporcionado, no estoy usando .attr()en absoluto, solo .data(), y la longitud del selector $(".someLink[data-tofilllater='theater5link']"), es cero. así que es como si tuviera que usar .attr(): /
Ian Davis
0

Puedo ver que esto ha generado cierta división sobre cómo abordar la configuración de atributos de datos.

Yo también me encontré con este problema y descubrí que el problema parecía ser simplemente el formato del nombre del atributo de datos .

En mi experiencia, debe evitar el uso de guiones en la variable de datos (el nombre de la variable que viene después de " datos- ").

Esto no funcionó para mí:

[Margen]

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

[jQuery]

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

¡Pero lo siguiente funcionó bien! :):

(Uso un guión bajo en lugar de un guión cuando es necesario)

[Margen]

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

[jQuery]

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

Espero que ayude. ¡Salud a todos!

mmmdearte
fuente