Tipos de propiedades y deserialización de atributos en componentes Polymer

  • Por
Propiedades en Polymer 2 y los tipos de datos posibles, explicando los mecanismos de deserialización de atributos para recibir los valores de las propiedades.

En Polymer 2, de manera similar a su predecesor Polymer 1, existe la posibilidad de trabajar de una manera ágil con propiedades de los componentes, así como asignar valores desde fuera de éstos, de manera declarativa en el propio HTML. Ésto no debe ser nuevo para ti, puesto que las propiedades son una de las principales herramientas con las que contamos para el desarrollo basado en componentes y ya hemos trabajado con ellas anteriormente.

En esta ocasión vamos a ahondar sobre el tema de las propiedades, explicando los tipos de datos que puedes asignarles y cómo se deserializan valores en las etiquetas de los componentes. Recuerda consultar el artículo de introducción a las propiedades y estado de los componentes para encontrar información más básica y general.

Tipos de datos en las propiedades

Al declarar propiedades podemos indicar el tipo de datos que vamos a usar en esa propiedad, de modo que en el momento de su creación Polymer se ocupará por asignar ese tipo a la variable.

Como sabes, Javascript es un lenguaje con tipado dinámico, por lo que una variable puede cambiar de tipo con el tiempo, ya sea porque el programador lo requiera o porque algún despiste en el código produzca ese cambio de tipo en el tiempo de ejecución. De modo que Polymer nos asegurará que la propiedad tenga inicialmente un tipo, pero no puede asegurarnos que lo mantenga durante toda la ejecución de la aplicación, pues es algo que depende más de cómo la hayas programado.

En el siguiente código tenemos dos propiedades declaradas, miNumero y miString. Esas propiedades tendrán los tipos definidos en la declaración que puedes ver a continuación.

static get properties() {
  return {
    miNumero: Number,
    miString: {
      type: String,
      value: 'Esto es una cadena'
    },
}

Al declarar las propiedades podemos simplemente indicar el tipo, como es el caso de "miNumero" en el código anterior. Pero, si además queremos declarar otras cosas sobre la propiedad, como su valor por defecto, una función observadora, etc., entonces necesitas pasar a una declaración compuesta en notación de objeto, como el caso de "miString".

Deserialización de los atributos en valores de propiedades

Para entendernos, ahora vamos a llamar atributos a los valores que se colocan en una etiqueta HTML de un componente, como por ejemplo "mi-atributo" en el siguiente código:

<mi-componente mi-atributo="valor del atributo"></mi-componente>

Una propiedad es algo interno del componente y Polymer nos ofrece de manera automática el traspaso del valor de un atributo en su correspondiente propiedad.

Mayúsculas y minúsculas en atributos y propiedades

Solo debes tener en cuenta que en HTML no existe distinción entre mayúsculas y minúsculas, y sin embargo Javascript sí que lo tiene en cuenta. Por ello al trabajar con nombres de atributos y propiedades hay que ir con cuidado especial.

La propiedad, al ser algo del lenguaje Javascript, se suele escribir con notación "CamelCase", colocando las primeras letras de la segunda y siguientes palabras en mayúscula. Para adaptar el CamelCase en el HTML se utilizan guiones, separando las distintas palabras. Por ejemplo la propiedad "miNumero" se correspondería con el atributo "mi-numero" en el HTML.

Nota: Es un error frecuente olvidarse los guiones en el HTML y escribir los atributos con CamelCase. En este caso no se realizará la correspondencia a la propiedad con su nombre CamelCase, sino a su nombre con minúsculas. Por ejemplo el atributo HTML "ciudadOrigen" se haría corresponder con la propiedad Polymer "ciudadorigen".

Tipos de datos en la deserialización

El proceso por el que se traspasan los valores del HTML a las propiedades es la deserialización y en él se intentará corresponder el valor del atributo con el tipo de dato indicado en la declaración de la propiedad.

Esto es importante porque todos los datos colocados como valores de atributos en el HTML son cadenas de texto. HTML no distingue si lo que se ha colocado como valor de un atributo es una cadena, un número o un boleano y sin embargo, Polymer nos hará el trabajo de corresponder y transformar aquella información al tipo correcto.

  • Cadenas (String): Aquí Polymer no realiza ningún trabajo especial, puesto que los valores de los atributos son siempre cadenas.
  • Números (Number): Polymer se encarga de convertir la cadena en un valor numérico, haciendo lo posible porque esa conversión sea lógica. Convierte por ejemplo la cadena "4" por el número 4. Convierte la cadena "092" por el número 92. Aunque algo como "x665" no lo podría convertir en un número y otorgará a la propiedad el valor "NaN" (not a number).
  • Boleanos (Boolean): En este caso no importa el valor del atributo, sino simplemente su presencia. En el caso que el atributo se encuentre en la etiqueta, entonces la propiedad tendría valor asignado como "true". Si el atributo no está presente, la propiedad nacerá con el valor "false" (a no ser que se le indique otro valor predeterminado).
  • Arrays y objetos (Array / Object): Para estos dos tipos de datos los valores de los atributos se tienen que indicar con notación JSON. En este caso es importante tener en cuenta que en un JSON las comillas válidas son las comillas dobles (") y por tanto no nos queda otro remedio de colocar el valor del atributo HTML con comillas simples. Luego verás un ejemplo.
  • Fechas (Date object): Polymer tratará de convertir la fecha en un objeto de la clase Date de Javascript. Ten en cuenta que debes usar un formato de fecha apropiado como valor de atributo, como 2017-09-01.

Ejemplo de componente y declaración de propiedades

Con lo aprendido en este artículo vamos a mostrar el código de un componente que podríamos tener para practicar con diferentes tipos de propiedades.

Nota: Encontrarás algunas cosas de las que no hemos hablado todavía, como un template de repetición, que usamos para recorrer los elementos de un array. La declaración de eventos diversos. O un $ misterioso en un binding sobre un atributo hidden (hidden$) que en este caso sirve para ocultar o mostrar elementos dependiendo de una propiedad boleana.
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../bower_components/polymer/lib/elements/dom-repeat.html"
>
<dom-module id="deserializacion-tipos">
  <template>
    <style>
      :host {
        display: block
      }
    </style>

    <div on-click="mostrarTipoMiString">[[miString]]</div>
    <div on-click="mostrarTipoMiNumero">[[miNumero]]</div>
    <div hidden$="[[miBoleano]]" on-click="mostrarTipoMiBoleano">miBoleano es false</div>
    <div hidden$="[[!miBoleano]]" on-click="mostrarTipoMiBoleano">miBoleano es true</div>
    
    <template is="dom-repeat" items="[[miArray]]">
      [[item]]
    </template>
    
    <div>
      Datos del objeto propiedad1: [[miObjeto.prop1]], propiedad2: [[miObjeto.prop2]], propiedad3: [[miObjeto.prop3]] 
    </div>
    
    <div on-click="mostrarTipoMiFecha">Fecha: [[miFecha]]</div>

  </template>
  <script>
    class DeserializacionTipos extends Polymer.Element {
      
      static get is() {
        return 'deserializacion-tipos';
      }

      static get properties() {
        return {
          miString: {
            type: String,
            value: 'Esto es una cadena'
          },
          miNumero: {
            type: Number
          },
          miBoleano: Boolean,
          miArray: Array,
          miObjeto: Object,
          miFecha: Date
        };
      }

      constructor() {
        super();
      }

      mostrarTipoMiNumero() {
        console.log('Tipo es: ', typeof(this.miNumero));
      }
      mostrarTipoMiBoleano() {
        console.log('Tipo es: ', typeof(this.miBoleano));
      }
      mostrarTipoMiString() {
        console.log('Tipo es: ', typeof(this.miString)); 
      }
      mostrarTipoMiFecha() {
        console.log('Tipo es: ', typeof(this.miFecha), ' y valor: ', this.miFecha); 
      }
    }

    window.customElements.define(DeserializacionTipos.is, DeserializacionTipos);
  </script>
</dom-module>

En el componente se agregaron unos eventos "click" que nos sirven para mandar mensajes a la consola en los que mostramos el tipo de datos que tienen las propiedades en el componente.

Ahora puedes apreciar el código que tendríamos para usar este componente, enviando los valores de los atributos en el código HTML. Realmente no tiene mucho misterio, pero sobre todo resulta interesante ver cómo se han definido los valores de tipo array y tipo objeto en formato JSON.

<deserializacion-tipos 
  mi-numero="090" 
  mi-boleano
  mi-array="[3, 67, 9]"
  mi-objeto='{"prop1": "valor1", "prop2": "valor2", "prop3": "valor3"}'
  mi-fecha="1975-02-21"
></deserializacion-tipos>
Nota: Recuerda que para funcionar tendrías que importar como siempre el componente, usando el correspondiente "import".

Insistimos en tomar en cuenta las comillas dobles en un JSON. Observa cómo el atributo "mi-objeto" tiene comillas simples para indicar su valor. En el caso del valor de "mi-array", aunque el valor es otro JSON, nos hemos tomado la licencia de usar comillas dobles, dado que no hemos necesitado usar cadenas dentro del JSON.

Deserialización personalizada de atributos

Una utilidad interesante en Polymer 2 es la deserialización personalizada, que permite al desarrollador sobreescribir el procedimiento para deserializar un valor de un tipo de atributo.

Vamos a suponer que en un componente debemos reconocer los tipos "Date" con valores que admitan el formato de fechas en español. En este caso no nos sirve el deserializador de Date disponible, por lo que podemos sobreescribirlo, rescribiendo simplemente el método de Polymer _deserializeValue(value, type). Lo vamos a ver con un ejemplo de un nuevo componente:

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

<dom-module id="deserializacion-personalizada">
  <template>
    <style>
      :host {
        display: block
      }
    </style>

    <div on-click="mostrarTipoMiFechaEspanola">Fecha: [[miFechaEspanola]]</div>


  </template>

  <script>
    
    class DeserializacionPersonalizada extends Polymer.Element {
      
      static get is() {
        return 'deserializacion-personalizada';
      }

      static get properties() {
        return {
          miFechaEspanola: Date
        };
      }

      constructor() {
        super();
      }

      _deserializeValue(value, type) {
        if (type == Date) {
          return this.fechaEspanolaToDate(value);
        }
        return super._deserializeValue(value, type);
      }

      fechaEspanolaToDate(valor) {
        console.log('parseando a fecha: ', valor);
        //realizaría el trabajo de comprobar el formato y parsear...
        return 'devuelvo el valor que toque';
      }

      mostrarTipoMiFechaEspanola(){
        console.log('Tipo es: ', typeof(this.miFechaEspanola), ' y valor: ', this.miFechaEspanola); 
      }
    }

    window.customElements.define(DeserializacionPersonalizada.is, DeserializacionPersonalizada);
  </script>
</dom-module>

En este componente tenemos una propiedad llamada "miFechaEspanola" que es de tipo Date.

Al sobreescribir _deserializeValue() preguntamos si el tipo es Date, en cuyo caso lo dirigimos por una función propia, que en este caso hemos llamado fechaEspanolaToDate(). Esa función se encargaría de realizar el trabajo necesario, devolviendo el valor deserializado.

Nota: en nuestro caso el código de la función fechaEspanolaToDate() es solo un mensaje para la consola y un return de una cadena. Lógicamente no es correcto, pues tendría que hacer el trabajo de convertir la cadena en un objeto Date correcto. Pero ese algoritmo no es el objetivo de este artículo.

Incluso podríamos crearnos nuestro propio tipo personalizado con una clase ES6, por ejemplo:

class TipoPersonalizado {
  //aquí el código de un nuevo tipo inventado, definido por medio de una clase ES6
}

Esa clase ahora nos permite definir propiedades de este tipo personalizado.

static get properties() {
  return {
    miFechaEspanola: TipoPersonalizado
  };
}

Y ahora para deserializar este tipo personalizado nuevo, tenemos que sobreescribir _deserializeValue(), igual que antes.

_deserializeValue(value, type) {
  if (type == Date) {
    return this.fechaEspanolaToDate(value);
  }
  if (type = TipoPersonalizado) {
    return this.parsearOtraCosa(value);
  }
  return super._deserializeValue(value, type);
}

Eso es todo de momento. Hemos hablado mucho de los types en las propiedades. Es un asunto importante, pero hay muchas otras funcionalidades y posibilidades en las properties de Polymer que todavía no hemos explicado y que veremos en los próximos artículos del manual de Polymer 2.