Apuntes Curso Phonegap

Profesor: @ciberado

Días 1 a 5: 18/11/2014 -> 24/11/2014

Enlaces

Instalación entorno

Módulos necesarios:

Creación de proyecto Phonegap

$ phonegap create 00HolaMundo --name HolaMundo --id es.eduardofilo.hm

Compilación y ejecución de proyecto Phonegap

$ cd 00HolaMundo
$ phonegap build android
$ phonegap run android

En realidad la tarea run hace build.

Inspeccionar dispositivos externos en Chrome

about:inspect

REST WebServices

Servidor mock (mockable): demo0034470.mockable.io

Respuesta:

{
	"votosTotal": 6000,
	"votosPositivos": 3500,
	"votosNegativos": 2500,
	"fecha": "2013-04-17T12:32:12"
}

jQuery

Hay una convención por la cual cuando invocamos jQuery ($) para localizar un elemento del DOM, la variable donde se carga se pone con el prefijo $. Por ejemplo:

var $paragrafos = $('p');

Ejemplo1

var $paragraphs = $('p');
for (var i=0; i < $paragraphs.length ; i++) {
    console.log(i, $paragraphs[i]);
}
$paragraphs.css('color', 'red');
$paragraphs.css('background-color', 'green');

$paragraphs.css({'color': 'blue',
                 'background-color': 'yellow'});

console.log($paragraphs.css('color'));
$firstp = $('p:first');
//$firstp = $('p').first();
//$firstp = $('p').eq(0);
$firstp.addClass('importante');

Ejemplo2

var $firstp = $('p:first');
var texto = $firstp.text();
$firstp.text(texto.toUpperCase());
//var $strong = $('<strong>');
//$strong.text('contenido');
//$firstp.prepend($strong);

$('<strong>')
    .text('contenido')
    .prependTo($firstp);

Ejemplo3

var colores = ['Rojo', 'Verde', 'Azul'];
var $ol = $('ol');
$ol.empty();
for (var i=0; i<colores.length; i++) {
    $ol.append($('<li>').text(colores[i]));
    //$('<li>').text(colores[i]).appendTo($ol);
}

Ejemplo4

$('p').on('click', function(evt) {
    evt.preventDefault();
    $(this).css('background-color', 'green');
});

Ejemplo5

$('p').on('click', function(evt) {
    $(this).fadeOut(function() {
        $(this).text($(this).text().toUpperCase());
        $(this).fadeIn();
    });
});

Día 6: Martes 25/11/2014

Enlaces

Montaje de entorno y workflow

  1. Bajamos el instalador de git y lo instalamos. Durante la instalación, en una página del asistente, marcamos la tercera opción que lleva una leyenda en rojo.
  2. Inicializamos el proyecto local:
    • mkdir 06YesNoGit
    • cd 06YesNoGit
    • git init
  3. Entramos con nuestra cuenta (o creamos una) en GitHub y creamos un repositorio con el mismo nombre que el local (06YesNoGit).
  4. Creamos un “remote” desde el git local hacia GitHub:
    • git remote add origin https://github.com/eduardofilo/06YesNoGit.git
  5. Sincronizamos:
    • git pull origin master
  6. Modificamos un fichero en local (.gitignore), lo commiteamos en local y sincronizamos con GitHub:
    • git add .gitignore
    • git commit -m "Primer commit"
    • git push origin master
  7. Inicializamos gitflow en SourceTree (pulsando el botón de la toolbar).
  8. Instalamos Bower (la opción g lo instala en global, es decir accesible desde todos los proyectos):
    • npm install -g bower
  9. Grunt no se suele instalar de forma global porque cambia mucho de versión. Vamos a instalar algo que ejecuta el grunt local del proyecto:
    • npm install -g grunt-cli
  10. Instalamos Yeoman:
    • npm install -g yo
  11. Si falla la descarga de algún paquete, es mejor no reejecutar yeoman. Se habrá generado un fichero package.json que contiene los paquetes npm que necesitamos. Ejecutando lo siguiente se bajará lo que falte:
    • npm install
  12. Si npm install nos da warnings se puede solucionar inicializando npm (que genera el fichero package.json):
    • npm init
  13. Instalamos la plantilla webapp de Yeoman:
    • npm install -g generator-webapp
  14. En este punto tenemos todas las herramientas instaladas.
  15. “Start New Feature” en gitflow@SourceTree. Le damos el nombre “InicializarConYeoman”.
  16. Generamos un proyecto con la plantilla webapp de Yeoman:
    • yo
    • Instalamos “webapp”
    • Incluimos las librerías Bootstrap y Modernizr
    • Hacemos overwrite de .gitignore
  17. Lanzamos los tests para probar Grunt:
    • grunt test
  18. Compilamos con Grunt:
    • grunt build
  19. Aparece el directorio dist con la versión final (lista para desplegar) de nuestro proyecto.
  20. Arrancamos un servidor web para probar el proyecto:
    • grunt serve

Día 7: Miércoles 26/11/2014

Enlaces

Bower

La diferencia con npm es que éste instala cosas en máquina; Bower añade librerías al proyecto. Lo vamos a manejar con los siguientes comandos por terminal:

Grunt

El fichero de configuración tiene forma de script Javascript. Es el fichero Gruntfile.js.

git

Vamos a hacer el merge de la feature que creamos ayer en la rama develop:

  1. SourceTree / Commit / Marcamos “Unstage all” para pasar a stage todos los ficheros modificados / Incluimos el comentario del commit en la caja de abajo.
  2. SourceTree / Gitflow / Finish Feature (marcamos Delete branch)
  3. SourceTree / Push (marcamos las dos ramas: develop y master)

Phonegap app developer

  1. Se instala en el emulador o dispositivo la aplicación.
  2. Se abre la aplicación “PhoneGap” en el dispositivo o emulador.
  3. Se configura poniendo la URL que aparece al arrancar el servidor phonegap en la máquina de desarrollo:
    • phonegap serve

Día 8: Jueves 27/11/2014

Enlaces

CSS

Siguiendo la lección sobre CSS del curso de HTML5 de Javier.

Unidades de medida

em es una de las más interesantes. Procede del mundo de la tipografía y es la anchura de la M mayúscula en un tamaño que se considera legible con facilidad por un usuario normal. Las em se basan en el tamaño de letra del elemento y son relativas entre elementos que se contienen, es decir, si un elemento tiene 2em pero está afectado por un contenedor en el que se aplica 2em, el tamaño resultante será equivalente a 6em. Para evitar la acumulación está la unidad rem (=root em).

Preferencias

Hay un orden de prioridad entre los selectores de CSS. Algunos detalles:

Position

top y left no aplican si position es static. El position: absolute toma el origen de coordenadas del primer contenedor que no es static.

Display

El display de un div por defecto (useragent stylesheet rules) es block. El de span es inline. Se puede modificar tanto un caso como el otro. Por ejemplo si ponemos display: inline a un div, pasa a comportarse como un span. El display: inline hace que se ignore el width. Con display: inline-block sí que se tiene en cuenta el width. Hay un truco para conservar la indentación en el código HTML mientras se evita que se introduzca un carácter espacio que ocupa sitio y descoloca la maquetación y es introducir un comentario entre líneas de esta forma:

   <section></section><!--
--><section></section>

Float

La propiedad clear sólo funciona si el display no es inline. Se usa mucho el apaño del clearfix para solucionar la pérdida de dimensión vertical de un contenedor cuando todo su contenido más alto flota. El selector :after selecciona después del contenido al que se aplica el estilo, no el contenido del siguiente elemento en el DOM. El clearfix favorito de Javier es:

.clearfix:after {
    content: ".";
    visibility: hidden;
    display: block;
    height: 0;
    clear: both;
}

viewport

Diferencia entre layout-viewport y display-viewport. La relación entre ambos es el nivel de zoom. El layout-viewport predeterminado es 960px. Se definió pixel-ratio cuando apareció iPhone 4, inicialmente con un valor de 2 (pantalla retina de 640px de ancho por los 320px del iPhone original). Terminales con pantallas QuadHD tienen un pixel-ratio de alrededor de 4. Tiene el inconveniente de que hace que las imágenes se redimensionen, lo que en terminales con mucha densidad de pixel produce desenfoque. La solución es enviar la imagen adecuada a cada dispositivo. De momento se hace con condiciones en CSS.

mediaqueries

Permite hacer condiciones en CSS. @media es un if. En las mediaqueries los em son siempre rem es decir son absolutos, no relativos. Más que hablar de resoluciones debemos pensar en tamaños. Se suelen considerar cuatro tamaños:

Tipografía

La tipografía cada vez tiene más importancia. Para pantalla, Javier no recomienda Helvetica. Existen muchos tipos buenos, muy legibles y gratuitos, como:

Javier recomienda no descartar las fuentes de pago. La inversión puede tener un impacto muy importante en el proyecto. Algunas foundries:

Nos describe el uso de Google Fonts usando el sitio para definir un paquete con dos fuentes.

Frameworks

Algunos framewoks:

Día 9: Viernes 28/11/2014

Enlaces

Bootstrap

En Customize se puede compilar una versión personalizada (sólo con los componentes que vayamos a utilizar, lo que además hace más pequeña la librería). Dentro de este Customize se pueden cambiar por ejemplo los Media queries breakpoints que son los que hacen que el diseño cambie entre los distintos tamaños de pantalla.
Es recomendable instalarlo con Bower si se va a integrar en Phonegap (para tenerlo en local y minimizar la latencia que supondría el descargarlo).
El menú superior del sitio de Bootstrap está bien estructurado en cuanto a la dificultad de menor a mayor de izquierda a derecha. Conviene leer por lo menos la sección Getting Started.
Bootstrap utiliza CDN para minimizar la latencia.
Si vamos a utilizar Angular, en lugar de poner bootstrap normal hay que integrar UI Bootstrap que no utiliza jQuery.
Es importante meter en la cabecera lo siguiente (aparece en Basic Template):

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">

Importante leer todo el documento sobre CSS, sobre todo lo relativo al Grid System y a los Forms.
Los componentes que no encontremos en Bootstrap buscarlos en Bootsnipp.
Si utilizamos la parte de JS de Bootstrap hay que integrar jQuery.

Buenas prácticas con Bootstrap

Interesa poner siempre un label en los campos de formulario por accesibilidad, pero en pantallas pequeñas se suele usar un placeholder, lo que es redundante. Para evitarlo se oculta añadiendo la clase sr-only (sr de Screen Reader), lo que mantiene el label en el código pero no lo muestra.

JavaScript

Almacenamiento de un conjunto de variables en localStorage:

var l1 = ["Alice", "Bob", "Charly"];
var l2 = [1, 2, 3]
var listas = { listaPrincipal: l1, listaSecundaria: l2};
var listasComoCadena = JSON.stringify(listas);
localStorage.setItem('listas', listasComoCadena);

Deberes para el fin de semana

Día 10: Lunes 1/12/2014

Enlaces

Proyecto

Vamos a montar el proyecto poniéndolo todo junto desde cero:

  1. Creamos el proyecto dentro del directorio \curso:
    • cd \curso
    • phonegap create 10Votaciones --name Votaciones --id es.eduardofilo.yesno
  2. Nos metemos en el directorio y lanzamos Yeoman para instalar la plantilla web:
    • cd 10Votaciones
    • yo
  3. Durante la configuración de Yeoman indicamos que incluya Bootstrap.
  4. Configuramos en Gruntile.js (línea 21 del fichero que la carpeta de la aplicación es www en lugar de app que es la que viene por defecto.
  5. Movemos carpeta 10Votaciones\www\res a 10Votaciones\res, es decir un nivel hacia arriba. Esta carpeta contiene los recursos que utilizará Phonegap (por ejemplo el icono de escritorio que ajustará en las distintas plataformas). No tiene mucho sentido que esté dentro de www ya que Phonegap duplicará hacia el directorio dist los recursos correspondientes a la plataforma con la que estemos trabajando. Si está en www es porque cuando utilizamos el servidor de compilación de Adobe para montar las aplicaciones nativas, sólo pasamos el directorio www y el que contenga res es una forma de enviar todo de una vez.
  6. Configuramos en config.xml el movimiento del directorio res que acabamos de hacer. Para ello sustituimos las rutas de los recursos, es decir www/res por res.
  7. Borramos el contenido de www y lo sustituimos por el de app.
  8. Borramos la carpeta app que ha quedado vacía.
  9. Movemos la carpeta bower_components al interior de www.
  10. Cambiamos la configuración de Bower en el fichero .bowerrc poniendo www/bower_components donde sólo ponía bower_components.
  11. Finalmente lanzamos la tarea que inyectará las dependencias entre los ficheros de código:
    • grunt wiredep

Con esto ya tendríamos la base.

Vamos a utilizar el patrón MVC. Pondremos tanto el modelo como el controlador en el mismo fichero main.js, aunque sería más correcto separarlo.

Vamos a hacer una Single Page Application. Cada página “virtual” de la app lo vamos a codificar mediante un div de clase pagina. Ese div tendrá un atributo personalizado “data-title” que contendrá el título de la página. El controlador sólo va a hacer:

Todo lo que no tenga que ver con esto irá al modelo/servicio.

Día 11: Martes 2/12/2014

Enlaces

Proyecto

Vamos a programar el envío del voto al servidor (mockable) en un POST. El voto va en la URL por lo que aunque utilicemos https no ocultamos la información. Sería más correcto enviar el voto en el cuerpo del POST, por ejemplo:

var url = this.URL_POST_NUEVO_VOTO.replace('{value}', tipoDeVoto);
var promesa = $.post(url, {value: yes});

En segundo lugar programamos la gestión de la navegación, es decir cómo cambia la aplicación de pantalla, ya sea hacia delante o hacia atrás (incluyendo por ejemplo el evento “Back” del dispositivo que equivale al botón “Atrás” del navegador). Javier nos comenta el truco de añadir anchors (#) a la URL. Con esto se consigue que no se recargue el documento y que sí afecte al historial. Como nuestra app es SPA nos viene muy bien este truco. En Phonegap, la tecla “Atrás” de los dispositivos equivale al Back del WebView. La gestión del historial la vamos a hacer con history.js.

  1. Buscamos/instalamos history.js con Bower (--save actualiza el fichero bower.json con lo que hacemos que la inclusión de este componente sea persistente, es decir, si luego borramos los componentes, para no subirlos al control de versiones por ejemplo, lo podremos recuperar fácilmente):
    • bower search history
    • bower install --save history.js
  2. Inyectamos dependencias (grunt wiredep no funciona => hay que mirar en la documentación). Elegimos la librería que nos conviene consultando la documentación. En index.html incluimos la referencia en el bloque scripts/main.js del build.js final:
    • <script src="bower_components/history.js/scripts/bundled/html4+html5/native.history.js"></script>

Con history.js manejamos el historial del navegador/webview por medio del objeto History. El objeto history sería la parte nativa del navegador. En muchas ocasiones se comportan igual.

Los parámetros de la función pushState tanto de history como de History son:

history.pushState({
    id: idDestino
}, $('#' + idDestion).attr('data-title'), idDestion);
  1. Objeto arbitrario en el que podemos almacenar datos
  2. Descripción o Título de esa entrada en el historial
  3. Dirección/URL

Día 12: Miércoles 3/12/2014

Enlaces

Proyecto

document recibe todos los eventos. Implementamos un cartel “Cargando…” que aparece/desaparece cuando hay peticiones AJAX mientras se espera la respuesta del servidor. Se gestiona mediante los eventos ajaxStart y ajaxStop.

Instalamos jQuery-Color para hacer un efecto de transición al jumbotron:

  1. Buscamos/instalamos:
    • bower search jquery-color
    • bower install --save jquery-color
  2. Inyectamos la librería a mano al final del index.html ya que Bower no puede:
    • <script src="bower_components/jquery-color/jquery.color.js"></script>

Hasta ahora había algo mal en el proyecto. No esperábamos a que cargara el DOM ni jQuery. Para conseguirlo envolvemos la función de inicialización de la siguiente forma:

$(document).ready(function () {
    controlador.inicializar();
});

Vemos una forma de desactivar el highlight en el tap que hace el sistema operativo en WindowsPhone. Se consigue añadiendo la siguiente etiqueta meta en el head del HTML:

<meta name="msapplication-tap-highlight" content="no"/>

Vamos a programar un gesto para volver a la pantalla de votaciones desde resultados.
Instalamos TouchSwipe-Jquery-Plugin:

  1. Buscamos/instalamos:
    • bower search touchswipe
    • bower install --save jquery-touchswipe

Sustituimos el servicio votar para que guarde en local. Hay que generar promesa. Se puede hacer con la librería Q o con jQuery. Lo hacemos así:

var servicioPreguntas = {
    //...

    votar: function (tipoDeVoto) {
        localStorage.setItem('voto', tipoDeVoto);
        var deferred = $.Deferred();
        var promesa = deferred.promise();
        deferred.resolve({
            total: 0,
            positives: 0,
            negatives: 0
        });
        return promesa;
    }
};

Día 13: Jueves 4/12/2014

Enlaces

Proyecto Plugins

Vamos a hacer una pequeña aplicación para consultar la geolocalización:

  1. Creamos el proyecto:
    1. cd \curso
    2. phonegap create 13GeoDemo --name GeoDemo --id es.eduardofilo.geodemo
  2. Añadimos el plugin de geolocalización:
    1. cd 13GeoDemo
    2. phonegap plugin add org.apache.cordova.geolocation

Buscar el código nativo para Android en git.

Javier no recomienda getCurrentPosition porque da timeout rápidamente si estás en HighAccuracy. Mejor con watchPosition.

Vamos a ver cómo se crearía un plugin. Para ello instalamos un plugin hecho por Javier y estudiamos detenidamente su sencillo código:

  1. Creamos el proyecto:
    1. cd \curso
    2. phonegap create 13.1DatosTelefono --name DatosTelefono --id es.eduardofilo.datostfn
  2. Añadimos el plugin de Javier:
    1. cd 13.1DatosTelefono
    2. phonegap plugin add https://github.com/ciberado/domina-phonegap-infotelefonoplugin.git

Día 14: Viernes 5/12/2014

Enlaces

Proyecto Plugins

Continuamos con el proyecto de ayer:

  1. Comprobamos la instalación:
    1. phonegap plugin list

Proyecto Acelerómetro

Vamos a hacer un pequeño proyecto que controlará un pequeño cuadrado por pantalla con los acelerómetros del teléfono. En el proyecto haremos uso del plugin oficial de Phonegap que se encarga de acceder al dispositivo.

  1. Creamos el proyecto:
    1. phonegap create 14DemoAcelerometro --name DemoAcelerometro --id es.eduardofilo.demoaccel
  2. Instalamos plugin:
    1. phonegap plugin add org.apache.cordova.device-motion

Por defecto watchAcceleration llama al callback cada 10 segundos:

var watchID = navigator.accelerometer.watchAcceleration(
      accelerometerSuccess,
      accelerometerError,
      accelerometerOptions);

Para conseguir que la aplicación sea más “responsiva”, se puede cambiar el periodo de consulta pasando un objeto con un parámetro de clave frecuency en su interior (aunque la documentación menciona period, es frecuency). Hubiera funcionado cada segundo por ejemplo llamando al watchAcceleration así:

var options = { frequency: 1000 };
var watchID = navigator.accelerometer.watchAcceleration(onSuccess, onError, options);

Subir a Google Play

Para generar un certificado con que firmar las aplicaciones:

$ keytool -genkey -alias demoiconos -keyalg RSA -validity 20000 -keystore demoiconos.keystore

Compilar en Adobe PhoneGap Build

Para delegar la construcción del proyecto en el servicio de compilación de Adobe en la nube:

$ phonegap remote build android

Se puede firmar la aplicación desde Adobe subiendo el fichero keystore que hemos creado antes.

Inicialización del controlador

Si usamos jQuery:

$(document).ready(function () {
    document.addEventListener('deviceready', function () {
        controlador.inicializar();
    });
});

Si no usamos jQuery:

document.addEventListener('deviceready', function () {
    controlador.inicializar();
});

Pendiente de preguntar