¿Cuáles son las fases de los eventos en javscript?
Cada vez que pulsamos una tecla, hacemos click u otras interacciones dentro de una página web se esta produciendo un evento. En este post vamos a ver, a través de un ejemplo, el recorrido que tienen los eventos y un posible problema que nos podemos encontrar si no lo conocemos.
Arranquemos con el ejemplo : Vamos a crear una página que solo tenga un contenedor y dentro de ese contenedor un botón que al presionarlo cambia el color del contenedor a amarillo. Para realizar esto, necesitamos que el elemento button esté escuchando el evento. Para esto le vamos a asignar al botón un manejador de eventos (event handler) que responda al evento 'click' utilizando Jquery:
Hago click y cambia el color:
Veamos el código html:
Y el script js:
var boton = $(".pnt-js-boton-cambia-color"); boton.click(function(event) { $(".pnt-js-contenedor-boton").css("background-color","yellow"); });
Nuestro botón tiene asignado un manejador de eventos que al hacer click cambia el color a amarillo. Nada de otro mundo. Probemos con otra cosa... quiero que al hacerle click al fondo (el contenedor que tiene el botón) se cambie el color a rojo. Para eso debemos asignarle handlers al div (en nuestro ejemplo es la variable contenedorBoton) :
var boton = $(".pnt-js-boton-cambia-color"); var contenedorBoton = $(".pnt-js-contenedor-boton"); boton.click(function(event) { $(".pnt-js-contenedor-boton").css("background-color","yellow"); }); contenedorBoton.click(function() { $(".pnt-js-contenedor-boton").css("background-color","red"); });
Efectivamente al hacer click en el fondo se cambia el color a rojo. Pero si hacemos click nuevamente a nuestro botón amarillo, vemos que sigue el fondo rojo. ¿Qué paso?
Vamos a corroborar que los handlers estén escuchando los eventos, pongamos logs desde cada elemento. Cuando el elemento boton escuche el evento logueara por pantalla "HOLA DESDE EL BOTON" y el contenedorBoton "HOLA DESDE EL CONTENEDOR". Deberiamos obtener en la consola el mensaje del botón. Veamos:
var boton = $(".pnt-js-boton-cambia-color"); var contenedorBoton = $(".pnt-js-contenedor-boton"); boton.click(function(event) { $(".pnt-js-contenedor-boton").css("background-color","yellow"); console.log("HOLA DESDE EL BOTON"); }); contenedorBoton.click(function() { $(".pnt-js-contenedor-boton").css("background-color","red"); console.log("HOLA DESDE EL CONTENEDOR"); });
Obtenemos el siguiente resultado:
Al visualizar el mensaje ¿"HOLA DESDE EL CONTENEDOR"? en la consola ya nos podemos imaginar lo que está ocurriendo. Cuando hacemos click, ambos hanlders son activados y por lo tanto el segundo 'pisa' el color del primero. Pero... ¿por qué ocurre esto?. Para comprender lo sucedido, hay que hablar sobre el flujo de los eventos o fases.
Flujo de los eventos
Al producirse un evento, éste viaja desde la raíz del DOM y se detiene en el elemento que lo desencadenó (conocido como elemento target) y luego vuelve sobre sus pasos hasta llegar a la raíz nuevamente. Podemos definir 3 fases:
- Capturing (el evento desciende al elemento que lo desencadenó).
- Target (el evento llega y dispara la acción del handler).
- Bubbling (el evento 'burbujea' hasta la raíz sobre sus pasos)
En estas fases se activan los manejadores de eventos que estén a la escucha del evento que se está ejecutando.
Expliquémoslo con nuestro ejemplo del botón. Al hacer click en el botón comienza la fase capturing de nuestro evento que viaja desde el exterior hacia el interior hasta llegar a nuestro target (botón). Si hay un manejador de eventos asociado con el elemento en esta ruta que coincide con el evento que está pasando actualmente, se detectará y activará el handler. Lo mismo ocurre en la ruta de bubbling. El flujo del evento en el ejemplo se graficaría así:
Entonces lo que está pasando es que cuando el evento llega al target y luego burbujea, activa el handler del contenedor, por eso es que el contenedor queda de color rojo, es el unico handler que se ejecuta. Pero banca...siguiendo la lógica de lo que vimos se debería activar el handler del elemento contenedor en la fase capturing, ¿no?. Lo curioso de esto es que para que los eventos se detecten en la fase de capturing, hay que indicarle al handler que esté a la escucha en esa fase, ya que por default los eventos se detectan en la fase target y en bubbling. En nuestro ejemplo, utilizamos Jquery (los handlers en jquery solo se detectan en la fase bubbling), si estuvieramos utilizando javascript puro tendríamos la opción de habilitar y deshabilitar la fase de capturing agregandole un parámetro más al método addEventListener.
Todo muy lindo pero ¿Cómo podríamos solucionar nuestro problema?.
Los handlers reciben una referencia a una funcion que se le puede pasar por parametro el evento, por lo tanto podemos acceder a los métodos que tiene el objeto event. Entre sus métodos tiene stopPropagation() que impide que siga 'burbujeando'. O sea que el evento viaja hasta el target y luego se detiene su propagación hacia arriba.
var boton = $(".pnt-js-boton-cambia-color"); var contenedorBoton = $(".pnt-js-contenedor-boton"); boton.click(function(event) { $(".pnt-js-contenedor-boton").css("background-color","yellow"); console.log("HOLA DESDE EL BOTON"); event.stopPropagation(); }); contenedorBoton.click(function() { $(".pnt-js-contenedor-boton").css("background-color","red"); console.log("HOLA DESDE EL CONTENEDOR"); });
Voilà!