Eventos personalizados en Polymer

  • Por
Por qué son tan importantes en el desarrollo de componentes en Polymer y cómo podemos disparar custom events en unos elementos y captarlos en otros.

En este artículo vamos a explicar una de las herramientas más importantes a la hora de trabajar con Custom Elements, los eventos personalizados. Para entender el presente texto ten en cuenta que ya conocemos cómo se trabaja con eventos en Polymer, por lo que continuaremos las explicaciones a partir del conocimiento ya adquirido en el Manual de Polymer.

A continuación te explicaremos qué son los eventos personalizados y luego pasaremos a la práctica explicando cómo disparar custom events en componentes Polymer. Por supuesto completaremos la práctica explicando cómo capturar estos eventos en otros componentes que necesiten estar informados de los sucesos ocurridos.

Para qué se usan los eventos personalizados

Los eventos que conocemos en el desarrollo Javascript en general son eventos genéricos, que se producen cuando el usuario realiza acciones sobre elementos de la página en general. Acciones de clic, envío de un formulario, escribir texto dentro de un campo input, etc. Todo eso está genial, pero con el desarrollo de componentes necesitamos ampliar ese espectro.

El motivo de la existencia de los eventos personalizados, en Web Components en general y Polymer en particular, se basa en la necesidad de informar a otros componentes del momento en el que ocurren cosas. Piensa que en aplicaciones basadas en componentes tenemos una cantidad de elementos que colaboran entre sí para poder solucionar las necesidades del proyecto. Los eventos personalizados son una manera de poder informar a otros componentes de que han pasado cosas, de modo que unos elementos puedan reaccionar a sucesos ocurridos con otros.

Por ejemplo piensa en un componente que muestra un listado de items, cuyo contenido lo recibe por medio de un JSON recibido vía servicio web. Cuando el componente recibe ese listado cambia su contenido. Otros elementos pueden necesitar saber cuándo ese contenido ha cambiado, para actualizar también su estado. Con eventos estándar yo podré estar enterado de cosas simples, por ejemplo que alguien ha hecho clic en ese elemento, pero no existe un evento en Javascript que me diga cuando han pasado cosas tan específicas..

Casos necesarios tenemos todos los que quieras. Por ejemplo un componente que tiene un formulario y queremos saber cuándo, después de haberlo enviado, se recibe respuesta del servidor. Y además quieres saber qué respuesta has recibido. Por medio de Javascript estándar podrías saber cuándo el formulario se ha "submitido", pero no cuándo se ha recibido tal respuesta vía Ajax.

Tal como los eventos nativos Javascript, los eventos personalizados se transmiten siempre hacia arriba en el árbol del DOM, produciéndose la "burbuja de evento" que explicaremos en un punto posterior. En resumen, los eventos personalizados son el mecanismo que tenemos para informar acerca de situaciones de hijos a padres. Cuando en los hijos se produzcan determinadas situaciones podrán disparar eventos que los padres se enteren y, si lo necesitan, hagan cosas.

Nota: No solo nosotros podemos desarrollar componentes sensibles a eventos personalizados, el propio catálogo de elementos de Polymer tiene componentes que lanzan multitud de tipos de eventos personalizados. Explora la documentación de los elementos y podrás observar todos los eventos personalizados que podemos recibir al usar cada componente.

Cómo disparar eventos personalizados

Disparar, o producir, eventos personalizados es muy sencillo en Polymer. Simplemente, en cualquier método de un componente podemos invocar al método this.fire() pasando como parámetro indispensable el nombre del evento personalizado que se desea lanzar.

El nombre del evento personalizado nos lo inventamos nosotros tal como nos venga en gana. Lo indicamos como primer parámetro en el método fire().

this.fire('nombre-evento-personalizado');

Nota: Obviamente no sería normal elegir "click" como nombre de tu evento personalizado, puesto que ese ya es un evento estándar. Aunque nadie te lo impediría. Al final, si con el método fire() lanzas un evento click cuando recojas el evento tendrás que averiguar si se ha producido por un clic de un usuario o porque has lanzado ese evento personalizado. Vamos, un poco de lío, pero sería posible trabajar en ese supuesto.

Como puedes ver, el método fire depende de this. La variable this hace referencia al propio componente, a la propia instancia que se está ejecutando y está solamente disponible en métodos del componente.

Además es muy interesante el hecho que, al lanzar un evento personalizado, podemos pasar datos a aquellos otros elementos que capturen el evento. Para ello podremos indicar qué datos queremos enviar por medio del segundo parámetro del método fire().

this.fire('nombre-evento-personalizado', datoAdicional);

Al recoger este evento, recibiremos el contenido de la variable datoAdicional, ahora veremos cómo.

Cómo capturar eventos personalizados

Los eventos personalizados se capturan exáctamente igual que los eventos comunes. Todo lo que aprendiste en el artículo de los eventos básicos de Polymer es aplicable a eventos personalizados.

Por ejemplo, si tu evento personalizado se llama "ocurrio-algo", entonces puedes declarar el manejador de eventos encargado de procesarlo en la etiqueta del componente que lo produzca con el atributo "on-" seguido del nombre del evento personalizado.

<elemento-x on-ocurrio-algo="manejadorEvento">

También podrías usar la propiedad "listeners", como sabes. Nada de esto cambia. Lo nuevo es que, en los eventos personalizados, podemos recibir datos adicionales. En este caso los recibiremos como segundo parámetro en la función manejadora de eventos.

manejadorEvento: function(evento, dato){
  console.log('objeto evento', evento, 'dato recibido', dato);
}

Como sabes, todos los manejadores de eventos en Javascript reciben como primer parámetro el objeto evento. Además en Polymer podemos recibir en el segundo parámetro datos adicionales que nos quiera entregar el elemento que lanza los eventos.

Ejemplo de componente con eventos personalizados

Veremos más fácil todo esto si nos ocupamos del desarrollo de un componente que lance un evento personalizado. Nuestro ejercicio tendría el siguiente enunciado:

Tenemos un componente que encapsula un campo de texto. El usuario escribe en el campo de texto y se comporta de manera normal. Pero además este campo es sensible a las pulsaciones de la tecla "enter", informando por medio de un evento personalizado a su padre.

Es un buen ejercicio, porque podremos practicar tanto con eventos normales (en este caso tenemos que capturar un evento de teclado), como con eventos personalizados. Para facilitarnos las cosas, en vez de un campo INPUT de toda la vida, usaremos un componente de Polymer llamado paper-input, del que ya hemos hablado en otras ocasiones.

Para aclararnos antes de comenzar a desarrollar, en esta práctica tendremos dos custom elements

  1. input-enter (el hijo): Es el campo input sensible a las pulsaciones de la tecla enter. Es el que dispara el evento personalizado. Este componente además enviará un dato adicional al disparar el evento con el instante en tiempo en el que se produjo la pulsación del enter.
  2. ver-enters (el padre): Es un componente que hacemos simplemente para poder capturar eventos personalizados. Recibirá el instante actual y lo meterá en un array. En el contenido de ese elemento listaremos todos los instantes en los que se ha pulsado enter.

Nuestro componente hijo tiene este código:

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/paper-input/paper-input.html">

<dom-module id="input-enter">
  <template>
  <style>
    :host {
      display: block;
    }
  </style>
  <paper-input label="Escribe y pulsa enter" on-keydown="checkForEnter"></paper-input>
  </template>
  <script>
  Polymer({
    is: "input-enter",
    checkForEnter: function (e) {
      if (e.keyCode === 13) {
        //detectado enter
        var instante = new Date().toString();
        this.fire('pulsado-enter', instante);
      }
    }
  });
  </script>
</dom-module>

Nos fijamos en el elemento "paper-input". En él hemos colocado un evento on-keydown que usamos para saber cuándo se pulsa una tecla. Ese es un evento estándar de Javascript implementado a través de Polymer. Al pulsar una tecla llamaremos al método checkForEnter().

<paper-input label="Escribe y pulsa enter" on-keydown="checkForEnter"></paper-input>

En el código de checkForEnter() encuentras el la lógica para saber si la tecla pulsada fue la tecla "enter". Como ves, nos apoyamos en el objeto evento nativo de Javascript, que es donde se encuentra toda la información sobre el evento capturado.

Si se detecta el enter, entonces se dispara el evento personalizado:

this.fire('pulsado-enter', instante);

Puedes apreciar como el evento personalizado estará acompañado de un dato, contenido en la variable "instante", que tiene la fecha y hora cuando se detectó el enter.

Ahora veamos el código del componente padre, que se encarga de capturar el evento personalizado.

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="input-enter.html">

<dom-module id="ver-enters">
  <template>
  <style>
    :host {
      display: block;
    }
  </style>
  <input-enter on-pulsado-enter="pulsadoEnter"></input-enter>
  <template is="dom-repeat" items="{{instantes}}">
    <p>
      Enter detectado en el instante: {{item}}
    </p>
  </template>
  </template>
  <script>
  Polymer({
    is: "ver-enters",
    properties: {
      instantes: {
        type: Array,
        value: function() {
          return []
        }
      }
    },
    pulsadoEnter: function(e, tiempo){
      console.log('En el padre, detectado pulsado enter...', tiempo);
      this.push('instantes', tiempo);
    }
  });
  </script>
</dom-module>

Este componente hace varias cosas, pero lo que respecta a los eventos personalizados hay que centrarse en el template, donde se usa el componente hijo y la declaración del manejador de evento para "on-pulsado-enter".

<input-enter on-pulsado-enter="pulsadoEnter"></input-enter>

Ese es el modo de capturar eventos personalizados, aunque también podríamos usar la declaración de manejadores por medio de la propiedad "listeners", como ya aprendimos.

Ahora definimos el método pulsadoEnter() que es el manejador de eventos. Tendrás que apreciar de qué manera se recibe el dato que se transmite con el evento personalizado.

pulsadoEnter: function(e, tiempo){
      console.log('En el padre, detectado pulsado enter...', tiempo);
      this.push('instantes', tiempo);
}

Aquello que recibimos en el parámetro "tiempo" es el dato adicional que nos envía el evento personalizado.

Nota: Además en este código estamos usando algo que quizás todavía no habías visto, es el método this.push(). Ese método sirve para inyectar datos dentro de un array. En Polymer lo usas preferiblemente antes que el método push() nativo, que invocas sobre un array Javascript. Ese this.push() es una función de Polymer que te asegura que cuando insertas un dato en un array ese nuevo dato se bindea a todos los lugares donde se está usando ese array. Lo estudiaremos con calma más adelante.

Burbuja de eventos

Una cosa interesante que ocurre con los eventos en Polymer, y en Javascript en general, es que siempre escalan hacia arriba en el árbol del DOM de manera automática. Esto permite que un evento que produzca un componente Polymer, o un objeto del DOM nativo Javascript, también pueda ser captado por sus componentes padre, abuelo, etc. o por el script principal de un hipotético template "dom-bind" que englobe toda nuestra aplicación.

Por tanto, el evento irá escalando componentes hacia arriba y avisando a todos por si alguno quiere efectuar acciones con algún manejador declarado. Este es un comportamiento nativo de los eventos en Javascript que también afecta a los eventos personalizados.

Veamos la siguiente imagen, donde cada caja representa un elemento Polymer.

El componente "c" es hijo de "b" y a su vez, "b" es hijo de "a". Si lanzamos un evento personalizado desde "c", ese evento escalará hacia "b" por medio de la mencionada burbuja. En "b" podremos capturarlo si se desea, declarando los manejadores que vengan al caso. Pero se capture o no, el evento seguirá escalando y llegará al elemento "a", donde podremos capturarlo de nuevo si se desea.

Esto de la burbuja es algo que hace el navegador de manera transparente para el desarrollador. Por tanto, si yo quisiera declarar un manejador de evento en "a" para ese evento personalizado disparado en "c", no necesitaría capturarlo en "b" y volver a lanzarlo, puesto que va a escalar de cualquier manera hasta llegar a "a", o hasta llegar al elemento raíz del DOM.

Como decimos, la burbuja de eventos es el comportamiento predeterminado. Si en algún momento decidimos que un evento no debe seguir escalando el DOM podemos detenerlo mediante el objeto evento (que recibimos en todo manejador de evento como primer parámetro) y el método stopPropagation();

evento.stopPropagation();

De momento eso es todo. Con lo que sabes podrás definir eventos personalizados para todas las necesidades de tus proyectos. Es algo de vital importancia para el desarrollo de aplicaciones y base del patrón "mediador" que analizaremos más adelante.

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir