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