diff --git a/15_event.md b/15_event.md new file mode 100644 index 000000000..2a24a4658 --- /dev/null +++ b/15_event.md @@ -0,0 +1,1155 @@ +# Manejo de Eventos + +{{quote {author: "Marco Aurelio", title: Meditaciones, chapter: true} + +Tienes poder sobre tu mente, no sobre los acontecimientos. Date cuenta de esto, +y encontrarás la fuerza. + +quote}} + +{{index stoicism, "Marcus Aurelius", input, timeline}} + +{{figure {url: "img/chapter_picture_15.jpg", alt: "Imagínese una máquina de Rube Goldberg", chapter: "framed"}}} + +Algunos programas funcionan con la entrada directa del usuario, como las +acciones del mouse y el teclado. Ese tipo de entrada no está disponible como una +estructura de datos bien organizada, viene pieza por pieza, en tiempo real, y se +espera que el programa responda a ella a medida que sucede. + +## Manejador de eventos + +{{index polling, button, "real-time"}} + +Imagina una interfaz en donde la única forma de saber si una tecla del +((teclado)) está siendo presionada es leer el estado actual de esa tecla. Para +poder reaccionar a las pulsaciones de teclas, tendrías que leer constantemente +el estado de la tecla para poder detectarla antes de que se vuelva a soltar. +Esto sería peligroso al realizar otros cálculos que requieran mucho tiempo, ya +que se podría perder una pulsación de tecla. + +Algunas máquinas antiguas manejan las entradas de esa forma. Un paso adelante +de esto sería que el hardware o el sistema operativo detectaran la pulsación de +la tecla y lo pusieran en una cola. Luego, un programa puede verificar +periódicamente la cola por nuevos eventos y reaccionar a lo que encuentre allí. + +{{index responsiveness, "user experience"}} + +Por supuesto, este tiene que recordar de mirar la cola, y hacerlo con +frecuencia, porque en cualquier momento entre que se presione la tecla y que el +programa se de cuenta del evento causará que que el programa no responda. Este +enfoque es llamado _((sondeo))_. La mayororía de los programadores prefieren +evitarlo. + +{{index "callback function", "event handling"}} + +Un mejor mecanismo es que el sistema notifique activamente a nuestro código +cuando un evento ocurre. Los navegadores hacen esto permitiéndonos registrar +funciones como _manejadores_ (_((manejadores))_) para eventos específicos. + +```{lang: "text/html"} +

Da clic en este documento para activar el manejador.

+ +``` + +{{index "click event", "addEventListener method", "window object", [browser, window]}} + +La _vinculación_ `window` se refiere a un objeto integrado proporcionado por el +navegador. Este representa la ventana del navegador que contiene el documento. +Llamando a su método `addEventListener` se registra el segundo argumento que se +llamará siempre que ocurra el evento descrito por su primer argumento. + +## Eventos y nodos DOM + +{{index "addEventListener method", "event handling", "window object", browser, [DOM, events]}} + +Cada manejador de eventos del navegador es registrado dentro de un contexto. En +el ejemplo anterior llamamos a `addEventListener` en el objeto `window` para +registrar un manejador para toda la ventana. Este método puede también ser +encontrado en elementos DOM y en algunos otros tipos de objetos. Los +controladores de eventos son llamados únicamente cuando el evento ocurra en el +contexto del objeto en que están registrados. + +```{lang: "text/html"} + +

No hay manejadores aquí.

+ +``` + +{{index "click event", "button (HTML tag)"}} + +Este ejemplo adjunta un manejador al nodo del botón. Los clics sobre el botón +hacen que se ejecute ese manejador, pero los clics sobre el resto del documento +no. + +{{index "onclick attribute", encapsulation}} + +Dar a un nodo un atributo `onclick` tiene un efecto similar. Esto funciona para +la mayoría de tipos de eventos—se puede adjuntar un manejador a través del +atributo cuyo nombre es el nombre del evento con `on` en frente de este. + +Pero un nodo puede tener únicamente un atributo `onclick`, por lo que se puede +registrar únicamente un manejador por nodo de esa manera. El método +`addEventListener` permite agregar cualquier número de manejadores siendo seguro +agregar manejadores incluso si ya hay otro manejador en el elemento. + +{{index "removeEventListener method"}} + +El método `removeEventListener`, llamado con argumentos similares a +`addEventListener`, remueve un manejador: + +```{lang: "text/html"} + + +``` + +{{index [function, "as value"]}} + +La función dada a `removeEventListener` tiene que ser el mismo valor de función +que se le dio a `addEventListener`. Entonces, para desregistrar un manejador, se +le tiene que dar un nombre a la función (`unaVez`, en el ejemplo) para poder +pasar el mismo valor de función a ambos métodos. + +## Objetos de evento + +{{index "button property", "event handling"}} + +Aunque lo hemos ignorado hasta ahora, las funciones del manejador de eventos +reciben un argumento: el _((objeto de evento))_. Este objeto contiene +información adicional acerca del evento. Por ejemplo, si queremos saber _cuál_ +((botón del mouse)) fue presionado, se puede ver la propiedad `button` del +objeto de evento. + +```{lang: "text/html"} + + +``` + +{{index "event type", "type property"}} + +La información almacenada en un objeto de evento es diferente por cada tipo de +evento. Se discutirán los distintos tipos de eventos más adelante en el +capítulo. La propiedad `type` del objeto siempre contiene una cadena que +identifica al evento (como `"click"` o `"mousedown"`) + +## Propagación + +{{index "event propagation", "parent node"}} + +{{indexsee bubbling, "event propagation"}} + +{{indexsee propagation, "event propagation"}} + +Para la mayoría de tipos de eventos, los manejadores registrados en nodos con +hijos también recibirán los eventos que sucedan en los hijos. Si se hace clic a +un botón dentro de un párrafo, los manejadores de eventos del párrafo también +verán el evento clic. + +{{index "event handling"}} + +Pero si tanto el párrafo como el botón tienen un manejador, el manejador más +específico—el del botón—es el primero en lanzarse. Se dice que el evento se +_propaga_ hacia afuera, desde el nodo donde este sucedió hasta el nodo padre del +nodo y hasta la raíz del documento. Finalmente, después de que todos los +manejadores registrados en un nodo específico hayan tenido su turno, los +manejadores registrados en general ((ventana)) tienen la oportunidad de +responder al evento. + +{{index "stopPropagation method", "click event"}} + +En cualquier momento, un manejador de eventos puede llamar al método +`stopPropagation` en el objeto de evento para evitar que los manejadores que se +encuentran más arriba reciban el evento. Esto puede ser útil cuando, por +ejemplo, si tienes un botón dentro de otro elemento en el que se puede hacer clic +y que no se quiere que los clics sobre el botón activen el comportamiento de +clic del elemento exterior. + +{{index "mousedown event", "pointer event"}} + +El siguiente ejemplo registra manejadores `"mousedown"` tanto en un botón como +el párrafo que lo rodea. Cuando se hace clic con el botón derecho del mouse, el +manejador del botón llama a `stopPropagation`, lo que evitará que se ejecute el +manejador del párrafo. Cuando se hace clic en el botón con otro ((botón del +mouse)), ambos manejadores se ejecutarán. + +```{lang: "text/html"} +

Un párrafo con un .

+ +``` + +{{index "event propagation", "target property"}} + +La mayoría de objetos de eventos tienen una propiedad `tarjet` que se refiere al +nodo donde se originaron. Se puede usar esta propiedad para asegurar de que no +se está manejando accidentalmente algo que se propagó desde un nodo que no se +desea manejar. + +También es posible utilizar la propiedad `target` para lanzar una red amplia para +un evento específico. Por ejemplo, si tienes un nodo que contiene una gran +cantidad de botones, puede ser más conveniente el registrar un manejador en un +solo clic en el nodo externo y hacer que use la propiedad `target` para +averiguar si se hizo clic en un botón, en lugar de registrar manejadores +individuales en todos los botones. + +```{lang: "text/html"} + + + + +``` + +## Acciones por defecto + +{{index scrolling, "default behavior", "event handling"}} + +La mayoría de eventos tienen una acción por defecto asociada a ellos. Si haces +clic en un ((enlace)), se te dirigirá al destino del enlace. Si presionas la +flecha hacia abajo, el navegador desplazará la página hacia abajo. Si das clic +derecho, se obtendrá un menú contextual. Y así. + +{{index "preventDefault method"}} + +Para la mayoría de los tipos de eventos, los manejadores de eventos de +JavaScript se llamarán _antes_ de que el comportamiento por defecto se produzca. +Si el manejador no quiere que suceda este comportamiento por defecto, +normalmente porque ya se ha encargado de manejar el evento, se puede llamar al +método `preventDefault` en el objeto de evento. + +{{index expectation}} + +Esto puede ser utilizado para implementar un atajo de ((teclado)) propio o +((menú contextual)). Esto también puede ser utilizado para interferir de forma +desagradable el comportamiento que los usuarios esperan. Por ejemplo, aquí hay +un enlace que no se puede seguir: + +```{lang: "text/html"} +MDN + +``` + +{{index usability}} + +Trata de no hacer tales cosas a menos que tengas una buena razón para hacerlo. +Será desagradable para las personas que usan tu página cuando el comportamiento +esperado no funcione. + +Dependiendo del navegador, algunos eventos no pueden ser interceptados en lo +absoluto. En Chrome, por ejemplo, el atajo de ((teclado)) para cerrar la pestaña +actual ([control]{keyname}-W o [command]{keyname}-W) no se puede manejar con +JavaScript. + +## Eventos de teclado + +{{index keyboard, "keydown event", "keyup event", "event handling"}} + +Cuando una tecla del teclado es presionado, el navegador lanza un evento +`"keydown"`. Cuando este es liberado, se obtiene un evento `"keyup"`. + +```{lang: "text/html", focus: true} +

Esta página se pone violenta cuando se mantiene presionado la tecla V.

+ +``` + +{{index "repeating key"}} + +A pesar de su nombre, `"keydown"` se lanza no solamente cuando la tecla es +físicamente presionada. Cuando una tecla se presiona y se mantiene presionada, +el evento se lanza una vez más cada que la tecla se _repite_. Algunas veces se +debe tener cuidado con esto. Por ejemplo, si tienes un botón al DOM cuando el +botón es presionado y removido cuando la tecla es liberada, puedes agregar +accidentalmente cientos de botones cuando la tecla se mantiene presionada por +más tiempo. + +{{index "key property"}} + +El ejemplo analizó la propiedad `key` del objeto de evento para ver de qué tecla +se trata el evento. Esta propiedad contiene una cadena que, para la mayoría de +las teclas, corresponde a lo que escribiría al presionar esa tecla. Para teclas +especiales como [enter]{keyname}, este contiene una cadena que nombre la tecla +{`"Enter"`, en este caso}. Si mantienes presionado [shift]{keyname} mientras +que presionas una tecla, esto también puede influir en el nombre de la +tecla-`"v"` se convierte en `"V"` y `"1"` puede convertirse en `"!"`, es lo que +se produce al presionar [shift]{keyname}-1 en tu teclado. + +{{index "modifier key", "shift key", "control key", "alt key", "meta key", +"command key", "ctrlKey property", "shiftKey property", "altKey property", +"metaKey property"}} + +La teclas modificadoras como [shift]{keyname}, [control]{keyname}, +[alt]{keyname} y [meta]{keyname} ([command]{keyname} en Mac) generan eventos de +teclado justamente como las teclas normales. Pero cuando se busque combinaciones +de teclas, también se puede averiguar si estas teclas se mantienen presionadas +viendo las propiedades `shiftKey`, `ctrlKey`, `altKey` y `metaKey` de los +eventos de teclado y mouse. + +```{lang: "text/html", focus: true} +

Presiona Control-Espacio para continuar.

+ +``` + +{{index "button (HTML tag)", "tabindex attribute", [DOM, events]}} + +El nodo DOM donde un evento de teclado se origina depende en el elemento que +tiene el ((foco)) cuando la tecla es presionada. La mayoría de los nodos no +pueden tener el foco a menos que se les de un atributo `tabindex`, pero +elementos como ((enlace))s, botones y campos de formularios sí pueden. +Volveremos a los ((campo))s de formularios en el capítulo [Chapter?](http#forms). +Cuando nadie en particular tiene el foco, `document.body` actua +como el nodo objetivo de los eventos de teclado. + +Cuando el usuario está escribiendo texto, usando los eventos de teclado para +averiguar qué se está escribiendo es problematico. Algunas plataformas, sobre +todo el ((teclado virtual)) en ((teléfono))s ((Android)), no lanzan eventos de +teclado. Pero incluso cuando se tiene un teclado antiguo, algunos tipos de +entradas de texto no coinciden con las pulsaciones de teclas de forma sencilla, +como el software _editor de métodos de entrada_ (((IME))) usado por personas +cuyos _guiones_ (_((script))_) no encajan en un teclado, donde se combinan +varias teclas para crear caracteres. + +Para notar cuando se escribió algo, los elementos en los que se puede escribir, +como las etiquetas `` y ` + +``` + +{{index "sloppy programming"}} + +Dar un valor indefinido a `clearTimeout` o llamándolo en un tiempo de espera que +ya ha se ha lanzado no tiene ningún efecto. Por lo tanto, no debemos tener +cuidado sobre cuándo llamarlo, y simplemente se hace para cada evento. + +{{index "mousemove event"}} + +Podemos utilizar un patrón ligeramente diferente si se quiere espaciar las +respuestas de modo que estén separadas por al menos una cierta longitud de +((tiempo)) pero se quiere lanzar _durante_ una serie de eventos, no solo +después. Por ejemplo, se podría querer responder a los eventos `"mousemove"` +mostrando las coordenadas actuales del mourse pero solo cada 250 milisegundos. + +```{lang: "text/html"} + +``` + +## Resumen + +Los manejadores de eventos hacen posible detectar y reaccionar a eventos que +suceden en nuestra página web. El método `addEventListener` es usado para +registrar un manejador de eventos. + +Cada evento tiene un tipo (`"keydown"`, `"focus"`, etc.) que lo identifica. La +mayoría de eventos son llamados en un elemento DOM específico y luego se +_propagan_ a los ancentros de ese elemento, lo que permite que los manejadores +asociados con esos elementos los manejen. + +Cuando un manejador de evento es llamado, se le pasa un objeto evento con +información adicional acerca del evento. Este objeto también tiene métodos que +permiten detener una mayor propagación (`stopPropagation`) y evitar que el +navegador maneje el evento por defecto (`preventDefault`). + +Al presiosar una tecla se lanza los eventos `"keydown"` y `"keyup"`. Al +presionar un botón del mouse se lanzan los eventos `"mousedown"`, `"mouseup"` y +`"click"`. Al mover el mouse se lanzan los eventos `"mousemove"`. Las +interacción de la pantalla táctil darán como resultado eventos `"touchstart"`, +`"touchmove"` y `"touchend"`. + +El desplazamiento puede ser detectado con el evento `"scroll"` y los cambios de +foco pueden ser detactados con los eventos `"focus"` y `"blur"`. Cuando el +documento termina de cargarse, se lanza el evento `"load"` en la ventana. + +## Ejercicios + +### Globo + +{{index "balloon (exercise)", "arrow key"}} + +Escribe una página que muestre un ((globo)) (usando el globo ((emoji)), 🎈). +Cuando se presione la flecha hacia arriba, debe inflarse (crecer) un 10 por +cierto, y cuando se presiona la flecha hacia abajo, debe desinflarse +(contraerse) un 10 por cierto) + +{{index "font-size (CSS)"}} + +Puedes controlar el tamaño del texto (los emojis son texto) configurando la +propiedad CSS `font-size` (`style.fontSize`) en su elemento padre. Recuerda +incluir una unidad en el valor, por ejemplo pixeles (`10px`). + +Los nombres de las teclas de flecha son `"ArrowUp"` y `"ArrowDown"`. Asegúratede +que las teclas cambien solo al globo, sin desplazar la página. + +Cuando eso funcione, agrega una nueva función en la que, si infla el globo más +allá de cierto tamaño, explote. En este caso, explotar significa que se +reemplaza con un emoji 💥, y se elimina el manejador de eventos (para que no se +pueda inflar o desinflar la explosión). + +{{if interactive + +```{test: no, lang: "text/html", focus: yes} +

🎈

+ + +``` + +if}} + +{{hint + +{{index "keydown event", "key property", "balloon (exercise)"}} + +Querrás registrar un manejador para el evento `"keydown"` y mirar `event.key` +para averiguar si se presionó la teclas de flecha hacia arra o hacia abajo. + +El tamaño actual se puede mantener en una vinculación para que puedas basar el +nuevo tamaño en él. Será útil definir una función que actualice el tamaño, tanto +el enlace como el estilo del globo en el DOM, para que pueda llamar desde su +manejador de eventos, y posiblemente también una vez al comenzar, para +establecer el tamaño inicial. + +{{index "replaceChild method", "textContent property"}} + +Puedes cambiar el globo a una explosión reemplazando el texto del nodo con otro +(usando `replaceChild`) o estableciendo la propiedad `textContent` de su no +padre a una nueva cadena. + +hint}} + +### Mouse trail + +{{index animation, "mouse trail (exercise)"}} + +En los primeros días de JavaScript, que era el momento de ((páginas de inicio +llamativas)) con muchas imágenes, a la gente se le ocurrieron formas realmente +inspiradoras de usar el lenguaje. + +Uno de estos fue el _rastro del mouse_, una serie de elementos que seguirían el +puntero del mouse mientras lo movías por la página. + +{{index "absolute positioning", "background (CSS)"}} + +En este ejercicio, quiero que implementes un rastro del mouse. Utiliza elementos +`
` con un tamaño fijo y un color de fondo (consulta a +[code](event#mouse_drawing) en la sección "Clics del mouse" por un ejemplo). +Crea un montón de estos elementos y, cuando el mouse se mueva, muestralos +después del puntero del mouse. + +{{index "mousemove event"}} + +Hay varios enfoques posibles aquí. Puedes hacer tu solución tan simple o tan +compleja como desees. Una solución simple para comenzar es mantener un número de +elementos de seguimiento fijos y recorrerlos, moviendo el sigueinte a la +posición actual del mouse cada vez que ocurra un evento `"mousemove"`. + +{{if interactive + +```{lang: "text/html", test: no} + + + +``` + +if}} + +{{hint + +{{index "mouse trail (exercise)"}} + +La creación de los elementos se realiza de mjor manera con un ciclo. Añadelos al +documento para que aparezcan. Para poder acceder a ellos más tarde para cambiar +su posición, querrás alamacenar los elementos en una matriz. + +{{index "mousemove event", [array, indexing], "remainder operator", "% +operator"}} + +Se puede hacer un ciclo a través de ellos manteniendo una ((variable contador)) +y agregando 1 cada vez que se activa el evento `"mousemove"`. El operador +restante (`% elements.length`) pueden ser usado para obtener un índice de matriz +válido para elegir el elemento que se desaea colocar durante un evento +determinado. + +{{index simulation, "requestAnimationFrame function"}} + +Otro efecto interesante se puede lograr modelando un sistema simple ((físico)). +Utiliza el evento `"mousemove"` para actualizar un par de enlaces que rastrean +la posición del mouse. Luego usa `requestAnimationFrame` para simular que los +elementos finales son atraidos a la posición a la posición del puntero del +mouse. En cada paso de la animación, actualiza la posición en función de su +posición relativa al puntero (y, opcionalmente, una velocidad que se alamacena +para cada elemento). Averiguar una buena forma de hacerlo depende de ti. + +hint}} + +### Pestañas + +{{index "tabbed interface (exercise)"}} + +Los paneles con pestañas son utilizados ampliamente en las interfaces de +usuario. Te permiten seleccionar un panel de interfaz eligiendo entre una serie +de pestañas "que sobresalen" sobre un elemento. + +{{index "button (HTML tag)", "display (CSS)", "hidden element", "data +attribute"}} + +En este ejercicio debes implementar una interfaz con pestañas simple. Escribe +una función, `asTabs`, que tome un nodo DOM y cree una interfaz con pestañas que +muestre los elementos secundarios de ese nodo. Se debe insertar una lista de +elementos `