Mostrando entradas con la etiqueta javaScript. Mostrar todas las entradas
Mostrando entradas con la etiqueta javaScript. Mostrar todas las entradas

martes, 19 de diciembre de 2017

Polymer: Verificando shadow DOM

Podría ser algo seriamente trivial, pero decidí revisar que tanto significa lo de shadow DOM para Polymer. Así que aplique un estilo CSS tanto ridículo, tanto lo suficiente para verificar lo que quiero.

En src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html, agrego un ligero estilo y una pequeña línea, de tal forma que el fichero queda de la siguiente forma:
        
<link href="../../bower_components/polymer/polymer-element.html" rel="import"></link>
<link href="/src/componentes/entradas/vt-entrada.html" rel="import"></link>
<link href="/src/componentes/etiquetas/vt-etiqueta.html" rel="import"></link>

<dom-module id="polymer-muestra-inicial-app">
    <template>
        <style>
        :host {
            display: block;
        }
        p,label {
            background-color: aliceblue;
        }
        </style>

            <h2>
Hello [[prop1]]!</h2>
<vt-entrada contenido="{{contenido}}"></vt-entrada>
            <vt-etiqueta contenido="[[contenido]]"></vt-etiqueta>
            Este es texto desde el componente padre polymer-muestra-inicial-app<br />
        
    </template>

    <script>
    /**
     * @customElement
     * @polymer
     */
    class PolymerMuestraInicialApp extends Polymer.Element {
        static get is() { return 'polymer-muestra-inicial-app'; }
        static get properties() {
            return {
                prop1: {
                    type: String,
                    value: 'polymer-muestra-inicial-app'
                },
                contenido:{
                    type: String,
                    value: 'Desde componente host'
                }

            };
        }
    }
    
    window.customElements.define(PolymerMuestraInicialApp.is, PolymerMuestraInicialApp);
    </script>
</dom-module>
Luego, src/componentes/entradas/vt-entrada.html ha quedado de la siguiente forma: Y sí, sólo agregue un color de fondo y subrayado para el texto:
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
<dom-module id="vt-entrada">
    <template>
        <style>
            :host {
                display: block;
            }
            p {
                background-color: beige;
                text-decoration: underline; 
            }
        </style>
        <input type="text" value="{{contenido::input}}">
        <p> Este es nuestro contenido escribiente: [[contenido]]</p>
    </template>
    <script>
        class VtEntrada extends Polymer.Element {
            static get is() {
                return 'vt-entrada';
            }
            static get properties() {
                return {
                    contenido: {
                        type: String,
                        notify: true,
                    }
                }
            }
        }

        window.customElements.define(VtEntrada.is, VtEntrada);
    </script>
</dom-module>
El resultado viene siendo elsiguiente:
Que básicamente viene a confirmar que:
  • Los estilos no suben desde un componente hijo a un componente padre. Lo que se esperaba pues. Esto se puede demotrar mejor si se elimina el estilo en src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html
  • Parece que los estilos no bajan desde el componente padre al componente hijo. Esto se puede demostrar si se elimina el estilo en src/componentes/entradas/vt-entrada.html
Esto último me parecía un poco preocupante, así que decidí maquetar un poco para ver como se comporta. Spoiler: Lo hace tal como se espera
Primer instalamos Pure mediante bower de la siguiente forma:
bower install pure --save
Luego, modificamos a src/index.html ara agregar los estilos de Pure:
 
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

        <title>polymer-muestra-inicial</title>
        <meta name="description" content="Breve descripción de mi persona">

        <!-- See https://goo.gl/OOhYW5 -->
        <link rel="manifest" href="/manifest.json">

        <script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>

        <link rel="import" href="/src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html">
        <link rel="stylesheet" href="/bower_components/pure/base.css">
        <link rel="stylesheet" href="/bower_components/pure/grids.css">
        <link rel="stylesheet" href="/bower_components/pure/grids-responsive.css">
    </head>
    <body>
            <polymer-muestra-inicial-app></polymer-muestra-inicial-app>
    </body>
</html>
Y hacemos la maquetación dentro de polymer-muestra-inicial-app en src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html de la siguiente forma:
 
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="/src/componentes/entradas/vt-entrada.html">
<link rel="import" href="/src/componentes/etiquetas/vt-etiqueta.html">

<dom-module id="polymer-muestra-inicial-app">
    <template>
        <style>
        :host {
            display: block;
        }
        p,label {
            background-color: aliceblue;
        }
        </style>
        <div class="pure-g">
            <div class="pure-u-1 pure-u-md-2-5">
                <h2>Hello [[prop1]]!</h2>
            </div>
            <div class="pure-u-1 pure-u-md-3-5">
                    <vt-entrada contenido="{{contenido}}"></vt-entrada>
                    <vt-etiqueta contenido="[[contenido]]"></vt-etiqueta>
                    <p>Este es texto desde el componente padre polymer-muestra-inicial-app</p>        
            </div>
        </div>
    </template>

    <script>
    /**
     * @customElement
     * @polymer
     */
    class PolymerMuestraInicialApp extends Polymer.Element {
        static get is() { return 'polymer-muestra-inicial-app'; }
        static get properties() {
            return {
                prop1: {
                    type: String,
                    value: 'polymer-muestra-inicial-app'
                },
                contenido:{
                    type: String,
                    value: 'Desde componente host'
                }

            };
        }
    }
    
    window.customElements.define(PolymerMuestraInicialApp.is, PolymerMuestraInicialApp);
    </script>
</dom-module>
Que ahora se ve de la siguiente forma:
Así que supongo que maquetar bien, descender clases bien, pero sobre los detalles específicos y generales de como va funcionando esto tendrán que ir esperando un poco más

viernes, 15 de diciembre de 2017

Siguiendo con la Introducción a Polymer 2

Lo de los webcomponents es algo genial: Dividimos la complejidad en pequeños pequeños, cuyas soluciones pueden ser reutilizables. El siguiente paso es verificar no sólo como trabaja la relación padre-hijo, sino la de aquellos componentes que pudieran tener el mismo nivel jerárquico en un momento dado.

Sobre nuestro ejemplo anterior, haremos un nuevo componente vt-etiqueta
mkdir src/componentes/etiquetas/
El contenido del fichero src/componentes/etiquetas/vt-etiqueta.html es el siguiente:
<link href="../../../bower_components/polymer/polymer-element.html" rel="import"></link>
<dom-module id="vt-etiqueta">
    <template>
        <style>
        :host {
            display: block;
        }
        </style>
        <label>Etiqueta componente: [[contenido]] </label>
    </template>
    <script>
        class VtEtiqueta extends Polymer.Element {
            static get is() {
                return 'vt-etiqueta';
            }
            static get properties (){
                return {
                    contenido: {
                        type: String,
                    }
                }
            }
        }
        window.customElements.define(VtEtiqueta.is, VtEtiqueta);
    </script>
</dom-module>
Luego, hemos de añadirlo en el componente polymer-muestra-inicial-app, que tiene la mayor jerarquía en nuestra aplicación. Primero un import en forma de etiqueta <link> y luego una etiqueta propiamente dicha. El código de src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html queda ahora de la siguiente forma:
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="/src/componentes/entradas/vt-entrada.html">
<link rel="import" href="/src/componentes/etiquetas/vt-etiqueta.html">

<dom-module id="polymer-muestra-inicial-app">
    <template>
        <style>
        :host {
            display: block;
        }
        </style>

        <h2>Hello [[prop1]]!</h2>
        <vt-entrada contenido="{{contenido}}"></vt-entrada>
        <vt-etiqueta contenido="[[contenido]]"></vt-etiqueta>
    </template>

    <script>
    /**
     * @customElement
     * @polymer
     */
    class PolymerMuestraInicialApp extends Polymer.Element {
        static get is() { return 'polymer-muestra-inicial-app'; }
        static get properties() {
            return {
                prop1: {
                    type: String,
                    value: 'polymer-muestra-inicial-app'
                },
                contenido:{
                    type: String,
                    value: 'Desde componente host'
                }

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

El contenido de vt-etiqueta, expuesto en la propiedad contenido será actualizado desde nuestro componente anterior, vt-entrada.
Para que eso suceda, modificamos la definición de este último en src/componentes/entradas/vt-entrada.html, configurando el atributo notify de dicha propiedad a true. Una pequeña línea no debería ser razón para volver a presentar todo el código, pero es gratis:
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
<dom-module id="vt-entrada">
    <template>
        <style>
            :host {
                display: block;
            }
        </style>
        <input type="text" value="{{contenido::input}}">
        <p> Este es nuestro contenido escribiente: [[contenido]]</p>
    </template>
    <script>
        class VtEntrada extends Polymer.Element {
            static get is() {
                return 'vt-entrada';
            }
            static get properties() {
                return {
                    contenido: {
                        type: String,
                        notify: true,
                    }
                }
            }
        }

        window.customElements.define(VtEntrada.is, VtEntrada);
    </script>
</dom-module>
Al escribir en la caja de texto del componente vt-entrada, el contenido de vt-etiqueta se va modificando.

Para tener en mente según lo visto:

  • Los binding [[propiedad]] son unidireccionales. (Padre al hijo)
  • Los binding {{propiedad}} son bidireccionales. (Padre al hijo y viceversa)
  • Para que esto último funcione con propiedades que han de modificarse en un componente hijo, es preciso que la propiedad tenga configurada notify: true en dicho hijo.
Sí, los componente son bastante reutilizables
Para ejemplo, basta con agregar otro vt-entrada en src/polymer-muestra-inicial-app/polymer-muestra-inicial-app.html cuyo contenido haga binding bidireccional con prop1, que es la propiedad que muestra del Hola Mundo de nuestra aplicación. Es un pequeño cambio, pero como copiar y pegar es sencillo:
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="/src/componentes/entradas/vt-entrada.html">
<link rel="import" href="/src/componentes/etiquetas/vt-etiqueta.html">

<dom-module id="polymer-muestra-inicial-app">
    <template>
        <style>
        :host {
            display: block;
        }
        </style>

        <h2>Hello [[prop1]]!</h2>
        <vt-entrada contenido="{{contenido}}"></vt-entrada>
        <vt-entrada contenido="{{prop1}}"></vt-entrada>
        <vt-etiqueta contenido="[[contenido]]"></vt-etiqueta>
    </template>

    <script>
    /**
     * @customElement
     * @polymer
     */
    class PolymerMuestraInicialApp extends Polymer.Element {
        static get is() { return 'polymer-muestra-inicial-app'; }
        static get properties() {
            return {
                prop1: {
                    type: String,
                    value: 'polymer-muestra-inicial-app'
                },
                contenido:{
                    type: String,
                    value: 'Desde componente host'
                }

            };
        }
    }
    
    window.customElements.define(PolymerMuestraInicialApp.is, PolymerMuestraInicialApp);
    </script>
</dom-module>
Desde el navegador, lo vemos de la siguiente forma:
Seguimos sin hacer que esto parezca algo mínimamente serio, pero al menos se esta volviendo cada vez más divertido.

jueves, 14 de diciembre de 2017

Especie de Introducción a Polymer 2

Este artículo debió llamarse "Especie de introducción a AngularJS". Es decir, estuve trabajando por mucho tiempo en una aplicación con AngularJS como framework: Fue una muy buena experiencia, y ha sido mi culpa empezar a aprender algo que años antes ya se sabía iba a quedar obsoleto por rediseño. Todos los cambios me superan: Claro que así descritos parecen una mejora, pero el sólo hecho de cambiar a TypeScript me hizo repensar desde cero toda la aplicación.

Así que reconsideré a Polymer: Esta basado en webcomponents, algo que ya había alcanzado a vislumbrar con AngularJS 1.5. Webcomponents aspira a ser el futuro de la web, bueno, así que veremos que tal se comporta en el presente.

Para instalarlo en Fedora, bastará con correr los siguiente comandos como root. Para npm, -g implica global, con lo que se supone que otros usuarios en el sistema podrían usar las herramientas así instaladas:
dnf install npm 
npm -g install bower
npm -g install polymer-cli
Luego seguimos las indicaciones que nos hace la gente de polymer. Desde consola, como cualquier usuario disponible en el sistema y ubicados desde cualquier parte que querramos (Preferentemente no "cualquier" parte), ejecutamos lo siguiente:
mkdir polymer-muestra-inicial
cd polymer-muestra-inicial/
polymer init --name polymer-2-application
Donde --name permite seleccionar la plantilla inicial para nuestro proyecto. polymer-2-application arranca con poco código, lo que es conveniente para aprender lo más posible.
polymer init --name polymer-2-application
info:    Running template polymer-init-polymer-2-application:app...
? Application name polymer-muestra-inicial
? Main element name polymer-muestra-inicial-app
? Brief description of the application Breve descripción de mi persona
A continuación, se instalan todos los paquetes necesarios para nuestra aplicación mediante bower, y es mediante él que debemos agregar nuevas dependencias.
En este momento, podemos probar lo que tenemos hasta ahora (Casi nada a decir verdad) ejecutando desde consola:
polymer serve --open
No subestimen este comando: Incluso permite servir contenido como HTTPS.

Ahora empezamos la introducción propiamente dicha: 

Aclaro que a este momento sigo sin pensar en una estructura de directorios para el código, así que voy a separarlo en componentes tanto como sea posible. 
mkdir -p src/componentes/entradas
cd src/componentes/entradas
Además, voy a trabajar modificando el componente que se ha creado originalmente para esta aplicación: polymer-muestra-inicial-app, no tanto por añorar mi época con AngularJS, sino por aprovechar todo el conocimiento previo de dicha época.

Así, creamos el fichero src/componentes/entradas/vt-entrada.html, que define un input casi sin modificación alguna, con el siguiente contenido
<link href="../../../bower_components/polymer/polymer-element.html" rel="import"></link>

<dom-module id="vt-entrada">
    <template>
        <style>
            :host {
                display: block;
            }
        </style>
        <input type="text" value="{{contenido}}" />
    </template>
    <script>
        class VtEntrada extends Polymer.Element {
            static get is() {
                return 'vt-entrada';
            }
            static get properties() {
                return {
                    contenido: {
                        type: String,
                    }
                }
            }
        }

        window.customElements.define(VtEntrada.is, VtEntrada);
    </script>
</dom-module> 
A ver, para explicaciones más detalladas las teoría. Desde aquí, sin embargo, podemos decir:
  • Esta es casi, casi la forma en que se define un webcomponent nativo al día de hoy: Dentro de <template></template> tenemos el HTML de toda la vida; dentro de <script></script> la definición de nuestro componente. Todo dentro de <dom-module>
  • properties define las propiedades de este componente, en resumen, las variables con las que nos relacionamos con otros componente y que controlamos mediante código. En realidad, extenderse en la teoría de este tema con la documentación oficial es un muy buen primer paso a tomar.
  • El estilo CSS definido dentro de <style> se circunscribe a este componente, mediante una técnica llamada DOM Shadow que hoy en día ya sirve para muchas otras cosas además de encapsular CSS (Igual nace por esta necesidad específica)
Ahora modificamos el componente que se venía de la plantilla, básicamente haciendo un import y usando nuestro componente. Al final se tiene que ver de la siguiente forma:
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="/src/componentes/entradas/vt-entrada.html">

<dom-module id="polymer-muestra-inicial-app">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <h2>Hello [[prop1]]!</h2>
    <vt-entrada contenido="Configurado desde plantilla padre"></vt-entrada>
  </template>

  <script>
    /**
     * @customElement
     * @polymer
     */
    class PolymerMuestraInicialApp extends Polymer.Element {
      static get is() { return 'polymer-muestra-inicial-app'; }
      static get properties() {
        return {
          prop1: {
            type: String,
            value: 'polymer-muestra-inicial-app'
          }
        };
      }
    }

    window.customElements.define(PolymerMuestraInicialApp.is, PolymerMuestraInicialApp);
  </script>
</dom-module>
Al verlo en la navegador, tenemos algo como esto:

Ahora hacemos que esta introducción valga la pena:

Visto así,  polymer casi nos parece un sistema de plantillas estático, así que para despertar el interés haremos unos cambios en nuestro componente vt-entrada: Los cambios se realizan en la plantilla, agregando un <p></p> cuyo contenido se hace refencia a la propiedad contenido de nuestro componente.

Por otra parte, vamos a cambiar el data binding de nuestro <input> para que funcione en forma bidireccional: En lugar de contenido, ahora será contenido::input.

Lo último a resaltar es que tenemos dos forma de hacer los binding: En una vía mediante llaves y bidireccional mediante corchetes. Esto es otro tema bastante complicado en el que se debería extender con la documentación oficial. Al final, nuestro componente debe verse de la siguiente forma:
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">

<dom-module id="vt-entrada">
    <template>
        <style>
            :host {
                display: block;
            }
        </style>
        <input type="text" value="{{contenido::input}}">
        <p> Este es nuestro contenido escribiente: [[contenido]]</p>
    </template>
    <script>
        class VtEntrada extends Polymer.Element {
            static get is() {
                return 'vt-entrada';
            }
            static get properties() {
                return {
                    contenido: {
                        type: String,
                    }
                }
            }
        }

        window.customElements.define(VtEntrada.is, VtEntrada);
    </script>
</dom-module>
Lo vemos de la siguiente forma en el navegador:
Y sí, el contenido se actualiza conforme se escribe en la caja de texto. Así como que ya dan más ganas de trabajar con esto.

Supongo que para una breve introducción ya está.

lunes, 19 de octubre de 2015

Primeros pasos con Mustache

Mustache es un sistema de plantillas sin lógica, (Traducción literal de la descripción que hacen de si mismos en el sitio web), lo que significa que a diferencia de otros sistemas de plantillas para JavaScript este no define estructura de control, sino que trabaja definiendo etiquetas.

Es un gran adelanto usar un sistema de plantillas. Separar la "lógica de negocios" de la presentación siempre es un gusto, pese a los inconvenientes que pueda presentar en cuánto a rendimiento. Por otra parte, no es díficil aprender a usar Mustache, y su sencillez su no es problema para las capacidades que presenta:

Su uso puede resumirse de la siguiente forma:

  • Se crea una plantilla a usar sobre como han de tratarse los datos, tomando como referencia un objeto JSON. En mi caso, mi JSON es un array cuyos datos son los que necesito mostrar:

<tbody id="respuesta">
    <script id="respuesta-template" type="text/x-custom-template">
        {% verbatim %}
            {{#datos}}
                {{#.}}
                    <tr id="{{uid}}"><td>
                        <p class="col-md-12"><b>{{ cn }}</b></p>
                        <p class="col-md-12 col-sm-12 small">{{ #title }} {{ title }} en {{ /title }}{{ #ou }}{{ ou }} de {{ /ou }}{{ establecimiento }}</p>
                        <p class="col-md-6 col-sm-12 small">{{ mail }}</p>
                        <p class="col-md-6 col-sm-12 small">{{ telephoneNumber }</p>
                    </td></tr>
                {{/.}}
            {{/datos}}
        {% endverbatim %}
    </script>
</tbody>

  • El objeto JSON va definido, datos más, datos menos, de la siguiente forma:

{
    "datos": [
        {
            "cn": "AdaCruz",
            "mail": "acruz@salud.gob.sv",
            "uid": "acruz"
        },
        {
            "cn": "Adalberto Chavez",
            "mail": "achavez@salud.gob.sv",
            "uid": "achavez"
        }
    ],
    "mensajeError": null
}
La idea es que la etiqueta {{#datos}} permite acceder no a los datos de la clave en el objeto JSON, sino más bien a la funcionalidad por defecto que siendo un array es la de recorrerlo. Lo de {{#.}} es un extraño workaround ya que parece que el objeto JSON es recibido seccionado.
Por otra parte {{#.}}, podría ser útil para recorrer un objeto JSON que consista en una seria de array sin índice alguno.

Lo demás, es que cuando estemos recorriendo cada objeto json que compone el array tendremos el dato que cada índice contiene.

Luego, vale la pena mencionar el uso que se le ha vuelto a dar a la etiqueta de tipo {{ #etiqueta }} en el caso del segundo bloque:
<p class="col-md-12 col-sm-12 small">{{ #title }} {{ title }} en {{ /title }}{{ #ou }}{{ ou }} de {{ /ou }}{{ establecimiento }}</p>
Las etiquetas de este modo funcionan como un condicionante. Lo que este dentro de ellas no será mostrado a menos que haya contenido que mostrar. En este caso particular, lo he usado para construir la frase según haya uno u otro contenido, pero supongo que será posible enmarcar toda una etiqueta HTML si se antoja necesario.
  • Por último, el código que realiza todo el trabajo de presentación va de la siguiente forma:
var template = $('#respuesta-template').html();
Mustache.parse(template);
var contenido = Mustache.render(template, respuesta);
pmostrarError(respuesta);
pmostrarMensaje(respuesta);
$("#respuesta tr").remove();
$("#respuesta").append(contenido);

El resultado viene quedando de la siguiente forma:
Los íconos son parte del proyecto Material Icons de Google  y omití las etiquetas de imagen para hacer legible al ejemplo 

Tener en cuenta que este intento de manual ha obviado muchas cosas: El que este usando Bootstrap y JQuery por ejemplo.

Fuentes:
Defining a HTML "template" to append using JQuery
mustache.js - Logic-less {{mustache}} templates with JavaScript
Mustache.js + jQuery: what is the minimal working example ?
How to use Mustache with JS / jQuery – a working example

Otros apuntes interesantes

Otros apuntes interesantes