DOM Declarativo: Texto
En este artículo, exploraremos cómo implementar data-bindings de texto utilizando un DOM Declarativo en Javascript Vanilla. Esta técnica nos permite actualizar el contenido de los elementos de forma sencilla y eficiente.
La manera antigua #
Vamos a ver un ejemplo de lo que queremos conseguir:
<html>
<head>
<title>DOM Declarativo</title>
</head>
<body>
<main>
<section>
<button id="incrementar">Incrementar</button>
<span class="contador1">0</span>
</section>
<section>
<button id="decrementar">Decrementar</button>
<span class="contador2">100</span>
</section>
<section>
El resultado es:
<span class="contador1">0</span>
+ <span class="contador2">100</span>
= <span id="resultado">100</span>
</section>
</main>
</body>
</html>
Tenemos dos contadores, en la primera sección uno a 0, con un botón de incrementar, y en la segunda sección uno a 100, con un botón de decrementar. Por último, tenemos otra sección que muestra el resultado de la suma de ambos. Mostrando la operación completa.
Lo que queremos es que al pulsar los botones, se actualicen los contadores y se refleje el resultado en las secciones correspondientes.
Como los contadores aparecen múltiples veces, en lugar de ponerle un Id, lo ponemos un clase para identificarlos. Vamos a añadirle la funcionalidad.
let contador1 = 0;
let contador2 = 100;
let suma = 100;
incrementar.addEventListener("click", () => {
contador1 += 1;
suma = contador1 + contador2;
const c1 = document.querySelectorAll(".contador1");
[...c1].forEach((el) => el.textContent = contador1);
resultado.textContent = suma;
});
decrementar.addEventListener("click", () => {
contador2 -= 1;
suma = contador1 + contador2;
const c2 = document.querySelectorAll(".contador2");
[...c2].forEach((el) => el.textContent = contador2);
resultado.textContent = suma;
});
Y ya está, funciona. Pero es una pena que tengamos que repetir código para todos los elementos dinámicos queramos añadir. Tenemos otras alternativas.
DOM Declarativo #
Podemos aprovechar la reactividad que ganamos con las Señales, de las que hablamos el otro día, para implementar un DOM declarativo.
Primero, hacemos unos cambios en el HTML.
<html>
<head>
<title>DOM Declarativo</title>
</head>
<body>
<main>
<section>
<button id="incrementar">Incrementar</button>
<span data-texto="contador1">0</span>
</section>
<section>
<button id="decrementar">Decrementar</button>
<span data-texto="contador2">100</span>
</section>
<section>
El resultado es:
<span data-texto="contador1">0</span>
+ <span data-texto="contador2">100</span>
= <span data-texto="resultado">100</span>
</section>
</main>
</body>
</html>
Simple. Solamente hemos cambiado la clase de los contadores y el ID
del resultado por atributos data-texto. Esto nos
servirá para enlazar una señal a sus elementos del DOM. Hagamos
ahora las señales.
import { señal, efecto, derivado } from "señales.js";
const contador1 = señal(0);
const contador2 = señal(100);
const suma = derivado(() => contador1 + contador2);
efecto(() => {
const c1 = document.querySelectorAll("[data-texto='contador1']");
[...c1].forEach((el) => el.textContent = contador1);
});
efecto(() => {
const c2 = document.querySelectorAll("[data-texto='contador2']");
[...c2].forEach((el) => el.textContent = contador2);
});
efecto(() => {
const res = document.querySelectorAll("[data-texto='resultado']");
[...res].forEach((el) => el.textContent = suma);
});
incrementar.addEventListener("click", () => contador1.valor += 1);
decrementar.addEventListener("click", () => contador2.valor -= 1);
Bien. Hemos aprovechado la potencia y la reactividad de las señales para reducir la complejidad de los manejadores de eventos de los botones. Eso sí, no hemos simplificado mucho la actualización de la UI. Veamos cómo se puede generalizar.
import { señal, efecto, derivado } from "señales.js";
const contador1 = señal(0);
const contador2 = señal(100);
const resultado = derivado(() => contador1 + contador2);
const contexto = { contador1, contador2, resultado };
const textos = [...document.querySelectorAll("[data-texto]")];
textos.forEach(el => {
const clave = el.dataset.texto;
efecto(() => el.textContent = contexto[clave]);
el.removeAttribute("data-texto");
});
incrementar.addEventListener("click", () => contador1.valor += 1);
decrementar.addEventListener("click", () => contador2.valor -= 1);
Mucho mejor! Ahora tenemos una función genérica que se puede conectar a cualquier elemento del DOM con la directiva correcta. Además el DOM queda muy limpio, porque eliminamos los atributos de los que dependemos, ya que el binding ya está hecho.
El camino está marcado para crear otras directivas, ¿quién sabe cuáles? ¿data-binding bidireccional, condicionales, bucles?
El código completo estará disponible en los retales