Ya sabes encontrar nodos en el árbol. El siguiente paso es cambiarlos: actualizar un contador, marcar una tarea como hecha, añadir una fila a una tabla. Todo eso se reduce a unas pocas operaciones sobre el DOM, y elegir bien cuál usar marca la diferencia entre un código seguro y uno con agujeros.
1 // Cambiar el contenido
Un elemento tiene dos propiedades principales para leer o escribir su contenido:
const saludo = document.querySelector('#saludo');
saludo.textContent = 'Hola, Ana'; // inserta texto literal
saludo.innerHTML = 'Hola, <strong>Ana</strong>'; // interpreta el texto como HTMLtextContent trata el valor como texto: si contiene <strong>, se verán los símbolos < y > en pantalla. innerHTML lo trata como HTML: el navegador lo analiza, crea los nodos correspondientes y sustituye todos los hijos anteriores.
Nunca uses innerHTML con datos que vengan del usuario o de una fuente externa (un formulario, una API, la URL). Si el texto contiene etiquetas, el navegador las ejecuta: un nombre de usuario como <img src="x" onerror="..."> lanzaría código en el navegador de quien lo vea. Es un ataque de Cross-Site Scripting (XSS). Con datos externos, usa siempre textContent.
2 // Clases, estilos y atributos
Para cambiar el aspecto de un elemento, la opción recomendada es cambiar sus clases y dejar que el CSS haga el resto:
const tarea = document.querySelector('.tarea');
tarea.classList.add('hecha');
tarea.classList.remove('pendiente');
tarea.classList.toggle('seleccionada'); // la pone si no está, la quita si está
tarea.classList.contains('hecha'); // trueTambién puedes modificar estilos concretos con el.style, que usa los nombres de CSS en camelCase (el.style.backgroundColor). Resérvalo para valores que se calculan en tiempo de ejecución, como una posición o un ancho que depende de un dato.
Los atributos del HTML se leen y escriben con getAttribute y setAttribute, aunque los más comunes están disponibles también como propiedades:
const enlace = document.querySelector('a');
enlace.href = '/contacto.html';
enlace.setAttribute('target', '_blank');
const campo = document.querySelector('#email');
campo.disabled = true;
campo.value; // lo que ha escrito el usuarioPara guardar datos propios en un elemento existen los atributos data-*, que se leen con la propiedad dataset. Son la forma estándar de asociar un identificador u otro dato a un elemento:
<li data-id="42" data-prioridad="alta">Entregar práctica</li>li.dataset.id; // '42' (siempre texto)
li.dataset.prioridad; // 'alta'3 // Crear, insertar y eliminar
Para añadir contenido nuevo sin innerHTML, se crean los nodos uno a uno:
const lista = document.querySelector('#lista');
const li = document.createElement('li');
li.textContent = 'Repasar el DOM';
li.classList.add('urgente');
li.dataset.id = '7';
lista.append(li); // al final de la lista
lista.prepend(li); // al principio (lo mueve, no lo copia)
li.before(otroElemento); // justo antes de li, como hermano
li.remove(); // lo quita del documentoDos detalles importantes de este mecanismo:
- Un nodo sólo puede estar en un sitio. Si insertas un elemento que ya estaba en la página, se mueve. Para tener una copia, usa
el.cloneNode(true). - Cada inserción en el documento tiene un coste, porque el navegador puede tener que recalcular estilos y posiciones. Con unos pocos elementos no se nota; con cientos, conviene construir primero todo fuera del documento e insertarlo de una vez.
append acepta varios nodos a la vez e incluso textos: lista.append(li1, li2, li3). Y replaceChildren(...nodos) sustituye todo el contenido de un elemento, lo que resulta muy práctico para volver a pintar una lista entera.
4 // De los datos a la pantalla
El patrón que más vas a repetir es este: tienes un array de datos y quieres que la página lo muestre. Si los datos cambian, se vuelve a pintar a partir del array. Es exactamente el problema que Vue resolverá por ti más adelante, y conviene entenderlo antes a mano.
Tienes un array de tareas y una lista vacía <ul id="lista"></ul>. Hay que mostrar cada tarea, con la clase hecha en las completadas.
const tareas = [
{ id: 1, texto: 'Leer la unidad', hecha: true },
{ id: 2, texto: 'Hacer los ejercicios', hecha: false },
{ id: 3, texto: 'Preparar dudas', hecha: false },
];
function crearItem(tarea) {
const li = document.createElement('li');
li.textContent = tarea.texto;
li.dataset.id = tarea.id;
li.classList.toggle('hecha', tarea.hecha);
return li;
}
function pintar(tareas) {
const lista = document.querySelector('#lista');
lista.replaceChildren(...tareas.map(crearItem));
}
pintar(tareas);- Una función por elemento:
crearItemconvierte un objeto de datos en un nodoli. UsatextContent, así que un texto con etiquetas nunca se interpretará. - Toggle con condición:
classList.toggle('hecha', tarea.hecha)pone la clase si el segundo argumento estruey la quita si esfalse. - Transformar el array:
tareas.map(crearItem)produce un array de nodos, que el spread pasa como argumentos areplaceChildren. - Sustituir de una vez:
replaceChildrenborra lo que hubiera y coloca los tres elementos en una única operación.
pintar(tareas). Los datos son la fuente de verdad y el DOM es sólo su reflejo: esa separación es la idea central de los frameworks que verás al final del curso.5 // Para practicar
Cambia textos y clases de una página existente y construye filas de una tabla a partir de datos. Cada ejercicio trae el enunciado en los comentarios del código.
Modificar el blogtraining-daw-sv/modulo-01/02-modificar-el-documento/01_modify_domAñadir filas a una tablatraining-daw-sv/modulo-01/02-modificar-el-documento/02_tableAutoevaluación
Un comentario escrito por un usuario se muestra con div.innerHTML = comentario. ¿Qué riesgo hay y cómo se evita?
<img> y un atributo onerror ejecutaría JavaScript en la página de todos los que lo lean (XSS). Con textContent el texto se inserta como texto literal y nunca se interpreta.¿Por qué es preferible boton.classList.add('activo') a boton.style.backgroundColor = 'green' para marcar un botón como activo?
Si haces destino.append(elemento) con un elemento que ya estaba en otra parte de la página, ¿se duplica?
elemento.cloneNode(true).