viernes, 30 de mayo de 2014

Plugin geolocalización

Este plugin de Cordova/Phonegap nos provee información útil sobre la localización física del dispositivo, por ejemplo, la latitud (1) o la longitud (2).
fuente: wikipedia.org

Este plugin funciona nutriéndose de información que le es suministrada tanto por el GPS del dispositivo, como por señales de red(Wifi, bluetooth, RFID, direcciónes IP y las IDs de GSM/CDMA.

Debemos saber que con el uso de este plugin podrían surgir problemas de privacidad, ya que el almacenamiento de esta información sensible de forma periódica podría revelar costumbres y hábitos de los usuarios. Por lo tanto, en la política de privacidad de nuestras aplicaciones, debemos dejar claro y explicar qué uso damos a los datos obtenidos por nuestras apps.

Soporte

Las plataformas que soportan este plugin son numerosas, y entre ellas encontramos las más importantes:

  • Amazon Fire OS
  • Android
  • BlackBerry 10
  • Firefox OS
  • iOS
  • Tizen
  • Windows Phone 7 and 8
  • Windows 8


Instalación

Para poder instalar en nuestro proyecto el plugin geolocation, debemos situarnos en la carpeta de la aplicación y teclear por consola:
phonegap plugin add org.apache.cordova.geolocation

Componentes

Al trabajar con este plugin, contamos con tres métodos para acceder a la información que nos interesa:

  • navigator.geolocation.getCurrentPosition
Uso: navigator.geolocation.getCurrentPosition(geolocationSuccess, [geolocationError], [geolocationOptions]);

Devuelve la posición actual a la función geolocationSuccess por un objeto Position pasado como parámetro. Opcionalmente podemos incluir dos parámetros más. Uno es la función callback que será llamada si sucede algún error(con un objeto PositionError como parámetro, en este caso), y un objeto que representa las opciones. Veremos qué opciones podemos pasarle más adelante.


  • navigator.geolocation.watchPosition
Uso: var watchId = navigator.geolocation.watchPosition(geolocationSuccess, [geolocationError], [geolocationOptions]);

Devuelve la posición del dispositivo cuando hay un cambio en ésta, como sucedía con el acelerómetro, usamos una variable para referenciar el intervalo. Puede incluirse una función callback de error por si algo no va bien, y el objeto con las opciones que hemos llamado geolocationOptions.


  • navigator.geolocation.clearWatch
Su uso es el siguiente: navigator.geolocation.clearWatch(watchID);
Detiene la espera de cambios en la posición del dispositivo, es decir, su refresco de datos que referencia el parámetro watchID, en este caso.


El objeto geolocationOptions

Podemos incluir este objeto en nuestras llamadas a los métodos del plugin, que contiene las opciones con las que se hará la llamada. Un ejemplo válido podría ser: { maximumAge: 3000, timeout: 5000, enableHighAccuracy: true };

¿Qué opciones podemos incluir?
  • enableHighAccuracy: valor booleano con el que establecemos una mayor precisión en la obtención de datos. En el ejemplo anterior estaríamos solicitando, además de las redes inalámbricas, el gps como fuente de datos.
  • maximumAge: Valor numérico que expresa el mayor tiempo en milisegundos que tendrá un valor cacheado, obtenido tiempo atrás, para que lo tenga en cuenta la aplicación. En el ejemplo anterior desechamos todo muestreo de datos con una antigüedad mayor a tres segundos.
  • timeout: Si en el tiempo especificado, expresado en milisegundos, no se ha llamado a la función geolocationSuccess de los métodos getCurrentPosition o watchPosition, pasará a llamarse a la función geolocationError(o como hubiésemos llamado a las funciones en nuestro programa).

El objeto Position

Es un objeto de sólo lectura que tiene como propiedades coords y timestamp. La propiedad coords devuelve a su vez un objeto, el objeto Coordinates(coordenadas). El objeto Coordinates tiene varias propiedades que son la información definitiva que normalmente vamos a solicitar con el uso de la geolocalización, tales como la latitud, o la altura.

Estas son las propiedades de Coordinates(todas son de tipo numérico):
  • latitude: Latitud en grados decimales.
  • longitude: Igual, pero con la longitud.
  • altitude: Expresa la altura.
  • accuracy: Nivel que expresa los metros de exactitud con los que se dan los valores de latitud y longitud.
  • altitudeAccuracy: Lo mismo pero con la altura.
  • heading: Dirección del viaje, expresado en grados en sentido de las agujas del reloj y tomando como 0 el norte real.
  • speed: Velocidad actual del dispositivo, expresada en metros por segundo.

El objeto PositionError

Disponemos del objeto PositionError para especificar las causas que provocaron un error al acceder a la geolocalización del terminal. cuenta con dos propiedades:
  • message: un mensaje en el que se describen los detalles del error encontrado.
  • code: puede ser uno de los códigos de error listados a continuación.
Constantes de PositionError
  • PositionError.PERMISSION_DENIED: es devuelto cuando el usuario no ha dado permiso para recolectar datos de posicionamiento.
  • PositionError.POSITION_UNAVAILABLE: es devuelto cuando el dispositivo no puede encontrar la posición. Suele ocurrir cuando no estamos conectados a una red, o no se encuentra el satélite.
  • PositionError.TIMEOUT: devuelto cuando el dispositivo no puede encontrar su posición en el tiempo que especificamos en las opciones de la llamada a los métodos anteriormente descritos que sirven para obtener la posición(getCurrentPosition o watchPosition).

Ejemplo práctico

Una vez vista la teoría, pasemos a ver cómo se usa este plugin a nivel práctico. Para ello, he preparado un ejemplo sencillo partiendo del esqueleto preparado en apartados anteriores.

Personalizamos un poco el config.xml...

Vamos a mostrar en pantalla de forma constante datos de posicionamiento, o el error que se produjera en caso de no poder acceder a éstos.

El html de la aplicación quedaría así(más bien la etiqueta body):
<body>
        <div class="container">
            <h1 class="text-center">Geolocalización</h1>
            <div id="longitud"></div>
            <div id="latitud"></div>
            <div id="mensaje"></div>
            <div id="error"></div>
        </div>
        
        <script type="text/javascript" charset="utf-8" src="cordova.js"></script>
        <script type="text/javascript" src="./js/jquery-1.11.0.min.js"></script>
        <script type="text/javascript" src="./js/bootstrap.min.js"></script>
        <script type="text/javascript" src="./js/fastclick.js"></script>
        <script type="text/javascript" src="./js/index.js"></script>
        <script type="text/javascript">
            window.addEventListener('load', function() {
                new FastClick(document.body);
            }, false);
            app.initialize();
        </script>
    </body>

No tiene mucho misterio, dejamos 4 divs listos para que se rellenen mediante javascript. El fichero index.js es el que se adjunta a continuación:
var app = {
    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicity call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        // función callback onSuccess
// este método recibe el objeto Position, que contiene los datos que nos interesan
function onSuccess(position) {
            longitud.html("Longitud: " + position.coords.longitude);
            latitud.html("Latitud: " + position.coords.latitude);
            divError.html("No hay errores");
            //damos información de latitud en palabras
            if(position.coords.latitude >= 0){
                mensaje.html("Latitud norte");
            }else{
                mensaje.html("Latitud sur");
            }
}

//La función callback onError recibe el objeto PositionError
function onError(error) {
            longitud.html("ERROR");
            latitud.html("ERROR");
            divError.html(error.code);
            mensaje.html("Mensaje: " + error.message);
}

        //guardamos los elementos DOM en variables
        var longitud = $("#longitud");
        var latitud = $("#latitud");
        var mensaje = $("#mensaje");
        var divError = $("#error");
        //referenciamos watchID para almacenar los datos cada 3 segundos
        //Si cada 3 segundos no recibimos una actualización de la posición, se lanzará un error
var watchID = navigator.geolocation.watchPosition(onSuccess, onError, { timeout: 3000, enableHighAccuracy: true });
    }
};

Despues de obtener el evento deviceready, procedemos con el funcionamiento de la aplicación. Rellenamos los div del html con los datos que nos proporciona el objeto Position, o PositionError, según se llame a onSuccess o a onError, en la penúltima línea del código. Podemos fijarnos que hemos incluído la opción timeout con valor de 3000. Si pasan tres segundos sin datos, por ejemplo por tener desactivado el gps y las redes, el error, que debe informarnos sobre la imposibilidad de acceder al sistema de posicionamiento, debe cambiar al error que lanza la opción timeout.

Todo este código está muy bonito, pero no serviría de nada si no instalamos el plugin antes de compilar.

Finalmente adjunto imágenes de la app ejecutándose en un terminal Android:

Sin acceso a ubicación:
 Error de código 2 (recordemos, POSITION_UNAVAILABLE):


Pasan 3 segundos sin datos, así que el error cambia a código 3 (TIMEOUT):


Activamos la localización:

Ahora todo va como esperábamos:

martes, 27 de mayo de 2014

Plugin para el acelerómetro

El acelerómetro es un elemento que detecta cambios en movimientos relativos en la orientación del dispositivo, en los ejes x, y, z. En otras palabras, mide la aceleración bajo el efecto de la gravedad. En este apartado vamos a conocer el plugin que nos permite acceder al acelerómetro del dispositivo.

fuente: http://w3c.github.io/deviceorientation/start.png


Este plugin se llama device-motion es compatible con las principales plataformas actuales, por lo que no habrá problemas de compatibilidad si lo implementamos en nuestras aplicaciones. Es soportado para:

  • Amazon Fire OS
  • Android
  • BlackBerry 10
  • Firefox OS
  • iOS
  • Tizen
  • Windows Phone 7 and 8
  • Windows 8
Métodos

Disponemos de tres métodos principales:

  • navigator.accelerometer.getCurrentAcceleration(accelerometerSuccess, accelerometerError);
Este método obtiene la aceleración en los ejes x y z, y los valores son enviados a la función callback accelerometerSuccess.
  • navigator.accelerometer.watchAcceleration
Con este método obtenemos la aceleración en un intervalo regular de tiempo. Veamos un ejemplo de uso:

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

En watchID guardamos una referencia al muestreo por intervalos de watchAccerelarion. Si todo va bien lanzamos la función accelerometerSuccess(se le pasa el objeto Acceleration como parámetro, explicado a continuación), en caso contrario, la función accelerometerError. AccelerometerOptions es un objeto en el que podremos incluir el siguiente parámetro, a modo de opción: frequency. Este valor se lo podemos pasar expresado en milisegundos(por defecto vale 10000) e identifica el intervalo de muestreo.

  • navigator.accelerometer.clearWatch
Detiene el muestreo de la aceleración que estaba referenciado por watchID. Su uso sería:
navigator.accelerometer.clearWatch(watchID);

Objetos

Tenemos el objeto Acceleration, que es pasado como parámetro en la función accelerometerSuccess, o onSuccess, o como queramos llamarla. Contiene los datos capturados por el acelerómetro en un momento determinado de tiempo. 
El objeto Acceleration tiene tres propiedades, x, y y z, que expresan los valores numéricos de la aceleración en cada eje (en m/s^2). Y también timestamp, que es el tiempo del instante de captura expresado en milisegundos.
Incluye el efecto de la gravedad(9.81 m/s^2), por lo que si el dispositivo yace boca arriba en una superficie horizontal los valores x,y,z serían 0,0, 9.81.

Ejemplo

Partimos del proyecto esqueleto que ya teníamos preparado en apartados anteriores. Lo copiamos y modificamos algunos valores en el fichero de configuración config.xml, básicamente para que no se vea "esqueleto" en el icono de la app en el móvil, cuando vayamos a ejecutarlo.


A continuación ejecutamos la orden que nos instala el plugin en nuestra carpeta de proyecto:
Phonegap plugin add org.apache.cordova.device-motion

Y ya tenemos el acelerómetro accesible desde nuestra aplicación. 

Vamos a implementar una aplicación simple que nos informa cada segundo (1000 milisegundos) de la aceleración en los tres ejes.

El contenido del body del html es el siguiente:
<div class="container">
            <h1 class="text-center">Acelerometro</h1>
            <div class="well">
                <div id="ejeX">
                    
                </div>
                <div id="ejeY">
                    
                </div>
                <div id="ejeZ">
                    
                </div>
            </div>
</div>
<script type="text/javascript" charset="utf-8" src="cordova.js"></script>
        <script type="text/javascript" src="./js/jquery-1.11.0.min.js"></script>
        <script type="text/javascript" src="./js/bootstrap.min.js"></script>
        <script type="text/javascript" src="./js/fastclick.js"></script>
        <script type="text/javascript" src="./js/index.js"></script>
        <script type="text/javascript">
            window.addEventListener('load', function() {
                new FastClick(document.body);
            }, false);
            app.initialize();
</script>

He ignorado las líneas de los scripts. Como vemos se muestran 3 divs donde informaremos con javascript de las aceleraciones.

Este es el contenido del fichero javascript:
var app = {
    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicity call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        //iniciamos el muestreo
        //si hay exito, lanzamos la funcion mostrar
        //si hay error, lanzamos la funcion error
        //le pasamos el tiempo de muestreo en un objeto, como tercer parametro
        watchID = navigator.accelerometer.watchAcceleration(mostrar, error, {frequency: 1000});

        //función con la que muestro las aceleraciones
        //accedo al objeto Acceleration, concretamente a sus propiedades
        function mostrar(acceleration){
            $("#ejeX").html("Acceleration X: " + acceleration.x);
            $("#ejeY").html("Acceleration y: " + acceleration.y);
            $("#ejeZ").html("Acceleration z: " + acceleration.z);
        }

        //funcion error que dios quiera que no se ejecute
        function error() {
            alert('error!!');
        };
    }
};

Así luce la aplicación una vez compilada y ejecutada en nuestro terminal

Ejemplo 2

Lo correcto más bien sería decir "mejora del ejercicio anterior", ya que tomamos como base el ejemplo ya realizado y jugamos un poco con los datos que nos da el acelerómetro.
Vamos a implementar un objeto dentro de una caja que "se deje caer" según como tengamos orientado el teléfono(o tablet, o dispositivo compatible, en definitiva).
Añadimos los siguientes div debajo de los tres que ya teníamos:

<div id="caja">
                    <div id="punto">
                        
                    </div>
</div>

Con CSS vamos a hacer que se muestren como una caja y un punto(puntazo, para ser exactos).

#punto{
    width:50px;
    height:50px;
    background-color:black;
    position:absolute;
    top:0;
    left:0;
}

#caja{
    margin:10px auto;
    width:200px;
    height:200px;
    border:1px solid black;
    position:relative;

}



Ahora vamos a capturar esos div con javascript y a aplicar la función animate de jquery:

var app = {
    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicity call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        //iniciamos el muestreo
        //si hay exito, lanzamos la funcion mostrar
        //si hay error, lanzamos la funcion error
        //le pasamos el tiempo de muestreo en un objeto, como tercer parametro
        watchID = navigator.accelerometer.watchAcceleration(mostrar, error, {frequency: 200});

        //función con la que muestro las aceleraciones
        //accedo al objeto Acceleration, concretamente a sus propiedades
        function mostrar(acceleration){
            $("#ejeX").html("Acceleration X: " + acceleration.x);
            $("#ejeY").html("Acceleration y: " + acceleration.y);
            $("#ejeZ").html("Acceleration z: " + acceleration.z);

            //cargo en variables la caja, el punto móvil y los datos que me interesan
            var puntoMovil = $('#punto');
            var limite = $('#caja');
            var posicionPunto = puntoMovil.position();
            var bordeIzquierdo = 0;
            var bordeTop = 0;
            var bordeDerecho = limite.width() - puntoMovil.width() - 10; // 10 represents the 10px for the margin
            var bordeBottom = limite.height() - puntoMovil.height() - 10; // 10 represents the 10px for the margin
            
            //según los datos del acelerómetro muevo el punto a la derecha o izquierda, arriba o abajo
            //con CSS
            if( acceleration.x < -2 && posicionPunto.left <= bordeDerecho ) {
                puntoMovil.animate({
                    left:'+=10'
                },100);
            } else if( acceleration.x > 2 && posicionPunto.left > bordeIzquierdo ) {
                puntoMovil.animate({
                    left:'-=10'
                },100);
            }
            if( acceleration.y < -2 && posicionPunto.top > bordeTop ) {
                puntoMovil.animate({
                    top:'-=10'
                },100);
            } else if(acceleration.y > 2 && posicionPunto.top <= bordeBottom ) {
                puntoMovil.animate({
                    top:'+=10'
                },100);
            }
        }

        //funcion error que dios quiera que no se ejecute
        function error() {
            alert('error!!');
        };
    }

};

He cambiado la opción frequency a 200 milisegundos para que se vea más fluido el movimiento del cuadrado en la caja. También podemos observar que para que el cuadrado negro se mueva, los valores de los ejer x e y tienen que ser mayores que 2 o menores que -2, por lo que si dejamos el dispositivo "más o menos" plano, impediremos que se mueva.

Así se ve la aplicación después de compilar, ejecutar en un teléfono y tenerlo ligeramente inclinado hacia la derecha y hacia arriba:


Plugin para vibración (y ejemplo)

Vamos a enriquecer la aplicación de las vistas "principal" y "opciones" añadiendo la capacidad de vibrar el dispositivo en el que se ejecute. Vamos a hacer que el dispositivo vibre durante un periodo de tiempo determinado al pulsar los botones que cambian el color del texto en la pantalla de opciones.

En la página de GitHub podemos obtener información sobre este plugin, que nos proporciona la forma de hacer vibrar el dispositivo con Phonegap y Cordova. Antes de instalarlo, comprobamos que es compatible con las principales plataformas, por lo que es ventajoso su uso si pretendemos hacer la app compatible con varios sistemas móviles. Está soportado en:

  • Amazon Fire OS
  • Android
  • BlackBerry 10
  • Firefox OS
  • iOS
  • Windows Phone 7 and 8


Para instalarlo debemos ejecutar, en línea de comandos, y situados en la carpeta del proyecto, la siguiente orden:

phonegap plugin add org.apache.cordova.vibration



Con esto, nuestra aplicación ya es capaz de, con la programación adecuada, hacer vibrar nuestro dispositivo. Para hacerlo hay que ejecutar la siguiente línea de código, cambiando la variable t por un tiempo expresado en milisegundos:
navigator.notification.vibrate(t)

En iOS, este tiempo es ignorado y el dispositivo vibra durante un tiempo preestablecido.

Lo único que nos queda es llevar la orden de vibración al punto concreto de nuestra app en el que queremos que lo haga. Vamos al fichero index.js y añadimos lo siguiente a los eventos click de los botones "botonRojo" y "botonAzul":

...final del index.js...
    botonRojo.click(function(){
        $('p').addClass("fondoRojo");
        $('p').removeClass("fondoAzul");
        //el dispositivo vibrará durante medio segundo
        navigator.notification.vibrate(500);
    });
    botonAzul.click(function(){
        $('p').removeClass("fondoRojo");
        $('p').addClass("fondoAzul");
        navigator.notification.vibrate(200);
    });
}

He puesto tiempos de vibración diferentes para que se note la diferencia entre pulsar uno u otro botón.

Con esto ya sólo nos queda compilar, en local o en la nube, y probar la aplicación. 
Yo he optado por hacerlo en local desde consola y copiarme el .apk cuyo nombre termina en "unaligned" localizado en platforms\android\ant-build.



Plugins

Por defecto, al crear un proyecto Phonegap mediante línea de comandos en nuestro sistema, éste es muy básico. Se crea sólo la estructura de documentos, además de que usa la API justa y necesaria para funcionar. Para acceder a elementos hardware de los dispositivos móviles tales como cámara o vibración desde nuestra aplicación, necesitamos ampliar la API en nuestro proyecto. En Phonegap y Cordova, lo hacemos incluyendo plugins, que aumentan las capacidades de nuestra aplicación, pudiendo acceder a elementos a los que antes no se podía.

He aquí una lista con los plugins de Phonegap:

  • Battery Status: Sirve para monitorizar el estado de la batería.
  • Camera: Plugin que nos permite lanzar la cámara del dispositivo.
  • Contacts: Con este plugin podemos tener acceso a la agenda del teléfono.
  • Device: Obtenemos información básica del dispositivo.
  • Device Motion (Acelerómetro): Nos da acceso al acelerómetro del dispositivo.
  • Device Orientation (Compass): Podemos obtener la dirección a la que apunta el dispositivo.
  • Dialogs: Notificaciones.
  • FileSystem: Accedemos al sistema de ficheros mediante javascript.
  • File Transfer: También tendremos acceso al sistema de ficheros del dispositivo.
  • Geolocation: Hace que nuestras aplicaciones puedan localizar físicamente al usuario.
  • Globalization: Permite determinadas representaciones según las preferencias locales del usuario.
  • InAppBrowser: Lanza URLs a través de otra instancia de navegador en la app.
  • Media: Grabación y reproducción de audio.
  • Media Capture: Captura ficheros multimedia.
  • Network Information (Conexión): Nos da información sobre el estado de conexión del dispositivo.
  • Splashscreen:  Manipulamos las llamadas splash screen de las aplicaciones.
  • Vibration: Con esta API podemos hacer vibrar el dispositivo.


Si queremos añadir plugins para acceder a los elementos de nuestro dispositivo, podemos hacerlo mediante línea de comandos. Los comandos para añadir los plugins podemos consultarlos aquí. De este modo podemos añadir a nuestro código fuente las líneas necesarias para usar esos plugins sin temor de que haya fallos al compilar. Por ejemplo, si tecleamos el siguiente código:
phonegap local plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-battery-status.git
estaríamos añadiendo las utilidades necesarias para acceder a los datos de la batería del dispositivo tales como el nivel de carga.
Podemos saber qué plugins tenemos instalados en el proyecto haciendo uso del siguiente comando:
phonegap local plugin list

jueves, 22 de mayo de 2014

Eventos Phonegap


Con javascript podemos capturar eventos en nuestras aplicaciones web, de forma que, por ejemplo, al hacer "click" en algún elemento nuestra aplicación lo detecte y reaccione de la forma que nos interese.

Con Phonegap podemos hacer uso de algunos eventos predeterminados pensados para tener lugar en aplicaciones móviles. Por ejemplo, en lugar de tener listener de eventos tales como hover con el ratón(entrar con el mouse en algún elemento), nos puede interesar que la aplicación reaccione al pulsar teclas de los terminales móviles, como las teclas de volumen, o las teclas de retroceso u opciones, o simplemente cuando nos quede poca batería mientras usamos la aplicación. Estos son los eventos comunes:
  • deviceready (este es el más importante, se lanza cuando Phonegap/Cordova se haya cargado por completo)
  • pause
  • resume
  • backbutton
  • menubutton
  • searchbutton
  • startcallbutton
  • endcallbutton
  • volumedownbutton
  • volumeupbutton
Los siguientes,
  • batterycritical
  • batterylow
  • batterystatus
han sido añadidos por org.apache.cordova.battery-status, y los eventos online y offline han sido creados por org.apache.cordova.network-information.

Compatibilidad

No todos los eventos son compatibles con todas las plataformas. Para verlos todos y comprobar compatibilidades podemos mirar este enlace. Es importante comprobarlo porque en mi caso, por ejemplo, intenté implementar el evento volumeupbutton en Android y al no reaccionar, pude comprobar tiempo después(tiempo de desesperación y frustración informática) que sólo es compatible con BlackBerry.

Este punto puede ser crucial a la hora de decantarnos por la programación con Phonegap/Cordova o programación nativa para cada sistema, para nuestras aplicaciones móviles. Como casi siempre, depende del uso de la aplicación, y la importancia que le demos a las funciones que pueden no estar soportadas.

Por ejemplo, puede no ser muy importante el uso de la tecla de volumen, por lo que podríamos añadir esa funcionalidad como algo extra en nuestra aplicación. En BlackBerry, que soporta este evento, éste sería capturado, y la aplicación reaccionaría de la forma que programemos. Por contra en Android, que no soporta este evento, al pulsar las teclas de volumen subiríamos o bajaríamos el volumen del terminal, siendo ignorado el evento que pretendemos capturar. En este caso podríamos considerar las otras opciones positivas que nos proporciona Phonegap y usarlo para hacer nuestro proyecto, dada la baja importancia que hemos decidido darle al hecho de pulsar las teclas de volumen. Sin embargo, si el uso de las teclas de volumen es muy importante, la cosa cambia. Imaginemos que nuestro proyecto es un videojuego, y que las teclas de volumen son cruciales para el manejo del mismo. En este caso es más conveniente recurrir a la programación nativa de cada sistema operativo, puesto que con Phonegap este problema de compatibilidad no nos permitiría acceder a los eventos que queremos en todos los dispositivos.

Ejercicio

A continuación implementaré un ejercicio en el que nuestra aplicación reacciona ante algunos de los eventos anteriores.

Para el siguiente ejemplo voy a tomar como base la aplicación llamada intenciones del capítulo anterior. En ella, recordemos, se puede cambiar de la vista principal a otra de opciones pulsando botones dibujados en pantalla. Esto es lo más común que vamos a tener en cualquier aplicación, pero deseamos que desde la vista principal se pueda acceder también a la de opciones pulsando la tecla de opciones de Android, y que desde la pantalla de opciones, al pulsar la tecla de retroceso(la flecha pequeña) volvamos a la principal.

Partimos, por tanto, del código fuente que ya teníamos, así que copiamos el ejemplo anterior, y si lo deseamos modificamos su nombre e id desde el config.xml.

Consideraciones importantes

Hay que prestar atención a algunos detalles antes de analizar el código, y en general los códigos a partir de este ejemplo. Dado que hasta ahora hemos realizado aplicaciones web normales, no teníamos más añadir el código necesario para que al probarlo en nuestro navegador web de escritorio el resultado fuera satisfactorio. Pero ahora entran en juego elementos exclusivos de los terminales móviles, como algunos eventos exclusivos para estos dispositivos, y posteriormente plugins para acceder al hardware de los móviles, por lo que hay que añadir algunas líneas de código y tener claras algunas cosas.

Es necesario añadir la siguiente línea en el código html:
<script type="text/javascript" charset="utf-8" src="cordova.js"></script>
De hecho, viene por defecto al crear un proyecto nuevo Phonegap o Cordova. La había ignorado hasta ahora, e incluso eliminado, porque esa línea, al ejecutar los ficheros html en el navegador de escritorio sólo lanzaba un error por consola(la del navegador), porque no encontraba el fichero cordova.js. Sin embargo, al probar este ejemplo, me he dado cuenta de que sin esa línea, la aplicación móvil no funciona.

La explicación es la siguiente. El fichero no se encuentra en el www de nuestro proyecto, a pesar de que en el html, en la declaración del script, podemos pensar que está ahí. Donde realmente busca el fichero, es el la carpeta platform_www que se encontrará en las plataformas que tengamos agregadas a nuestro proyecto. Si no tenemos plataformas agregadas, no lo encontraremos. Como partimos del proyecto esqueleto que creamos en capítulos anteriores, tenemos añadida la plataforma Android(recordemos, para poder compilar en local sin tener que recurrir a Phonegap Build). Si entramos en platforms/android/plaform_www, efectivamente, veremos el fichero cordova.js ubicado ahí. En tiempo de compilación, el framework hace uso de los ficheros situados tanto en el www del proyecto como en el platform_www de cada plataforma añadida en platforms.




Por lo tanto, si eliminamos esa línea, nuestra aplicación funcionará como una aplicación web sin más, sin las posibilidades que ofrecen Phonegap/Cordova de acceso a eventos o hardware móvil.Es el archivo cordova.js, el que proporciona los enlaces a la API.

Otra cosa a tener en cuenta, si vamos a trabajar con eventos de Phonegap, y más adelante con las API que nos proporcionan los plugins Phonegap, es tratar de lanzar el evento "deviceready" antes que nada, y a partir de ahí, cargar el resto de código javascript, incluyendo los listeners para los demás eventos.

Con esas consideraciones presentes, he modificado ligeramente el código html y javascript, que quedan de la siguiente manera.

/////////////////////////////////////////////////index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="format-detection" content="telephone=no" />
    <!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
    <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="css/index.css" />
    <title>Proyecto Phonegap</title>

    <script type="text/javascript" src="./js/jquery-1.11.0.min.js"></script>
    <script type="text/javascript" charset="utf-8" src="cordova.js"></script>
    <script type="text/javascript" src="./js/bootstrap.min.js"></script>
    <script type="text/javascript" src="./js/fastclick.js"></script>
    <script type="text/javascript" src="./js/index.js"></script>
    <script type="text/javascript">
        window.addEventListener('load', function() {
            new FastClick(document.body);
        }, false);
    </script>
</head>
<body onload="onLoad()">
    <div id="vistaOpciones" class="oculto container-fluid">
        <div class="">
            <h2>Opciones</h2>
            <p>Color de texto</p>
            <button id="botonRojo" class="btn btn-danger">
                Rojo
            </button>
            <button id="botonAzul" class="btn btn-primary">
                Azul
            </button>
            <br><br>
            <button id="botonPrincipal" class="btn btn-default">
                Volver
            </button>
        </div>
    </div>

    <div id="vistaPrincipal" class="visible container-fluid">
        <div class="">
            <h2>Vista principal</h2>
            <p>Pulsa para cargar la vista de las opciones, al volver se recordará la opción elegida</p>
            <button id="botonMenu" class="btn btn-default">
                OPCIONES
            </button>
        </div>
    </div>
</body>
</html>

Nos fijamos que hemos subido todos los scripts al header, esto no tiene mayor importancia, pero sí el hecho de haber añadido la línea <script type="text/javascript" charset="utf-8" src="cordova.js"></script>
de la que ya he hablado antes.

También hemos añadido código al <body>, que una vez cargado, llamará a la función onLoad() del index.js.

Echemos ahora un vistazo al index.js:


//////////////////////////////////////////////////////index.js

function onLoad() {
    $(document).bind( "deviceready", function( event ) {
        onDeviceReady();
    });
}

function onDeviceReady() {
    // Register the event listener
    $(document).bind({
        backbutton: function(){
            if(!($('#vistaOpciones').hasClass("visible"))){
                alert("Has pulsado la tecla atras, pero no estás en el menú");
            }else{
                hacerVisible(vistaPrincipal);
                hacerOculto(vistaOpciones);
            } 
        },
        menubutton: function(){
            hacerVisible(vistaOpciones);
            hacerOculto(vistaPrincipal);
        },
        volumedownbutton: function() {
            alert("down");
        },
        volumeupbutton: function() {
            alert("up");
        }
    });

    var vistaPrincipal = $('#vistaPrincipal');
    var vistaOpciones = $('#vistaOpciones');
    var botonMenu = $('#botonMenu');
    var botonPrincipal = $('#botonPrincipal');
    var botonAzul = $('#botonAzul');
    var botonRojo = $('#botonRojo');
    function hacerVisible(elemento){
        elemento.addClass("visible");
        elemento.removeClass("oculto");
    }
    function hacerOculto(elemento){
        elemento.addClass("oculto");
        elemento.removeClass("visible");
    }
    botonMenu.click(function(){
        hacerVisible(vistaOpciones);
        hacerOculto(vistaPrincipal);
    });
    botonPrincipal.click(function(){
        hacerVisible(vistaPrincipal);
        hacerOculto(vistaOpciones);
    });
    botonRojo.click(function(){
        $('p').addClass("fondoRojo");
        $('p').removeClass("fondoAzul");
    });
    botonAzul.click(function(){
        $('p').removeClass("fondoRojo");
        $('p').addClass("fondoAzul");
    });

}

Nada más ejecutarse el onLoad(), añadimos un event listener al documento, de forma que cuando se produzca el evento deviceready, lanzaremos lo que sea, en este caso la función onDeviceReady, y ya en ella escribimos el resto del código. Pero al hacerlo así, nos aseguramos de que lo que se ejecute desde ese punto, lo hace tras el evento deviceready, lo que nos garantiza que Phonegap se ha cargado por completo.

Podríamos decir que deviceready es el evento más importante, y una vez suceda éste, podremos implementar el resto de eventos.

Como vemos, lo he hecho con bind, gracias a jquery. Podríamos usar document.addEventListener("deviceready", miFuncion, false);
Pero he optado por aprovechar la sintaxis que me brinda jquery.

Cuando se ejecuta la función onDeviceReady(), vemos que tenemos el mismo código que ya teníamos en el ejemplo del capítulo anterior, pero hemos añadido listeners para los eventos backbutton, menubutton, volumedownbutton y volumeupbutton. Si se pulsa el botón de menú, la aplicación reacciona ocultando la capa principal y haciendo visible la capa de opciones(hacerVisible() y hacerOculto() ). Si se pulsa alguna tecla de volumen, y como ya he comentado antes estamos en BlackBerry, la aplicación nos muestra un alert. Si pulsamos la tecla de retroceso, comprobamos si tenemos la capa de opciones visible, preguntando si tiene la clase CSS visible. Si no lo tiene, significa que estamos en la pantalla principal, y lanzo un alert avisando de que no se hará nada. Si esa capa está visible, la tecla retroceso hace que volvamos a la pantalla principal. Podemos comprobar que los botones de las opciones y en general el resto de funcionalidades de la aplicación sigue funcionando.



De este modo hemos visto algunos eventos de Phonegap, y hemos añadido con ellos más funcionalidades a una aplicación que ya teníamos, haciéndola reaccionar a los eventos propios del dispositivo móvil.


martes, 20 de mayo de 2014

Cargar vistas nuevas manteniendo el estado de la aplicación

Si en nuestras aplicaciones queremos cargar varias pantallas, y pasar de una a otra manteniendo el estado de la aplicación, en este capítulo veremos algunas soluciones. Puede ocurrir que queramos tener una vista para las opciones de la aplicación aparte de la vista principal, por ejemplo.

En Android, para pasar de una pantalla a otra pasando información(las opciones de configuración de la app, por seguir con el ejemplo anterior), existen las intenciones, de forma que preparamos un array asociativo con los datos que nos interesan antes de cargar la nueva pantalla.

Voy a proponer dos soluciones para que las usemos en Phonegap.

La primera requiere aprender herramientas nuevas. Como estamos usando javascript, podemos aprovecharnos de frameworks como Emberjs.


fuente: http://emberjs.com/

Ember está basado en el patrón MVC(modelo vista controlador) y permite lanzar aplicaciones "single-page" complejas. Si ya tenemos conocimientos sobre este framework, nos podría servir. En este enlace podemos ver un ejemplo sencillo de registro y login con Emberjs, si queremos implementarlo en Phonegap solamente tendríamos que pegar el fichero html y los javascript en la carpeta www de nuestro proyecto esqueleto.



Otra opción es tener todas nuestras vistas cargadas en la aplicación, pero tener en todo momento sólo una visible en pantalla, aplicando con javascript clases CSS a las vistas que queramos mostrar u ocultar. Podemos tener una clase CSS .oculto{display: none;} y .visible{display: block;}. En el html de la aplicación un <div class="oculto"> y un <div class="visible">. La capa visible será la principal de la aplicación, y la oculta será la que mantendremos sin mostrar, hasta que se solicite. En ese caso aplicamos y eliminamos las clases CSS oculto y visible según nos convenga.
A continuación veremos un ejemplo en el que ponemos esto en práctica. Usaremos el esqueleto creado en capítulos anteriores.
El body del html es el siguiente:

<body>
        <div id="vistaOpciones" class="oculto container-fluid">
            <div class="col-xs-4 col-xs-offset-4">
                <h2>Opciones</h2>
                <p>Color de texto</p>
                <button id="botonRojo" class="btn btn-danger">
                    Rojo
                </button>
                <button id="botonAzul" class="btn btn-primary">
                    Azul
                </button>
                <br><br>
                <button id="botonPrincipal" class="btn btn-default">
                    Volver
                </button>
            </div>
        </div>

        <div id="vistaPrincipal" class="visible container-fluid">
            <div class="col-xs-4 col-xs-offset-4">
                <h2>Vista principal</h2>
                <p>Pulsa para cargar la vista de las opciones, al volver se recordará la opción elegida</p>
                <button id="botonMenu" class="btn btn-default">
                    OPCIONES
                </button>
            </div>
        </div>
        <script type="text/javascript" src="./js/jquery-1.11.0.min.js"></script>
        <script type="text/javascript" src="./js/bootstrap.min.js"></script>
        <script type="text/javascript" src="./js/fastclick.js"></script>
        <script type="text/javascript" src="./js/index.js"></script>
        <script type="text/javascript">
            window.addEventListener('load', function() {
                new FastClick(document.body);
            }, false);
        </script>
    </body>

Nos fijamos que en el mismo html tenemos dos div que representan a las dos "pantallas" de nuestra aplicación. Uno de ellos tiene la clase visible y otro la clase oculto, por lo que sólo se mostrará el div con id vistaPrincipal. Mediante javascript, al pulsar en los botones, se producirán  reacciones en la aplicación(reacciones instantáneas, porque tenemos FastClick) que harán que se cambie de pantalla en nuestra aplicación, pulsando en el botón OPCIONES o en el botón Volver. En la vista vistaOpciones hay dos botones además del que nos devuelve a la pantalla principal.

Estos botones al ser pulsados producen cambios que se mantienen al cambiar de pantalla. No estamos cambiando de documento html en ningún momento, por lo que las variables que tengamos en javascript y las clases CSS que tengan los elementos html no se recargan. En este ejemplo sólo mantenemos clases CSS al cambiar de pantalla(el color del texto), pero podríamos guardar valores en variables que afecten el comportamiento de nuestra aplicación.
A continuación vemos el código javascript del ejemplo:

$(function(){
    var vistaPrincipal = $('#vistaPrincipal');
    var vistaOpciones = $('#vistaOpciones');
    var botonMenu = $('#botonMenu');
    var botonPrincipal = $('#botonPrincipal');
    var botonAzul = $('#botonAzul');
    var botonRojo = $('#botonRojo');
    function hacerVisible(elemento){
        elemento.addClass("visible");
        elemento.removeClass("oculto");
    }
    function hacerOculto(elemento){
        elemento.addClass("oculto");
        elemento.removeClass("visible");
    }
    botonMenu.click(function(){
        hacerVisible(vistaOpciones);
        hacerOculto(vistaPrincipal);
    });
    botonPrincipal.click(function(){
        hacerVisible(vistaPrincipal);
        hacerOculto(vistaOpciones);
    });
    botonRojo.click(function(){
        $('p').addClass("fondoRojo");
        $('p').removeClass("fondoAzul");
    });
    botonAzul.click(function(){
        $('p').removeClass("fondoRojo");
        $('p').addClass("fondoAzul");
    });
});

Y este es el CSS de la aplicación usada:

.visible{
    display:block;
}

.oculto{
    display:none;
}

.fondoAzul{
    color: #2222FF;
}

.fondoRojo{
    color: #ff2222;
}

Aquí tenemos el código de la aplicación, listo para compilar. Si deseamos, en config.xml podemos editar su id y su nombre.