DOM Reactivo: Mostrar

En este artículo, exploraremos cómo implementar la funcionalidad de mostrar u ocultar elementos dentro de un DOM Reactivo en Javascript Vanilla. Esta técnica nos permite actualizar la visibilidad 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 Reactivo</title>
  </head>
  <body>
    <main>
      <section>
        <button id="mostrar1">Mostrar/Ocultar seccion 1</button>
        <p class="contenido1">¡Hola mundo!</p>
        <p class="contenido1">¡Estoy visible!</p>
      </section>
      <section>
        <button id="mostrar2">Mostrar/Ocultar seccion 2</button>
        <p class="contenido2">¡Hola gente!</p>
        <p class="contenido2">¡Sigo aquí!</p>
      </section>
    </main>
  </body>
</html>
          

Repartidos en dos secciones, tenemos en cada una un par de elementos con la clase contenido, mostrados por defecto, y un botón Mostrar/Ocultar que cambiará la visibilidad de los elementos.

Lo que queremos es que al pulsar el botón, se oculten los elementos y, al pulsarlo de nuevo, se vuelvan a mostrar.

let mostrado1 = true;
let mostrado2 = true;

mostrar1.addEventListener("click", () => {
  mostrado1 = !mostrado1;
  
  const contenido1 = document.querySelectorAll(".contenido1");
  [...contenido1].forEach((el) => el.style.display = mostrado1 ? "block" : "none");
});

mostrar2.addEventListener("click", () => {
  mostrado2 = !mostrado2;
  
  const contenido2 = document.querySelectorAll(".contenido2");
  [...contenido2].forEach((el) => el.style.display = mostrado2 ? "block" : "none");
});
          

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 Reactivo #

Podemos aprovechar la reactividad que ganamos con las Señales, de las que hablamos el otro día, para implementar un DOM reactivo.

Primero, hacemos unos cambios en el HTML.

<html>
  <head>
    <title>DOM Reactivo</title>
  </head>
  <body>
    <main>
      <section>
        <button id="mostrar1">Mostrar/Ocultar seccion 1</button>
        <p data-mostrar="visible1">¡Hola mundo!</p>
        <p data-mostrar="visible1">¡Estoy visible!</p>
      </section>
      <section>
        <button id="mostrar2">Mostrar/Ocultar seccion 2</button>
        <p data-mostrar="visible2">¡Hola gente!</p>
        <p data-mostrar="visible2">¡Sigo aquí!</p>
      </section>
    </main>
  </body>
</html>
          

Simple. Solamente hemos cambiado la clase de los contenidos por atributos data-mostrar. Esto nos servirá para enlazar una señal a sus elementos del DOM. Hagamos ahora las señales.

import { señal, efecto } from "señales.js";

const visible1 = señal(true);
const visible2 = señal(false);

efecto(() => {
  const c1 = document.querySelectorAll("[data-mostrar='visible1']");
  [...c1].forEach((el) => el.style.display = visible1.valor ? "block" : "none");
});

efecto(() => {
  const c2 = document.querySelectorAll("[data-mostrar='visible2']");
  [...c2].forEach((el) => el.style.display = visible2.valor ? "block" : "none");
});

mostrar1.addEventListener("click", () => visible1.valor = !visible1.valor);
mostrar2.addEventListener("click", () => visible2.valor = !visible2.valor);
          

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.

Hemos aprovechado para añadir que el segundo contenido esté oculto por defecto, algo que antes no podíamos conseguir de manera sencilla.

import { señal, efecto } from "señales.js";

const visible1 = señal(true);
const visible2 = señal(false);

const contexto = { visible1, visible2 };

const mostrados = [...document.querySelectorAll("[data-mostrar]")];
mostrados.forEach((el) => {
  const clave = el.dataset.mostrar;
  efecto(
    () => (el.style.display = contexto[clave].valor ? "block" : "none"),
  );
  el.removeAttribute("data-mostrar");
});

mostrar1.addEventListener(
  "click",
  () => (visible1.valor = !visible1.valor),
);
mostrar2.addEventListener(
  "click",
  () => (visible2.valor = !visible2.valor),
);
          

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.

En este caso hemos puesto un valor booleano directamente en la señal, pero podríamos crear una señal derivada que compruebe que otra señal es mayor que 0 o que un texto contenga una palabra.

El camino está marcado para crear otras directivas, ¿quién sabe cuáles? ¿data-binding bidireccional, bucles?

El código completo estará disponible en los retales