Módulo 01 // Unidad 02

Modificar el documento

Cambiar textos, clases y atributos, y crear o eliminar elementos desde JavaScript

Unidad para alumnos del curso

Comprobando tu sesión…

Inicia sesión con la cuenta de Google con la que estás matriculado para ver esta unidad.

Esa cuenta no tiene acceso a este curso. Prueba con otra.

No se ha podido cargar el inicio de sesión de Google. Comprueba la conexión o si algún bloqueador lo impide, y recarga la página.

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 HTML

textContent 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');           // true

Tambié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 usuario

Para 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 documento
CREAR, CONFIGURAR E INSERTAR UN ELEMENTO en memoria · todavía no se ve 1 · crear document.createElement('li') 2 · configurar li.textContent = 'Repasar' li.classList.add('urgente') li.dataset.id = '7' 3 · append en el documento · se pinta ul#lista li li li.urgente nuevo hijo, al final Configurar antes de insertar: el navegador sólo recalcula y pinta cuando el nodo entra en el árbol.
La clave está en el paso 3: mientras el nodo está en la zona de la izquierda no forma parte de la página. Sólo al insertarlo el navegador lo añade al árbol y lo pinta.

Dos 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);
  1. Una función por elemento: crearItem convierte un objeto de datos en un nodo li. Usa textContent, así que un texto con etiquetas nunca se interpretará.
  2. Toggle con condición: classList.toggle('hecha', tarea.hecha) pone la clase si el segundo argumento es true y la quita si es false.
  3. Transformar el array: tareas.map(crearItem) produce un array de nodos, que el spread pasa como argumentos a replaceChildren.
  4. Sustituir de una vez: replaceChildren borra lo que hubiera y coloca los tres elementos en una única operación.
La lista muestra las tres tareas y cada vez que cambie el array basta con llamar otra vez a 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_table

Autoevaluación

Un comentario escrito por un usuario se muestra con div.innerHTML = comentario. ¿Qué riesgo hay y cómo se evita?
El navegador interpreta el texto como HTML, así que el usuario puede inyectar marcado y código. Un comentario con una etiqueta <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?
Porque separa el estado (JavaScript) de la apariencia (CSS). JavaScript sólo dice que el botón está activo; la hoja de estilos decide cómo se ve. Cambiar el diseño no obliga a tocar el código, y se aprovechan transiciones, temas y media queries.
Si haces destino.append(elemento) con un elemento que ya estaba en otra parte de la página, ¿se duplica?
No: se mueve. Un nodo sólo puede estar en un lugar del árbol, así que insertarlo en otro sitio lo quita de donde estaba. Para duplicarlo hay que clonarlo antes con elemento.cloneNode(true).