Binding entre componentes Polymer

  • Por
Cómo se realiza binding entre componentes Polymer, cuáles son las herramientas que en Polymer facilitan la interoperabilidad entre componentes.

El binding es un enlace a un dato, de modo que todos los elementos que estén "bindeados" a ese dato puedan recibir cualquier cambio de manera automácia. Esto ya lo conocimos en el artículo de introducción al binding en Polymer.

Ya vimos que dentro de un componente puedo bindear datos, generalmente propiedades, hacia el template. De modo que si cambia esa propiedad también cambie la vista sin que tengamos que hacer nada. Ahora vamos a ver cómo entre componentes también puede producirse el binding, permitiendo que un dato que se usa en un componente también pueda viajar hacia otros componentes que lo necesiten.

Binding a través de las propiedades / atributos

En realidad enviar información por medio de binding a otros componentes es muy sencillo. Ya hemos enviado datos literales varias veces cuando usamos los componentes. Por ejemplo recuerda el componente "romper-cadena" que mostramos en el artículo Registro de propiedades: tipos, valores y otras configuraciones.

<rompe-cadena str="Estas son las dos caras de la moneda" len="10"></rompe-cadena>

Al usar ese elemento le pasamos datos literales para adaptar su modo de funcionamiento. Cada vez que lo usamos le enviamos datos distintos para mostrar diferentes cadenas con sus recortes. Hasta ahora simplemente le hemos enviado datos literales, pero también podemos enviarle datos variables.

Por ejemplo imagínate que estás dentro de un componente y tienes la cadena a romper en una propiedad llamada "cadena". Podríamos usar el elemento anterior indicándole que su propiedad "str" esté bindeada con la variable "cadena".

<rompe-cadena str="{{cadena}}" len="10"></rompe-cadena>

Lo hemos explicado muy rápido, pero ahora lo veremos con un ejemplo completo para que te quede perfectamente claro.

interoperabilidad entre componentes

En una aplicación basada en componentes (y esto aplica a todo el desarrollo basado en el estándar Web Components) tenemos una arquitectura basada en Custom Elements, por medio de un árbol de elementos que interactúan entre si para cumplir el propósito de las aplicaciones. Esa actuación coordinada entre los componentes, necesaria para la resolución de los problemas se conoce como interoperabilidad. En Polymer se resuelve permitiendo que los componentes se pasen datos los unos a los otros por medio del binding y también gracias a la comunicación de eventos personalizados.

Pongamos que queremos hacer un componente que muestre un párrafo en un espacio reducido de la página. El párrafo tiene mucho texto así que a veces se puede recortar. Pero también queremos que el usuario pueda verlo entero si lo requiere, pulsando un botón para evitar ese recorte.

Nota: Esta tarea es bastante simple como para hacer un único componente, quizás no es el mejor ejemplo, pero piensa en una aplicación pequeña o mediana, la cantidad de subtareas que puede tener. Cada una de ellas se encapsula en un componente y eso permite que su complejidad sea pequeña, facilitando el desarrollo, pero sobre todo el mantenimiento, y ubicando perfectamente cada cosa en su sitio.

Para esta tarea podemos pensar en dos componentes distintos. Uno que se encargue únicamente del recorte de la cadena y otro que se encargue de la interacción con el usuario, permitiendo captar el evento de recorte o despliegue del párrafo cuando se pulsa el correspondiente botón. Quebrar esta tarea en dos componentes todavía es una solución más acertada cuando pensamos que ya existe el componente de romper una cadena, porque lo tenemos desarrollado ya en un mencionado artículo anterior.

Así que tendremos dos componentes uno que envuelve al otro.

En nuestro componente padre "mostrar-recortar" tendremos el siguiente template:

<template>
  <p>
    <rompe-cadena str="{{cadena}}" len="{{longitud}}"></rompe-cadena>
    <iron-icon icon="{{icono}}" on-tap="mostrarRecortar"></iron-icon>
  </p>
</template>

Como ves, unos componentes incluyen a otros y así se va generando ese árbol de componentes que son capaces de hacer funcionar una aplicación completa.

En este caso tenemos el componente rompe-cadena, que se encarga de romper una cadena a una longitud dada. Si te fijas, la cadena y la longitud no se entregan de manera literal, sino que las tenemos bindeadas a dos propiedades del componente padre ("cadena" y "longitud").

Luego, en vez de un botón propiamente dicho, hemos usado un icono, del catálogo de elementos de Polymer. Lo divertido es que la imagen del icono que se debe mostrar no se especifica tampoco, sino que está bindeada la propiedad "icono". Aquí tienes otro ejemplo de interoperabilidad entre componentes y lo hacemos así simplemente para que, cuando el párrafo está expandido se muestre un icono de contraer y cuando el párrafo está contraído se muestre un icono distinto. Bueno, en realidad es el mismo elemento iron-icon, solo que indicando valores distintos al atributo "icon", con lo que cambiará el aspecto.

Nota: Ten cuidado cuando uses elementos del catálogo de componentes de Polymer, puesto que los tendrás que importar debidamente y haberlos descargado previamente con Bower. Esto ya lo vimos al tratar los capítulos de usar elementos de Polymer.

Hay otra cosa importante en este template y es que el icono tiene un evento. No hemos llegado a hablar de eventos todavía, pero básicamente "on-tap" nos sirve para detectar un "click" o un "tap" (clic de dispositivos touch) sobre ese icono. En ese evento se invoca un método del propio componente llamado mostrarRecortar() que veremos enseguida.

Ahora veamos cómo hemos registrado este componente, en el script Javascript. Aunque mostrarmos el listado completo, nos centraremos primero en la declaración de propiedades.

Polymer({
  is: "mostrar-recortar",
  properties: {
    cadena: String,
    longitud: {
      type: Number,
      value: 20
    },
    longitudDefault: Number,
    icono: {
      type: String,
      value: 'remove'
    }
  },
  ready: function() {
    if(this.longitud == 0){
      this.icon = 'add';
    }
  },
  mostrarRecortar: function() {
    if(this.longitud > 0) {
      this.longitudDefault = this.longitud;
      this.longitud = 0;
      this.icono = 'add';
    } else {
      this.longitud = this.longitudDefault;
      this.icono = 'remove';
    }
  }
});

Tengo cuatro propiedades que paso a comentar:

  • cadena: es el texto completo del párrafo. No lo inicializo a valor alguno por defecto, porque este componente sin que se le entregue el párrafo a mostrar no tiene sentido utilizarlo.
  • longitud: es la longitud del recorte cuando está contraido el texto del párrafo. Valor predeterminado es 20. Esta propiedad es la que se bindea sobre el componente "romper-cadena" para decirle qué longitud se debe mostrar en cada momento.
  • longitudDefault: este dato, que tampoco inicializo, lo he creado para guardar el valor indicado como longitud por defecto. Como el campo longitud será variable, en algún lugar debo de almacenar la longitud de recorte, para no perderla cuando asigne otro valor a la propiedad "longitud".
  • icono: esta es la propiedad que usaré para guardar el valor del icono, inicialmente es un icono que indica "remove", que usaré porque entiendo que el texto pueda aparecer con su tamaño completo inicialmente.

Ahora veamos los métodos:

  • ready: este método es uno de los del ciclo de vida de los componentes de Polymer. Cuando el componente esté listo e inicializado mirará si la longitud > 0, porque entiendo que en ese caso quiero mostrar un icono en concreto y no el otro con el que hemos inicializado la propiedad "icono". (Ten en cuenta que he entendido en este componente que si la longitud es cero, entonces se mostrará el texto completo sin recorte).
  • mostrarRecortar: este es el corazón de la funcionalidad del componente. Es el método que se invoca con el evento "tap" sobre el icono. Si detecta que está expandido se contrae y si detecta que está contraido se expande. Esto lo consigo vigilando la propiedad longitud (si es igual a cero es que está expandido). En cualquiera de los casos caso simplemente se modifican las propiedades necesarias para que todo reaccione como debe. Haz un breve seguimiento al código y creo que lo entenderás bien.

Como las propiedades están bindeadas al template, en los métodos que orquestan el funcionamiento del componente simplemente me dedico a cambiar sus valores. Además, lo interesante en este ejemplo es que algunas propiedades viajan hacia los componentes hijo, produciéndose la interoperabilidad.

Nota: Es importante mencionar con respecto al componente rompe-cadena que hicimos en el artículo sobre el Registro de propiedades que hemos tenido que hacer una pequeña modificación para adaptarlo a estas necesidades y es el hecho de evaluar también si la longitud es igual a cero, en cuyo caso hemos dicho que no queríamos realizar recorte.

calculaRecorte(len, str){
       if (str.length <= len || len == 0) {
         return str;
       }
       var recortada = str.substr(0, len);
       return recortada.substr(0, Math.min(recortada.length, recortada.lastIndexOf(' '))) + '...';
 }

El código completo de los componentes que hemos visto en este artículo lo puedes encontrar en GitHub, en el repositorio del Manual de Polymer.

En este artículo hemos visto bastante información sobre cómo bindear datos entre componentes, pero todavía nos quedan cosas interesantes que analizar con detalle, como es el caso del bindeo de datos desde los hijos a los padres, que veremos en el próximo capítulo del Manual de Polymer.

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