Lección 7: DOM | Curso JavaScript

Lección 7: DOM

Una característica muy importante y utilizada es la capacidad de poder manipular el HTML, también denominado DOM, como necesitemos: Crear nuevas etiquetas, modificar algunas ya existentes, borrar, cambiar textos, atributos, añadir estilos… y casi cualquier elemento que se te pase por la cabeza.

Actualmente hay un cambio de estrategia para dibujar una página dinámica. Tradicionalmente se generaba HTML en el backend y el frontend se encargaba solamente de modificar las etiquetas ante algún evento. Actualmente se tiende a recoger toda la información del backend, por medio de una API usando Fetch en formato JSON, y dibujar en el frontend el HTML con algún Framework de renderizado (Vue, React, Angular…). Sus ventajas son numerosas, pero no es objetivo de esta lección profundizar en el enfoque mencionado.

Por la red encontrarás una librería popular denominada JQuery. Se encuentra en desuso por las últimas versiones del estándar de JavaScript que incorporan herramientas más que suficientes para lograr los mismos resultados, por lo tanto evita incorporarlo a tu proyecto.

Centremos la atención a todo lo que nos ofrece JavaScript.

Capturar un elemento

Un selector con una coincidencia

Posiblemente será la opción que más utilices. Con document.querySelector(), capturarás un solo elemento, y te devolverá un HTMLElement. Un objeto que representa la etiqueta. Como si fuera un selector de CSS, debes indicar en su parámetro de entrada que elemento quieres obtener.

<h1 id="titulo">Curriculum vitae</h1>
const titulo = document.querySelector("#titulo");

console.log(titulo);
// <h1 id="titulo">Curriculum vitae</h1>

Por id

¡No recomendada! Utiliza en su lugar document.querySelector().

Puedes capturar únicamente por id.

<h1 id="titulo">Curriculum vitae</h1>
const titulo = document.getElementById("titulo");

console.log(titulo);
// <h1 id="titulo">Curriculum vitae</h1>

Capturar varios elementos

Disponemos de diferentes formas para capturar un elemento del DOM.

Un selector con varias coincidencias

Con document.querySelectorAll() podrás capturar varios elementos a la vez. Devolverá un listado. También debes usar un selector, como CSS, para indicar que grupo de etiquetas estas buscando.

<p class="contacto">Direcciones</p>
<p class="contacto">Teléfono</p>
const contactos  = document.querySelectorAll(".contacto");

console.log(contactos);
// [<p class="contacto">Direcciones</p>, <p class="contacto">Teléfono</p>]

Por etiqueta

¡No recomendada! Utiliza en su lugar document.querySelector().

<p>Experiencia</p>
<p>Actitudes</p>
<p>Habilidades</p>
const parrafos = document.getElementsByTagName("p");

console.log(parrafos);
// [<p>Experiencia</p>, <p>Actitudes</p>, <p>Habilidades</p>]

Por clase

¡No recomendada! Utiliza en su lugar document.querySelectorAll().

<h1 id="titulo">Curriculum vitae</h1>
<p>Experiencia</p>
<p>Actitudes</p>
<p>Habilidades</p>
<p class="contacto">Direcciones</p>
<p class="contacto">Teléfono</p>
<a href="http://papelera.com/" id="enlace">Ya le llamaremos</a>
const contactos = document.getElementsByClassName("contacto");

console.log(contactos);
// [<p class="contacto">Direcciones</p>, <p class="contacto">Teléfono</p>]

Estilos

Ya que sabemos capturar elementos del DOM, ahora vamos a gestionar sus estilos.

Leer

A partir del siguiente HTML y CSS.

#titulo {
  color: orange;
}
<h1 id="titulo">Curriculum vitae</h1>

Capturamos la etiqueta, que no el CSS, donde obtendremos la información y jugamos con style seguido del estilo que busquemos.

const DOMTitulo = document.querySelector("#titulo");
const colorTitulo = DOMTitulo.style.color;

console.log(colorTitulo);
// orange

Añadir o sobreescribir

Continuamos con la misma estrategia.

#titulo {
  color: orange;
}
<h1 id="titulo">Curriculum vitae</h1>

En esta ocasión sobrescribimos el estilo del elemento asignando una nueva variable.

const DOMTitulo = document.querySelector("#titulo");
titulo.style.color = "white";

console.log(titulo.style.color);
// white

console.log(titulo);
// <h1 id="titulo" style="color: white">Curriculum vitae</h1>

Como puedes comprobar, ha creado el atributo style con la nueva propiedad. Lo hace para que el estilo que hemos aplicado no pueda ser modificado por la cascada CSS.

Borrar

Solo puedes eliminar los estilos añadidos por JavaScript. Para ello solo debes asignar como valor null.

#titulo {
  color: orange;
}
<h1 id="titulo">Curriculum vitae</h1>
const titulo = document.querySelector("#titulo");
titulo.style.color = "white";
titulo.style.color = null;

console.log(titulo.style.color);
// orange

console.log(titulo);
// <h1 id="titulo" style="">Curriculum vitae</h1>

Atributos

Leer

<a href="http://dominio.com/" id="enlace">Otra página</a>
const miHref = document.querySelector("#enlace").getAttribute("href");

console.log(miHref);
// "http://dominio.com/"

Modificar

<a href="http://dominio.com/" id="enlace">Otra página</a>
const enlace = document.querySelector("#enlace");
enlace.setAttribute("href", "http://falso.com/");

console.log(enlace.getAttribute("href"))
// "http://falso.com/"

Borrar

<a href="http://dominio.com/" id="enlace" target="_blank">Otra página</a>
const enlace = document.querySelector("#enlace");
enlace.removeAttribute("target");

console.log(enlace)
// <a href="http://dominio.com/" id="enlace">Otra página</a>

Clases

classList: Leer

<article id="mi-articulo" class="container center">
const miArticulo = document.querySelector("#mi-articulo");

console.log(miArticulo.classList);
// ["container", "center"]

add: Añadir

<article id="mi-articulo" class="container center">
const miArticulo = document.querySelector("#mi-articulo");
miArticulo.classList.add("hide");

console.log(miArticulo.classList);
// ["container", "center", "hide"]

remove: Quitar

<article id="mi-articulo" class="container center">
const miArticulo = document.querySelector("#mi-articulo");
miArticulo.classList.remove("container");

console.log(miArticulo.classList);
// ["center"]

toggle: Intercambiar

Si la clase existe, la elimina. Si no existe, la añade.

<article id="mi-articulo" class="container center">
const miArticulo = document.querySelector("#mi-articulo");
miArticulo.classList.toggle("container");
miArticulo.classList.toggle("hide");

console.log(miArticulo.classList);
// ["center", "hide"]

DOM

Crear

Vamos a generar una etiqueta nueva, no existente en el HTML.

Partimos del siguiente código.

<div id="articulo"></div>

1) Creamos nos la etiqueta y la guardamos en una variable para gestionarla.

const titulo = document.createElement("h1");

2) La modificamos. En este caso añadiré un contenido.

titulo.textContent = "Mi título";

3) Lo añadiré a otro DOM existente.

document.querySelector("#articulo").appendChild(titulo);

El resultado.

<div id="articulo">
  <h1>Mi título</h1>
</div>

Borrar

Podemos usar la referencia que habíamos guardado al crearla con removeChild().

const titulo = document.createElement("h1");

document.querySelector("#articulo").removeChild(titulo);

O directamente capturar el elemento para a continuación borrar con remove().

document.querySelector("h1").remove();

Plantillas

Cuando debes generar un bloque grande de HTML que lleva tras de sí una cierta complejidad puedes simplificar tu vida creando una plantilla que a continuación clonarás para utilizar en tus iteraciones.

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8"/>
        <title>Document</title>
    </head>
    <body>

        <nav id="nav">
            <ul id="nav__ul"></ul>
        </nav>

        <!-- Plantillas -->
        <template id="enlace">
            <li>
                <a href=""></a>
            </li>
        </template>

        <script>

         // Variables
         const misEnlaces = ["inicio", "nosotros", "contacto"];
         const navUl = document.querySelector("#nav__ul");
         // Capturo el contenido de la plantilla
         const templateLi = document.querySelector("template#enlace").content.firstElementChild;

         misEnlaces.forEach(function(enlace) {
             // Clono la plantilla
             const nuevoLi = templateLi.cloneNode(true);
             // Personalizo la información
             nuevoLi.querySelector("a").textContent = enlace;
             nuevoLi.querySelector("a").setAttribute("href", "".concat(enlace, ".html"));
             // Incluyo a mi <ul>
             navUl.appendChild(nuevoLi);
         });

        </script>
    </body>
</html>

El resultado generado quedaría así.

<nav id="nav">
    <ul id="nav__ul">
        <li>
            <a href="inicio.html">inicio</a>
        </li>
        <li>
            <a href="nosotros.html">nosotros</a>
        </li>
        <li>
            <a href="contacto.html">contacto</a>
        </li>
    </ul>
</nav>

cloneNode(true) clona el objeto con todos sus hijos mientras que cloneNode() clona el objeto padre.

Clonar objetos

¡Cuidado con las referencias! ¿Por qué podemos guardar un elemento del DOM en una constante y aún así es posible modificar su contenido? Porque en JavaScript se guardar las referencias al objeto, como si fuera un puntero de C. En otras palabras, guardas la dirección donde se encuentra en la memoria. Ello provoca las siguientes situaciones.

const persona1 = { edad: 42 };
const persona2 = persona1;
persona1 === persona2; // true, misma referencia

const persona3 = { edad: 42 };
persona1 === persona3; // false, diferente referencia, aunque misma data

Solución: Siempre clona, puedes usar structuredClone(objecto) si es un objeto o JSON. En caso de ser elementos del DOM capturados, cloneNode() como hemos visto anteriormente.

const persona4 = structuredClone(persona1);
persona1 === persona4; // false

Evita el uso de la técnica JSON.parse(JSON.stringify(objecto)) para clonar objetos ya que convierte los objetos a string. Por ejemplo, transformará un valor new Date(123) a "1970-01-01T00:00:00.123Z".

Parsear texto a HTML

La función insertAdjacentHTML te permite convertir un texto en elementos del DOM.

Por ejemplo:

<p id="mi-p"></p>
const miP = document.querySelector("#mi-p");
miP.insertAdjacentHTML("beforeend", "<span>Texto</span>");

El resultado sería:

<p id="mi-p">
    <span>Texto</span>
</p>

No es una sustitución rápida a todo lo visto anteriormente. En primer lugar pierdes las referencias a los elementos creados, por lo que no podrás modificarlos. Y en segundo lugar, no es seguro porque puede ser un vector de ataque XSS (inyección de código). Por lo tanto, úsalo con precaución, y si es posible, evítalo.

El primer parámetro de insertAdjacentHTML puede ser:

  • beforebegin: Antes del elemento.
  • afterbegin: Dentro del elemento, antes de su primer hijo.
  • beforeend: Dentro del elemento, después de su último hijo.
  • afterend: Después del elemento.

Mientras que el segudo parámetro es el texto que deseas convertir en elementos del DOM.

No confundas insertAdjacentHTML con innerHTML. La primera añade elementos HTML, mientras que la segunda sobrescribe todo el contenido del elemento. Por ejemplo, si usas innerHTML para añadir un nuevo campo a un formulario, perderás los valores añadidos por el usuario.

Actividad 1

Muestra un nombre que acompañe 2 botones en su lateral: 'Presente', 'Ausente'.

  • Cuando se pulse en 'Presente' debe colorearse el nombre de color verde y desaparecer los botones.
  • Cuando se pulse en 'Ausente' debe colorearse el nombre de color rojo y desaparecer los botones.

Nivel pesadilla 👹

  • Muestra una lista con varios nombres. La funcionalidad debe ser la misma.
  • Si se pulsa en 'Ausente', en lugar de borrar ambos botones aparecerá un botón con el texto 'Llega tarde'. Al apretarlo se coloreará en amarillo.
Actividad 2

A partir de un JSON, donde has estructurado la información de un portafolio, muestra en contenido con un formato adecuado y complejo en HTML. Puedes apoyarte en un framework HTML.

Una posibilidad.

const portafolio = [
    {
	nombre: 'Gafas el tuerto',
	anyo: 2021,
	categoria: 'Web',
	imagen: 'foto.jpg',
	descripcion: 'Una e-commerce para la venta de monoculos baratos.'
	},
	{
	nombre: 'Ropa sucia',
	anyo: 2019,
	categoria: 'APP',
	imagen: 'foto2.jpg',
	descripcion: 'Red social de solteros que viven solos.'
    }
...
]
  • Crea un campo que al escribir filtre por nombre.
  • Añade botones para filtrar el contenido por categorias.
  • Acompaña el filtro de un select que listo todos los años en el JSON.
Actividad 3

En 2 inputs introduce números. Al ser pulsado el botón, llamado Calcular, debe sumarlos y mostrar el resultado.

Ejercicio 7-3

Actividad 4

Cuando se pulse en el botón de la campana, debe aumentar el número que lo acompaña.

Ejercicio 7-4

Actividad 5

Crea una web que cambie su color de fondo cada segundo de forma aleatoria.

Pista: En la documentación de Mozilla existen diferentes técnicas para obtener números aleatorios dentro de un rango.

Actividad 6

Cuando se pulse en el elemento debe desplegar, o crecer, hasta mostrar la respuesta a la pregunta. Si es pulsado de nuevo debe esconderse, o volver al estado anterior.

Ejercicio 7-6

Actividad 7

Al ser pulsado el botón debe aparecer un mensaje (modal).

Nivel pesadilla 👹

Cuando se pulse en el aspa del modal, se ocultará.

Ejercicio 7-7

Actividad 8

Crea un textarea con un límite de carácteres es 100. Para informar al usuario, enseña los carácteres restantes.

Ejercicio 7-8

Actividad 9

Cuando se pulse en el botón Inicio debe incrementarse el número cada segundo.

Ejercicio 7-9

Actividad 10

Muestra el día actual usando Date().

Nivel pesadilla 👹

Enseña el día y la hora.

Ejercicio 7-10

Actividad 11

Crea un editor visual para un sencillo párrafo. En el ejemplo se usa el texto "Karate", de color negro y tamaño 12.

Ejercicio 7-11

Actividad 12

Aleatoriamente, dibuja 3 números entre el 0 y el 9.

Ejercicio 7-12

Nivel pesadilla 👹

  • Añade un campo de créditos.
  • Si coinciden los 3 números, añade 10 créditos.
  • Si pierdes, resta 1 crédito.
Actividad 13

Cuando se pida una carta debe mover una carta aleatoria de la baraja a la mano del jugador. Si llega a 21,5 ganará. En caso contrario perdará.

Parte con la siguiente información.

const baraja = [
  1, 2, 3, 4, 5, 6, 7, 0.5, 0.5, 0.5
  1, 2, 3, 4, 5, 6, 7, 0.5, 0.5, 0.5
  1, 2, 3, 4, 5, 6, 7, 0.5, 0.5, 0.5
  1, 2, 3, 4, 5, 6, 7, 0.5, 0.5, 0.5
];
let mano = [];

Ejercicio 7-13

Actividad 14

Crea un carrousel con las siguientes características.

  • Si se pulsa en el botón derecho, debe pasar a la siguiente.
  • Si se pulsa en el botón izquierdo, debe retroceder.
  • Debe comportarse como un bucle. Si llega a la última debe volver a la primera, por ejemplo.
  • Añade un circulo, con un radio, por cada imagen. Al ser pulsada sobre ella debe cambiar a esa imagen.

Ejercicio 7-14

Nivel pesadilla 👹

Añade un botón que al ser pulsado, haga que cada segundo cambie a la siguiente imagen.

Actividad 15

Fabrica el juego del ahorcado.

  • Si no se acierta la letra, debe decrecer el contador de fallos.
  • Si se acierta la letra, debe mostrarse.
  • Cuando no existan huecos, se informa al jugador que se ha ganado.
  • En caso que no queden más fallos, también se informará al jugador.

Ejercicio 7-15

Nivel pesadilla 👹

  • En cada partida la palabra a adivinar debe cambiar.
  • Muestra un dibujo de un hombre en lugar del contador de fallos.
Actividad 16

¡Estamos sufriendo un ataque nuclear en 8 Bits!

  • Aleatoriamente crea un misil que descienda del cielo a la base de la página. Tendrás 3 carriles verticales donde colocarlo.
  • Si se pulsa sobre él, debe desaparecer (evento `click`).
  • Si colisiona con el suelo se destruirá la base (evento `animationend`).
  • Cuando no queden bases se perderá.
  • Al destruir 10 misiles, se ganará.

Ejercicio 7-16

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me invitas a un café? ☕

Puedes hacerlo usando el terminal.

ssh customer@andros.dev -p 5555

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario