Client side Meteor. Parte 4


Ionic, frontend framework líder en el desarrollo de aplicaciones híbridas. Está desarrollado sobre la base de AngularJS y se caracteriza por ser sumamente rápido y vistozo. En esta entrada le daré a la app Contactos un toque de este framework. Comencemos.

Instalando Ionic para Meteor

Utilizar el propio framework Ionic supondría integrar AngularJS también con Meteor. Es por ello que utilizaré un paquete creado específicamente para evitar este inconveniente. Los chicos de Meteoric han hecho un buen trabajo en la integración de Ionic con Blaze, cero Angular, funciona de maravillas.

meteor add meteoric:ionic meteoric:ionic-sass meteoric-ionicons-sass

Con la instalación de estos paquetes también deberíamos instalar iron:router, un gestor de rutas diseñado específicamente para Meteor, pero como nuestra app tiene una sola vista, no es necesario.

Desafortunadamente, hay un problema, con estos pasos realizados, aún no tenemos la integración completada, pues por alguna razón, los ficheros SASS no se están registrando correctamente. Para solucionarlo sigue estos pasos:

$ mkdir client/estilos
$ nano client/estilos/app.scss

@import "meteoric_ionic-sass/_ionic";
@import "meteoric_ionicons-sass/ionicons";

Lo que se realizó aquí fue crear un fichero SASS e importar los estilos del paquete meteoric:ionic de forma manual. Estos estilos actualmente no existen en la carpeta client/estilos/app.scss, por lo que debes copiarlos de una dirección un poco extraña.

Las carpetas meteoric_ionic-sass y meteoric_ionicos-sass cópialas de la ruta .meteor/local/build/programs/server/assets/packages/. Si no están ahí es porque no has iniciado el servidor con el comando meteor.

Luego de realizado este rústico procedimiento para integrar Ionic, podemos comenzar a usarlo.

Otra forma de incrustar templates

En los artículos anteriores te enseñé que en Spacebars se incrustan templates dentro de otros templates con la etiqueta {{> NombreTemplate }}. Sin embargo, existe otra forma más flexible que imita el comportamiento de las directivas de AngularJS cuando se usa el atributo ng-transclude. Observa su funcionamiento:

<template name="ListaDeAlgo">
  <p> Hola, saludos desde un template </p>
  <p> 
     {{> UI.contentBlock }}
  </p>
</template>

<template name="UtilizoElTemplateDeArriba">
  {{#ListaDeAlgo}}
    Este texto se insertará en la segunda etiqueta <p>
    del template ListaDeAlgo
  {{/ListaDeAlgo}}
</template>

¿Lo captas? Con esto realizado la plantilla UtilizoElTemplateDeArriba se renderiza con el siguiente contenido:

<p> Hola, saludos desde un template </p>
<p> 
    Este texto se insertará en la segunda etiqueta <p>
    del template ListaDeAlgo
</p>

Este mecanismo es el utilizado para integrar Ionic con Blaze sin utilizar Angular. Dicho esto, a modificar la app Contactos!

Estructurando la aplicación

Dirígete al fichero index.html y envuelve la inclusión del template ListaContactos de esta forma:

{{#ionBody }}
    {{> ionNavBar class='bar-positive' }}

    {{#ionNavView }}
        {{> ListaContactos }}
    {{/ionNavView }}

{{/ionBody }}

Una app típica de Ionic se divide en: barra superior, área de contenido y, opcionalmente, una barra inferior. En este caso solo se incluyó una barra superior (ionNavBar) y el área de contenido (ionNavView), todo esto tiene que estar dentro de ionBody obligatoriamente.

El template ListaContactos

Lo primero que se debe hacer aquí es establecer el contenido que tendrá la sección ionNavBar. Esto es necesario pues en una app mobile es típico ver como cambian las opciones de la barra superior en dependencia de la vista en que se encuentra el usuario.

En este caso, el NavBar debe tener un texto central que diga App Contactos y un botón a la derecha para agregar uno nuevo (pues separaré el formulario de la lista):

<template name="ListaContactos">

{{#contentFor 'headerTitle' }}
    <h1 class="title">App Contactos</h1>
{{/contentFor}}

{{#contentFor "headerButtonLeft"}}
<!-- vacío, no necesito un botón a la izquierda del navbar -->
{{/contentFor}}

{{#contentFor "headerButtonRight"}}
    <button class="button" data-ion-modal="NuevoContacto">{{> ionIcon icon="plus"}}</button>
{{/contentFor}}

... <!-- seguimos enseguida -->

Observa que el template NuevoContacto se mostrará en un modal, y se logra con definiendo el atributo data-ion-modal en el botón.

La lista de contactos la definimos dentro de un ionList, que se encuentra dentro de un ionContent y este a su vez dentro de un ionView. Qué enredo, pero bueno, así es Ionic:

{{#ionView}}
    {{#ionContent}}

        {{#ionList }}
            <div class="item">
                <p> Hola, existen {{ cantidad }} contactos en la aplicación </p>
            </div>

            {{#each contactos}}
                {{#ionItem}}
                    {{> Contacto }}
                {{/ionItem}}
            {{/each}}

        {{/ionList }}
    {{/ionContent}}
{{/ionView}}
</template>

El template NuevoContacto

Aquí solo hay que ajustar la estructura actual a un modal Ionic. Dicho modal está dividido en dos partes: una barra superior y un cuerpo:

<template name="NuevoContacto">
{{#ionModal customTemplate=true}}
  <!-- barra superior -->
  <div class="bar bar-header bar-stable bar-positive">
      <button data-dismiss="modal" class="button button-clear">
          Cerrar
      </button>

      {{#if editando}}
          <h1 class="title">Editar Contacto</h1>
      {{else}}
          <h1 class="title">Crear Contacto</h1>
      {{/if}}      
  </div>

  <!-- cuerpo -->
  <div class="content has-header overflow-scroll">
    <form>
      <label type="floating" class="item item-input item-floating-label">
        <span class="input-label">Nombre</span>
        <input id="nombre" label-type="floating"
               name="nombre" placeholder="Nombre"
               value="{{ contacto.nombre }}"/>
       </label>

       <label type="floating" class="item item-input item-floating-label">
         <span class="input-label">Apellidos</span>
         <input id="apellido" label-type="floating"
                name="apellido" placeholder="Apellidos"
                value="{{ contacto.apellidos }}"/>
       </label>

       <button type="submit" class="button">
       {{#if editando}}
           Editar
         {{else}}
           Crear
         {{/if}}
       </button>
     </form>
 </div>
{{/ionModal}}
</template>

No es el formulario más vistoso con que cuenta Ionic pero muestra el punto al que se quiere llegar.

Iron Router

¿Recuerdas la parte donde te comenté que no necesitamos iron router porque tenemos una sola vista? Mentí.

Sucede que el objetivo primario de este artículo es enseñarte a integrar Ionic con Meteor, así que no puedo en sus inicios comenzar con esta sección. Al final de la jornada, ¿qué aplicación decente no necesita un sistema de ruteo? Bueno, ésta :), pero igual vamos a incluirlo para cuando necesites añadir más vistas, lo hagas en un 2×3.

$ meteor add iron:router

Corta todo el contenido que está dentro de la etiqueta body, borra esta etiqueta y crea un template ahí mismo (en el index.html) con nombre layout. Pega el contenido del body. A continuación, reemplaza la inclusión del template ListaContactos por yield:

<template name="layout">
    {{#ionBody }}
        {{> ionNavBar class='bar-positive' }}
        {{#ionNavView }}
            {{> yield }}
        {{/ionNavView }}
    {{/ionBody }}
</template>

El template {{> yield }} es como una puerta hacia el contenido. Cuando defina las rutas, los templates asociados a cada una de ellas, serán insertados en el lugar de yield. Crea el fichero client/rutas.js y configura el Router:

$ nano client/rutas.js

Router.configure({ layout : 'layout' });

Aquí especifico que el layout de la aplicación es un template con el nombre layout. Observa que el nombre de este template puede ser cualquiera. A continuación, define la ruta raíz, la cual debe utilizar el template ListaContactos, para ajustarnos a la configuración que teníamos antes de instalar iron:router:

Router.map(function(){ 
  this.route('contactos' , { 
    path:'/' , 
    template:'ListaContactos' 
  });
});

Las rutas se definen con la función map del Router. Ésta acepta una función como parámetro donde se especifica la ruta con this.route. El primer parámetro de esta función es el nombre de ruta, el cual se utiliza para ser referenciado desde varias partes. Por ejemplo, para crear un link que apunte a esta ruta, se utiliza un markup como este:

<a href="{{ pathFor 'contactos' }}"> Inicio </a>

Si por ejemplo, deseas mover el formulario de insertar contacto desde el modal hacia una nueva vista, debes crear una nueva ruta:

this.route('contactos_nuevo' , { 
  path:'/contactos/nuevo' , 
  template:'NuevoContactos' 
});

El link que lleve a esta ruta es:
<a href="{{ pathFor 'contactos_nuevo' }}"> Crear </a>

Ejecuta la aplicación

Como añadimos varios assets nuevos a la app, probablemente al ejecutar el comando meteor veas una advertencia de que la caché local pesa más que 5mb, que es lo máximo recomendado.

Para evitar ver esta advertencia, ejecuta la app como si estuviera en producción:
meteor --production. Con este comando se minimizarán los assets y pesarán mucho menos. Deberás ver algo como esto:

Conclusiones

He aquí una app creada con MeteorJS que puede convertirse en una app nativa tanto para Android como para IOS usando Córdova.

Te enseñé en esta entrada el uso básico de Iron Router, otra forma más flexible de incrustar templates y el plato fuerte: integración con Ionic.

El proyecto se encuentra en Github, puedes hacerle un fork y crear tu propia versión, extenderla y, por qué no, enseñársela al mundo.

Keep Learing!