Módulo 01 // Unidad 02

Modificar el documento

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

Comprobando la sesión…

Esta unidad es para alumnos del curso. Inicia sesión con tu cuenta de Google para verla.

Esa cuenta no tiene acceso a este curso. Prueba con la cuenta con la que te inscribiste.

No se ha podido cargar el inicio de sesión de Google. Revisa la conexión o si un 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).