La relación entre atributos HTML y propiedades JavaScript

21/09/2018
Artículo original

Cuando uno programa para la Web en la parte Front-End tiene muy interiorizados algunos conceptos. Grosso modo podríamos afirmar lo siguiente:

  1. Un documento HTML es un conjunto de etiquetas que representan el contenido del mismo. Estas etiquetas llevan atributos para establecer sus propiedades y cambiar su comportamiento. Su aspecto se gestiona mediante hojas de estilo en cascada (CSS).
  2. Cuando el navegador renderiza la página, construye internamente un "árbol" jerárquico de elementos, cada uno de los cuales representa a una de las etiquetas anteriores. A este árbol se le denomina DOM (Document Object Model).
  3. Cada uno de estos elementos en el DOM es accesible desde JavaScript mediante un objeto. Los atributos de las etiquetas son accesibles como propiedades de estos objetos.

Esta es la sabiduría popular. Así, por ejemplo, si tenemos este simple documento HTML (solo muestro el cuerpo):

<h1 id="Titulo">Titulo del documento</h1>
<div id="Contenido" class="contenido">
    <p>Lorem ipsum dolor sit amet <span>consectetur adipisicing 
elit</span>. Consequatur harum cumque....</p> </div>

Podemos observar la estructura del árbol del DOM usando las herramientas del desarrollador de cualquier navegador (por ejemplo Chrome: pulsa F12), y veríamos esto:

DOM visualizado en el navegador

A la izquierda de la imagen puedes ver un árbol de elementos que representa a la página actual.

Si queremos además examinar las propiedades de cualquier elemento de ese DOM usando JavaScript, podemos hacerlo simplemente localizándolo, y mostrándolas por la consola (pulsa ESC para mostrar la consola si no está visible ya).

Por ejemplo, como los elementos HTML que tienen un identificador (atributo id) disponen automáticamente de variables globales con ese mismo nombre para permitir su acceso directo, podemos escribir en la consola, por ejemplo:

Se teclea Titulo.id y luego Contenido.className

En este caso mostramos por consola la propiedad id de uno de los elementos, que se corresponde con el atributo del mismo nombre en la etiqueta HTML. En el caso del otro elemento, la propiedad correspondiente al atributo class no tiene idéntico nombre, pero sí parecido: className. Pero se cumple la máxima anterior: cada atributo en HTML tiene una correspondiente propiedad en JavaScript.

La sabiduría popular, además, dice que si cambiamos el valor de una propiedad mediante JavaScript, se cambia el valor del atributo correspondiente. Es decir, que la relación es en los dos sentidos. Por ejemplo:

Se cambia con JavaScript tanto el id como las clases aplicadas a dos elementos

En este caso cambiamos mediante código tanto el identificador del título como la clase aplicada al div de contenidos.

Esto lleva a pensar a muchos programadores que existe una relación bidireccional entre los atributos de un elemento HTML y las propiedades del objeto JavaScript correspondiente, de modo que si cambias una se ve reflejado en la otra y a la inversa.

De hecho, y esto es lo que quería aclarar con este post, esto no es así habitualmente, y en realidad podemos distinguir varios casos:

  • En efecto, unos pocos atributos poseen una relación 1:1 con su correspondiente propiedad JavaScript. Por ejemplo es el caso del identificador de un elemento que acabamos de ver. Si la cambias por código también cambia el atributo correspondiente. Curiosamente este no es el caso más habitual aunque se pueda pensar lo contrario.
  • Algunas propiedades de elementos del DOM no están relacionadas con atributos HTML. Un ejemplo típico es la propiedad textContent de los elementos, que sirve para leer o escribir sus contenidos textuales (sin considerar etiquetas HTML). Si la cambiamos no estamos cambiando ningún atributo del elemento.
  • Ciertos atributos no poseen una propiedad equivalente en el DOM que los respalde. Es el caso, por ejemplo, del atributo colspan en una tabla.
  • En ocasiones, el valor del atributo no sirve para nada y lo que cuenta es su mera presencia. Además, el valor de la propiedad no coincide con el asignado al atributo. Un ejemplo es, precisamente la propiedad disabled de un botón en un formulario.

Pero una cosa que veces se escapa es que en la mayoría de los casos el atributo HTML sólo inicializa la correspondiente propiedad JavaScript, pero luego quedan desvinculados. Esto quiere decir que el navegador toma el valor del atributo al analizar la página para ponerle un valor inicial a la correspondiente propiedad del elemento en el DOM, pero a partir de ese instante quedan totalmente desconectadas. Eso significa que si cambiamos el valor de la propiedad mediante código, el atributo permanece inalterado en la fuente HTML de la página.

Y esta distinción que parece un tanto rebuscada es importante conocerla.

Por ejemplo, supongamos que tenemos un cuadro de texto en una aplicación web, algo sencillo, como esto:

<input id="miInput" value="Nombre">

Este input, tras haber renderizado la página, tendrá el valor del atributo y el de la propiedad value del elemento que lo representa con valores idénticos: "Nombre". Si un usuario escribe algo dentro del cuadro de texto, el valor de la propiedad del objeto JavaScript también se actualizará, pero sin embargo el valor del atributo en el HTML seguirá siendo el valor original:

Muestra cómo cambiando el valor de un control no cambia el atributo en el DOM

Esto se debe a que lo que se está modificando es el valor de la propiedad en el DOM, pero el atributo HTML no cambia.

Esto puede tener su importancia en escenarios de data-binding en dos sentidos entre la interfaz de usuario y el modelo de datos de nuestra aplicación (arquitecturas MVC o MVVM que usan muchos frameworks actuales de JavaScript). Ya que no es tan sencillo como enlazar un atributo a una propiedad en el HTML y olvidarnos. Hay que saber qué parejas de atributos/ propiedades estamos enlazando y cómo funcionan.

Por ejemplo, he preparado un página muy simple que lo que hace es mostrar el valor de un determinado atributo y de su correspondiente propiedad en el objeto JavaScript, simulando el data-binding mediante código sencillo en algún evento.

En el primer caso tenemos un div al que se le modifica con un botón su id aumentando un contador para cambiarle el nombre. Como sabemos esto funciona de maravilla y se actualizan tanto el atributo como la propiedad:

Ejemplo 1, identificador

Después tenemos un campo de texto. Al escribir en él se van reflejando debajo los valores y vemos cómo el atributo no cambia, algo que ya sabíamos y acabamos de ver. Esto implica que, en un hipotético enlace en dos sentidos, no podríamos simplemente enlazar el atributo y el valor directamente (salvo que el framework lo tenga en cuenta claro y provoque el cambio con un evento, cosa que ocurre en algunos de ellos):

Ejemplo de atributo/propiedad value

Finalmente tenemos un botón que se habilita o deshabilita mediante la propiedad enabled. Este atributo tiene la particularidad de que para deshabilitar un elemento solamente tiene que estar presente, pero el valor que le asignemos es indiferente, y es otro de los casos especiales que hemos comentado antes. Así, si lo inicializamos con este valor:

<button id="boton1" disabled="cualquiercosa">Botón 1</button>

A pesar de que lo que le hemos puesto como valor del atributo no tiene ningún sentido, el botón se inicia como deshabilitado simplemente porque está presente, y no existe además una relación directa entre el valor del atributo y el de la propiedad. La propiedad JavaScript, si la consultamos, tiene el valor true, independientemente del valor inicial real asignado a este atributo, por el mero hecho de estar presente en la etiqueta.

Luego, a medida que lo habilitamos y deshabilitamos utilizando la propiedad disabled el valor del atributo correspondiente no se actualiza, de hecho nunca hay una relación directa entre ellos, y lo que conseguimos es quitar o poner el atributo en el HTML pero no cambiar su valor:

Ejemplo de atributo disabled

Como vemos, la relación directa atributo/propiedad que muchas vences tenemos en mente cuando trabajamos con una pagina Web, no existe en muchos casos y se pueden dar situaciones que no esperábamos, sobre todo a la hora de trabajar con frameworks para situaciones de enlazado a datos.

¡Espero que te sea útil!