En esta ocasión añadiremos al ejercicio anterior la opción de compartir, de forma que desde nuestra aplicación podamos enviar la lista de tareas mediante un mensaje de WhatsApp, un correo electrónico, un SMS, etc.
Además, añadiremos el código necesario para disponer de un menú lateral donde podremos añadir más opciones, y donde sugerimos además colocar la opción de borrar la lista de tareas, para que no esté tan accesible desde la pantalla principal:
El menú lateral
Primero añadiremos en la barra superior de la pantalla principal de nuestra aplicación un botón para desplegar el menú lateral:
Como podemos leer en la documentación de IONIC, el elemento <ion-menu-button></ion-menu-button> se encarga de todo, ya que crea automáticamente el icono del botón y añade la funcionalidad necesaria para desplegar el menú en la página actual.
Sólo nos falta añadir el código necesario con el menú lateral, que tendrá su propio encabezado y una lista con las opciones que deseemos. En nuestro caso pondremos la opción de reordenar, de compartir y de borrado de todos los elementos de la lista seleccionada. Además, añadiremos también un botón para volver a ocultar el menú:
Observaremos que el elemento <ion-menu></ion-menu> por defecto creará un menú que aparece desde la izquierda de la pantalla actual, tal como se especifica en la documentación de IONIC. Se podrá ocultar pulsando de nuevo el botón de menú, o con un gesto swipe hacia la izquierda, o incluso pulsando fuera del menú. Además, utilizaremos el elemento <ion-menu-toggle></ion-menu-toggle> para conseguir que al pulsar en cualquier opción, el menú también se cierre automáticamente.
Utilizaremos la funcionalidad nativa de nuestros dispositivos móviles para poder enviar la lista de tareas a alguno de nuestros contactos. Para incluir en nuestra aplicación el plugin necesario para acceder a dicha funcionalidad, bastará con añadir la siguiente línea al archivo config.xml:
A continuación modificaremos el código fuente del archivo todo.js para añadir la función share(), que creará una cadena de texto y se la proporcionará al plugin de compartir para que se pueda enviar utilizando otra aplicación.
Bastará con crear la función share() con el código necesario para obtener en una única cadena de texto todas las tareas una detrás de otra, separadas por saltos de línea. Después sólo necesitamos llamar a la función socialsharing.share() del plugin para activar y mostrar el menú de compartir de nuestros móviles:
function share() {
let text = "";
getList().forEach((task, index) => {
text += (index+1) + ". " + task.text + " - " + task.date.slice(0,10) + "\n";
});
window.plugins.socialsharing.share(text);
}
En este ejercicio pretendemos desarrollar una aplicación para visualizar imágenes que se encuentran agrupadas en categorías. Para ello sugerimos implementar la siguiente funcionalidad:
Crear pestañas o tabs para cada una de las categorías, agrupando así las imágenes, y permitiendo con un solo click cambiar entre una categoría u otra.
Utilizando la pantalla táctil de nuestros dispositivos, habilitar el gesto swipe para acceder a la imagen anterior o posterior.
Activar la reproducción automática mediante un botón, permitiendo a su vez pausar la visualización mediante otro botón.
Permitir ajustar la velocidad en la que se van mostrando las imágenes.
Una posible sugerencia de la interfaz de la aplicación sería la siguiente:
Elementos HTML de IONIC 4
Slides
La funcionalidad principal de este ejercicio nos la proporciona IONIC mediante el elemento <ion-slides></ion-slides>, donde ya se encuentra encapsulada la respuesta a los gestos de swipe y la reproducción automática, tal como se explica en la documentación.
Por ejemplo, para poder visualizar de manera secuencial las imágenes alicante1.jpg, alicante2.jpg y alicante3.jpg, ubicadas en la carpeta img, bastaría con insertarlas dentro de elementos <ion-slide></ion-slide>, y agruparlas todas con <ion-slides></ions-slides> de la siguiente forma:
También podríamos haber utilizado el elemento <ion-img></ion-img>, que tiene un comportamiento casi idéntico al <img> de HTML. La diferencia es que esta última versión de IONIC optimiza especialmente aquellas páginas que tienen una lista muy grande de imágenes, ya que sólo se cargarían cuando fueran a estar visibles.
Tabs
El siguiente elemento clave que utilizaremos es <ion-tabs></ion-tabs>, ya que nos permitirá agrupar las fotos por categoría. Nos proporciona la funcionalidad necesaria para mostrar unas secciones de nuestro código HTML mientras se ocultan otras.
Conviene destacar además las numerosas opciones de personalización, tales como el color, que se pueden ajustar especificando el valor deseado en el atributo correspondiente (se puede consultar la documentación para más detalles). Por ejemplo, si queremos que se muestren dos pestañas con el fondo de color rojo, una de ellas etiquetada con el texto Madrid y el icono train, y la otra con París y airplane, procederíamos de la siguiente forma:
Al hacer clic en la categoría Madrid, se ocultarían las diapositivas de la categoría París, y viceversa.
Además, hacemos uso de los elementos ion-label e ion-icon, que nos permiten establecer el texto y el icono que aparecen en la pestaña, respectivamente. El elemento ion-label acepta un texto libre, mientras que ion-icon permite elegir uno de entre todos los posibles iconos que nos proporciona IONIC.
Botones play/pause
Para iniciar y pausar la reproducción automática de las imágenes, insertaremos dos botones formados simplemente por los iconos play y pause que nos proporciona IONIC:
Un poco más adelante veremos la funcionalidad JavaScript que se ejecutará al pulsar dichos botones.
Para ajustar la velocidad
Para poder ajustar el tiempo que permanece cada diapositiva en la pantalla, sugerimos utilizar un elemento range (<ion-range></ion-range>). Por ejemplo, para poder establecer un tiempo que oscile entre 0 y 5 segundos (5000 milisegundos), podríamos utilizar el siguiente código:
Con el objetivo de mejorar el impacto visual del control deslizante, en el ejemplo utilizamos el icono speedometer, que será más pequeño a la izquierda (slot="start" size="small") para indicar que las imágenes irán apareciendo lentamente, y más grande a la derecha (slot="end") para dar a entender que las diapositivas se mostrarán una tras otra más rápidamente.
Sugerimos insertar dicho control también en el elemento <ion-header></ion-header> de la aplicación, para que aparezca justo debajo de la barra que contiene el título y los botones:
Por simplificar un poco el código, utilizaremos tres funciones para acceder a cada uno de los elementos clave (range, tabs y slides):
function getDelay() {
return(document.querySelector('ion-range'));
}
function getTabs() {
return(document.querySelector('ion-tabs'));
}
function getSlides() {
return(document.querySelectorAll('ion-slides'));
}
Al pulsar play/pause
Para iniciar la reproducción automática de diapositivas bastará con acceder al elemento <ion-slides></ion-slides> correspondiente y ejecutar el método startAutoplay(), tal como se indica en la documentación.
Puesto que tendremos varias pestañas (una por cada categoría) primero deberemos acceder al tab que se encuentre seleccionado (getTabs().getSelected()), que será devuelto en una promesa. Utilizaremos luego simplemente el atributo id para acceder a la categoría activa, iniciando (s.startAutoplay()) o parando (s.stopAutoplay()) la reproducción según el botón que se haya pulsado (play o pause):
function play() {
getTabs().getSelected().then(function(tab) {
document.getElementById(tab).startAutoplay();
});
}
function pause() {
getSlides().forEach(function(s) {
s.stopAutoplay();
});
}
Al ajustar la velocidad de reproducción
Cada vez que movamos el control deslizante, se generará un evento ionChange, que nos indicará que tenemos que cambiar la velocidad con la que se muestran las diapositivas:
Puesto que la posición izquierda del control deslizante indicará menos velocidad (tiempo de espera mayor), y la posición derecha mayor velocidad (tiempo de espera menor), bastará con hacer una resta del tiempo máximo de visualización (5000 milisegundos) para calcular el tiempo (delay) que deberá permanecer cada imagen en pantalla:
autoplay: {
delay: 5000 - getDelay().value
}
Inicializamos también la anchura de cada diapositiva indicando que ocupan toda la ventana:
s.options = {
width: window.innerWidth,
...
};
Al cambiar de pestaña
Cada vez que cambiemos de pestaña, pararemos la reproducción:
Utilizaremos los eventos onload y resize para inicializar la velocidad de reproducción de las diapositivas que hemos especificado en el control deslizante, así como la anchura de cada una para que ocupen toda la pantalla:
<body onload="onLoad()">
...
function onLoad() {
window.addEventListener('resize', init);
...
init();
}
...
function init() {
getSlides().forEach(function(s) {
...
});
}
function onLoad() {
getDelay().addEventListener("ionChange", init);
window.addEventListener('resize', init);
getTabs().addEventListener("ionTabsWillChange", pause);
init();
}
function getDelay() {
return(document.querySelector('ion-range'));
}
function getTabs() {
return(document.querySelector('ion-tabs'));
}
function getSlides() {
return(document.querySelectorAll('ion-slides'));
}
function init() {
getSlides().forEach(function(s) {
s.options = {
width: window.innerWidth,
autoplay: {
delay: 5000 - getDelay().value
}
};
});
}
function play() {
getTabs().getSelected().then(function(tab) {
document.getElementById(tab).startAutoplay();
});
}
function pause() {
getSlides().forEach(function(s) {
s.stopAutoplay();
});
}
El resultado
Puedes hacer clic aquí para observar el aspecto que tiene la galería de imágenes y probar la funcionalidad resultante utilizando el código especificado.
Más adelante veremos el código JavaScript necesario para llevar a cabo las funciones correspondientes:
deleteItem()
toggleReorder()
addEditItem()
Las listas de tareas
Para mantener dos listas separadas, desde el archivo index.html utilizaremos el elemento <ion-tabs></ion-tabs> que personalizaremos escogiendo un icono y un texto descriptivo. Por ejemplo, si quisiéramos utilizar una lista de tareas para el instituto y otra para casa podríamos utilizar los iconos school y home respectivamente:
Además utilizaremos el nuevo elemento <ion-reorder-group></ion-reorder-group> que nos permitirá reordenar las tareas que coloquemos dentro. Bastará con pulsar encima de un botón habilitado a tal efecto, y sin soltar arrastrar la tarea en cuestión a la nueva posición. En la documentación de IONIC podemos observar cómo esta simple etiqueta gestiona todo el proceso de arrastrar y soltar.
Las tareas dentro de las listas
Utilizaremos elementos <ion-item-sliding></ion-item-sliding> para cada tarea dentro de las listas. De esta forma podremos tener oculto un botón rojo con una papelera, que aparecerá al deslizar la tarea hacia la derecha, y nos permitirá borrar de la lista sólo ese elemento:
Utilizaremos también un elemento <ion-item></ion-item> para agrupar todos los datos de la tarea. De esta forma, bastará con hacer clic sobre una determinada tarea para acceder a los detalles de la misma: <ion-item onclick="..."></ion-item> . Un poco más adelante detallaremos la funcionalidad JavaScript necesaria para crear y mostrar un formulario que nos permita visualizar y editar la tarea seleccionada.
Resaltaremos la descripción de la tarea con una etiqueta <h2></h2>, y a continuación mostremos la fecha de la misma dentro de un párrafo (etiqueta <p></p>). Encerraremos ambos elementos con una etiqueta <ion-label text-wrap></ion-label> que permitirá que el tamaño de cada elemento de la lista se ajuste para contener todo el texto descriptivo de la tarea.
Resumiendo, cada elemento de la lista de tareas vendrá especificado por el siguiente código HTML:
Para visualizar y editar los detalles de cada tarea (al crear una nueva, o al hacer clic sobre una ya existente) utilizaremos el siguiente código HTML:
En el encabezado (<ion-header></ion-header>) simplemente tenemos dos botones para confirmar o cancelar la edición o creación de la tarea.
En el contenido principal del formulario (<ion-content></ion-content>) tendremos tres campos:
<ion-datetime><ion-datetime>: Para indicar la fecha de la tarea. Si estamos creando una nueva tarea, en dicho valor introduciremos la fecha actual utilizando código JavaScript.
<ion-input></ion-input>: Para introducir o modificar la descripción de la tarea. Estará en blanco si se trata de una tarea nueva.
<ion-segment></ion-segment>: Para seleccionar la prioridad de la tarea. En nuestro ejemplo proponemos 4 opciones (radio-button-off para tareas pendientes, radio-button-on para tarea finalizada, snow para una prioridad baja, y flame para una prioridad alta), aunque los iconos utilizados son una simple sugerencia, y se pueden cambiar fácilmente según el gusto de cada uno.
El código JavaScript
Accediendo a cada una de las listas
Por simplificar un poco el código, utilizaremos tres funciones para acceder y actualizar los dos elementos principales (tabs y lists):
function getTab() {
return(document.querySelector('ion-tab:not(.tab-hidden)'));
}
function getList(tab = getTab()) {
let list = localStorage.getItem('todo-list-'+tab.tab);
return list ? JSON.parse(list) : [];
}
function saveList(tab, list) {
localStorage.setItem('todo-list-'+tab.tab, JSON.stringify(list));
printList(tab);
}
La última función (saveList()) nos permitirá guardar de manera persistente cada una de las listas de tareas, utilizando localStorage, de forma que al actualizar el contenido del navegador (o cerrar la app y volverla a abrir), podamos volver a cargar la lista de tareas.
Construyendo cada elemento de la lista de tareas
Mediante la siguiente función obtendremos todo el código necesario para cada tarea, variando simplemente el texto descriptivo de la misma, la fecha, y el icono indicativo de la prioridad:
Para añadir una nueva tarea o editar los detalles de una tarea existente
Utilizaremos la misma función (addEditItem(index)) para añadir una nueva tarea o modificar una existente. En ella, siguiendo las instrucciones indicadas en la documentación de IONIC, crearemos un cuadro de diálogo modal que mostrará el formulario para introducir o actualizar los detalles de la tarea:
function addEditItem(index = false) {
closeItems();
let list = getList();
let item = null;
if (index !== false) item = list[index];
else item = { text:"", date:new Date().toISOString(), icon:"radio-button-off" };
const modal = document.createElement('ion-modal');
modal.component = document.createElement('div');
modal.component.innerHTML = `
<ion-header>
...
</ion-header>
<ion-content>
...
</ion-content>`;
modal.component.querySelector('[color="danger"]').addEventListener('click', () => {
modal.dismiss();
});
modal.component.querySelector('[color="primary"]').addEventListener('click', () => {
let newDate = modal.component.querySelector('ion-datetime').value;
let newText = modal.component.querySelector('ion-input').value;
let newIcon = modal.component.querySelector('ion-segment').value;
if (!newText.length) {
error('The task cannot be empty');
}
else {
let newItem = { text:newText, date:newDate, icon:newIcon };
if (index !== false) list[index] = newItem;
else list.unshift(newItem);
saveList(getTab(), list);
modal.dismiss();
}
});
document.querySelector('ion-app').appendChild(modal);
modal.present();
}
En la primera parte de la función, si recibimos como parámetro la tarea a modificar, recuperaremos los detalles de la misma (fecha, texto descriptivo e icono con la prioridad):
Como se puede observar en la fecha, si en vez de modificar una tarea existente, estamos creando una nueva (parámetro de la función con valor false), entonces obtendremos la fecha actual.
A continuación, crearemos el formulario de edición o creación de tareas, con un encabezado con los botones correspondientes para cancelar y confirmar, y también con el contenido principal, utilizando el código HTML indicado previamente:
Finalmente, nos queda capturar los eventos de clic en el botón de cancelar (color="danger") o confirmar (color="primary"). En el primer caso simplemente cerraremos el cuadro de diálogo sin hacer nada más:
En caso de confirmar la edición o creación de una nueva tarea, primero cogeremos los valores introducidos en el formulario:
modal.component.querySelector('[color="primary"]').addEventListener('click', () => {
let newDate = modal.component.querySelector('ion-datetime').value;
let newText = modal.component.querySelector('ion-input').value;
let newIcon = modal.component.querySelector('ion-segment').value;
...
});
En segundo lugar comprobaremos la longitud de la descripción de la tarea, para asegurarnos que no está vacía, en cuyo caso mostraríamos un error:
function error(message) {
const alert = document.createElement('ion-alert');
alert.message = message;
alert.buttons = ['OK'];
document.querySelector('ion-app').appendChild(alert);
alert.present();
}
function addEditItem(index = false) {
...
if (!newText.length) {
error('The task cannot be empty');
}
...
}});
Si estamos modificando una tarea existente, crearemos la nueva tarea con los nuevos datos del formulario, la reemplazaremos por la existente, guardaremos la lista, y cerraremos el cuadro de diálogo.
Si estamos añadiendo una nueva tarea, crearemos un nuevo elemento <ion-item-sliding></ion-item-sliding> que contendrá la fecha, descripción e icono introducidos en el formulario. A continuación añadiremos dicha tarea a la lista que estemos modificando, la guardaremos, y cerraremos el cuadro de diálogo:
La totalidad de la funcionalidad necesaria para reordenar las tareas de una lista ya nos la proporciona IONIC mediante el elemento <ion-reorder></ion-reorder>. Lo único que debemos hacer por nuestra parte, es cambiar el valor del atributo disabled del elemento <ion-reorder-group></ion-reorder-group> de la lista seleccionada:
function toggleReorder() {
closeItems();
let reorder = getTab().querySelector('ion-reorder-group');
reorder.disabled = !reorder.disabled;
}
En caso de que se esté mostrando algún botón de borrado de una tarea, se ocultará para habilitar correctamente la funcionalidad de reordenar elementos.
Cuando la opción de reordenar se encuentra activa, se mostrará un nuevo icono en todas las tareas. Si lo mantenemos pulsado, nos permitirá arrastrar la tarea correspondiente para soltarla en una nueva posición.
Al volver a pulsar el botón de reordenar ubicado en el encabezado de la aplicación, la opción de reordenar se desactivará, evitando que cambiemos accidentalmente el orden de las tareas.
Borrando tareas
Para borrar todas las tareas de la lista seleccionada, pulsaremos el botón que hemos puesto en el encabezado principal de la aplicación. Y para borrar una tarea específica, pulsaremos el botón que aparece al deslizar la tarea hacia la derecha. En ambos casos, utilizaremos la misma función JavaScript (deleteItem(index)), pasando como parámetro el elemento HTML que contiene la tarea a borrar, o el valor false para borrar todas las tareas de la lista seleccionada.
Para evitar borrados accidentales, pediremos confirmación al usuario utilizando un cuadro de diálogo de tipoAlert que ya nos proporciona IONIC:
Como se puede observar, al confirmar el borrado, si se recibe un elemento por parámetro, sólo se borra dicho elemento. En caso contrario, se borrará toda la lista, utilizando una cadena vacía para eliminar todo el código HTML que contenía. Finalmente, guardaremos la lista correspondiente:
...
let list = getList();
if (index !== false) { list.splice(index, 1); }
else { list.length = 0; }
saveList(getTab(), list);
...
Eventos a tener en cuenta
Cada vez que reordenemos las tareas de alguna lista, deberemos volverla a guardar para que aparezcan en el orden correcto al volver a abrir la aplicación. Para ello capturaremos el evento ionItemReorder, tal como se indica en la documentación de IONIC, apartado de JavaScript:
document.addEventListener('ionItemReorder', (event) => { moveItem(event.detail); });
...
function moveItem(indexes) {
let tab = getTab();
let list = getList(tab);
let item = list[indexes.from];
list.splice(indexes.from, 1);
list.splice(indexes.to, 0, item);
indexes.complete();
saveList(tab, list);
}
Cada vez que cerremos un cuadro de diálogo (eventos ionModalDidDismiss y ionAlertDidDismiss) cerraremos los elementos de la lista que pudieran estar deslizados hacia la derecha.
document.addEventListener("ionModalDidDismiss", closeItems);
document.addEventListener("ionAlertDidDismiss", closeItems);
...
function closeItems() {
getTab().querySelector('ion-list').closeSlidingItems();
}
Y por último, cada vez que iniciemos la aplicación (evento onload), leeremos las listas que se encuentran guardadas en el navegador:
function onLoad() {
...
document.querySelectorAll('ion-tab').forEach(function(t) { printList(t); });
}
Puedes hacer clic aquí para observar el aspecto que tiene la aplicación de lista de tareas y probar la funcionalidad resultante utilizando el código especificado.
Ya desde la primera versión vinieron pisando fuerte, y apostaron por utilizar Angular, que ya había demostrado ser un framework muy recomendable, ya que proporcionaba una estabilidad, modularidad y escalabilidad únicas en el código generado. Gracias a ese alto potencial, los creadores de IONIC predijeron que esa fusión permitiría el desarrollo de aplicaciones híbridas que podrían llegar a competir incluso con las nativas (https://ionicframework.com/present-ionic/slides).
Angular hace posible que podamos desarrollar aplicaciones complejas con tecnología web. Esto se consigue gracias a que permite al programador crear nuevas etiquetas HTML, y componentes específicos incrustados en una arquitectura vista-controlador, muy adecuada en el desarrollo de aplicaciones para dispositivos móviles.
Además, desde un principio se ha mantenido la filosofía de no obligar al programador a adquirir conocimientos complejos ni específicos de cada plataforma, ni a tener que desarrollar un código diferente para acceder al hardware de los distintos dispositivos móviles:
Por un lado, se utiliza Angular para aprovechar las habilidades de muchos desarrolladores web que ya conocen los conceptos de componentes, directivas y servicios, que además son completamente compatibles con cualquier navegador, y permiten por lo tanto utilizar un sólo código fuente compatible con dispositivos muy diferentes.
Por otro lado, se utiliza Cordova para acceder al hardware de los dispositivos, permitiendo interactuar con el mismo utilizando código Javascript.
IONIC destacó principalmente porque aportaba una interfaz de usuario muy atractiva que combinada con Angular y Cordova, proporcionaba al programador un entorno único en su época para el desarrollo de aplicaciones híbridas:
Fuimos muchos los que descubrimos y utilizamos esas primeras versiones para desarrollar aplicaciones (https://showcase.ionicframework.com/apps/archive), y prueba de ello fue que la Play Store y la App Store comenzaron a recibir una gran cantidad de aplicaciones híbridas, que además era rápidas y estables, unas características que hace años sólo podían encontrarse en aplicaciones nativas.
Sin embargo, esas primeras versiones obligaban a los desarrolladores de IONIC a ir adaptando su código a medida que Angular iba evolucionando. Aunque eso ya ha cambiado…
¿Por qué IONIC 5?
Somos muy afortunados de poder disfrutar actualmente de todo el potencial de este framework que desde su versión 4 incluye mejoras muy importantes. En el siguiente enlace podemos consultar la documentación actualizada: https://ionicframework.com/docs.
Los cambios respecto a la versión 3 son muy significativos (https://blog.ionicframework.com/introducing-ionic-4-ionic-for-everyone) y desde luego no dejan lugar a dudas para decantarnos por la última versión de IONIC para desarrollar nuestros proyectos web, o aplicaciones móviles híbridas.
El objetivo original de IONIC era desarrollar una librería compatible con cualquier tecnología web, y que pudiera incluirse en cualquier proyecto independientemente de la librería o framework que ya estuviera utilizando el programador. Y esto no era posible, hasta ahora…
En la práctica, los últimos cambios hechos en la arquitectura de la librería, permiten que los componentes de IONIC se puedan utilizar con la misma simpleza que cualquier otra etiqueta HTML, pero nos proporcionan una funcionalidad ampliada. Por ejemplo, podríamos hacer uso de la etiqueta <ion-button></ion-button> en cualquier framework, e incluso de manera independiente (sin ninguna arquitectura o código base específico), utilizando la sencilla sintaxis de Javascript. En esta última versión, el navegador puede reconocer los nuevos elementos HTML sin realizar ningún tipo de modificación en nuestro código.
Ya que tenemos el lujo de poder utilizar IONIC 5, no vamos a dejar escapar la posibilidad de disfrutar de la sencillez, facilidad de desarrollo y versatilidad que nos proporciona esta última versión, tal como apreciaremos en los próximos ejercicios.
De esta forma quedarán definidos automáticamente los nuevos elementos, haciéndolos completamente accesibles al navegador para que nosotros sólo tengamos que escribir la etiqueta correspondiente en nuestro archivo HTML. A su vez además podremos interactuar con ellos utilizando código JavaScript.
Esta magnífica innovación que nos proporciona IONIC desde su versión 4 simplifica muchísimo el acceso a sus archivos, ya que podemos utilizarlos sin necesidad de descargar ni instalar absolutamente nada.
Como se puede observar, estamos utilizamos la versión 5.0.7 de IONIC, que es la última que actualmente podemos encontrar en repositorio. Por otro lado, si deseáramos utilizar siempre la última versión, también podríamos acceder a dichos archivos utilizando la referencia «@ionic/core@latest». En tal caso, debemos tener en cuenta que con el paso del tiempo deberíamos comprobar si los últimos cambios pudieran afectar al código que escribimos inicialmente.
Más adelante describiremos el procedimiento para acceder a la funcionalidad de IONIC sin enlazar el código directamente desde Internet y así evitaremos que se descargue cada vez. Sin embargo, de momento podemos centrarnos en el código y el resultado, utilizando un simple editor de texto, sin necesidad de tener que instalar nada en nuestros equipos para poder utilizar IONIC.
Colocando los botones
En el ejemplo anterior hemos utilizado una tabla de HTML para distribuir los botones en filas y columnas. Sin embargo, las tablas se usan cada vez menos con este propósito. En su lugar se suele utilizar código CSS para ubicar o distribuir los diferentes elementos.
En este aspecto, IONIC nos proporciona una herramienta increíble conocida como grid, que tiene un comportamiento muy similar al de otras librerías de desarrollo web, como por ejemplo Bootstrap.
IONIC ha creado para nosotros tres nuevos elementos HTML que nos permiten establecer una cuadrícula muy fácilmente. Delimitamos los elementos que vamos a colocar mediante <ion-grid></ion-grid> y luego establecemos las filas mediante <ion-row></ion-row> y las columnas con <ion-col></ion-col>.
Gracias a esto, podemos distribuir los botones de la calculadora utilizando el elemento grid de IONIC con un código casi idéntico al que utilizaríamos con una simple tabla de HTML, simplemente reemplazando las etiquetas de la siguiente forma:
<table></table> -> <ion-grid></ion-grid>
<tr></tr> -> <ion-row></ion-row>
<td></td> -> <ion-col></ion-col>
Y no sólo nos proporciona una forma sencilla de realizar dicha distribución, sino que nos aporta mucha más flexibilidad que las tablas de HTML, tal como se puede observar en la documentación oficial. Además, por defecto ocupará todo el ancho de la pantalla (el comportamiento esperado en cualquier dispositivo móvil) y cada fila podrá tener un número diferente de columnas, cuya anchura se distribuirá por defecto de manera uniforme.
Por ejemplo, para dibujar la siguiente tabla:
0
1
2
3
+
4
5
6
–
7
8
9
*
0
/
AC
.
=
En el ejercicio anterior hemos utilizado el siguiente código HTML:
Tenemos que destacar la forma que nos ofrece IONIC 5 para establecer el tamaño de una celda en concreto. Por ejemplo, en nuestro caso, para conseguir que la tecla del cero ocupe 3/4 de la pantalla, y que la tecla de borrado ocupe la mitad, ajustamos el valor del atributo size en el elemento ion-col:
IONIC establece por defecto que en la pantalla caben 12 columnas. Por lo tanto, al especificar <ion-col size="9">0</ion-col>, estamos diciéndole al navegador que esa celda ocupará 9 columnas de un total de 12, es decir, 3/4 de la pantalla. Y al especificar <ion-col size="6">AC</ion-col>, estamos diciéndole al navegador que esa celda ocupará 6 columnas de un total de 12, es decir, la mitad de la pantalla.
Observamos además que en la primera fila, donde mostramos el resultado, no hace falta especificar el tamaño, ya que por defecto, la celda se expande automáticamente para ocupar todo el ancho de la fila.
Veremos más adelante que el número de columnas por defecto se puede configurar cambiando el valor de la variable CSS --ion-grid-columns.
Los nuevos botones
Con IONIC 5 disponemos de un nuevo elemento <ion-button></ion-button>. Se utiliza de forma similar al <button></button> de HTML, pero como podemos leer en la documentación, ahora podemos cambiar el aspecto del botón mediante atributos específicos tales como expand, fill, size o color. Por ejemplo, podríamos definir el botón de borrado de la siguiente forma, sin necesidad de utilizar ningún otro código adicional:
El navegador nos mostrará automáticamente un botón de color rojo que ocupará todo el ancho de la columna.
Como podemos observar, la sencillez y similitud respecto al código básico HTML son obvias. El mayor cambio en este nuevo elemento es que ahora no necesitamos ajustar ninguna propiedad CSS para obtener el aspecto deseado, tal como hacíamos antes:
.ac {
color: red;
}
<button class="ac">AC</button>
Para cambiar el estilo
En este apartado comenzaremos a apreciar la verdadera magia de IONIC 5. Fijémos en la similitud entre este código:
<select>
<option value="clear">Sin borde ni color</option>
<option value="outline">Sólo borde</option>
<option value="solid" selected>Con color de fondo</option>
</select>
y este otro:
<ion-select value="solid">
<ion-select-option value="clear">Sin borde ni color</ion-select-option>
<ion-select-option value="outline">Sólo borde</ion-select-option>
<ion-select-option value="solid">Con color de fondo</ion-select-option>
</ion-select>
A simple vista podemos intuir que el resultado debería ser el mismo. Sin embargo, al pulsar sobre el segundo select, nos aparecerá un cuadro de diálogo con un diseño muy cuidado que además será diferente en móviles IOS y en Android. Además, por defecto dispondremos de un botón para aceptar y otro para cancelar.
En la documentación de IONIC podemos deleitarnos observando el resultado que llegaremos a obtener haciendo uso de este nuevo elemento. Encontraremos además en ese enlace todos los posibles atributos con sus posibles respectivos valores, que nos permitirán ajustar el comportamiento del mismo a nuestros gustos y necesidades.
Debemos matizar que para obtener una correcta visualización, deberemos incluir cada elemento dentro de una lista, tal como se observa en los ejemplos, y que utilizaremos el elemento <ion-label></ion-label> para establecer el texto descriptivo, que también disparará el evento onclick automáticamente al pulsar encima. Por ejemplo, en nuestro caso:
La verdad es que resulta difícil contestar a esa pregunta, y por eso los desarrolladores de IONIC nos permiten elegir el aspecto que van a tener los diferentes elementos de nuestros formularios. Para ello han añadido el atributo mode a muchos elementos, de forma que podamos especificar los valores «ios» o «md».
En caso de que queramos cambiar el estilo de todos los elementos de la calculadora al mismo tiempo, IONIC también nos permite hacerlo modificando la url, tal como se especifica en la documentación:
index.html?ionic:mode=md
index.html?ionic:mode=ios
Para mejorar el diseño de la calculadora, añadiremos a la misma un encabezado (elemento <ion-header></ion-header>) que contendrá un título y dos botones con un atributo href que apuntarán a esas dos urls. Además, observaremos al probar la nueva calculadora que el propio estilo del encabezado que contiene esos dos botones también se actualizará:
Los elementos <ion-select></ion-select> generarán un evento ionChange automáticamente cada vez que cambie el valor seleccionado. Lo único que tenemos que hacer es estar atentos y volver a ajustar el diseño de la calculadora cuando se dispare dicho evento. Para que se ejecute una acción automáticamente al detectar cualquier cambio, utilizamos la siguiente línea:
document.addEventListener("ionChange", setStyle);
Una vez se ejecute la función setStyle(), lo único que tendremos que hacer es recorrer todos los botones y actualizar las propiedades que queramos. En nuestro caso podemos cambiar las siguientes propiedades:
expand: «block»
strong: «true»
fill: «clear», «outline» o «solid»
size: «small», «default» o «large»
Podemos volver a consultar la respectiva documentación si deseamos realizar cualquier modificación en el código.
En este ejercicio vamos a demostrar que puede resultar muy sencillo desarrollar una aplicación que nos permita mantener un registro de lugares interesantes, guardando la ubicación precisa y una imagen identificativa de cada uno.
Será el mismo navegador el que nos proporcione la ubicación actual de forma automática, y nos permita elegir las imágenes a utilizar, de entre las que tengamos en nuestros equipos. Además, al ejecutarla como app en nuestros móviles, podremos utilizar también la cámara en el mismo momento, pudiendo hacer una foto que se quedará guardada en nuestra aplicación. De esa forma, tendremos imágenes asociadas con la latitud y la longitud de los sitios donde hayamos hecho las fotos, pudiendo acceder además al mapa correspondiente con un solo clic.
Con sólo 100 líneas de código
Veremos que al utilizar IONIC 4 + Angular, con sólo 100 líneas de código (sumando HTML y Typescript) podemos desarrollar una aplicación multiplataforma, que funcionará perfectamente en cualquier navegador, o instalada como app en nuestros dispositivos móviles. Y todo ello, con el mismo código fuente, sin cambiar ni una sola línea.
La funcionalidad de la aplicación
La aplicación dispondrá de un cuadro de texto donde podamos especificar la descripción del nuevo lugar que queramos registrar.
Si ya hemos introducido alguna descripción, se habilitará un nuevo control para poder elegir una imagen. Si estamos ejecutando la aplicación desde nuestro ordenador de escritorio, podremos elegir cualquier imagen de las que se encuentran en nuestro equipo. Si ejecutamos la aplicación desde nuestros dispositivos móviles, podremos hacer una foto en ese mismo momento.
Una vez introducida la descripción y elegida o hecha la foto, la aplicación obtendrá automáticamente la ubicación en la que nos encontremos y creará un elemento en la lista de lugares en el que aparecerá la foto y la descripción.
Al hacer clic sobre cada elemento de la lista, si estamos utilizando el navegador de nuestro ordenador de escritorio, abrirá la página web correspondiente de Google Maps para mostrarnos la ubicación precisa. Si estamos ejecutando la aplicación desde nuestros dispositivos móviles, nos permitirá elegir qué aplicación se utilizará para visualizar la ubicación.
Además, podremos reordenar los elementos de la lista activando un control por cada elemento, que se podrá ocultar o mostrar mediante un botón ubicado en la barra superior de la aplicación.
Por último, permitiremos borrar cualquier elemento de la lista utilizando un botón oculto que se mostrará al deslizar el elemento correspondiente a la derecha.
Primeros pasos
Código base de la aplicación
Para comenzar a desarrollar nuestra aplicación deberemos partir de un código base generado automáticamente por IONIC. Bastará con ejecutar el siguiente comando para generar los ficheros necesarios:
Al finalizar el proceso, se habrá creado un directorio con el nombre lugares, que constituye el directorio raíz de nuestro proyecto, donde tendremos todo el código necesario para continuar con el desarrollo de nuestra aplicación. Para realizar los siguientes pasos deberemos acceder primero a dicha carpeta:
cd lugares
Y a partir de ahora ya podemos comenzar a añadir el código específico de nuestra aplicación. Puesto que toda la funcionalidad se encuentra ubicada en una única pantalla, sólo vamos a modificar dos ficheros:
src/app/home/home.page.html: Código HTML.
src/app/home/home.page.ts: Código TypeScript.
Probando la aplicación en el navegador
La misma herramienta de consola de IONIC se puede utilizar como servidor web, lo que nos permitirá probar nuestra aplicación fácilmente en el navegador. Además, cada vez que realicemos cualquier cambio en el código fuente, veremos cómo se actualiza el resultado automáticamente. Bastará con ejecutar el siguiente comando desde la carpeta raíz del proyecto donde se encuentra nuestra aplicación, dejando el terminal abierto:
ionic serve
Si todo ha ido bien, se compilará la aplicación y deberíamos visualizar en el navegador algo del siguiente estilo:
(bastará con pulsar Ctrl+C en el terminal para finalizar la ejecución del comando)
Código HTML
El encabezado
Como ya viene siendo habitual, colocaremos una barra superior que contenga el título y el botón de reordenar:
En cada pulsación del botón de reordenar, activaremos o desactivaremos dicha funcionalidad cambiando simplemente el valor del atributo correspondiente.
El formulario para la descripción y la imagen
Utilizaremos un simple cuadro de texto para introducir la descripción del lugar que queramos registrar y un elemento HTML estándar de tipo fichero para elegir la imagen:
Debemos destacar de nuevo el uso de la directiva ngModel, que nos permitirá enlazar el valor del atributo text, de forma que cualquier modificación en el código HTML cambiará automáticamente el valor del atributo en el código TypeScript y viceversa.
En segundo lugar, también destacamos el uso de la directiva ngIf, que automatiza la creación del campo de tipo fichero, de forma que dicho campo no existirá hasta que se introduzca algún texto en el campo de la descripción. Además, al inicializar de nuevo la descripción desde el código TypeScript, dicho campo desaparecerá automáticamente.
Por último, al final del cuadro de elección de imagen colocaremos un icono para acceder a la cámara de nuestros dispositivos móviles, utilizando como imagen la fotografía que realicemos en ese momento. Esta funcionalidad no estará operativa cuando utilicemos la aplicación desde el navegador.
Los elementos de la lista
Cada elemento de la lista será un enlace a la página web correspondiente de Google Maps para conocer la ubicación del lugar, de forma que podamos acceder fácilmente con un simple clic en el elemento correspondiente. Para ello bastará con especificar la url mediante el atributo href del elemento <ion-item></ion-item> (más información aquí).
Además, utilizaremos el elemento <ion-thumbnail></ion-thumbnail> para mostrar una miniatura de la imagen. Por último, añadiremos dos botones, uno para reordenar (<ion-reorder></ion-reorder>) y otro para borrar (<ion-item-option></ion-item-option>):
Vamos a hacer uso del potencial de Angular para mostrar la lista completa de lugares:
<ion-reorder-group [disabled]="!reorder" (ionItemReorder)="move($event.detail)">
<ion-item-sliding *ngFor="let item of list; let i=index">
...
</ion-item-sliding>
</ion-reorder-group>
En primer lugar utilizaremos la expresión [disabled]="!reorder" para activar o desactivar la opción de reordenar los elementos. Mediante los corchetes enlazamos el valor del atributo reorder de TypeScript para poder acceder a su valor directamente. El evento (ionItemReorder) nos indicará que se ha cambiado de sitio algún elemento, y por lo tanto ejecutaremos el método move(), cuya funcionalidad veremos más adelante.
Por último, sólo necesitamos realizar un bucle utilizando la directiva ngFor, que además nos proporcionará cada elemento item del array que representa la lista, así como el índice i de cada uno de ellos.
En este ejercicio sólo necesitamos cuatro campos para guardar todos los datos necesarios:
reorder: boolean;
list: any;
text: string;
El propósito de cada uno de ellos es el siguiente:
reorder: Su valor (falso o verdadero) indicará si se encuentra habilitada o no la funcionalidad de reordenar.
list: Array que contendrá todos los lugares.
text: Descripción del nuevo lugar que queremos registrar.
Los métodos
constructor: Recuperará la lista guardada para que se muestre al iniciar la aplicación.
add: Añadirá un nuevo lugar a la lista de lugares.
save: Guardará en localStorage el listado de lugares para que no se pierda al cerrar la aplicación o recargar el contenido del navegador.
delete: Borrará un elemento de la lista.
move: Moverá un elemento de la lista a otra posición.
Obteniendo la ubicación
Para conocer la ubicación desde la que vamos a registrar un nuevo lugar, utilizaremos la funcionalidad de geolocalización de HTML, de forma que consigamos que el código funcione perfectamente tanto en el navegador, como en la app una vez compilada (más información aquí):
navigator.geolocation.getCurrentPosition(pos => {
let url = "https://maps.google.com/maps?&z=15&t=k&q="+pos.coords.latitude+" "+pos.coords.longitude;
...
});
Además, crear un enlace a Google Maps conociendo la ubicación resulta muy sencillo. En nuestro caso especificamos un zoom razonable (z=15), el tipo de mapa satélite (t=k), y la latitud y la longitud (q=…). Se puede obtener más información en la página oficial de Google, o también por ejemplo aquí.
Indicador de ejecución en proceso
Mientras se obtiene la ubicación y se procesa la imagen, resulta conveniente bloquear la aplicación mediante algún indicador para informar al usuario que todavía no se ha completado la acción. Para ello mostraremos un indicador en pantalla utilizando la funcionalidad que nos proporciona IONIC (más información aquí):
Utilizando simple código HTML y JavaScript, podemos obtener la imagen seleccionada en formato base 64, de forma que la podemos guardar como si se tratara de cualquier otra cadena de texto (más información aquí, o aquí):
Además, añadiremos la funcionalidad necesaria para abrir la cámara cuando ejecutemos la aplicación en nuestros dispositivos móviles (más información en la documentación de Cordova):
El fichero config.xml (plugins de geolocalización y cámara)
Para que la aplicación tenga los permisos correctos, y podamos utilizar la funcionalidad nativa para conocer la ubicación y acceder a la cámara, añadiremos los plugins correspondientes al fichero config.xml (más información aquí y aquí):
Debemos recordar que si compilamos la aplicación utilizando PhoneGap Build, el archivo config.xml se deberá guardar dentro de la carpeta www.
Por último, tengamos en cuenta que toda la funcionalidad de la aplicación se encontrará disponible tanto en el navegador como en la app, exceptuando la cámara, ya que es específica del dispositivo, y no estará operativa en el navegador.
El fichero index.html
Si utilizamos PhoneGap Build deberemos modificar también el fichero src/index.html para enlazar el fichero cordova.js (no tenemos que crearlo nosotros, sólo enlazarlo, ya que dicho archivo se creará automáticamente al compilar el código con PhoneGap Build):
Puedes hacer clic aquí para probar la aplicación propuesta. Se puede observar que el mismo código que hemos estado desarrollando se puede subir a cualquier servidor y el resultado se puede visualizar perfectamente en el navegador como cualquier otra página web.
Una vez hayas probado tu aplicación en el navegador con el comando ionic serve, puedes compilar el código para subirlo a PhoneGap Build y obtener el fichero .apk para instalarlo en tus dispositivos móviles. Bastará con seguir los pasos que se indican a continuación.
En primer lugar debemos cambiar el archivo tsconfig.json para que el campo target tenga el valor es5 (más información en stackoverflow):
...
"target": "es5"
...
El segundo paso consistirá en ejecutar el siguiente comando desde el directorio del proyecto (puedes encontrar más información aquí):
ionic build --prod -- --base-href .
Ese comando generará una carpeta www dentro de tu proyecto con todo el código web necesario. A continuación deberemos añadir allí el fichero config.xml, incluyendo el plugin cordova-plugin-ionic-webview (puedes obtener más información en la página de github de dicho plugin):
Si deseamos utilizar algún plugin (la cámara, por ejemplo) también deberemos modificar el fichero src/index.html para enlazar el fichero cordova.js (no tenemos que crearlo nosotros, sólo enlazarlo, ya que dicho archivo se creará automáticamente al compilar el código con PhoneGap Build):
Como último paso sólo deberemos comprimir todos los archivos dentro de la carpeta www (index.html, config.xml, …) para generar el archivo zip que subiremos a PhoneGap Build.
En los ejercicios que hemos desarrollado hasta ahora sólo hemos utilizado IONIC de manera independiente, sin echar mano de ningún otro framework adicional, ya que hemos interactuado con los nuevos elementos HTML utilizando código JavaScript.
Para aplicaciones sencillas, puede ser una opción completamente válida y muy recomendable, dado el bajo nivel de requerimientos. Sólo necesitamos conocimientos básicos de desarrollo web, y con una simple referencia desde el código HTML, ya tenemos a nuestro alcance todo el potencial ofrecido por IONIC 4.
Sin embargo, si pretendemos desarrollar aplicaciones más complejas, resulta muy recomendable usar la segunda posibilidad que nos ofrece IONIC 4, al permitirnos combinar unos resultados visuales muy buenos, con un estilo de programación muy estructurado, y con amplias posibilidades dentro del desarrollo web.
En este ejercicio vamos a dar un paso adelante, y observaremos el potencial del que disponemos al usar IONIC con un framework tan utilizado y conocido como Angular. Desarrollaremos la misma aplicación que en un ejercicio anterior, pero veremos que el código fuente estará mucho mejor estructurado, y nos ofrecerá muchas más posibilidades que el simple código JavaScript.
Además, al tener la suerte de poder utilizar IONIC 4, disponemos de toda la documentación por duplicado para que podamos escoger la opción que más nos interese. En las versiones anteriores, IONIC se encontraba empaquetado con Angular, con lo que sólo teníamos una alternativa. Ahora mismo tenemos a nuestro alcance las mismas facilidades, tanto si escogemos desarrollar nuestra app utilizando HTML+JavaScript o Angular. Además, sea cual sea la opción por la que nos declinemos, IONIC nos proporciona la funcionalidad y la documentación necesaria para cada uno de sus componentes en ambos casos:
Como opinión personal, me gustaría decir que una vez se aprende Angular, resulta difícil desprenderse de él, ya que se pueden desarrollar aplicaciones más complejas y mucho más estables, con un código fuente más legible y mucho más fácil de mantener y actualizar.
La funcionalidad de la aplicación
La funcionalidad a implementar será la misma que en el ejercicio anterior, aunque en esta ocasión vamos a estructurar mejor el código y dividiremos la aplicación en dos pantallas.
Vamos a enumerar de nuevo los aspectos principales de la aplicación, y observaremos que podemos obtener el mismo resultado desarrollando la aplicación con Angular.
La pantalla principal
Gestionar varias listas de tareas independientes.
Borrar de golpe todas las tareas de una lista.
Borrar una sola tarea de una lista determinada mediante un botón oculto que se visualizará al deslizar hacia la derecha la tarea a borrar.
Pedir confirmación antes de borrar cualquier tarea.
Permitir ordenar las tareas arrastrando cualquier elemento a una nueva posición.
La pantalla de detalles
Añadir tareas indicando la fecha, la descripción de la tarea, y un icono identificativo de la prioridad.
Visualizar y editar los detalles de cada tarea haciendo clic sobre el elemento correspondiente de la lista.
Primeros pasos
Requisitos
La mayoría de las aplicaciones de IONIC se crean y desarrollan principalmente utilizando la herramienta de la línea de comandos. A continuación enumeraremos los pasos necesarios para instalar el software necesario en nuestros ordenadores para poder desarrollar y compilar desde la consola aplicaciones de IONIC utilizando Angular.
En primer lugar, deberemos instalar Node.js (la versión LTS, recomendada para la mayoría de usuarios, tal como se indica en la web). Podremos comprobar si se ha instalado correctamente de la siguiente forma:
node --version
npm --version
A continuación deberemos instalar el cliente de IONIC, de la siguiente forma:
npm install -g ionic
La opción -g significa que se va a instalar de manera global en nuestros equipos, y por lo tanto necesitaremos permisos de administrador. En el caso de utilizar un sistema operativo Windows es recomendable abrir un terminal en modo administrador. Para sistemas operativos Mac/Linux, el comando se deberá ejecutar con sudo.
Código base de la aplicación
Para comenzar a desarrollar nuestra aplicación deberemos partir de un código base generado automáticamente por IONIC. Bastará con ejecutar el siguiente comando para generar los ficheros necesarios:
Al ejecutar el comando, IONIC nos informará del proceso con algunos avisos y si todo va bien, al final nos mostrará un mensaje similar al siguiente:
[INFO] Next Steps:
- Go to your newly created project: cd ./tareas
- Run ionic serve within the app directory to see your app
- Build features and components: https://ion.link/scaffolding-docs
- Get Ionic DevApp for easy device testing: https://ion.link/devapp
Al finalizar el proceso, se habrá creado un directorio con el nombre tareas, que constituye el directorio raíz de nuestro proyecto, donde tendremos todo el código necesario para continuar con el desarrollo de nuestra aplicación. Para realizar los siguientes pasos deberemos acceder primero a dicha carpeta:
cd tareas
Y por último, puesto que la aplicación básica que acabamos de crear sólo dispone de una pantalla principal, procederemos además a añadir una página adicional, y un fichero auxiliar (servicio) donde ubicaremos la funcionalidad básica para gestionar las listas y sus elementos:
ionic generate page AddEditItem
ionic generate service list
Y a partir de ahora ya podemos comenzar a añadir el código específico de nuestra aplicación. Aunque la funcionalidad sea la misma, al utilizar Angular, el código fuente sí tendrá variaciones, que como vamos a observar a continuación, nos permitirán generar un código mejor estructurado y más legible que el de una aplicación del mismo tamaño desarrollada simplemente con JavaScript.
Si hemos ejecutado correctamente los comandos anteriores, IONIC ya nos habrá creado todos los ficheros que vamos a utilizar en este ejercicio:
src/app/list.service.ts: Funciones auxiliares.
src/app/home/home.page.html: Código HTML de la pantalla principal.
src/app/home/home.page.ts: Código TypeScript de la pantalla principal.
src/app/add-edit-item/add-edit-item.page.html: Código HTML de la pantalla con los detalles de las tareas.
src/app/add-edit-item/add-edit-item.page.ts: Código TypeScript de la pantalla con los detalles de las tareas.
Probando la aplicación en el navegador
Una de las primeras ventajas con las que nos encontramos es que la misma herramienta de consola de IONIC se puede utilizar como servidor web, con lo que nos permitirá probar nuestra aplicación en el navegador al mismo tiempo que se actualiza automáticamente después de realizar cualquier cambio en el código fuente. Bastará con ejecutar el siguiente comando desde la carpeta raíz del proyecto donde se encuentra nuestra aplicación, dejando el terminal abierto:
ionic serve
Si todo ha ido bien, se compilará la aplicación y deberíamos visualizar en el navegador algo del siguiente estilo:
(bastará con pulsar Ctrl+C en el terminal para finalizar la ejecución del comando)
Funciones auxiliares
En el fichero list.service.tsdesarrollaremos el código de una clase que contenga un array de listas de tareas y las funciones básicas para acceder a las mismas y a sus elementos:
getList(index): Nos devolverá la lista completa ubicada en la pestaña index (la primera pestaña vendrá indicada por el valor cero).
saveList(listIndex): Guardará la lista indicada de manera permanente utilizando localStorage.
getItem(listIndex, itemIndex): Devolverá una tarea en particular.
setItem(listIndex, item, itemIndex?): Añadirá una tarea a la lista indicada, o si se indica el índice de una tarea concreta, la actualizará en vez de añadirla.
deleteItem(listIndex, itemIndex): Borrará la tarea indicada.
deleteList(listIndex): Borrará una lista entera.
moveItem(listIndex, from, to): Cambiará de orden una tarea, moviéndola de la posición from a la posición to.
En el fichero home.page.ts desarrollaremos el código de una clase que contendrá las variables y las funciones necesarias para manipular las listas y las tareas que se visualizan en la pantalla principal:
constructor(ListService, AlertController): En el constructor inicializaremos el array de objetos con las propiedades que definen las diferentes listas (etiqueta, icono y lista de tareas). Si ya existen tareas al iniciar la aplicación, se leerán para que aparezcan en pantalla.
toggleReorder(): Para activar o desactivar el botón de los elementos para reordenarlos.
setTab(tabIndex): Activará la pestaña con el índice indicado.
deleteItem(item?): Después de pedir confirmación, borrará todos los elementos de la lista de tareas activa, o sólo el elemento indicado, si éste se ha pasado por parámetro.
moveItem(indexes): Recibe un objeto con la posición inicial de una tarea en la lista y la posición final donde se debe mover.
Para implementar la barra superior de la pantalla principal, ahora utilizaremos el siguiente código:
Ya podemos observar algunas diferencias respecto al código JavaScript. En primer lugar, en vez de utilizar el atributo del evento onclick utilizaremos la sintaxis (click) (más detalles en la documentación de Angular). En segundo lugar, observamos una gran diferencia respecto a la opción de añadir un nuevo elemento. En este caso vamos a diseñar una nueva página (a la que accederemos con la url /add-edit-item) en vez de abrir un cuadro de diálogo modal. La sintaxis de Angular utilizada en este caso, nos permite pasar información ({ tab:tabIndex, item:-1 }) de una página a otra, de forma que sepamos a qué lista debemos añadir la nueva tarea, colocando además el índice del item a -1 para indicar que no queremos modificar ninguna tarea existente, sino añadir una nueva:
Respecto a la versión con JavaScript, la mayor diferencia la observamos en las siguientes líneas:
<ion-item-sliding *ngFor="let item of tabs[tabIndex].list; let i=index">
...
<ion-tab-button *ngFor="let tab of tabs; let i=index" (click)="setTab(i)" [tab]="tab.label">
Utilizando Angular podemos insertar bucles dentro de nuestro código HTML que nos permitirán mostrar muy fácilmente varios elementos que tengan las mismas propiedades (consultar la documentación de angular para más detalles). De esta forma tendremos el mismo código independientemente de la cantidad de listas de tareas que vayamos a utilizar. Bastará con especificar en el constructor las etiquetas y los iconos de cada pestaña utilizando un simple array:
En este caso, la propiedad disabled se encierra entre corchetes para unirla a la variable reorder, y el evento ionItemReorder se encierra entre paréntesis.
En el fichero add-edit-item-page.ts desarrollaremos el código de una clase que contendrá las variables y las funciones necesarias para editar o crear una tarea:
constructor(Router, ActivatedRoute, AlertController, ListService): En el constructor inicializaremos por un lado un array con los nombres de los iconos para indicar las prioridades de las tareas, y a continuación recogeremos los parámetros proporcionados desde la pantalla principal (índices de la lista y elemento a modificar, si es el caso).
error(message): Para mostrar mensajes de error si por ejemplo no se ha especificado ningún texto en la descripción de la tarea y pulsamos el botón de confirmar.
save(): Comprobará si desde la pantalla principal se ha elegido una tarea a modificar o si por el contrario se ha pulsado el botón de nueva tarea, y llamará a la función del servicio, volviendo a la pantalla principal a continuación.
La barra superior de esta pantalla sólo contendrá un título y dos botones, uno para cancelar y otro para confirmar. Ambos nos llevarán de nuevo a la pantalla principal, pero si pulsamos el botón de confirmar, previamente se grabarán los datos introducidos:
La novedad a destacar es el uso de [(ngModel)], una de las indudables ventajas que nos ofrece Angular, ya que nos permite enlazar un atributo de la clase con un campo del formulario, de forma que las modificaciones que se produzcan queden reflejadas en los dos sentidos: el valor que tenga dicho atributo en el código TypeScript se mostrará en el código HTML, y cualquier modificación que hagamos en el formulario, también cambiará el valor del atributo de la clase. Se puede consultar la documentación oficial para más detalles.
Por otro lado, también estamos implementando un bucle para mostrar los posibles iconos indicativos de la prioridad de la tarea, lo que sin duda nos ahorra código HTML:
<ion-segment-button *ngFor="let button of buttons" [value]="button">
<ion-icon [name]="button"></ion-icon>
</ion-segment-button>
Al igual que en el constructor de la página principal, bastará con especificar mediante un array todos los botones que deseemos visualizar:
El procedimiento utilizado en este ejemplo supera obviamente al de la versión basada sólo en JavaScript, ya que ahora no sólo hemos mejorado a nivel de código, sino que hemos agilizado el proceso de desarrollo y actualización, obteniendo además una app que no requiere Internet, ya que ahora IONIC no se encuentra enlazado directamente como librería, sino empaquetado dentro de la propia aplicación.
Puedes hacer clic aquí para observar que también podemos acceder al código generado desde el navegador, como cualquier otra página web.
En este ejercicio vamos a demostrar que puede resultar muy sencillo desarrollar una aplicación que nos permita mantener un registro de lugares interesantes, guardando la ubicación precisa y una imagen identificativa de cada uno.
Será el mismo navegador el que nos proporcione la ubicación actual de forma automática, y nos permita elegir las imágenes a utilizar, de entre las que tengamos en nuestros equipos. Además, al ejecutar la app en nuestros móviles, podremos utilizar también la cámara en el mismo momento, pudiendo hacer una foto que se quedará guardada en nuestra aplicación. De esa forma, tendremos imágenes asociadas con la latitud y la longitud de los sitios donde hayamos hecho las fotos, pudiendo acceder además al mapa correspondiente con un solo clic.
Con menos de 100 líneas de código
Veremos que al utilizar IONIC 4, con menos de 100 líneas de código JavaScript podemos desarrollar una aplicación multiplataforma, que funcionará perfectamente en cualquier navegador, o instalada como app en nuestros dispositivos móviles. Y todo ello, con el mismo código fuente, sin cambiar ni una sola línea.
La funcionalidad de la aplicación
La aplicación dispondrá de un cuadro de texto donde podamos especificar la descripción del nuevo lugar que queramos registrar.
Además de poder introducir alguna descripción, se habilitará un nuevo control para poder elegir una imagen. Si estamos ejecutando la aplicación desde nuestro ordenador de escritorio, podremos elegir cualquier imagen de las que se encuentran en nuestro equipo. Si ejecutamos la aplicación desde nuestros dispositivos móviles, podremos hacer una foto en ese mismo momento.
Una vez introducida la descripción y elegida o hecha la foto, la aplicación obtendrá automáticamente la ubicación en la que nos encontremos y creará un elemento en la lista de lugares en el que aparecerá la foto y la descripción.
Al hacer clic sobre cada elemento de la lista, si estamos utilizando el navegador de nuestro ordenador de escritorio, abrirá la página web correspondiente de Google Maps para mostrarnos la ubicación precisa. Si estamos ejecutando la aplicación desde nuestros dispositivos móviles, nos permitirá elegir qué aplicación se utilizará para visualizar la ubicación.
Además, podremos reordenar los elementos de la lista activando un control por cada elemento, que se podrá ocultar o mostrar mediante un botón ubicado en la barra superior de la aplicación.
Por último, permitiremos borrar cualquier elemento de la lista utilizando un botón oculto que se mostrará al deslizar el elemento correspondiente a la derecha.
Código HTML
El encabezado
Como ya viene siendo habitual, colocaremos una barra superior que contenga el título y el botón de reordenar:
En cada pulsación del botón de reordenar, activaremos o desactivaremos dicha funcionalidad cambiando simplemente el valor del atributo correspondiente.
El formulario para la descripción y la imagen
Utilizaremos un simple cuadro de texto para introducir la descripción del lugar que queramos registrar y un elemento HTML estándar de tipo fichero para elegir la imagen:
Destacamos el uso del elemento <input type="file">, que automatiza la creación del campo de tipo fichero, y nos permitirá elegir fácilmente una imagen para asociarla al lugar que queramos dejar registrado.
Por último, al final del cuadro de elección de imagen colocaremos un icono para acceder a la cámara de nuestros dispositivos móviles, utilizando como imagen la fotografía que realicemos en ese momento. Esta funcionalidad no estará operativa cuando utilicemos la aplicación desde el navegador.
Los elementos de la lista
Cada elemento de la lista será un enlace a la página web correspondiente de Google Maps para conocer la ubicación del lugar, de forma que podamos acceder fácilmente con un simple clic en el elemento correspondiente. Para ello bastará con especificar la url mediante el atributo href del elemento <ion-item></ion-item> (más información aquí).
Además, utilizaremos el elemento <ion-thumbnail></ion-thumbnail> para mostrar una miniatura de la imagen. Por último, añadiremos dos botones, uno para reordenar (<ion-reorder></ion-reorder>) y otro para borrar (<ion-item-option></ion-item-option>):
Sólo necesitamos recorrer la lista utilizando por ejemplo un bucle forEach, que además nos proporcionará cada elemento item del array, así como el índice index de cada uno de ellos.
En este ejercicio sólo necesitamos gestionar una lista de lugares, con lo que desarrollaremos funciones para leer, actualizar e imprimir dicha lista, utilizando para ello localStorage, como en ejercicios anteriores:
function getList() {
let list = localStorage.getItem('places-list');
return list ? JSON.parse(list) : [];
}
function saveList(list) {
localStorage.setItem('places-list', JSON.stringify(list));
printItems();
}
function printItems() {
document.querySelector('ion-reorder-group').innerHTML = "";
getList().forEach((item, index) => {
document.querySelector('ion-reorder-group').innerHTML += ... ;
});
}
Al iniciar la aplicación
Al iniciar la aplicación capturaremos el evento de IONIC que nos indicará cuándo debemos reordenar los elementos de la lista, y además, imprimiremos los lugares que tengamos guardados de una ejecución anterior:
Para conocer la ubicación desde la que vamos a registrar un nuevo lugar, utilizaremos la funcionalidad de geolocalización de HTML, de forma que consigamos que el código funcione perfectamente tanto en el navegador, como en la app una vez compilada (más información aquí):
navigator.geolocation.getCurrentPosition(pos => {
let url = "https://maps.google.com/maps?&z=15&t=k&q="+pos.coords.latitude+" "+pos.coords.longitude;
...
});
Además, crear un enlace a Google Maps conociendo la ubicación resulta muy sencillo. En nuestro caso especificamos un zoom razonable (z=15), el tipo de mapa satélite (t=k), y la latitud y la longitud (q=…). Se puede obtener más información en la página oficial de Google, o también por ejemplo aquí.
Indicador de ejecución en proceso
Mientras se obtiene la ubicación y se procesa la imagen, resulta conveniente bloquear la aplicación mediante algún indicador para informar al usuario que todavía no se ha completado la acción. Para ello mostraremos un indicador en pantalla utilizando la funcionalidad que nos proporciona IONIC (más información aquí):
Utilizando simple código HTML y JavaScript, podemos obtener la imagen seleccionada en formato base 64, de forma que la podemos guardar como si se tratara de cualquier otra cadena de texto (más información aquí, o aquí):
Además, añadiremos la funcionalidad necesaria para abrir la cámara cuando ejecutemos la aplicación en nuestros dispositivos móviles (más información en la documentación de Cordova):
function onLoad() {
document.addEventListener('ionItemReorder', (event) => { moveItem(event.detail); });
printItems();
}
function getList() {
let list = localStorage.getItem('places-list');
return list ? JSON.parse(list) : [];
}
function saveList(list) {
localStorage.setItem('places-list', JSON.stringify(list));
printItems();
}
function printItems() {
document.querySelector('ion-reorder-group').innerHTML = "";
getList().forEach((item, index) => {
document.querySelector('ion-reorder-group').innerHTML +=
`<ion-item-sliding>
<ion-item href='`+item.url+`'>
<ion-thumbnail slot="start"><img src="`+item.img+`" />
</ion-thumbnail>`+item.name+`<ion-reorder slot="end"></ion-reorder>
</ion-item>
<ion-item-options side="start">
<ion-item-option color="danger" onclick="deleteItem(`+index+`)">
<ion-icon slot="icon-only" name="trash"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>`;
});
}
function addItem(event = false) {
const loading = document.createElement('ion-loading');
loading.duration = 15000;
document.querySelector('ion-app').appendChild(loading);
loading.present();
navigator.geolocation.getCurrentPosition(pos => {
let text = document.querySelector('ion-input').value;
let url = "https://maps.google.com/maps?&z=15&t=k&q="+pos.coords.latitude+" "+pos.coords.longitude;
if (event) {
let reader = new FileReader();
reader.onload = (data) => {
saveItem({ name:text, img:data.target.result, url:url });
event.target.value = '';
loading.dismiss();
}
reader.readAsDataURL(event.target.files[0]);
}
else {
loading.dismiss();
navigator.camera.getPicture(function success(data) {
saveItem({ name:text, img:"data:image/jpeg;base64,"+data, url:url });
}, function error(msg) {
alert(msg);
}, { targetWidth:100, destinationType:Camera.DestinationType.DATA_URL });
}
});
}
function saveItem(item) {
let list = getList();
list.unshift(item);
saveList(list);
}
function deleteItem(item) {
document.querySelector('ion-list').closeSlidingItems();
let list = getList();
list.splice(item, 1);
saveList(list);
}
function moveItem(indexes) {
let list = getList();
let item = list[indexes.from];
list.splice(indexes.from, 1);
list.splice(indexes.to, 0, item);
indexes.complete();
saveList(list);
}
function toggleReorder() {
let reorder = document.querySelector('ion-reorder-group').disabled;
document.querySelector('ion-reorder-group').disabled = !reorder;
}
Icono y pantalla de bienvenida
Si queremos cambiar el icono o la pantalla de bienvenida, bastará con actualizar los archivos icon.png y splash.png respectivamente, colocándolos en el mismo archivo zip que el código fuente de nuestro proyecto.
Compilando la aplicación
Plugins de geolocalización y cámara
Para que la aplicación tenga los permisos correctos, y podamos utilizar la funcionalidad nativa para conocer la ubicación, añadiremos el plugin correspondiente (más información aquí):
<plugin name="cordova-plugin-geolocation" />
Además, para utilizar la cámara también echaremos mano de otro plugin, ya que sólo podemos acceder a dicha funcionalidad en nuestros dispositivos móviles (más información aquí):
<plugin name="cordova-plugin-camera" />
Debemos recordar que toda la funcionalidad de la aplicación se encontrará disponible tanto en el navegador como en la app, exceptuando la cámara, ya que es específica del dispositivo, y no estará operativa en el navegador.
Para instalar la aplicación en nuestros dispositivos móviles, tenemos que compilar el código fuente, con lo que deberemos incluir los siguientes archivos en el fichero zip que subiremos a la página web de PhoneGap:
index.html
places.js
config.xml
icon.png
splash.png
El resultado
Puedes hacer clic aquí para probar la aplicación propuesta. Se puede observar que el mismo código de la app generada también puede funcionar perfectamente en el navegador como una página web.
Como habremos observado, PhoneGap Build utiliza un icono por defecto al compilar nuestras aplicaciones. En este ejercicio vamos a ver cómo podemos cambiar el icono de nuestra aplicación de lista de tareas fácilmente y sin necesidad de instalar ningún software adicional en nuestros equipos. Además, también veremos cómo añadir una pantalla de bienvenida, lo que sin duda le dará un toque más profesional a nuestra aplicación.
El icono nos servirá para distinguir claramente cuál es nuestra app de entre todas las instaladas en el móvil, pudiendo elegir una imagen cualquiera que podemos descargar de Internet, o también diseñándola nosotros mismos.
La pantalla de bienvenida se visualizará al arrancar la aplicación, y permanecerá unos segundos, mientras el móvil carga nuestra app, o incluso se puede prorrogar el tiempo que nosotros decidamos.
Por ejemplo, yo he buscado un par de imágenes etiquetadas para reutilización, y he escogido las siguientes como icono y pantalla de bienvenida respectivamente:
El archivo config.xml
Deberemos incluir las opciones específicas en nuestro archivo config.xml para que PhoneGap Build pueda reconocer y enlazar ambos archivos. Además, deberemos añadir una línea con <plugin name="cordova-plugin-splashscreen" /> para poder utilizar el plugin para mostrar la pantalla de bienvenida:
Como opciones adicionales podemos configurar el tiempo que permanece mostrándose la pantalla de bienvenida. El valor por defecto es de tres segundos. Por ejemplo, con la siguiente línea lo establecemos a cinco segundos para que la podamos ver un poco mejor:
También debemos tener en cuenta que por defecto, la pantalla de bienvenida sólo se mostrará la primera vez que se abra la aplicación. Una vez se haya ejecutado la app, para volver a visualizar la imagen de nuevo, deberemos cerrar la aplicación por completo y volverla a abrir. Nosotros en este ejemplo vamos a configurar dicha opción para que se muestre siempre:
También debemos tener en cuenta que por simplificar, estamos utilizando únicamente una imagen como pantalla de bienvenida. Para asegurar que se ajuste automáticamente al tamaño de la pantalla, independientemente de la orientación y el tamaño del dispositivo, activaremos además la opción específica para que cambie la proporción de la imagen automáticamente:
Y por último, para asegurar que no tenemos problema con nuevos dispositivos, recomendamos añadir también la siguiente opción para utilizar la última versión del compilador hasta la fecha:
Finalmente deberemos incluir en el fichero ZIP que subamos a PhoneGap las dos imágenes que hayamos elegido como icono y pantalla de bienvenida (icon.pngy splash.png en nuestro ejemplo, aunque podríamos haber utilizado cualquier otro nombre). De esta forma, el archivo ZIP contendría los siguientes ficheros: