Los eventos son acciones que ocurren cuando interactuamos con una web, por ejemplo, cuando hacemos click en un botón, escribimos en un input text... son la forma que tienen de interactuar el html y el javascript. Cuando un evento se lanza, el navegador notifica a la aplicación que algo ha ocurrido y que hay que manejarlo. Los eventos se controlan con funciones de javascript, de forma que cuando dicho evento ocurre, el navegador alerta a nuestra función que está escuchando que debe ejecutarse.

¿Cómo manejamos los eventos?

Hay dos formas de añadir eventos. Una desde el html con atributos añadiendo por ejemplo un onClick() en el elemento que queremos que desencadene la acción y otra desde el javascript utilizando el método addEventListener(). No sé si hay una forma mejor u otra peor, pero es mejor separar el javascript del html y evitar el antipatrón de mezclar html y js.

Sin embargo, con este enfoque “tenemos un problema” cuando queremos añadir el mismo evento a varios elementos del DOM. De esta forma, tendríamos tantos eventos como elementos del DOM que desencadenan la acción. Además, los elementos que se cargan de forma dinámica no se verían afectados por los eventos. Para solucionar estos problemas surge la delegación de eventos que es de lo que vamos a hablar en este post.

La delegación de eventos consiste en escuchar los eventos en el elemento padre para capturarlo cuando ocurra en sus hijos.

Antes de entrar en profundidad en la delegación de eventos, vamos a ver cómo funcionan los eventos con un ejemplo sencillo, añadiendo un evento a un botón. Aquí en el codesandbox podéis verlo.

const button = document.querySelector(".btn");

const changeTextButton = () => {
 button.classList.toggle("clicked");
};

button.addEventListener("click", changeTextButton);

¿Qué hemos hecho? El método addEventListener es la forma más habitual que usamos para registrar eventos. Recibe dos parámetros: el tipo de evento que queremos “escuchar” y la acción (o función) que queremos ejecutar cuando el evento suceda.

¿Cómo funcionan los eventos? Cuando clicamos en un botón, se desencadenan las siguientes fases:

  1. Fase de captura: el evento empieza en el window, document y en el root y luego entra dentro de cada uno de los hijos.
  2. Fase target: el evento se lanza sobre el elemento en el que se hace click.
  3. Fase bubble: finalmente el evento sube hacia los ancestros desde el target hasta llegar de nuevo al root, document y window.

En este ejemplo podéis ver cómo se propagan las diferentes fases de los eventos.

Desde el punto de vista del código, un evento no es más que un objeto donde se almacena información relacionada con el mismo.

Algunas de las propiedades que podemos encontrar en el objeto evento son las siguientes:

Aquí podéis ver más información sobre las propiedades y métodos de los eventos.

Ahora que ya sabemos qué son los eventos y cómo funcionan, vamos a ver cómo podemos trabajar con ellos. Veamos este ejemplo en el que mostramos una tabla con varias filas y columnas y, en cada celda, un botón de borrar. En este caso, siguiendo el ejemplo anterior, tendríamos que hacer algo similar a esto para capturar el evento de cada botón:

const buttons = document.querySelectorAll(‘.btn_deleted’);

buttons.forEach(button => {
    button.addEventListener('click', () => console.log('Clicked!'));
});

¿Qué tendríamos en este caso?

Para ayudarnos con estos dos problemas podemos usar la técnica de delegación de eventos.

La delegación de eventos consiste en declarar un evento en el contenedor del elemento sobre el que queremos lanzarlo. Al estar declarado en el padre, ¿cómo sabe cuándo tiene que lanzarse el evento al hacer click en un botón determinado?

Cuando lanzamos un evento, el navegador crea un objeto evento, donde recoge las propiedades de dicho evento. Utilizando dicha información, comprobamos a través del target cuál es el elemento que lanza el evento. En este caso, lo que hacemos es comprobar si el target tiene la clase ‘buttonClass’ y si la tiene se lanzará el evento.

Para entenderlo de forma fácil, con la delegación de eventos tenemos un elemento que está continuamente escuchando si se lanza o no un evento y que tendrá que cumplir la condición que le pongamos. Para comprobar que se cumple esta condición podemos usar el método matches() que nos devuelve “true” si el elemento seleccionado coincide con el que le hemos pasado dentro del método o “false” si no coincide.

Otra forma que tenemos de comprobar es si el target está contenido en cierto elemento para esto utilizaremos Node.contains(). Este método igual que el matches() nos devuelve un booleano, en este caso, lo que comprueba es si el selector que le pasamos es descendiente directo del nodo. Es útil, por ejemplo, cuando queremos cerrar un modal haciendo click fuera de este.

Aquí os dejo una librería que a mí personalmente me ha simplificado mucho la vida.

Para ello utilizo dos funciones: las funciones on y off.

const on = (type, selector, handler) => {
 const useCapture = ["blur", "focus"].includes(type);
 if (events[type] == null) {
   events[type] = [];
   document.addEventListener(type, run, useCapture);
 }

 events[type].push({
   selector,
   handler
 });
};
const off = (type, selector, handler) => {
 const handlers = events[type];
 events[type] = handlers
   ? handlers.filter(h => {
       return h.selector !== selector && h.handler !== handler;
     })
   : [];

 if (events[type].length === 0) {
   events[type] = null;
   document.removeEventListener(type, run);
 }
};

En este caso, los eventos están declarados en el document. Aquí sentiros libres de declararlos en el document o al padre del elemento target. La ventaja de utilizar el document es que siempre está ahí independientemente de que hagamos renderizado de forma dinámica, es un elemento que nunca desaparece.

Con la función “on” declaramos un evento que se lanzará cuando el target coincida con el elemento. Con la función “off” borramos el evento cuando el target coincida con el elemento que le indiquemos.

¿Cómo utilizamos esta función?

Si os fijáis en el archivo index.js:

on(“click”, “.wadus”, click);

Siguiendo el ejemplo anterior (el de la tabla) vamos a ver ahora el mismo ejemplo esta vez utilizando esta librería.

Esta técnica ya está siendo utilizada por otros frameworks y librerías. Desde hace ya algún tiempo, jquery tiene un método on y off para añadir eventos y removerlos. Os dejo este artículo donde explica cómo maneja la delegación de eventos React.

Como podemos ver, la delegación de eventos es útil para escuchar eventos en múltiples elementos, con tan solo un manejador. Al delegar en el padre, los eventos estarán siempre ahí. Desde el punto de vista del rendimiento de nuestra aplicación, puede parecer contraproducente tener un elemento escuchando todos los eventos, pero es justo todo lo contrario No tiene el mismo coste lanzar 40 eventos que uno, con lo cual nuestra performance mejorará.

¿Qué ventajas tiene delegar los eventos en el document o en el window?

Como hemos visto hay diferentes formas de trabajar con la delegación de eventos, así que utilizad aquella con la que os sintáis más cómodos. Desde que encontré esta librería, con las funciones on y off, para mí es de esas cosas que te las guardas para siempre y que aplico en todos los proyectos. Adaptarlo como mejor os convenga según vuestras necesidades. Si tenéis experiencia ya con delegación de eventos o algún tipo de duda y te apetece que hablemos, cuéntamela :)

Recursos:

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.