duduromeroa.com

#Javascript, #DOMJs, #eventHandlers, #eventListeners, #duduromeroa, #Guayaquil

Javascript | DOM | API

Programación para web (sec. 1.7): Eventos, manejadores de eventos y escuchas de eventos (event handlers y event listeners) en JS


JS, eventos, escuchas de eventos, DOM. www.duduromeroa.com

Por Eduardo J. Romero Andrade
[Actualizado junio 26, 2024] | Guayaquil

Contacto duduromeroa@gmail.com

Aclaraciones
Dirijo este contenido para quienes ya cuentan con comprensión previa en HTML, CSS y JS. Para ampliar conceptos se propone una bibliografía en inglés al final de esta página.


Para resaltar algunos elementos de la sintaxis de JS, en algunos ejemplos de código se agregaron espacios entre caracteres. La sintaxis correcta NO usa esos espacios.


En algunos ejemplos expongo código y comentarios. También revisa el código fuente (click derecho desde mouse > Page Source). Allí también comento los códigos.






En interacción web los eventos son acciones externas ejecutadas por el usuario. Por ejemplo, al tocar un elemento en pantalla o al clickear sobre un botón en el interfaz de una página web. El código interno del sistema no podría detectar ese evento a menos que el usuario haya determinado de antemano (y en el sistema) cuál es ese evento, dónde debe ocurrir y cuál será el resultado de eso.

En el lenguaje de programación web Javascript los listeners funcionan como 'escuchas' o detectores de eventos. El verbo 'escuchar' es tan solo es una analogía dada por la documentación de ese lenguaje, pero a la que considero una analogía confusa.

Yo prefiero decir que un eventListener o un escucha de evento es una sintaxis de código en Javascript que permite al programador definir tres roles interactivos: un elemento activador (un botón, por ejemplo), un evento (la acción de click, por ejemplo) y un resultado (un intercambio de imágenes, por ejemplo). Luego de definir eso, el eventListener detectará esa conjunción de roles y responderá con un resultado perceptible (de forma visual o sonora) para el usuario. Es decir, más que un escucha de evento (eventListener), es un detector de evento (eventDetect). Pero dejemos la sintaxis original (inspirada en otro lenguaje de programación llamado Java, creado en 1995).

Gráfico explicativo del comportamiento de un eventListener en Javascript, y su relación con un evento y un elemento activador. Elaboración: https//:www.duduromeroa.com
Concepto del comportamiento de un eventListener en Javascript y su relación con un evento y un elemento activador. Fuente: www.duduromeroa.com

Para Flanagan [3, sección 13.2.1] los eventos son acciones físicas que el usuario intermedia con un sistema para obtener un resultado: mover el mouse, presionar una tecla o tocar un botón en pantalla. Por lo que las funciones listeners detectan esos eventos y accionan otras funciones para dar resultados (u otros eventos) en pantalla.

Para W3Schools un evento es toda acción o respuesta que un usuario o una página web hace, incluso luego de enterarse (o detectar, o escuchar) que otro evento ha sido ejecutado.

En otras palabras, si la interactividad (la madre de los eventos) refiere a obtener respuestas en tiempo real mediante acciones, los eventos son los vehículos por el cual esas acciones se ejecutan y son perceptibles por el ser humano.

Vamos paso a paso. Café a la mano, xfi ☕︎.

Para entender eventos en JS:

  • Eventos
  • Escuchas de eventos (Event Listeners:)
  • Manejadores de eventos (event handlers)
  • Objeto evento
  • Propagación y captura de eventos (Event Bubbling y Event Capturing)
  • Delegación de eventos
  • Eliminar eventos o escuchas de eventos (Removing Event Listeners)

EVENTOS

Para un navegador web los eventos son acciones externas que no están coordinadas en tiempo con ninguna otra. Es decir, se conoce qué podría ocurrir, pero se desconoce cuándo, una condición llamada asíncrona.

Para Haberke [1, pág. 248] algunos eventos funcionan con la acción directa (en forma de actividad de entrada) desde el usuario. Esa acción podría ser accionando el mouse o el teclado.

La documentación de desarrollo web usa interfaces para agrupar propiedades y métodos que sirven para crear, detectar y ejecutar eventos desde el navegador web. Las interfaces son colecciones de propiedades y métodos específicos. Por ejemplo, la interfaz para interacción táctil en pantalla (TouchEvent) contiene sus propios métodos y propiedades.

Mozilla mdn web doc da una extensa lista de herramientas de código basadas en el gran interface Event. Es decir, cada interface agrupa propiedades y métodos para una causa específica.

40 INTERFACES BASADAS EN EVENTOS


Podemos agrupar todo ese conjunto de propiedades y métodos para eventos en 4 grupos: eventos DOM, eventos de objetos DOM, propiedades de eventos HTML, y y métodos de eventos.

Algunos de los 85 eventos DOM -en negritas-

  • onafterprint
  • animationstart
  • keydown

Abajo, algunos de los 16 eventos de objetos

  • DragEvent
  • MouseEvent
  • WheelEvent

Abajo, algunos de las 81 propiedades de eventos

  • button
  • changeTouches
  • ctrlKey

Finalmente, algunos de los 5 métodos de eventos

  • preventDefault()
  • stopPropagation()

Podemos decir entonces que cada interface de eventos agrupan métodos y propiedades para crear y activar eventos. Y a su vez, los eventos se dividen en 4 grupos que combinan lo contenido en las interfaces. Estos grupos son (de mayor a menor): eventos de DOM, eventos de propiedades, eventos de objetos, eventos de métodos.

Gráfico que representa la infuencia de los interfaces basadas en eventos y la unión de los conjuntos de eventos y propiedades en el DOM de JS.Fuente: https//:www.duduromeroa.com

Entonces, si ya tenemos qué es un evento. ¿Quién se percata que es momento de activarlo?, ¿Quién decide eso? y si debe ser activado, ¿Mediante qué elemento o medio? Todavía hay preguntas en el aire.

Gráfico que representa la infuencia de los interfaces basadas en eventos y la unión de los conjuntos de eventos y propiedades en el DOM de JS.Fuente: https//:www.duduromeroa.com

ESCUCHAS DE EVENTOS (event listeners)

¿Por qué necesitamos de escuchas de eventos para activar ciertas porciones de código?, ¿no basta con invocar las funciones y ya?. La respuesta es que los event listeners permiten detectar cuándo un evento único y específico ha sido ejecutado; por ejemplo, por las acciones táctiles del usuario en pantalla. Y puesto que no todos los eventos son iguales, cada evento (acción, A, B o C) será identificado y su respuesta lista para ser dada.

En cambio, ¿qué activa una función? La respuesta es una invocación. Y si bien existen algunas formas de invocar una función, ese tipo de activación es reducida comparada con todos (toooodos) los tipos de eventos que un usuario puede activar. Desde órdenes mediante voz, gestos táctiles, deslizamientos de pantalla, agitación de un dispositivo (sí, agitarlo como una maraca para generar una respuesta), entre muchos otros.

Por lo tanto, addEventListener() primero vincula elemento con evento, luego detecta ese evento y finalmente da la respuesta.

Gráfico que representa la infuencia de los interfaces basadas en eventos y la unión de los conjuntos de eventos y propiedades en el DOM de JS. Fuente: https//:www.duduromeroa.com

Haverbeke [1, pág. 247-250] da ejemplos sencillos de sintaxis al usar escuchas de event listeners:

En el ejemplo de abajo y desde JS: al objeto window se le asocia un evento "click" que ejecutará la respuesta ('sí?'); solo en consola. El objeto window es toda la ventana del navegador. Al cliquear todo esa área, la respuesta (el 'sí?') se activará.

En HTML

<p class="puerta"> Toc toc!! </p>

En Javascript

/* - Desde el objeto 'window'.. 
- Un 'addEventListener' adjunta un 
tipo de evento "click" a un resultado

- Luego, una función flecha ejecutará una respuesta */
window.addEventListener("click", ()=> {
  console.log("Si?");
  })

// Se muestra en consola 'Si?'

En otro ejemplo, el addEventListener() asocia un elemento HTML junto a un evento de click para ejecutar (cuando el evento suceda) la respuesta 'Botón presionado' desde consola. En otras palabras, solo si el evento ocurre (presionar el botón) se ejecutará la respuesta.

En HTML

<!-- Elemento -->
<button id="botonxito">Presióname</button>

En Javascript

// Aloja y apunta a elemento id único
let alojaBoton = document.querySelector("#botonxito"); 

// Aloja y apunta al escucha de evento y lo asocia a un click
alojaBoton.addEventListener("click", () => {

// Se ejecuta una muestra en consola
console.log("Botón presionado."); 
});

Así mismo, podemos preparar desde el addEventListener() una respuesta más visual desde el navegador al alterar solamente el codigo JS:

En Javascript

// Aloja y apunta a elemento boton con ID
let alojaBotonx = document.querySelector("#botonxitox");

// Aloja y apunta a elemento contenedor de respuesta
let responseElementx = document.querySelector("#responsex");

// Magia del .addEventListener("", ()=> {})
alojaBotonx.addEventListener("click", () => {
responseElementx.textContent = "Sí? quién es?...";

// Altera
responseElementx.style.color = "white";
responseElementx.style.fontSize = "2rem";
responseElementx.style.padding = "15px";
});



Así mismo, addEventListener() permite añadir un segundo parámetro. Ese segundo parámetro corresponde al eventObject, activado junto al evento principal. Ese objeto permite acceder a más propiedades como button. Por convención, ese segundo parámetro se nombra como (event, o e, o evt), pero no es obligatorio.

Gráfico que representa la infuencia de los interfaces basadas en eventos y la unión de los conjuntos de eventos y propiedades en el DOM de JS. Fuente: https//:www.duduromeroa.com


Como ya vimos, la variedad de eventos llegan incluso desde el teclado de nuestro computador. Haberke [1, pág. 253] da un ejemplo con los eventos 'keydown' (tecla presionada) y 'keyup' (tecla soltada). Se debe ingrear la tecla de la letra en minúscula.

Si estas en teclado de computador o tablet, presiona la tecla A.

Mensajito subliminal!



Sin embargo, el evento podría activar solo una vez la animación del recuadro celeste. ¿Por qué?. Primero, porque el estado final de la animación es la finalización de la animación (siempre que no sea reiniciada). Y ese estado final es el que obtiene el elemento. Solo reiniciando la animación (línea de código elemento .style .animation = " "; esta se ejecuta en cada presión de la tecla 'A'.

EVENTOS TÁCTILES

Eventos con manejo de puntero/mouse podrían ser usados también como una experiencia tácil en pantalla. Para Haberke [1, pág. 257] esto es limitadísimo: Mediante lo tácil se toca, se arrastra, se desplaza, se 'peñizca'. Usar los dedos amplía el rango interactivo más de lo que un puntero hace encima de un elemento.

Haberke da un ejemplo de ejercicio táctil en un área dada.

Toca aquí si estás en pantalla táctil


Y abajo, una variante del código anterior permite crear trazos fijos, en la misma área. Todo eso aplicando eventos táctiles de toque en pantalla (touchstart), de deslizamiento de dedo en pantalla (touchmove) y de finalización del gesto táctil (touchend).

Toca aquí si estás en pantalla táctil



En Javascript

element.addEventListener("touchstart");
element.addEventListener("touchmove");
element.addEventListener("touchend");

MANEJADORES DE EVENTOS (event handlers)

Son porciones de código que, asociados a un evento específico se activan cuando un evento único ha ocurrido. Estos eventos suelen ser intervenciones de usuario como el ingreso de datos, la presión de botones o gestos táctiles. Es decir, los manejadores de eventos están listos para activarse cuando una acción esperada ya se activó antes.

Imaginemos al event handler como el guardián de un castillo. Cuando el rey regrese de cazar (uno de tantos eventos que no sabemos con exactitud cuándo sucederá) solo el guardia (el event handler) podrá abrir las puertas (ejecutar el código) y así dejar pasar al rey (el resultado o respuesta al evento).

Basado en el siguiente ejemplo de mdn web docs...

En HTML

<button> Hola </button>

En Javascript

// Aloja y apunta a elemento button 
var boton = document.querySelector("button");

/* Función con parámetro. 
Al activarse saludar() otra función activará la función */
function saludar(evento) { console.log(evento); }
// Muestra en consola la activación

/* Asigna cómo y qué activará el evento */
boton.onclick = saludar;

...intentaré una explicación de los roles de cada porción del código:

Gráfico que representa la infuencia de los interfaces basadas en eventos y la unión de los conjuntos de eventos y propiedades en el DOM de JS. Fuente: https//:www.duduromeroa.com

Según el Event handling (overview) de mdn web docs_ podríamos concluir que:

  • Un elemento espera un evento para alertar al event listener/event handler
  • Una asignación decide que ese elemento y esa evento activarán al event listener/event handler
  • El evento activa, el listener escucha y el handler ejecuta
  • Se genera el resultado

Recuerda: Ya que he comentado los códigos de todos los ejemplos, puedes revisarlos en click derecho desde el mouse en esta página --> Page Source; o click derecho desde el mouse en esta página --> Inspect Element --> Source.



BURBUJEO O PROPAGACIÓN DE EVENTOS (event bubbling)

Burbujear o propagar refiere en JS al orden de ejecución desde elementos HTML padre hacia elementos más internos. Basaré mi explicación desde la sección 'Event bubbling' de mdn web doc_ de Mozilla.

En forma estándar el código de un evento se ejecutará desde el primer elemento activado hacia afuera, hacia sus subelementos.

Por ejemplo, un evento click activado desde un elemento padre propagará el evento desde el padre hacia sus subelementos hijos. Esa acción de propagación es estándart, y puede ser cancelada si debe ser necesario hacerlo.

El siguiente ejercicio explica el 'burbujeo' o la propagación de la ejecución de código mediante manejadores de eventos o event handlers:

Se definió mediante código que el cliqueo en cada una de las dos áreas será registrado y mostrado. Área gris (elemento padre) y área amarilla (elemento hijo). Se aclara que el registro de los mensajes funciona muy bien desde console.log(). Es decir, cliquear padre e hijo entrega desde consola 'tocaste el gris' al tocar el gris; y 'tocaste el amarillo' al tocar el amarillo.

Sin embargo, cuando se crea un contenedor HTML para mostrar los mismos mensajes pero desde la página web, algo sucede. Tocar al padre gris sí muestra un mensaje. Pero al tocar el hijo amarillo no lo muestra; y el mensaje del padre se repite.

Padre
Hijo

La razón de que al tocar el gris el mensaje sea mostrado, y al tocar el amarillo el mensaje del gris vuelva a mostrarse (y no 'Tocaste dentro, en lo amarillo') se explica desde lo siguiente:

Primero, quedamos de acuerdo que el burbujeo o propagación de la ejecución (bubbling) crea una activación desde el elemento padre hacia sus subelementos hijos (en caso de que los tenga). Y que tocar el amarillo significa que el mismo evento de mostrar un mensaje también ocurrirá hacia el padre. En este caso, desde dentro hacia afuera. Es decir, desde los subelementos hacia el elemento mayor en jerarquía.

Sin embargo, al tocar el amarillo y al activarse la propagación del evento desde el hijo hacia el padre, el mensaje 'del hijo' se reemplaza por el del padre a causa de la propagación. Ese reemplazo da la impresión que tocar el área amarilla nunca se registró como evento. Realmente sí fue registrado, pero fue sobreescrito por el mensaje del padre.

¿Solución? Dar una 'solución' sería un enfoque incorrecto. Pues el burbujeo no es una acción errada de JS (aunque puede ocasionar eventos confusos). Simplemente es una forma de ejecución que ayuda a que un evento sea activado desde el padre hacia los hijos, evitando generar más código para un mismo evento.

En todo caso, el método stopPropagation() cancela la propagación desde un elemento dado. El método se vincula mediante una palabra clave (la que desees) y se invoca ese método en el elemento que desees cancelar la propagación.

En Javascript

// Se apunta al elemento HTML con el ID 'cosita'
// Se vincula el evento 'click'
/* En la función 'handler' o manejadora de evento se agrega 
el parámetro 'evitarBurbujeo', que no es más que nuestra palabra 
que representa al eventoObjeto. */
document.getElementById("elementoCosita").addEventListener("click", 
function(evitarBurbujeo) {

// Se apunta al elemento HTML y se inserta el mensaje 
elementoMostrar.textContent = "Tocaste dentro en lo amarillo";
// Se invoca al eventoObjeto y se aplica el método 'stopPropagation();'
evitarBurbujeo.stopPropagation();
});

Padre
Hijo

¿Cuál es la diferencia? El área padre gris mantendrá la propagación hacia afuera. pero el pobre no tiene con quién más compartir el evento de mostrar mensaje. Por lo tanto, sigue igual. En cambio, el área hijo amarilla ya no extenderá la acción hacia el padre (es decir, no la propagará desde dentro hacia afuera); y por lo tanto, su mensaje no será reemplazado. Por lo que también será mostrado al tocar el área amarilla.

DELEGACIÓN DE EVENTOS

Es activar un evento desde un solo elemento HTML padre hacia otros varios subelementos. Por lo tanto, si un padre tiene 1 o 100 sublementos, basta con agregar a ese padre un manejador de eventos para que se ejecute desde el padre hacia los hijos.


  • A
  • B
  • C
  • D

En Javascript

document.getElementById("SoloAlPadre").
addEventListener("click", function(objetoEvento){ 
/* Lo que se ejecute será para todos los descendientes del padre */
});

CAPTURA DE EVENTOS

Es lo inverso a la propagación (o burbujeo, o bubbling) de un evento. Si la propagación va desde el elemento más interno hacia el más externo; la captura va desde lo externo (o desde el elemento menos anidado) hacia lo interno o más anidado. Sin embargo, la captura de eventos estará siempre desactivada a menos que la activemos, dentro del controlador de eventos, de forma explícita mediante la propiedad par {capture: true}. OJO con los códigos de ejemplo de abajo, en donde el tercer contexto explica por qué a una función se le ha quitado los paréntesis del parámetro.

En Javascript

/* PRIMER CONTEXTO ---------------- */
/*- Al elemento 'A' se adjunta un controlador.
- Se agregó la propiedad par 'capture: true;'
- Esto es: al detectar un click activará eventos desde fuera hacia  
los elementos más anidados. */
elementoA.addEventListener("click", nombreFunción() { 
capture: true;
});

/* SEGUNDO CONTEXTO ---------------- */

// O, aplicándolo como tercer argumento
elementA.addEventListener("click", function() {
// código
}, true);

/* Y AHORA, QUÉ PASA AQUÍ? ---------------- */

/* La función está sin paréntesis. Porqué?¿ 
- Los paréntesis luego de una función obliga a 
la invocación de esa función.

- La función SIN paréntesis significa que 
se la está pasando solo como un valor más, 
y no para ser ejecutada de inmediato.

- En el contexto de un manejador de eventos, 
se desea que LUEGO del evento se ejecute la función. 
Por lo tanto, el nombre de la función va sin paréntesis.  
*/
elementA.addEventListener("click", nombreFunction {
// código
}, true);

En el ejemplo de abajo, al agregar el valor de 'true' como tercer argumento para activar la captura de eventos (desde el primer elemento externo activado hacia los elementos más internos) cada toque desde fuera hacia dentro se ejecuta con un mensaje. Incluso, aunque no hemos dado la orden de evitar la propagación, tampoco se sobreescriben los mensajes; como sí ocurría cuando estaba activa la fase de propagación (ver 'event bubbling', más arriba).

Padre
Hijo

Según mdn web docs_ de Mozilla la propagación o la captura fueron males necesarios en los inicios de la web, desde 1985. Netscape e Internet Explorer tenían capacidad diferente y débil para integrar interactividad. Gracias a consensos técnicos entre las decenas de navegadores existentes hoy en día, el comportamiento de propagación o burbujeo es el más estándar y predecible.

Gráfico que representa la fase de captación de eventos desde el DOM de Javascript. En donde el evento inicia desde su elemento activo padre, y se reproduce en sus subelementos internos o anidados. Fuente: https//:www.duduromeroa.com Gráfico que representa la fase de propagación de eventos desde el DOM de Javascript. En donde el evento inicia desde su elemento activo más interno y se reproduce en sus subelementos externos o no anidados. Fuente: https//:www.duduromeroa.com

eventListeners dentro de objetos

Un uso interesante de eventListeners es también como elemento de un objeto. En el ejemplo de abajo, el objeto contiene:

En Javascript

var semana = { propiedad: "valor", 
propiedad: function() { 
// eventListeners, etc
}  
};

¿Qué día es hoy?
Reiniciar

BIBLIOGRAFÍA:
[1] Eloquent JavaScript 3ra edición. 2018. Marijn Haverbeke. No starch press.
[2] MDN Web Docs, Javascript. Mozilla Foundation.
[3] JavaScript: The Definitive Guide. David Flanagan (2020). Editorial O'Reilly.
[4] Object-Oriented JavaScript Ved Antani, Stoyan Stefanov (2017). Packt Publishing.
[5] Laurence Lars Svekis, Maaike van Putten. JavaScript from Beginner to Professional: Learn JavaScript quickly by building fun, interactive, and dynamic web apps, games, and pages (2021). Editorial Packt.
[6] Black, A. P. (2013) Object-oriented programming: Some history, and challenges for the next fifty years. Information and Computation. Elsevier.
[7] John R. Pugh, Wilf R. LaLonde, David A. Thomas (1987) Introducing object-oriented programming into the computer science curriculum. ACM SIGCSE BulletinVolume 19Issue 1Feb
[8] W3C. JavaScript HTML DOM Navigation