Delegación de Eventos

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 13

Delegación de eventos https://es.javascript.

info/event-delegation

 Comprar EPUB/PDF 
ES

 → El navegador: Documentos, Eventos e Interfaces → Introducción a los eventos

 24 de octubre de 2022

Delegación de eventos

La captura y el propagación nos permiten implementar uno de los más poderosos patrones de manejo de
eventos llamado delegación de eventos.

La idea es que si tenemos muchos elementos manejados de manera similar podemos, en lugar de asignar
un manejador a cada uno de ellos, poner un único manejador a su ancestro común.

En el manejador obtenemos event.target para ver dónde ocurrió realmente el evento y manejarlo.

Veamos un ejemplo: El diagrama Pa kua que refleja la antigua filosofía china.

Aquí está:

 
Bagua Chart: Direction, Element, Color, Meaning
Northwest North Northeast
Metal Water Earth
Silver Blue Yellow
Elders Change Direction

West Center East


Metal All Wood
Gold Purple Blue
Youth Harmony Future

Southwest South Southeast


Earth Fire Wood
Brown Orange Green
Tranquility Fame Romance
El HTML es este:

1 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

1 <table>
2 <tr>
3 <th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning
4 </tr>
5 <tr>
6 <td class="nw"><strong>Northwest</strong><br>Metal<br>Silver<br>Elders
7 <td class="n">...</td>
8 <td class="ne">...</td>
9 </tr>
10 <tr>...2 more lines of this kind...</tr>
11 <tr>...2 more lines of this kind...</tr>
12 </table>

La tabla tiene 9 celdas, pero puede haber 99 o 999, eso no importa.

Nuestra tarea es destacar una celda <td> al hacer clic en ella.

En lugar de asignar un manejador onclick a cada <td> (puede haber muchos), configuramos un
manejador “atrapa-todo” en el elemento <table> .

Este usará event.target para obtener el elemento del clic y destacarlo.

El código:

1 let selectedTd;
2
3 table.onclick = function(event) {
4 let target = event.target; // ¿dónde fue el clic?
5
6 if (target.tagName != 'TD') return; // ¿no es un TD? No nos interesa
7
8 highlight(target); // destacarlo
9 };
10
11 function highlight(td) {
12 if (selectedTd) { // quitar cualquier celda destacada que hubiera antes
13 selectedTd.classList.remove('highlight');
14 }
15 selectedTd = td;
16 selectedTd.classList.add('highlight'); // y destacar el nuevo td
17 }

A tal código no le interesa cuántas celdas hay en la tabla. Podemos agregar y quitar <td> dinámicamente
en cualquier momento y el realzado aún funcionará.

Pero hay un inconveniente.

El clic puede ocurrir no sobre <td> , sino dentro de él.

En nuestro caso, si miramos dentro del HTML, podemos ver tags anidados dentro de <td> , como

2 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

<strong> :

1 <td>
2 <strong>Northwest</strong>
3 ...
4 </td>

Naturalmente, si el clic ocurre en <strong> , este se vuelve el valor de event.target .

<table>

<td>

<strong> event.target

En el manejador table.onclick debemos tomar tal event.target e indagar si el clic fue dentro de
<td> o no.

Aquí el código mejorado:

1 table.onclick = function(event) {
2 let td = event.target.closest('td'); // (1)
3
4 if (!td) return; // (2)
5
6 if (!table.contains(td)) return; // (3)
7
8 highlight(td); // (4)
9 };

Explicación:

1. El método elem.closest(selector) devuelve el ancestro más cercano que coincide con el selector.
En nuestro caso buscamos <td> hacia arriba desde el elemento de origen.
2. Si event.target no ocurrió dentro de algún <td> , el llamado retorna inmediatamente pues no hay
nada que hacer.
3. En caso de tablas anidadas, event.target podría ser un <td> , pero fuera de la tabla actual.
Entonces verificamos que sea realmente un <td> de nuestra tabla.
4. Y, si es así, destacarla.

Como resultado, tenemos un código de realzado rápido y eficiente al que no le afecta la cantidad total de

3 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

<td> en la tabla.

Ejemplo de delegación: acciones en markup


Hay otros usos para la delegación de eventos.

Digamos que queremos hacer un menú con los botones “Save”, “Load”, “Search” y así. Y hay objetos con los
métodos save , load , search … ¿Cómo asociarlos?

La primera idea podría ser asignar un controlador separado para cada botón. Pero hay una solución más
elegante. Podemos agregar un controlador para el menú completo y un atributo data-action a los
botones con el método a llamar:

1 <button data-action="save">Click to Save</button>

El manejador lee el atributo y ejecuta el método. Puedes ver el siguiente ejemplo en funcionamiento:

4 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

 
1 <div id="menu">
2 <button data-action="save">Save</button>
3 <button data-action="load">Load</button>
4 <button data-action="search">Search</button>
5 </div>
6
7 <script>
8 class Menu {
9 constructor(elem) {
10 this._elem = elem;
11 elem.onclick = this.onClick.bind(this); // (*)
12 }
13
14 save() {
15 alert('saving');
16 }
17
18 load() {
19 alert('loading');
20 }
21
22 search() {
23 alert('searching');
24 }
25
26 onClick(event) {
27 let action = event.target.dataset.action;
28 if (action) {
29 this[action]();
30 }
31 };
32 }
33
34 new Menu(menu);
35 </script>

Save Load Search

Ten en cuenta que this.onClick está ligado a this en (*) . Esto es importante, porque de otra
manera el this que está dentro haría referencia al elemento DOM ( elem ), no al objeto Menu , y
this[action] no sería lo que necesitamos.

Entonces, ¿qué ventajas nos ofrece la delegación aquí?

● No necesitamos escribir el código para asignar el manejador a cada botón. Simplemente hacer
un método y ponerlo en el markup.
● La estructura HTML es flexible, podemos agregar y quitar botones en cualquier momento.

5 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

Podríamos usar clases .action-save , .action-load , pero un atributo data-action es mejor


semánticamente. Y podemos usarlo con reglas CSS también.

El patrón “comportamiento”
También podemos usar delegación de eventos para agregar “comportamiento” a los elementos de forma
declarativa, con atributos y clases especiales.

El patrón tiene dos partes:

1. Agregamos un atributo personalizado al elemento que describe su comportamiento.


2. Un manejador rastrea eventos del documento completo, y si un evento ocurre en un elemento con el
atributo ejecuta la acción.

Comportamiento: Contador

Por ejemplo, aquí el atributo data-counter agrega un comportamiento: “incrementar el valor con un clic”
a los botones:

 
1 Counter: <input type="button" value="1" data-counter>
2 One more counter: <input type="button" value="2" data-counter>
3
4 <script>
5 document.addEventListener('click', function(event) {
6
7 if (event.target.dataset.counter != undefined) { // si el atributo exist
8 event.target.value++;
9 }
10
11 });
12 </script>

Counter: 1 One more counter: 2

Si hacemos clic en un botón, su valor se incrementa. Lo importante aquí no son los botones sino el enfoque
general.

Puede haber tantos atributos data-counter como queramos. Podemos agregar nuevos al HTML en
cualquier momento. Usando delegación de eventos “extendimos” el HTML, agregando un atributo que
describe un nuevo comportamiento.

6 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

 Para manejadores de nivel de documento: siempre addEventListener


Cuando asignamos un manejador de evento al objeto document , debemos usar siempre
addEventListener , no document.on<event> , porque este último causa conflictos: los
manejadores nuevos sobrescribirán los viejos.

En proyectos reales es normal que haya muchos manejadores en document , asignados en diferentes
partes del código.

Comportamiento: Conmutador (toggle)

Un ejemplo más de comportamiento. Un clic en un elemento con el atributo data-toggle-id mostrará/


ocultará el elemento con el id recibido:

 
1 <button data-toggle-id="subscribe-mail">
2 Show the subscription form
3 </button>
4
5 <form id="subscribe-mail" hidden>
6 Your mail: <input type="email">
7 </form>
8
9 <script>
10 document.addEventListener('click', function(event) {
11 let id = event.target.dataset.toggleId;
12 if (!id) return;
13
14 let elem = document.getElementById(id);
15
16 elem.hidden = !elem.hidden;
17 });
18 </script>

Show the subscription form

Veamos una vez más lo que hicimos aquí: ahora, para agregar la funcionalidad de conmutación a un
elemento, no hay necesidad de conocer JavaScript, simplemente usamos el atributo data-toggle-id .

Esto puede ser muy conveniente: no hay necesidad de escribir JavaScript para cada elemento. Simplemente
usamos el comportamiento. El manejador a nivel de documento hace el trabajo para cualquier elemento de la
página.

Podemos combinar múltiples comportamientos en un único elemento también.

El patrón “comportamiento” puede ser una alternativa a los mini-fragmentos de JavaScript.

Resumen

7 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

¡La delegación de eventos es verdaderamente fantástica! Es uno de los patrones más útiles entre los eventos
DOM.

A menudo es usado para manejar elementos similares, pero no solamente para eso.

El algoritmo:

1. Pone un único manejador en el contenedor.


2. Dentro del manejador revisa el elemento de origen event.target .
3. Si el evento ocurrió dentro de un elemento que nos interesa, maneja el evento.

Beneficios:

● Simplifica la inicialización y ahorra memoria: no hay necesidad de agregar muchos


controladores.
● Menos código: cuando agregamos o quitamos elementos, no hay necesidad de agregar y quitar
controladores.
● Modificaciones del DOM: podemos agregar y quitar elementos en masa con innerHTML y
similares.

La delegación tiene sus limitaciones por supuesto:

● Primero, el evento debe “propagarse”. Algunos eventos no lo hacen. Además manejadores de


bajo nivel no deben usar event.stopPropagation() .
● Segundo, la delegación puede agregar carga a la CPU, porque el controlador a nivel de
contenedor reacciona a eventos en cualquier lugar del mismo, no importa si nos interesan o no.
Pero usualmente la carga es imperceptible y no la tomamos en cuenta.

 Tareas

Ocultar mensajes con delegación


importancia: 5

Hay una lista de mensajes con botones para borrarlos [x] . Haz que funcionen.

Como esto:

8 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

[x]
Horse
The horse is one of two extant subspecies of Equus
ferus. It is an odd-toed ungulate mammal belonging to
the taxonomic family Equidae. The horse has evolved
over the past 45 to 55 million years from a small multi-
toed creature, Eohippus, into the large, single-toed
animal of today.

[x]
Donkey
The donkey or ass (Equus africanus asinus) is a
domesticated member of the horse family, Equidae. The
wild ancestor of the donkey is the African wild ass, E.
africanus. The donkey has been used as a working
animal for at least 5000 years.

[x]
Cat
The domestic cat (Latin: Felis catus) is a small, typically
P.D. Debe haber solamente un event lintener en el contenedor, usa delegación de eventos.

Abrir un entorno controlado para la tarea.

solución


Abrir la solución en un entorno controlado.

Menú de árbol
importancia: 5

Crea un árbol que muestre y oculte nodos hijos con clics:

9 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

• Animals
◦ Mammals
▪ Cows
▪ Donkeys
▪ Dogs
▪ Tigers
◦ Other
▪ Snakes
▪ Birds
▪ Lizards
• Fishes
◦ Aquarium
▪ Guppy

Requerimientos:

● Solamente un manejador de eventos (usa delegación)


● Un clic fuera de los nodos de títulos (en un espacio vacío) no debe hacer nada.

Abrir un entorno controlado para la tarea.

solución


La solución tiene dos partes.

1. Envuelve cada nodo de título del árbol dentro de <span> . Luego podemos aplicarles CSS-
style en :hover y manejar los clics exactamente sobre el texto, porque el ancho de <span>
es exactamente el ancho del texto (no lo será si no lo tiene).
2. Establece el manejador al nodo raíz del tree y maneja los clics en aquellos títulos
<span> .

Abrir la solución en un entorno controlado.

Tabla ordenable
importancia: 4

Haz que la tabla se pueda ordenar: los clics en elementos <th> deberían ordenarla por la columna
correspondiente.

Cada <th> tiene su tipo de datos en el atributo, como esto:

10 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

1 <table id="grid">
2 <thead>
3 <tr>
4 <th data-type="number">Age</th>
5 <th data-type="string">Name</th>
6 </tr>
7 </thead>
8 <tbody>
9 <tr>
10 <td>5</td>
11 <td>John</td>
12 </tr>
13 <tr>
14 <td>10</td>
15 <td>Ann</td>
16 </tr>
17 ...
18 </tbody>
19 </table>

En el ejemplo anterior la primera columna tiene números y la segunda cadenas. La función de ordenamiento
debe manejar el orden de acuerdo al tipo de dato.

Solamente los tipos "string" y "number" deben ser soportados.

Ejemplo en funcionamiento:

Age Name
5 John
2 Pete
12 Ann
9 Eugene
1 Ilya

P.D. La tabla puede ser grande, con cualquier cantidad de filas y columnas.

Abrir un entorno controlado para la tarea.

solución


Abrir la solución en un entorno controlado.

11 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

Comportamiento: Tooltip
importancia: 5

Crea código JS para el comportamiento “tooltip”.

Cuando un mouse pasa sobre un elemento con data-tooltip , el tooltip debe aparecer sobre él, y
ocultarse cuando se va.

Un ejemplo en HTML comentado:

1 <button data-tooltip="the tooltip is longer than the element">Short button


2 <button data-tooltip="HTML<br>tooltip">One more button</button>

Debe funcionar así:

LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa

LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa

Botón corto Un botón más

Desplaza la página para que los botones aparezcan arriba de todo, verifica que los
tooltips se muestren correctamente.

En esta tarea suponemos que todos los elementos con data-tooltip solo tienen texto dentro. Sin tags
anidados (todavía).

Detalles:

● La distancia entre el elemento y el tooltip debe ser 5px .


● El tooltip debe ser centrado relativo al elemento si es posible.
● El tooltip no debe cruzar los bordes de la ventana. Normalmente debería estar sobre el elemento, pero si
el elemento está en la parte superior de la página y no hay espacio para el tooltip, entonces debajo de él.
● El contenido del tooltip está dado en el atributo data-tooltip . Este puede ser HTML arbitrario.

Necesitarás dos eventos aquí:

● mouseover se dispara cuando el puntero pasa sobre el elemento.


● mouseout se dispara cuando el puntero deja el elemento.

Usa delegación de eventos: prepare dos manejadores en el document para rastrear todos los “overs” y
“outs” de los elementos con data-tooltip y administra los tooltips desde allí.

Después de implementar el comportamiento, incluso gente no familiarizada con JavaScript puede agregar
elementos anotados.

12 de 13 24/3/24, 19:13
Delegación de eventos https://es.javascript.info/event-delegation

P.D. Solamente un tooltip puede mostrarse a la vez.

Abrir un entorno controlado para la tarea.

solución


Abrir la solución en un entorno controlado.

 Lección anterior Próxima lección



Compartir    Mapa del Tutorial

 Comentarios

● Si tiene sugerencias sobre qué mejorar, por favor enviar una propuesta de GitHub o una solicitud
de extracción en lugar de comentar.
● Si no puede entender algo en el artículo, por favor explique.
● Para insertar algunas palabras de código, use la etiqueta <code> , para varias líneas –
envolverlas en la etiqueta <pre> , para más de 10 líneas – utilice una entorno controlado
(sandbox) (plnkr, jsbin, codepen…)

© 2007—2024 Ilya Kantoracerca del proyecto


contáctenos

13 de 13 24/3/24, 19:13

También podría gustarte