El DOM o Document Objet Module divide la página en objetos individuales que pueden ser seleccionados y manipulados.
Las páginas tienen un orden. Lo que primero esté en el código de la página es lo primero que va a cargar. Por eso se pone el CSS en el HEAD (para que cargue primero) y el JS en el BODY (para modificar una web primero tiene que cargar el elemento a modificar y si lo ponemos primero va a fallar).
La forma más habitual de ver el DOM es en forma árbol.
Podemos utilizar esta extensión de Chrome para ver la estructura del DOM en forma árbol de cualquier página: HTML Tree Generator.
Resumen del artículo
Document es el documento. La página donde estamos y que contiene todos los elementos.
La propiedad firstElementChild devuelve el primer elemento hijo del elemento especificado.
La propiedad lastElementChild devuelve el último elemento hijo del elemento especificado.
document;
// HTML document.firstElementChild; // HEAD document.firstElementChild.firstElementChild; // BODY document.firstElementChild.lastElementChild; // BUTTON document.firstElementChild.lastElementChild.firstElementChild;
Seleccionar elementos HTML
En vez de tener que ir de firstElementChild a firstElementChild podemos seleccionar directamente elementos en el DOM del HTML. Tenemos varios métodos:
- querySelector: Solo selecciona 1 elemento. Si hay varios elementos solo selecciona el primero.
- getElementsByTagName: Busca en toda la página ese TagName. La diferencia entre el anterior es que coge TODOS, no solo 1. No puedes cambiar directamente una propiedad (ej. color) si hay más de un objeto porque es un Array. Aunque sea solo 1 objeto, lo da como Array. Podemos seleccionar un objeto especifico de ese Array con los corchetes. Podemos ver el número de elementos con .length.
- getElementsByClassName: Busca por clases de CSS en vez por TagName como el anterior. Al estar en plural como el anterior da un Array.
- getElementById: Busca por ID de CSS. Esta en singular, así que va a dar el elemento directamente, no en Array. Eso es porque en CSS un ID debe ser único para un elemento.
- querySelectorAll: Exactamente igual que querySelector solo que en forma Array porque selecciona todos.
Se pueden hacer búsquedas sencillas del primer elemento o hacer búsquedas más profundas.
// Elemento p document.querySelector("p");
// Elemento li y elemento hijo a document.querySelector("li a");
// Elemento li y clase item document.querySelector("li.item");
// ID list y elemento hijo a document.querySelector("#list a");
Para manipular el DOM
Una vez tenemos seleccionado el elemento podemos realizar acciones sobre el.
Lo más normal es guardar la ruta del elemento en una variable y luego aplicar los métodos sobre esa variable, pero se puede hacer también directamente sobre la ruta.
Modificar texto de un elemento
Tenemos principalmente 2 métodos para modificar el texto de un elemento.
- innerHTML
- textContent
innerHTML vs textContent
Con innerHTML podemos modificar todo lo que contenga el objeto. Por ejemplo sí tiene una etiqueta <strong> también la mostrará y podremos modificarla.
Con textContent solo muestra el texto.
Así que se puede utilizar innerHTML para cambiar todas las etiquetas dentro de objetos y cualquier HTML. Y textContent para cambiar solo los valores de texto.
Normalmente si solo queremos modificar el texto de un elemento vamos a utilizar textContent.
// Muestra BUTTON al llamar la variable botón de nuevo var boton = document.firstElementChild.lastElementChild.firstElementChild; boton.innerHTML = "Cambio algo de texto dentro del botón";
// Muestra BUTTON al llamar la variable botón de nuevo var boton = document.firstElementChild.lastElementChild.firstElementChild; boton.innerHTML = "Cambio algo de texto dentro del botón";
Modificar estilos de un elemento
Podemos cambiar el estilo y el texto de los elementos. Del estilo podemos cambiar o modificar las clases.
Style
Después de style podemos poner cualquier tipo de CSS (color, background, display, font-size …) y el valor que queremos que tenga esa propiedad.
var boton = document.firstElementChild.lastElementChild.firstElementChild; boton.style.color = "red";
Clases
En una hoja de estilos externa tenemos las clases de CSS que luego se aplicaran sobre nuestro HTML.
Ejemplo de una hoja de estilos externa:
.unaClaseDeEjemplo{ background-color: grey; border: 1px solid black; font-size: 17px; } .otraClaseDeEjemplo{ background-color: blue; border: 3px solid red; font-size: 5px; }
Podemos asignar ver las clases que ya tenga ese elemento con classList (devuelve un Array). Y asignarle una o varias clase con className, esto va a sustituir todas las clases que ya tuviera el elemento.
var boton = document.querySelector("button"); boton.classList; boton.className = "unaClaseDeEjemplo"; boton.className = "unaClaseDeEjemplo otraClaseDeEjemplo";
También usando classList podemos realizar modificaciones a las clases ya existentes del elemento.
- add: Añade una o varias clases sin borrar las que ya existen.
- remove: Elimina una o varias clases específicamente.
- toggle: Hace el cambio. Si ya contiene la clase que se le pasa como valor da como resultado false y la elimina. Si el elemento no contiene la clase que se le pasa como valor entonces da true y la añade. Es muy usado sobre todo para pasar elementos de clase activo a inactivo o de visible a oculto.
var boton = document.querySelector("button"); boton.classList; // Add boton.classList.add("unaClaseDeEjemplo"); boton.classList.add("unaClaseDeEjemplo", "otraClaseDeEjemplo"); // Remove boton.classList.remove("unaClaseDeEjemplo"); boton.classList.remove("unaClaseDeEjemplo", "otraClaseDeEjemplo"; // Toggle boton.classList.toggle("unaClaseDeEjemplo");
Modificar atributos de un elemento
Un atributo es un componente de un elemento en HTML.
Por ejemplo en un elemento tipo INPUT podemos definir los atributos type, class o value. Y esos atributos tienen cada uno su valor.
O más sencillo, si usáis Visual Studio Code, todo lo que este de color azul claro dentro de cualquier elemento de HTML es un atributo.
Y con Javascript podemos manipular esos atributos.
- attributes: Muestra todos los atributos de un elemento en formato Array.
- getAttribute: Muestra el valor del atributo que especifiquemos.
- setAttribute: Realizamos modificaciones en el valor del atributo que especifiquemos.
- hasAttribute: Da true o false si el elemento contiene o no el atributo.
- removeAttribute: Elimina tanto el atributo como su valor.
var elementoA = document.querySelector("a"); elementoA.attributes; elementoA.getAttribute("href"); elementoA.setAttribute("href", "https://google.es"); elementoA.hasAttribute("href"); elementoA.removeAttribute("href");
Para realizar acciones en el DOM
Es muy útil realizar cambios sobre estilos y texto. Pero la forma que hemos visto hasta ahora es estática, no requiere que el visitante de la página realice acciones.
Podemos resolver esto con los eventos. Nos van a permitir que nuestra página sea dinámica. Para que por ejemplo un elemento cambie de estilo requiera que el usuario haga click.
Es un método y se añade igual que hemos visto anteriormente pero el segundo parámetro que vamos a introducir es la función que se va a ejecutar cuando el evento se cumpla.
Podemos crearlo de 2 formas, la primera es especificando la función que se va a ejecutar fuera y luego llamarla. O podemos utilizar una función anónima dentro del propio evento. El resultado es el mismo.
En este ejemplo vamos a mostrar una alerta con el texto “He pulsado el párrafo” cada vez que hagamos click sobre el primer elemento párrafo.
var elementoParrafo = document.querySelector("p"); // Función externa para el evento function ejemploDeFuncion(){ alert("He pulsado el párrafo. Función externa."); }; elementoParrafo.addEventListener("click", ejemploDeFuncion); // Función anónima dentro del evento elementoParrafo.addEventListener("click", function(){ alert("He pulsado el párrafo. Función anónima."); });
Hay muchos tipos de eventos, aunque los más usados son los relacionados con el uso del ratón (click, wheel, mouseenter) y con el uso del teclado (keydown, keypress, keyup). Aquí puedes ver todos los tipos de eventos: MDN Developer – Event Reference.
Event Bubbling
En nuestro código HTML es normal tener elementos anidados. Unos dentro de otros. Como por ejemplo un DIV centrado, que contengan otros DIV y que cada uno tenga un titulo H3 y un párrafo P.
Código HTML ejemplo:
<!DOCTYPE html> <html lang="es"> <head> <title>Ejemplo</title> </head> <body> <div id="general"> <div id="peque-1"> <h3 class="titulo">Primero</h3> <p>Lorem ipsum dolor sit amet.</p> </div> <div id="peque-2"> <h3 class="titulo">Segundo</h3> <p>Nam, voluptatem.</p> </div> </div> <script src="app.js"></script> </body> </html>
El Event Bubbling se basa en que que al estar anidados si pulsas un elemento hijo, también estas pulsando los elementos padre. Podríamos verlo como capas superpuestas unas encima de otras, en el momento que tocas una las que estén por encima también las estas tocando.
Entonces el problema esta en la propagación de los eventos. Si tenemos un Event Listener especifico no va a haber problema. Pero sí tenemos varios Event Listener en varios elementos superpuestos entonces en el momento que se ejecute el elemento hijo, todos los padres se van a ejecutar también.
let primerTitulo = document.getElementsByClassName("titulo")[0]; let divPequePrimero = document.getElementById("peque-1"); let segundoTitulo = document.getElementsByClassName("titulo")[1]; let divPequeSegundo = document.getElementById("peque-2"); let divGeneral = document.getElementById("general"); function funcionPrimerTitulo() { console.log("primer Titulo"); } function funcionDivPequePrimero() { console.log("div Peque Primero"); } function funcionDivGeneral() { console.log("div General"); } primerTitulo.addEventListener("click", funcionPrimerTitulo); divPequePrimero.addEventListener("click", funcionDivPequePrimero); divGeneral.addEventListener("click", funcionDivGeneral);
En el momento que pulsemos el primer título se van a ejecutar el evento del primer título, el del DIV pequeño que lo contiene y el del DIV general que los contiene.
Y si pulsamos sobre el DIV pequeño se va a ejecutar el evento propio del DIV pequeño y el del DIV general.
Evitar el Event Bubbling
Hay varias formas de evitar esta propagación de eventos pero la más usual es utilizar el método stopPropagation. Al especificar este método en nuestra función del evento va a evitar la propagación hacia las capas padre.
let primerTitulo = document.getElementsByClassName("titulo")[0]; let divPequePrimero = document.getElementById("peque-1"); let divGeneral = document.getElementById("general"); function funcionPrimerTitulo(event) { console.log("primer Titulo"); event.stopPropagation(); } function funcionDivPequePrimero(event) { console.log("div Peque Primero"); event.stopPropagation(); } function funcionDivGeneral(event) { console.log("div General"); event.stopPropagation(); } primerTitulo.addEventListener("click", funcionPrimerTitulo); divPequePrimero.addEventListener("click", funcionDivPequePrimero); divGeneral.addEventListener("click", funcionDivGeneral);
Event Delegation
Cuando tenemos muchos elementos, crear un evento para cada elemento no es factible ya que consumiría excesiva memoria. Entonces lo que se suele hacer es crear un evento a un elemento general y luego filtrar con un IF.
Vamos a crear una tabla en HTML. El elemento TABLE contiene las celdas, en este ejemplo son tan solo 9 celdas pero podrían ser cientos o miles. El CSS es simplemente un borde para poder diferencias las celdas. Luego cada celda tiene 2 tipos de clases, una y dos.
<!DOCTYPE html> <html lang="es"> <head> <title>Ejemplo</title> <style> table { border-collapse: collapse; } table,th,td { border: 1px solid black; } </style> </head> <body> <table id="mi"> <tr> <th class="uno">Nombre</th> <th class="dos">Apellido</th> <th class="uno">Edad</th> </tr> <tr> <td class="dos">Luis</td> <td class="uno">Dieguez</td> <td class="dos">20</td> </tr> <tr> <td class="uno">José</td> <td class="dos">Guerrero</td> <td class="uno">94</td> </tr> </table> <script src="app.js"></script> </body> </html>
Y ahora en Javascript en vez de crear un evento para cada elemento de la table, utilizamos el elemento general TABLE y filtramos. Hay 2 formas principalmente de hacer el Event Delegation aunque el resultado es el mismo. Se usa className normalmente cuando el elemento tiene solo 1 clase ya que si tiene más las va a devolver como una misma cadena de texto todo seguido. Se utiliza classList para listar todas las clases del elemento en formato Array y luego la función contains para comprobar si tiene esa clase en concreto.
let tablaGeneral = document.getElementById("mi"); let divPequePrimero = document.getElementById("peque-1"); function funcionPrimerTitulo(event) { if (event.target.className == "uno") { console.log("Ha pulsado las celdas uno"); } if (event.target.classList.contains("dos")) { console.log("Ha pulsado las celdas dos"); } } tablaGeneral.addEventListener("click", funcionPrimerTitulo);
Como vemos en el ejemplo simplemente es añadir un poco de lógica mediante IF. La propiedad target es especifica para crear este event delegation. Con la propiedad de className directamente especificamos la clase. Si utilizamos classList nos devuelve una lista de las clases de ese elemento, pero podemos utilizar contains para especificar la clase que queremos.