6 de noviembre de 2017

JavaScript - Eventos

JS es el lenguaje que usamos un nuestro trio famoso (HTML, CSS y JS) que define el comportamiento de una página web. Este comportamiento, como es lógico, debe manifestarse con las acciones del usuario. Por ese motivo existen los Eventos como parte del DOM.

Aunque ya hice una entrada acerca de DOM, he querido separar los eventos DOM por su singularidad a la hora de manejarlos. DOM tiene una serie de eventos que van desde acciones típicas del usuario como hacer click a los propios de la carga de la página o sus elementos sin que el usuario haya pedido conscientemente nada.

Cada Event (evento) tiene un EventHandler (manejador) que creará un objeto evento del tipo correspondiente en un elemento objetivo y notificará convenientemente a los EventListener (escuchadores)/función marcada. Este proceso es lo que se conoce como propagación de eventos DOM.

Veámos un ejemplo práctico (ver fiddle):
<script>
  function ejemplo() { alert("Gracias, vuelve a hacerlo"); }
</script>

<div onclick="ejemplo()">Haz click por favor</div>

¿Qué está pasando para que ocurra esto?

En este caso el Handler del evento onclick del div, genera un Event (en este caso un Mouse Event) y llama a la funcion ejemplo(). Hasta ahora no parece nada demasiado nuevo en la prática: es asignar un valor a un atributo de un elemento como ya hemos hecho antes. Pero incluso ya tenemos una conclusión: de todo el arbol DOM sólo los elementos HTML pueden manejar eventos pues son los que tienen atributos.

¿Pero para qué sirve que se genere un objeto de tipo Event?

El objeto Event generado va a contener información acerca del evento que ha ocurrido y del que podemos sacar información para reaccionar adecuadamente.

Cambiemos un poco el ejemplo anterior (ver fiddle):
<script>
  //Mensaje con las coordenadas del cursor dentro de la página 
  function ejemplo2 (evento) {
    alert('Hiciste click en coordenadas: ' 
      + evento.clientX + ', ' + evento.clientY
      + ' con botón ' + evento.button);
  }

  //Nos informa de las coordenadas del cursor DENTRO de "elemento" 
  function ejemplo3 (evento, elemento) {
    escribirEtiqueta ('Ahora estás en coordenadas: '
      + (evento.clientX - elemento.offsetLeft) + ', ' 
      + (evento.clientY - elemento.offsetTop));
  }

  //Funciones auxiliares para mensajes en pantalla 
  function resetearEtiqueta () { escribirEtiqueta('Pincha en lo verde'); }
  function escribirEtiqueta (texto) { document.querySelector('#texto').innerHTML = texto; }
</script> 

<div
style='background-color:green; color:white; width:50%; height:50%; margin:auto; text-align:center; display: flex; justify-content: center; align-items: center;'
  onmousedown='ejemplo2(event)'
  onmousemove='ejemplo3(event, this)'
  onmouseleave='resetearEtiqueta()'>

  <span id='texto'><script>resetearEtiqueta()</script></span>

</div>
Hemos creado un cuadrado interactivo donde hemos especificado tres eventos que vamos a manejar:
  1. onmousedown: que nos presentará un mensaje con las coordenadas de dónde hemos hecho click.
  2. onmousemove: que nos dice las coordenadas de nuestor cursor dentro del cuadrado verde.
  3. onmouseleave: resetea el texto del cuadrado incitándonos a pinchar.
Esto es posible porque ahora el argumento predeterminado event se le pasa a la función que vamos a ejecutar. Para nuestro caso, este objeto tiene los miembros del tipo Mouse Event que entre otros tiene las propiedades clientX, clientY y button como vemos en la función ejemplo2.
En la función ejemplo3 también podemos ver que le pasamos una referencia al elemento que ha producido el evento (lo hacemos con this), y al ser un HTML Element podemos saber la posición que ocupa (offsetLeft y offsetTop) en la página para calcular las coordenadas dentro del cuadrado verde.

Recordemos que los atributos se pueden asignar con JS a los elementos DOM. De esta forma podemos cambiar el ejemplo anterior por:
var capa = document.querySelector("div");
capa.onmousedown = function(event){ ejemplo2(event) };
capa.onmousemove = function(event){ ejemplo3(event, this) };
capa.onmouseleave = resetearEtiqueta; //Pasamos la función, NO la invocamos
Si queremos pasar argumentos lo mejor es encapsular la función dentro de una función anónima function(event){ ... }. La función anónima recibe event, pero this se pasa desde dentro del bloque de código. Así mismo se pueden usar funciones flecha (arrow =>).

¿Qué son los Event Listener?

Por ahora hemos asignado funciones a los manejadores, pero también admiten objetos Event Listener. Añadiremos EventListeners usando el método addEventListener en los objetivos de eventos, que incluye además de a los HTML Elements, los tipos que implementan la interfaz EventTarget como Document o Window.

¿Para qué usar addEventListener?

Asignar escuchadores a un manejador nos permite las siguientes ventajas:
  1. Permite tener varios escuchadores para un mismo evento (flexibilidad)
  2. Se pueden añadir a objetos que no son Element (document y window implementan EventTarget)
  3. Añade mayor control en la activación del escuchador
Un ejemplo del código anterior usando addEventListener quedaría así (para abreviar sólo onmousemove, puede ver fiddle con ejemplo):
var divPrimera = document.querySelector("div");
divPrimera.addEventListener("mousemove", function(event){ejemplo3(event, this);});
Hay que prestar atención pues el primer parámetro es el tipo de evento SIN el "on" delante (mousemove sólo).

Si queremos pasar argumentos en la función del segundo parámetro lo haremos igual que lo hicimos en el ejemplo anterior (encapsulando con una función anónima el código).
El método addEventListener admite un tercer parámetro opcionalmente, que es el que nos permite añadir mayor control a cómo activar el listener (propagación, si se activa sólo una vez, etc...)

Relación entre event y this

Cualquier evento tiene una referencia a su fuente. En JS se accede a esta referencia por las propiedades target (generador del evento) y currentTarget (propietario del listener). De esta forma en el ejemplo anterior podríamos reducir la firma de la función y quedarse así (ver fiddle):
function ejemplo3 (evento) {
  var elemento = evento.target;
  escribirEtiqueta ('Ahora estás en coordenadas: ' + (evento.clientX - elemento.offsetLeft) + ', ' + (evento.clientY - elemento.offsetTop));
}
...
divPrimera.addEventListener("mousemove", function(event){ejemplo3(event);});
La diferencia entre target y currentTarget se puede apreciar fácilmente en la siguiente respuesta de SO.

Relación entre event y window.event

Si probamos a usar la variable window.event veremos que generalmente tendremos acceso al event que se pasaría como argumento. Esto no es un estándar y algunos navegadores (antiguos) no funcionará correctamente. Para mayor compatibilidad, se recomienda pasar event como argumento en el navegador.

Espero que con esto y unas prácticas se comprenda cómo usar los eventos.

Práctica js-eve-1

No hay comentarios:

Publicar un comentario

Compárteme

Entradas populares