Vistas de página en total

lunes, 31 de julio de 2017

Usando las librerías de Telerik para Nativscript

Telerik, la empresa que mantiene Nativescript, ha desarrollado una librería de componentes avanzados para su uso con Nativescript. La librería se llama Progress Nativescript UI.
Progress Nativescript UI es un conjunto de componentes que permiten implementar aplicaciones avanzadas para iOS y Android. Esta librería está construido por encima de componentes nativos de iOS y Android, por lo que el look and feel será igual que el nativo.
Contiene los siguientes componentes:

  • Calendar: componente calendario que ofrece diversas vistas, animaciones y muchas opciones de configuración.
  • Chart: componente muy versátil para gráficas que soporta la mayoría de tipos de gráficas.
  • ListView: componente lista mucho más avanzado que el que trae Nativescript incluido. Tiene características como animación de items, pull para refrescar, layouts en los items, recarga on-demand, acciones al hacer swite, etc.
  • DataForm: componente que permite editar las propiedades de un objeto en un formulario.
  • SideDrawer: menú de opciones de navegación que está oculto y que puede desplegar el usuario cuando lo necesite.
  • Gauges: componente que permite mostrar de forma gráfica un valor dentro de un rango (mínimo, máximo).
  • AutoCompleteTextView: componente TextView avanzado que permite autocompletar según una serie de valores que tenga un data source.
Pero lo mejor es ver estos componentes en una imagen:


Versiones

Progress Nativescript UI se distribuye en dos versiones:

  • Progress Nativescript UI: Gratuita
  • Progress Nativescript UI Pro: De pago (aproximadamente 199$ por desarrollador, para toda la vida)

La versión gratuita incluye únicamente dos de los anteriores componentes:

  • ListView
  • SideDrawer

La versión Pro incluye todos los componentes.

Vamos a utilizar la librería

En este artículo vamos a modificar la mini-aplicación que hicimos en este artículo para sustituir la lista de ofertas por una nueva lista de la librería de Telerik. De esta forma, nos sirve gran parte de la aplicación y por otro lado, también nos sirve la librería gratuita (no tenemos que recurrir a versiones de evaluación...).
Como ya tenemos el proyecto creado, vamos a añadirle la librería de Telerik. Primero nos vamos al directorio del proyecto y:

    tns plugin add nativescript-telerik-ui

Este comando añade el plugin necesario para utilizar la librería.
Lo siguiente será ejecutar la aplicación:

    tns run android

Esto no es necesario ahora, porque todavía no hemos modificado el código para que utilice el nuevo componente, pero podremos comprobar que todo ha ido bien (la primera compilación tardará bastante, así que paciencia...)

Nueva página next2

En lugar de cambiar la página next del proyecto donde teníamos el componente ListView que viene por defecto en Nativescript, vamos a crear una nueva página. De esta forma será sencillo volver a la versión anterior si así lo necesitamos después. Otra ventaja es que el modelo asociado al nuevo componente es igual al modelo de ListView básico.

En app/main.ts cambiamos la página a la que navegamos al pulsar sobre el botón de próximas ofertas:

export function onNextDays() {
// navigate
    frame.topmost().navigate('next2/next');
}

Creamos el directorio app/next2 para la nueva página.

Creamos el fichero app/next2/next-view-model.ts que contiene el modelo para la nueva página (este modelo es igual que el que teníamos en la página anterior):

import observable = require("data/observable");
import {ObservableArray} from 'data/observable-array';


export class NextViewModel extends observable.Observable {
    public items:ObservableArray<object> = null;

    constructor() {
        super();
        this.items = new ObservableArray();
        this.items.push({date: 'Martes 28', offer: 'Portátil HP 400X' });
        this.items.push({date: 'Miércoles 29', offer: 'Mochila Thule 3' });
        this.items.push({date: 'Jueves 30', offer: 'iPhone 6S 32GB' });
        this.set('items', this.items);
    }

    addItem(d:string, o:string) {
        console.log('Add: ' + d + ',' + o);
        if (o.length > 0) {
            this.items.push({date:d, offer:o});
        } else {
            console.log("Empty string");
        }
    }
}

Ahora creamos el fichero XML de la nueva página app/next2/next.xml:

<Page xmlns="http://www.nativescript.org/tns.xsd"
    xmlns:lv="nativescript-telerik-ui/listview"
    navigatingTo="onPage">

    <Page.actionBar>
        <ActionBar title="Ofertas próximos días" class="header">
            <NavigationButton text="Back" android.systemIcon="ic_menu_back" tap="onBack" />
        </ActionBar>
    </Page.actionBar>

    <DockLayout stretchLastChild="true">
        <DockLayout dock="bottom">
            <Button dock="right" text="Añadir" tap="onAdd" />
            <TextField id="newText" class="input input-border" hint="New text" />
        </DockLayout>
        <ScrollView>
            <lv:RadListView row="1" items="{{ items }}">
                <lv:RadListView.itemTemplate>
                    <StackLayout class="itemStackLayout">
                        <Label text="{{ $value.date }}" class="date"/>
                        <Label text="{{ $value.offer }}" class="offer" />
                        <StackLayout height="2" backgroundColor="#FF0000"/>
                    </StackLayout>
                </lv:RadListView.itemTemplate>
            </lv:RadListView>
        </ScrollView>
    </DockLayout>

</Page>

 
Aquí sí que vemos algunas diferencias. En primer lugar, un nuevo namespace de XML: xmlns:lv. En segundo lugar, utilizamos el nuevo componente RadListView, en lugar de ListView. Por lo demás, todo es prácticamente igual.

Y para terminar, creamos el código de la página en el fichero app/next2/next.ts:

import { EventData } from "data/observable";
import { Page } from "ui/page";
import { NextViewModel } from "./next-view-model";
import frame = require('ui/frame');
import view = require("ui/core/view");

var page:Page = null;
var model:NextViewModel = null;

export function onPage(args: EventData) {
    console.log('next.onPage');
    page = <Page>args.object;
    model = new NextViewModel();
    page.bindingContext = model;
}

export function onBack() {
    frame.topmost().goBack();
}

export function onAdd() {
    let newTextTxt:any = view.getViewById(page, "newText");
    console.log('Text=' + newTextTxt.text);
    model.addItem('Hoy', newTextTxt.text);
    newTextTxt.text = "";
}

Este fichero también es prácticamente igual al anterior.

Por último, el fichero de estilos: app/next2/next.css

.offer {
    font-size: 25px;
    font-weight: bold;
    text-align: center;
}

.date {
    font-size: 14px;
    text-align: left;
}

.header {
    background-color: #333;
    color: #fff;
}

Ejecutemos la aplicación de nuevo:

    tns run android

Pulsamos el botón de próximas ofertas.... y.... sorpresa! No vemos ningún cambio!!! La página es igual que antes.


Pero podemos estar seguros de que es el nuevo componente, no es el antiguo, ya que la navegación desde main se produce a la página next2/next. Además, he cambiado la línea que separa items a color rojo, para confirmar que es realmente la nueva página (para los incrédulos).

El componente RadListView

El componente RadListView tiene las siguientes características:

  • Animación de items
  • Varios layouts y orientaciones
  • Gestos: selección al hacer pulsación larga, lista de acciones al hacer swipe, reordenación y recolocación al hacer pulsación larga y arrastre

Selección

RadListView permite selección simple o múltiple de los items (hay que seleccionar unos de estos dos métodos).  Para ello, tenemos el atributo multipleSelection, que puede valer true o false.

Además, el componente tiene un API para manejar las selecciones:
  • selectAll()
  • deselectAll()
  • selectItemAt(index)
  • deselectItemAt(index)
  • isItemSelected(index)
  • getSelectedItems()
Para activar la selección tenemos que añadir el atributo selectionBehavior al componente. Este atributo toma los siguientes valores:
  • None: los items no pueden ser seleccionados
  • Press: los items se seleccionan al hacer una pulsación sobre el item
  • LongPress: los items se seleccionan al hacer una pulsación larga sobre el item
Cambiemos el componente RadListView en nuestro fichero app/next2/next.xml:

<lv:RadListView row="1" items="{{ items }}" multipleSelection="true" selectionBehavior="LongPress">

Ahora ya podemos ver algunos cambios en nuestra lista. Al hacer una pulsación larga sobre un item, éste queda seleccionado. Para deseleccionarlo, hacemos otra pulsación larga sobre él.

Por último, podemos recibir eventos relacionados con la selección de items:

  • itemSelecting: se dispara antes de que un item sea seleccionado (se puede cancelar la selección)
  • itemSelected: se dispara cuando se ha seleccionado un item
  • itemDeselecting: se dispara antes de que un item sea deseleccionado (se puede cancelar la operación)
  • itemDeselected: se dispara cuando se ha deseleccionado un item

Layouts

Aunque el uso típico de una lista es mostrar los items de una forma lineal (uno encima de otro), hay casos donde esto no es así.
RadListView soporta tres estrategias de layout:
  • linear: los items se ponen uno encima del otro si el scroll es vertical (o bien uno al lado del otro si el scroll es horizontal). Los items se colocan uno a uno.
  • grid: los items se ordenan en filas y columnas (según la orientación del scroll). Hay que especificar la cantidad de columnas (orientación vertical) o de filas (orientación horizontal).
  • staggered grid: los items se ordenan en un grid escalonado (según la orientación del scroll).
El layout se define mediante la propiedad listViewLayout (es una propiedad, no un atributo). Esta propiedad puede tomar uno de los siguientes valores:

  • ListViewLinearLayout
  • ListViewGridLayout
  • ListViewStaggeredLayout

Ejemplo de linear layout:

<lv:RadListView row="1" items="{{ items }}" multipleSelection="true" selectionBehavior="LongPress">
    <lv:RadListView.listViewLayout>
        <lv:ListViewLinearLayout scrollDirection="Vertical" />
    </lv:RadListView.listViewLayout>

Ejemplo de grid layout:
<lv:RadListView.listViewLayout>
    <lv:ListViewGridLayout scrollDirection="Vertical" itemHeight="200" spanCount="2"/>
</lv:RadListView.listViewLayout>
Ejemplo de staggered Layout:
<lv:RadListView.listViewLayout>
    <lv:ListViewStaggeredLayout scrollDirection="Vertical" spanCount="3"/>
</lv:RadListView.listViewLayout>


jueves, 27 de julio de 2017

Uso de Typescript con Nativescript

Typescript es un lenguaje de programación que se "transpila" a Javascript, es decir, se convierte a código Javascript. En este enlace se pueden aprender rápidamente los fundamentos de Typescript.
Programar en Typescript tiene muchas ventajas sobre Javascript. Las más importantes son:

  • chequeo de tipos estático (aunque mantiene la posibilidad de tipos dinámicos de Javascript)
  • uso de interfaces, clases, herencia y genéricos
  • funciones cursor (arrow)
  • parámetros por defecto y opcionales
El hecho de que el código Typescript pueda convertirse a Javascript hace que sea posible utilizar este lenguaje en nuestros proyectos Nativescript. Veamos cómo.
El objetivo de este artículo es demostrar lo sencillo que es crear una aplicación Nativescript desde cero, con datos enlazados al modelo de una forma mucho más sencilla que en Javascript. No entraré en muchas explicaciones sobre los detalles del código (se irán viendo en posteriores artículos), pero es bastante sencillo de seguir.
Es importante hacer notar que el uso de Typescript no implica utilizar Angular. Ni mucho menos, en el ejemplo que hago a continuación no se utiliza Angular en ningún momento, simplemente se utiliza Typescript en sustitución de Javascript.

Definición del proyecto

El proyecto que vamos a crear es muy sencillo. El prototipo en papel (como a mí me gusta) se muestra a continuación:
En la primera pantalla (a la que llamaremos main) se muestra la oferta del día y un botón para navegar a la pantalla donde se muestran las ofertas para los próximos días. Llamaremos next a esta segunda pantalla.

Instalar Typescript

Instalemos Typescript en nuestro equipo:

    npm install -g typescript

Si ya estuviera instalado, este comando actualizará a la última versión.
Comprobemos que está correctamente instalado y la versión:

    tsc --version

Crear el proyecto Nativescript

Ir al directorio base y crear el proyecto usando la plantilla typescript:

    tns create OfertaDelDia --template typescript

Lo primero que vemos es que el proyecto no tiene un fichero app/app.js como ocurre en los proyectos Nativescript con Javascript, sino que el fichero se llama app/app.ts, o sea, un fichero Typescript.
El contenido de este fichero es:

import * as app from 'application';

app.start({
    moduleName: 'main/main'
});

En primer lugar, se importa el módulo application, para poder lanzar la página principal de la aplicación. Utilizando el objeto application, llamamos al método start() pasando como parámetro un objeto con el atributo moduleName que contiene el nombre de la página principal (en este caso la página main (main.xml, main.js y main.css) en el directorio app/main. Esta página la vamos a crear a continuación.

Página main

Creamos una carpeta app/main para los ficheros de la página main.

Creamos el fichero app/main/main.xml:

<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onPage">
    <StackLayout>
        <Label text="{{ today }}" class="today" />
        <Label text="{{ dayOffer }}" class="day-offer" textWrap="true" />
        <Label text="{{ todayPrice }}" class="today-price" />
        <Label text="{{ yesterdayPrice }}" class="yesterday-price" />
        <Button text="Próximos días..." tap="onNextDays" />
    </StackLayout>
</Page>

El atributo Page.navigatingTo define la rutina que será invocada cada vez que entremos en la página. Esta rutina se define en el fichero main.ts
Esta página simplemente contiene un StackLayout con cuatro Labels y un Button. Los labels muestran texto (atributo text) procedente del modelo, es decir, datos del objeto Observable que definiremos en el fichero main-view-model.ts Este objeto modelo tendrá al menos las propiedades today, dayOffer, price y yesterdayPrice.
El atributo class indica el estilo aplicable. Estos estilos se definen en el fichero main.css

Creamos el fichero app/main/main.ts:

import { EventData } from "data/observable";
import { Page } from "ui/page";
import { MainViewModel } from "./main-view-model";
import frame = require('ui/frame');

export function onPage(args: EventData) {
    var page = <Page>args.object;
    page.bindingContext = new MainViewModel();
    setTimeout(
        function() {
            console.log('timeout');
            page.bindingContext.set('todayPrice', 'Hoy 49€');
        },
        5000
    );
}

export function onNextDays() {
    frame.topmost().navigate('next/next');
}

En este fichero se define la función que se llamará cada vez que entremos en la página: onPage(). Esta función recibe entre sus argumentos el objeto Page, al cual enlazamos un bindingContext, el cual será un nuevo objeto de la clase MainViewModel. Este objeto sirve para enlazar datos de ese modelo con controles de la interfaz de usuario.
Por último, ponemos una ejecución diferida de Javascript (setTimeout) para cambiar el modelo después de 5 segundos. Cuando se ejecute esta función diferida, se actualiza el objeto modelo y por consiguiente se actualizará automáticamente la etiqueta asociada.

Creamos el fichero app/main/main.css:

.day-offer {
    font-size: 40px;
    font-weight: bold;
    text-align: center;
}

.today-price {
    font-size: 30px;
    font-weight: bold;
    text-align: center;
}

.yesterday-price {
    font-size: 20px;
    text-align: center;
}

.today {
    font-size: 12px;
    text-align: center;
}

Button {
    margin: 20px;
}

Creamos el fichero app/main/main-view-model.ts:

import observable = require("data/observable");

export class MainViewModel extends observable.Observable {
    constructor() {
        super();
        this.set('dayOffer', 'Maleta de viaje Samsonite L3R');
        this.set('todayPrice', 'Hoy 58€');
        this.set('yesterdayPrice', 'Ayer 96€');
        this.set('today', 'Lunes, 27 de julio');
    }
}

Este fichero contiene el modelo de la página main. Se trata de una clase llamada MainViewModel (que hereda de Observable) y que tiene una serie de propiedades que asignamos utilizando el método set() heredado de la clase Observable. Estas propiedades se podrán utilizar en controles de la página.

Página next

Creamos una carpeta app/next para los ficheros de la página next.

Creamos el fichero app/next/next.xml:

<Page xmlns="http://schemas.nativescript.org/tns.xsd"
navigatingTo="onPage">
    <Page.actionBar>
        <ActionBar title="Ofertas próximos días" class="header">
            <NavigationButton text="Back" android.systemIcon="ic_menu_back" tap="onBack" />
        </ActionBar>
    </Page.actionBar>
    <DockLayout stretchLastChild="true">
        <DockLayout dock="bottom">
            <Button dock="right" text="Add" tap="onAdd" />
            <TextField id="newText" class="input input-border" hint="New text" />
        </DockLayout>
        <ScrollView>
            <ListView items="{{ items }}">
                <ListView.itemTemplate>
                    <StackLayout>
                        <Label text="{{ $value.date }}" class="date" />
                        <Label text="{{ $value.offer }}" class="offer" />
                    </StackLayout>
                </ListView.itemTemplate>
            </ListView>
        </ScrollView>
    </DockLayout>
</Page>

Esta página es un poco más compleja que la anterior. En primer lugar contiene un DockLayout que incluye una lista de ofertas en la parte central, y un campo de texto junto con botón para añadir elementos, en la parte inferior. La lista de ofertas está enlazada al modelo, que será un array de objetos con dos campos: date y offer.

Creamos el fichero app/next/next.ts:


import { EventData } from "data/observable";
import { Page } from "ui/page";
import { NextViewModel } from "./next-view-model";
import frame = require('ui/frame');
import view = require("ui/core/view");

var page:Page = null;
var model:NextViewModel = null;

export function onPage(args: EventData) {
    console.log('next.onPage');
    page = <Page>args.object;
    model = new NextViewModel();
    page.bindingContext = model;
}

export function onBack() {
    frame.topmost().goBack();
}

export function onAdd() {
    let newTextTxt:any = view.getViewById(page, "newText");
    console.log('Text=' + newTextTxt.text);
    model.addItem('Hoy', newTextTxt.text);
    newTextTxt.text = "";
}

Creamos el fichero app/next/next.css:

.offer {
    font-size: 25px;
    font-weight: bold;
    text-align: center;
}

.date {
    font-size: 14px;
    text-align: left;
}

.header {
    background-color: #333;
    color: #fff;
}


Creamos el fichero app/next/next-view-model.ts:

import observable = require("data/observable");
import {ObservableArray} from 'data/observable-array';


export class NextViewModel extends observable.Observable {
    public items:ObservableArray<object> = null;

    constructor() {
        super();
        this.items = new ObservableArray();
        this.items.push({date: 'Martes 28', offer: 'Portátil HP 400X' });
        this.items.push({date: 'Miércoles 29', offer: 'Mochila Thule 3' });
        this.items.push({date: 'Jueves 30', offer: 'iPhone 6S 32GB' });
        this.set('items', this.items);
    }

    addItem(d:string, o:string) {
        console.log('Add: ' + d + ',' + o);
        if (o.length > 0) {
            this.items.push({date:d, offer:o});
        } else {
            console.log("Empty string");
        }
    }
}

Ejecutar la aplicación

Arrancar el emulador Android o el simulador iOS, o bien Genymotion.

    tns platform add android
    tns platform add ios
    tns run android --emulator
    tns run ios --emulator







jueves, 29 de junio de 2017

Navegación entre páginas

En este artículo vamos a construir una sencilla aplicación con múltiples páginas. Aprenderemos cómo navegar entre pantallas y cómo tratar eventos de la UI. En siguientes artículos iremos desarrollando poco a poco esta aplicación. No es el objetivo de este ejemplo la apariencia o usabilidad de la aplicación, así que no nos detendremos en los aspectos gráficos de la UI.

Definición de la aplicación

Esta sencilla aplicación consta de dos páginas:
  • home
  • about
La página home será la primera página que vean los usuarios al lanzar la aplicación. La página about mostrará información textual sobre la aplicación.

Creación del proyecto vacío

Para crear el proyecto utilizamos el CLI de Nativescript, como siempre:
    tns create TechnoShop --appid com.joseanquiles.technoshop
Ahora vaciamos el proyecto, para empezar desde cero:
Borrar el fichero app/bundle-config.js
Borrar el fichero app/main-view-model.js
Borrar el fichero app/main-page.xml
Borrar el fichero app/main-page.js
Abrir el fichero app/app.js y dejarlo como sigue:
var application = require("application");
application.cssFile = "./app.css";
application.start({ moduleName: "views/home-page" });
Crear carpeta app/views
Crear fichero app/views/home-page.xml y rellenarla con lo siguiente:
<Page>
</Page>
Page es un contenedor de todos los elementos de la página. Es el elemento padre de todos los demás elementos de la página.
Ejecutar el proyecto:
    cd TechnoShop
    tns platform add android
    tns run android --emulator
Si todo ha ido bien, tendremos una aplicación vacía ejecutándose en el emulador.

Añadir contenido a la página home

Vamos a poner una etiqueta de bienvenida en home-page.xml:

<Page>
    <StackLayout>
        <Label text="Bienvenido a TechnoShop!" />
    </StackLayout>
</Page>

StackLayout es un contenedor que sitúa a sus elementos hijos uno encima de otro. Los contenedores se llaman layouts en Nativescript. En otros artículos veremos otros tipos de layouts que permiten organizar a sus hijos de otras formas.
Label es una etiqueta de texto. El atributo text define el texto que muestra la etiqueta.

Crear la página about

Crear fichero app/views/about-page.xml  y rellenarla con lo siguiente:

<Page>
    <StackLayout>
        <Label text="TechnoShop, la tienda de tecnología online. Tenemos todo tipo de dispositivos y accesorios para ti" />
        <Label text="Madrid (España)" />
    </StackLayout>
</Page>
Si ejecutamos la aplicación ahora, nos daremos cuenta de que no hay forma de ver la nueva página que hemos creado.
Podríamos cambiar la página de arranque en app.js para que apunte a home-page, así podremos ver la nueva página:
    application.start({ moduleName: "views/about-page" });
Ahora sí podemos ver la página About. Lo primero que vemos es que el texto de la primera etiqueta está recortado, porque no cabe en el ancho del móvil. Para resolver este problema, utilizamos el atributo textWrap de Label.

    <Label textWrap="true" text="TechnoShop, la tienda de tecnología online. Tenemos todo tipo de dispositivos y accesorios para ti" />

Gracias a poner textWrap="true" el texto que no cabe en una línea pasará a la siguiente línea.
Y ahora, vamos a volver a poner home-page como página de inicio:

application.start({ moduleName: "views/home-page" });

Implementemos la navegación entre páginas...

Navegación entre páginas

Hasta ahora hemos ido construyendo páginas en su correspondiente fichero xml, pero no hemos añadido ninguna lógica a estas paginas. La lógica de las páginas va en el fichero .js asociado a cada página.
La navegación entre páginas ocurre en respuesta a una acción sobre un elemento de la página (por ejemplo, pulsando un botón). La acción asociada está en el fichero .js de la página.

Añadir un botón a la página home

La etiqueta Button inserta un botón en la página Home:

<Page>
    <StackLayout>
        <Label text="Bienvenido a TechnoShop!" />
        <Button text="Acerca de..." tap="onAbout" />
    </StackLayout>
</Page>

El atributo text de Button especifica el texto que mostrará el botón.
El atributo tap de Button especifica la función javascript que se ejecutará cuando se pulse el botón.

Tratar el evento tap

El evento tap de Button se produce al pulsar el botón. La función que hemos especificado para tratar este evento se llama onAbout y hay que definirla en el fichero javascript asociado a la página. El fichero que tenemos que crear es app/views/home-page.js

var frame = require("ui/frame");
function onAbout() {
    frame.topmost().navigate("views/about-page");
}
exports.onAbout = onAbout;

El módulo ui/frame se utiliza para navegar entre páginas. Este módulo se incluye en los core-modules de Nativescript.
La función onAbout() se ejecutará cuando se produzca el evento tap en el Button.
Por último, tenemos que exportar la función añadiéndola al objeto exports.

Una forma más breve de hacer lo mismo sería:

var frame = require("ui/frame");
exports.onAbout = function() {
    frame.topmost().navigate("views/about-page");
}

Si ejecutamos la aplicación veremos el botón debajo del texto de bienvenida. Si pulsamos el botón, pasaremos a la página About.

Aplicar transiciones

Una transición es una animación que ocurre cuando se navega de una página a otra.
Nativescript nos ofrece una serie de transiciones predefinidas que podemos utilizar tanto en iOS como en Android:
  • fade
  • flipRight
  • flipLeft
  • slideLeft
  • slideRight
  • slideTop
  • slideBottom
Para aplicar la transición, cambiaremos home-page.js de la siguiente forma:

var frame = require("ui/frame");
exports.onAbout = function() {
    var nav = {
        moduleName : "views/about-page",
        transition : {
            name : "slideBottom",
            duration : 2000
        }
    };
    frame.topmost().navigate(nav);
}

En este caso, hemos creado un objeto llamado nav que incluye la página a la que se navega (moduleName) y un objeto transition que define la transición (name y duration en milisegundos).






miércoles, 28 de junio de 2017

Programando para diversas plataformas y tamaños

Aunque Nativescript es un framework multiplataforma y el objetivo es que una aplicación Android e iOS compartan el máximo de código, la realidad es que en ocasiones queremos que la UI sea algo diferente en cada plataforma, e incluso que la lógica de la aplicación sea específica para una plataforma. Un ejemplo podría ser el uso de touch-id: en iOS podríamos autenticar al usuario con touch-id, pero en Android pediríamos usuario y password.


Nativescript proporciona varios mecanismos para hacer UI y lógica de aplicación específicos para cada plataforma y para cada tamaño.

Código específico de cada plataforma

XML específico de una plataforma

La primera forma de definir un UI específico para cada plataforma es separar el fichero.xml de una página en dos ficheros, uno para el UI de Android y otro para el UI de iOS. Para diferenciar los dos ficheros, añadimos .android o .ios al nombre del fichero:
  • main-page.android.xml
  • main-page.ios.xml
Esta aproximación es sencilla, pero obliga a duplicar el UI de la página para cada plataforma. Esto es bastante engorroso si sólo queremos un pequeño cambio de un UI a otro, ya que tendremos que duplicar casi todo el layout de la página. En este caso en que sólo queremos un pequeño cambio, tenemos una solución mucho mejor: mantener un fichero común y poner markup específico de cada plataforma:
    <StackLayout>
        <Button text="Este botón sale en ambas plataformas" />
        <android>
            <Button text="este texto sólo se muestra en android" />
        </android>
        <ios>
            <Button text="este texto sólo se muestra en ios" />
        </ios>
    </StackLayout>

Javascript específico de una plataforma

Al igual que ocurre con los fichero xml, también podemos utilizar la notacion .android y .ios en los ficheros javascript. Pero además, disponemos de la notación .common:
  • main-page.common.js
  • main-page.android.js
  • main-page.ios.js
En primer lugar, se carga el fichero .common y después se carga el fichero específico de la plataforma.
Además, Nativescript nos proporciona dos variables globales javascript: 'android' e 'ios', que nos indican la plataforma en la que estamos ejecutando. Utilizando estas variables podemos evitar partir el código javascript en 3 ficheros diferentes:
    if (android) {
        ......
    }
    if (ios) {
        ........
    }

Estilos específicos por plataforma

Para separar estilos por plataforma se utiliza el mismo criterio utilizado en XML:
  • main-page.android.css
  • main-page.ios.css

Código específico de cada tamaño

Device independent pixels

Los device independent pixels, o dp, son una forma de describir el tamaño físico de un dispositivo (ancho y alto) de la pantalla del dispositivo, sin decir el número de pixels que tiene.
En general, Android e iOS clasifican sus pantallas como si tuvieran 160 dp por pulgada (esto es aproximado, cada plataforma tiene un valor distinto). Así, podemos asumir que si un dispositivo tiene un ancho mayor de 600 dp, entonces es una tablet, ya que 600/160=3.75 pulgadas=9.5 cm. Si tiene un ancho menor de 600 dp entonces es un teléfono. En general, los dp son una forma de medir el tamaño físico de la pantalla.

Convención de nombres de fichero para distintos tamaños

Para utilizar ficheros específicos para distintos tamaños, se añaden los siguientes sufijos al nombre del fichero:
  • .minWxxx : si el ancho del dispositivo es al menos xxx dp
  • .minHxxx : si el alto del dispositivo es al menos xxx dp
  • .minWHxxx : si la más pequeña de las dimensiones del dispositivo es al menos xxx dp
Por ejemplo, si tenemos los ficheros main-page.xml y main-page.minWH600.xml, éste último se mostraría en una tablet y el primero en un teléfono, ya que la dimensión menor de una tablet es superior a 600 dp, como hemos  dicho antes.

Orientación del dispositivo

Otra necesidad muy común cuando se desarrollan aplicaciones es tener un UI distinto en función de la orientación del dispositivo. Para ello Nativescript nos ofrece la siguiente convención de nombres:
  • .land : para orientación landscape u horizontal
  • .port : para orientación portrait o vertical

Encadenando restricciones

Hasta ahora hemos visto cómo aplicar una única restricción en la convención de nombres de fichero: plataforma, tamaño mínimo, orientación. ¿Cómo podemos aplicar varias restricciones a la vez? En Nativescript esto es tan sencillo como encadenar. Por ejemplo, main-page.android.land.xml se aplica a la plataforma android en orientación landscape. El orden de las restricciones no importa, Nativescript aplicará lo que corresponda sin importar el orden.