JavGuerra,
El uso del DOM desde JavaScript ha mejorado mucho en los últimos años. Librerías como jQuery facilitaron enormemente la tarea del programador a la hora de acceder a identificadores, clases y etiquetas, y aunque ahora disponemos de nuevas formas de acceso en JavaScript que han hecho olvidar en parte aquella librería, la forma en que trabajamos con el DOM aún se puede mejorar. En esta entrada veremos unas pocas funciones que nos ayudarán en el día a día.
Selección al estilo jQuery
Para empezar, un canto a la nostalgia. Si usabas la famosa librería y echas de menos su forma tan simple de funcionar, aquí tienes una aproximación a la selección de elementos del DOM.
function $(el) { return document.querySelector(el); };
o en versión función flecha, como más te guste:
const $ = el => document.querySelector(el);
La función $(el) seleccionará un elemento (el) del DOM que podemos asignar a una variable o con el que podemos usar propiedades, por ejemplo:
$('#aviso').classList.add('rojo');
que añadirá la clase .rojo al elemento con el id aviso.
El uso de querySelector() ha venido a simplificar mucho el acceso a los elementos del DOM, pero con esta función $(), seleccionar elementos es aún más simple, y va a ser de ayuda en las siguientes funciones.
Podemos mejorar esta función dándole la posibilidad de buscar dentro de un elemento específico.
const $ = (el, parent = document) => parent.querySelector(el);
Podríamos usarla de esta forma:
const fila = $("#fila3", tabla);
Donde tabla es el elemento padre. De no indicarlo, el elemento padre será todo el documento (document).
También podemos seleccionar varios elementos con $$() con esta función:
const $$ = el => document.querySelectorAll(el);
E igualmente podemos mejorar esto:
const $$ = (el, parent = document) => [...parent.querySelectorAll(el)];
Aquí, además de poder indicar el nodo padre, obtendremos automáticamente el NodeList en un array ([…]).
Crear nuevos elementos y conectarlos al DOM
La creación de nuevos elementos del DOM requiere de varios pasos. Crear el elemento, introducir la información en el elemento si es necesario y conectarlo al elemento padre. ¿Qué tal si lo simplificamos con una función?
function createEl(parent, tag, content = "") {
const el = document.createElement(tag);
if (content) el.textContent = content;
parent.appendChild(el);
return el;
}
Con la función createEl() cubrimos los tres pasos necesarios para crear, completar y conectar un nuevo elemento, y la función nos devolverá el nuevo elemento para que podamos trabajar con él:
const $titulo = createEl($('body'), 'h1', 'Hola Mundo');
Con esta línea crearemos un elemento H1 dentro del body que contendrá el texto 'Hola Mundo', y la referencia al nuevo elemento está contenida en la variable $titulo.
El $ de la variable titulo no es necesario (ni que estuviésemos programando en PHP…), pero recordando el uso de jQuery, es una buena forma de identificar aquellas variables que hacen referencia a elementos del DOM para diferenciarlas de las otras.
Esta versión puede mejorarse mucho para, por ejemplo, añadir atributos (attrs) e hijos (children)… hasta donde te de la imaginación.
Uso práctico: Creación de tablas básicas con createEl()
Esta nueva función es muy potente, y simplifica mucho las líneas de código que tenemos que usar para crear estructuras del DOM complejas con JavaScript. Un ejemplo de ello son las tablas y sus etiquetas anidadas. Veamos cómo sería una función que crease una tabla simple con createEl():
function createTable(parent, id, headers, footers = null) {
const table = createEl(parent, 'table');
table.setAttribute('id', id);
const thead = createEl(table, 'thead');
let tr = createEl(thead, 'tr');
for (const header of headers) {
const th = createEl(tr, 'th', header);
th.setAttribute('scope', 'col');
}
if (footers) {
const tfoot = createEl(table, 'tfoot');
tr = createEl(tfoot, 'tr');
for (const footer of footers) {
createEl(tr, 'td', footer);
}
}
return createEl(table, 'tbody');
}
La función createTable() crea una tabla dentro de un elemento padre, le asigna un identificador, y crea las cabeceras y pies que le indiquemos, devolviendo la referencia al elemento tbody de la tabla para que podamos rellenarla con filas y columnas.
Un ejemplo sencillo de uso sería:
$tBody = createTable($('#section'), 'miTabla', ['núm.', 'producto']);
$tBody.innerHTML = '<tr><td>1</td><td>vegetales</td></tr>';
que mostrará la siguiente tabla a la que podemos seguir añadiendo elementos:
| núm | producto |
|---|---|
| 1 | vegetales |
Fácil y cómodo. ¿verdad?
Vaciar un elemento de forma eficiente
Vaciar un contenedor con innerHTML = "" funciona, pero la siguiente función es más eficiente y segura, porque no reemplaza el contenedor entero y no rompe event listeners internos, lo que puede provocar pérdida o duplicación de eventos.
function emptyEl(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
Un ejemplo para borrar el contenido de una lista:
emptyEl($("#lista"));
Añadir o quitar una clase CSS
La función:
function toggleClass(el, className) {
return el.classList.toggle(className);
}
aprovecha el.classList.toggle() para:
- Añadir una clase si el elemento no la tiene.
- Quitar una clase si el elemento ya la tiene.
y devuelve true si la clase se añadió y false si la clase se eliminó.
Esta función es ideal para interacciones de usuario tipo click para abrir/cerrar, expandir/contraer, activar/desactivar.
Establecer una clase según un estado booleano
La función setClass() permite añadir o quitar una clase según un booleano, garantizando que el elemento queda exactamente en el estado indicado. A diferencia de toggleClass(), no alterna: fuerza el estado.
function setClass(el, className, active) {
el.classList.toggle(className, active);
}
Ejemplos:
setClass(menu, 'open', true); // añade la clase
setClass(menu, 'open', false); // la quita
setClass(input, 'error', !isValid(input.value)); // Estado de error en un formulario
tabs.forEach(tab => {
setClass(tab, 'active', tab.id === selectedId); // Pestaña activa según ID
});
Esta función es perfecta para sincronizar la UI con estados lógicos (isOpen, hasError, isActive, etc.).
Mostrar u ocultar elementos del DOM
Una función que nos aporte una forma simple de hacer visible u ocultar cualquier elemento del DOM, por ejemplo las secciones de una página web de una única página (SPA) sería la siguiente:
function showEl(el, visible) {
visible
? el.style.removeProperty("display")
: el.style.display = "none";
}
La función showEl() requiere dos parámetros: elemento del DOM con el que vamos a trabajar y si va a estar visible (true) o invisible (false).
Digamos que en una SPA hemos completado un formulario, y ahora queremos mostrar los resultados. con esta función podríamos:
showEl($('#form'), false);
showEl($('#results'), true);
Partimos de la base de que el elemento con identificador results estaba oculto, y el elemento con identificador form visible. De esta forma el formulario se ocultaría y serían visibles los datos derivados de la gestión de formulario.
Comprobar si un elemento está visible
Esta función permite saber si un elemento está realmente visible en la página.
function isVisible(el) {
return !!(el.offsetWidth ||
el.offsetHeight ||
el.getClientRects().length);
}
La función showEl() e isVisible() se complementan perfectamente porque la primera controla si un elemento se muestra u oculta, y la segunda detecta si un elemento está actualmente visible.
Veamos un ejemplo de uso conjunto:
if (!isVisible(panel)) {
showEl(panel, true); // lo muestra solo si está oculto
}
Activar o desactivar un botón
Es una buena práctica desactivar un botón cada vez que se dispare un evento asociado a este. Nada más fácil con la siguiente función:
function setInactiveBtn(button, inactive) {
button.disabled = inactive;
inactive
? button.setAttribute('aria-disabled', 'true')
: button.removeAttribute('aria-disabled');
}
Con setInactiveBtn() cambiamos el estado disabled del botón que indiquemos pasándole los valores true o false. Un ejemplo:
function setInactiveBtn($('#submit'), true);
La función desactivará el botón con el identificador submit (disabled = true). Para volver a activarlo, será suficiente con usar la misma función con el status false.
Comprobar si un botón está activo
También podemos necesitar conocer el estado de un botón, si este está activo o inactivo.
function isInactiveBtn(button) {
return button.disabled === true;
}
Un ejemplo de uso con setInactiveBtn():
setInactiveBtn(btn, !isInactiveBtn(btn));
Activa o desactiva el botón invirtiendo su estado actual (si está activo lo desactiva, y si está inactivo lo activa).
Decodificar entidades HTML con ayuda del DOM
Pasemos ahora a cosas más específicas a modo de bonus.
Aunque JavaScript incluye funciones como encodeURIComponent() y decodeURIComponent(), estas se utilizan exclusivamente para codificar y decodificar URLs, no entidades HTML. Por tanto, una función como esta resulta práctica cuando recibimos cadenas que contienen entidades como & o ", ya que el lenguaje no ofrece un método nativo para convertirlas en texto legible. Podemos apoyarnos en el motor del DOM para conseguirlo.
La siguiente función crea un nodo temporal, asigna su contenido usando innerHTML y obtiene el texto decodificado mediante textContent. Es un ejemplo interesante de cómo el DOM puede ayudarnos más allá de la manipulación visual de la página.
Cuando la función recibe la información codificada con entidades HTML, por ejemplo, a través de una API, y queremos reconducir esa información a, por ejemplo, un fichero de logs en texto plano, o tal vez enviarla a consola, la información no se muestra correctamente. Una forma de convertir a un string la información que incluye entidades HTML sería la siguiente:
function decodeHTMLEntities(html) {
if (typeof html !== "string") return "";
const div = document.createElement("div");
div.innerHTML = html;
return div.textContent || "";
}
La función recibe la cadena a «decodificar» y devuelve la cadena en texto simple. Para ello obliga al DOM a trabajar para nosotros creando un elemento div en el que se incluye el texto «codificado» y del que se lee luego el texto «decodificado» para devolverlo con return. Como se ve, el elemento div nunca llega a conectarse al árbol DOM.
Tomemos el siguiente ejemplo:
let texto = decodeHTMLEntities('Los%20ni%C3%B1os%20com%C3%ADan%20y%20daban%20migajas%20a%20la%20cig%C3%BCe%C3%B1a.');
El valor de texto resultante será: ‘Los niños comían y daban migajas a la cigüeña.’.
Iniciar si el DOM está cargado
Concluyo con una función primordial.
Con onReady(), si el DOM ya está cargado, se ejecutará la función de inicio que le pasemos inmediatamente. Si el DOM aún se está cargando, añade un listener a DOMContentLoaded.
function onReady(fn) {
if (document.readyState !== "loading") {
fn();
} else {
document.addEventListener("DOMContentLoaded", fn);
}
}
Veamos un ejemplo de uso:
onReady(() => {
const btn = $("#btnStart");
setClass(btn, "ready", true);
});
La función onReady() permite escribir inicialización limpia sin preocuparte por dónde está ubicado el script o de qué forma se carga.
Tu propio kit
Espero que alguna de estas funciones te resulte útil, te hagan la vida más agradable y, sobre todo, que te animen a experimentar por tu cuenta.
El DOM ofrece muchísimas posibilidades, y a veces basta con probar una idea para descubrir una utilidad nueva que encaje contigo y ampliar de esta forma tu kit de herramientas. ¡Diviértete explorando!