Si ya hemos leído los artículos anteriores, ya deberíamos saber como emitir eventos personalizados y como propagar eventos. Sin embargo, en este artículo vamos a repasar como aplicarlo a eventos personalizados.
Pero antes de nada, recordemos los pasos a seguir:
- 1️⃣ Crear un evento (genérico o personalizado)
- 2️⃣ Darle un nombre y definir sus opciones (si las necesitamos).
- 3️⃣ Si queremos, añadir información adicional al evento para enviarla.
- 4️⃣ Emitir el evento hacia un elemento del DOM con
.dispatchEvent()
.
Si no entiendes o no tienes clara esta parte, es muy probable que no hayas leído o te hayas saltado algún tema anterior. Sería recomendable repasarlo antes de continuar.
Detectar fase del evento
Podemos acceder a la propiedad .eventPhase
de nuestro evento para conocer en la fase/modalidad de propagación en la que estamos. Esta propiedad nos devolverá un
Propiedad | Valor | Descripción |
---|---|---|
Propiedad | ||
.eventPhase | Devuelve la fase en la que se encuentra el evento. | |
Valores posibles | ||
Event.NONE | 0 | Evento no está procesándose. |
Event.CAPTURING_PHASE | 1 | El evento está en fase de captura (de raíz a elemento). |
Event.AT_TARGET | 2 | El evento se emitió directamente (sólo al elemento concreto). |
Event.BUBBLING_PHASE | 3 | El evento está en modo burbujeo (de elemento a raíz). |
Para entenderlo mejor, veamos este gráfico ilustrativo:
1️⃣ Cuando usamos
bubbles: true
al crear elCustom Event
permitimos la propagación de eventos, por lo que el evento se va propagando desde el elemento objetivo donde lo disparamos, hasta el elemento raíz del documento, burbujeando hacia arriba.2️⃣ Cuando usamos
bubbles: true
y en el.addEventListener()
usamos la opcióncapture: true
, entonces el evento se escucha sólo en la fase de captura, es decir, se ignora la fase de burbujeo, y en la vuelta desde el elemento raíz hasta el elemento donde fue disparado es donde se escuchan los eventos, invirtiendo el orden.
En el caso de que tuvieramos
bubbles: false
, no hay propagación de eventos, el evento no burbujea, y simplemente se disparará en el elemento del DOM indicado.
Propagación de eventos
Veamos un ejemplo de código de la propagación de eventos personalizados:
const parent = document.querySelector(".parent");
const child = document.querySelector(".child");
const baby = document.querySelector(".baby");
const options = {
detail: { name: "ManzDev test" },
bubbles: true
};
parent.addEventListener("test-event", (ev) => { /* ... */ }, { capture: true });
child.addEventListener("test-event", (ev) => { /* ... */ }, { capture: true });
baby.addEventListener("test-event", (ev) => { /* ... */ }, { capture: true });
const event = new CustomEvent("test-event", options);
baby.dispatchEvent(event);
- 1️⃣ Observa que escuchamos en
.parent
,.child
y.baby
. - 2️⃣ En las opciones del
CustomEvent
tenemosbubbles: true
, o sea, hay propagación. - 3️⃣ En las opciones del
.addEventListener()
tenemoscapture: true
, o sea, fase de captura. - 4️⃣ En el caso de omitir el
capture: true
obtendríamos fase de bubbling (burbujeo). - 5️⃣ En el caso de tener
bubbles: false
, obtendríamos fase target. Sólo el elemento disparado.
Trayectoria de propagación
Como hemos visto, los eventos se propagan a lo largo de los elementos HTML del DOM. Quizás, en algún momento necesitemos esa trayectoria o saber por qué elementos ha pasado. Para ello, tenemos unas propiedades muy interesantes:
Propiedad o Método | Descripción |
---|---|
.target | Indica el elemento objetivo (donde se hizo el dispatchEvent() ). |
.currentTarget | Indica el elemento actual donde se ha escuchado el evento. |
.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. Si tienes dudas, una buena forma de comprobar el camino que ha seguido el evento en su propagación es ejecutando el método .composedPath()
. Este nos mostrará por donde ha ido pasando el evento:
parent.addEventListener("test-event", (ev) => console.log(ev.composedPath()));
child.addEventListener("test-event", (ev) => console.log(ev.composedPath()));
baby.addEventListener("test-event", (ev) => console.log(ev.composedPath()));
// [div.baby, div.child, div.parent, body, html, document, Window]
Por otro lado, la propiedad .target
nos dará el elemento desde donde se emitió el evento, <div class="baby">
en nuestro caso, y la propiedad .currentTarget
nos devolverá el elemento actual en el que se escucha y procesa el evento.
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 |
---|---|---|
.cancelable | true | Indica si es posible cancelar el evento. |
.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 añadir un evento en el elemento intermedio .child
que cancele la propagación de eventos mediante .stopPropagation()
. Esta detención de la propagación de eventos en .child
debería evitar que el evento llegue hasta .parent
, a pesar de tener el flag bubbles
activo.
const parent = document.querySelector(".parent");
const child = document.querySelector(".child");
root.addEventListener("test-event", () => console.log("Recibido en el parent."));
parent.addEventListener("test-event", () => {
parent.stopPropagation();
console.log("Recibido en child");
});
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 en ese .addEventListener()
. Recuerda que para que estos métodos funcionen, el evento debe tener el flag .cancelable
a true
.