Ahora que conocemos lo básico sobre los eventos nativos y los eventos personalizados, así como la emisión y propagación de eventos que afecta a ambos, vamos a aprender un poco más sobre esta propagación, como gestionarla, prevenirla y modificarla si es necesario.
En primer lugar, recordemos como gestionamos un evento y su propagación:
<div class="root">
<div class="parent">
<div class="child">
<button>Click me!</button>
</div>
</div>
</div>
Tenemos esta estructura HTML, donde existe un botón dentro de tres elementos <div>
anidados uno dentro del otro en el DOM. Vamos a escuchar los eventos de tipo click
en el <button>
y cuando ocurra alguno, crearemos un evento personalizado y lo enviaremos al botón. Como tenemos el bubbles
activado, no se detendrá, sino que seguirá propagándose hacia sus contenedores padres:
const button = document.querySelector("button");
const root = document.querySelector(".root");
button.addEventListener("click", (event) => {
const customEvent = new CustomEvent("warning", { bubbles: true });
button.dispatchEvent(customEvent);
});
root.addEventListener("warning", (event) => {
console.log("Evento click recibido en el root.", event);
});
Observa que al final nos hemos puesto a escuchar los eventos ocurridos en el elemento <div>
raíz, es decir, el primero de todos. Si llega hasta ahí nuestro evento personalizado, ejecutará el console.log()
mostrando dicho evento. De no tener el bubbles
a true
, el custom event nunca se habría propagado hasta al elemento root, sino que se habría quedado en el el elemento <button>
.
Propagación de eventos
Vamos a analizar como funciona la propagación de eventos en Javascript. Los eventos tienen una serie de propiedades que analizaremos a continuación. Son las siguientes:
Propiedad o Método | Descripción |
---|---|
Propiedades | |
.bubbles | Indica si el evento se propagará hacia contenedores padres o se detendrá en el elemento emitido. |
.composed | Indica si el evento puede atravesar un Shadow DOM en su propagación, o no. |
Destino del evento | |
.target | Indica el elemento objetivo (donde se hizo el dispatchEvent() ). |
.currentTarget | Indica el elemento actual donde se ha escuchado el evento. |
Método | |
.composedPath() | Muestra el camino de elementos por donde se ha propagado el evento. |
Centremonos en el fragmento de código anterior, donde creamos el evento personalizado y establecemos si se va a propagar el evento. En el caso anterior, hemos activado la propiedad bubbles
del segundo parámetro de opciones de la instancia del evento personalizado con new CustomEvent()
:
const customEvent = new CustomEvent("warning", { bubbles: true });
button.dispatchEvent(customEvent);
Propagación y Shadow DOM
Hay que tener en cuenta que pueden ocurrir varias cosas:
- Si el flag
bubbles
está desactivado, el evento se emite a<button>
y se detiene ahí. - Si el flag
bubbles
está activado, el evento se emite a<button>
, luego a su contenedor padre, y así sucesivamente. - Si el flag
composed
está desactivado, el evento se detendrá al encontrar un Shadow DOM. - Si el flag
composed
está activado, el evento no se detendrá si encuentra un Shadow DOM.
Trayectoria de propagación
Si tienes dudas, una buena forma de comprobar el camino que ha seguido el evento emitido con bubbles
es ejecutando el método .composedPath()
. Este nos mostrará por donde ha ido pasando el evento:
root.addEventListener("warning", (event) => {
console.log("Evento click recibido en el root.");
const path = event.composedPath();
console.log(path);
});
// path = [button, div.child, div.parent, div.root, body, html, document, Window]
Observa que la constante path
tiene un <button>
, luego a su padre .child
, luego a su padre .parent
, luego a su padre .root
, luego al <body>
, luego al <html>
y por último al document
(el documento actual) y Window
la pestaña actual del navegador.
Por otro lado, la propiedad .target
nos dará el elemento desde donde se emitió el evento, <button>
en nuestro caso, y la propiedad .currentTarget
nos devolverá el elemento actual en el que se encuentra, .root
en nuestro caso.
Detener la propagación
Por defecto, los eventos nativos tienen la propiedad .cancelable
a true
. Esto significa que los eventos pueden cancelar su propagación utilizando los métodos .stopPropagation()
o .stopImmediatePropagation()
.
Propiedad o Método | Valor por defecto | Descripción |
---|---|---|
Propiedad | ||
.cancelable | true | Indica si es posible cancelar el evento. |
Métodos | ||
.stopPropagation() | Detiene la propagación en el evento en cuestión. | |
.stopImmediatePropagation() | Detiene la propagación en todos los eventos del mismo tipo. |
Vamos a modificar el ejemplo inicial que teníamos, y añadir un evento en el elemento intermedio .parent
que cancele la propagación de eventos mediante .stopPropagation()
. Esta detención de la propagación de eventos en .parent
debería evitar que el evento llegue hasta .root
, a pesar de tener el flag bubbles
activo.
const root = document.querySelector(".root");
root.addEventListener("warning", (event) => {
console.log("Evento click recibido en el root.", event);
});
const parent = document.querySelector(".parent");
parent.addEventListener("warning", (event) => {
parent.stopPropagation();
console.log("Recibido en parent");
});
La diferencia de .stopPropagation()
y .stopImmediatePropagation()
es que este último detiene la propagación en todos los eventos de su mismo tipo, mientras que el primero sólo detiene el evento concreto donde lo escribimos. Recuerda que para que estos métodos funcionen, el evento debe tener el flag .cancelable
a true
.