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
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 += ... ;
});
}
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í):
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
navigator.geolocation.getCurrentPosition(pos =>{
let url = "https://maps.google.com/maps?&z=15&t=k&q="+pos.coords.latitude+" "+pos.coords.longitude;
...
});
navigator.geolocation.getCurrentPosition(pos => {
let url = "https://maps.google.com/maps?&z=15&t=k&q="+pos.coords.latitude+" "+pos.coords.longitude;
...
});
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;
}
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í):
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<plugin name="cordova-plugin-geolocation" />
<plugin name="cordova-plugin-geolocation" />
<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í):
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<plugin name="cordova-plugin-camera" />
<plugin name="cordova-plugin-camera" />
<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.
Una emotiva historia de dos amigas inseparables con habilidades muy especiales
En clase siempre hemos tenido compañeros y compañeras con diferentes destrezas, y el trabajo en equipo nos permite hacer tareas que no podríamos llevar a cabo nosotros solos. Descubre las fantásticas habilidades de nuestras protagonistas cuando se ponen a trabajar juntas.
Nueva edición en color y para colorear
Un relato precioso en un libro de gran tamaño (21,5 x 28 cm), con más de 70 páginas y 100 ilustraciones en color y también para colorear. El cuaderno de actividades perfecto para niños y niñas, con el que aprenderán el abecedario y vocabulario básico en inglés. Todas las letras y palabras están ilustradas de manera sencilla para facilitar su aprendizaje.
Cuento bilingüe español-inglés y cuaderno de caligrafía
Ideal para aprender a leer y escribir, en castellano y en inglés. Incluye además ilustraciones relacionadas con el cuento para aprender el abecedario, repartidas en tres secciones:
Learn the alphabet
Letter … is for …
Spell it out
Otros libros de la serie «Cuentos para ser escuchados»
También está disponible la colección completa de cuentos en español en un solo libro, además de la edición especial de cada uno de los relatos en su versión bilingüe, con más ilustraciones, en formato de libro electrónico y en papel:
Una emotiva historia que ensalza el valor de la familia
Conoceremos a un médico que sólo receta inyecciones y medicinas. ¿Conseguirá curar a todos los pacientes que pasan por su consulta? En este emotivo relato nos meteremos en la piel de un niño que tiene miedo a los pinchazos y que además tal vez necesite un tratamiento especial.
Nueva edición en color y para colorear
Un relato precioso en un libro de gran tamaño (21,5 x 28 cm), con más de 70 páginas y 100 ilustraciones en color y también para colorear. El cuaderno de actividades perfecto para niños y niñas, con el que aprenderán el abecedario y vocabulario básico en inglés. Todas las letras y palabras están ilustradas de manera sencilla para facilitar su aprendizaje.
Cuento bilingüe español-inglés y cuaderno de caligrafía
Ideal para aprender a leer y escribir, en castellano y en inglés. Incluye además ilustraciones relacionadas con el cuento para aprender el abecedario, repartidas en tres secciones: Learn the alphabet, Letter … is for …, Spell it out.
Learn the alphabet
Letter … is for …
Spell it out
Otros libros de la serie «Cuentos para ser escuchados»
También está disponible la colección completa de cuentos en español en un solo libro, además de la edición especial de cada uno de los relatos en su versión bilingüe, con más ilustraciones, en formato de libro electrónico y en papel:
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
<pluginname="cordova-plugin-splashscreen"/>
<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:
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:
<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ú:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<ion-menucontent-id="content">
<ion-header>
...
<ion-menu-button></ion-menu-button>
...
</ion-header>
<ion-content>
<ion-menu-toggle>
<ion-list>
<ion-item-divider><ion-label>Reorder and Share</ion-label></ion-item-divider>
<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>
<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:
Más adelante veremos el código JavaScript necesario para llevar a cabo las funciones correspondientes:
deleteItem()
deleteItem()
toggleReorder()
toggleReorder()
addEditItem()
addEditItem()
Las listas de tareas
Para mantener dos listas separadas, desde el archivo index.html utilizaremos el elemento
<ion-tabs></ion-tabs>
<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
<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>
<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:
<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-itemonclick="..."></ion-item>
<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>
<h2></h2>, y a continuación mostremos la fecha de la misma dentro de un párrafo (etiqueta
<p></p>
<p></p>). Encerraremos ambos elementos con una etiqueta
<ion-label text-wrap></ion-label>
<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:
<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>
<ion-content></ion-content>) tendremos tres campos:
<ion-datetime><ion-datetime>
<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>
<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>
<ion-segment></ion-segment>: Para seleccionar la prioridad de la tarea. En nuestro ejemplo proponemos 4 opciones (
radio-button-off
radio-button-off para tareas pendientes,
radio-button-on
radio-button-on para tarea finalizada,
snow
snow para una prioridad baja, y
flame
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);
}
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()
saveList()) nos permitirá guardar de manera persistente cada una de las listas de tareas, utilizando
localStorage
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)
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();
}
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
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:
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');
}
...
}});
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>
<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:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let newItem = { text:newText, date:newDate, icon:newIcon };
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>
<ion-reorder></ion-reorder>. Lo único que debemos hacer por nuestra parte, es cambiar el valor del atributo
disabled
disabled del elemento
<ion-reorder-group></ion-reorder-group>
<ion-reorder-group></ion-reorder-group> de la lista seleccionada:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
functiontoggleReorder(){
closeItems();
let reorder = getTab().querySelector('ion-reorder-group');
reorder.disabled = !reorder.disabled;
}
function toggleReorder() {
closeItems();
let reorder = getTab().querySelector('ion-reorder-group');
reorder.disabled = !reorder.disabled;
}
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)
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:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
...
let list = getList();
if(index !== false){ list.splice(index, 1); }
else{ list.length = 0; }
saveList(getTab(), list);
...
...
let list = getList();
if (index !== false) { list.splice(index, 1); }
else { list.length = 0; }
saveList(getTab(), list);
...
...
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
ionItemReorder, tal como se indica en la documentación de IONIC, apartado de JavaScript:
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.
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>
<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-img></ion-img>, que tiene un comportamiento casi idéntico al
<img>
<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>
<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:
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>
<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:
Por simplificar un poco el código, utilizaremos tres funciones para acceder a cada uno de los elementos clave (range, tabs y slides):
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
functiongetDelay(){
return(document.querySelector('ion-range'));
}
functiongetTabs(){
return(document.querySelector('ion-tabs'));
}
functiongetSlides(){
return(document.querySelectorAll('ion-slides'));
}
function getDelay() {
return(document.querySelector('ion-range'));
}
function getTabs() {
return(document.querySelector('ion-tabs'));
}
function getSlides() {
return(document.querySelectorAll('ion-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>
<ion-slides></ion-slides> correspondiente y ejecutar el método
startAutoplay()
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()
getTabs().getSelected()), que será devuelto en una promesa. Utilizaremos luego simplemente el atributo id para acceder a la categoría activa, iniciando (
s.startAutoplay()
s.startAutoplay()) o parando (
s.stopAutoplay()
s.stopAutoplay()) la reproducción según el botón que se haya pulsado (play o pause):
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
functionplay(){
getTabs().getSelected().then(function(tab){
document.getElementById(tab).startAutoplay();
});
}
functionpause(){
getSlides().forEach(function(s){
s.stopAutoplay();
});
}
function play() {
getTabs().getSelected().then(function(tab) {
document.getElementById(tab).startAutoplay();
});
}
function pause() {
getSlides().forEach(function(s) {
s.stopAutoplay();
});
}
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
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
delay) que deberá permanecer cada imagen en pantalla:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
autoplay: {
delay: 5000 - getDelay().value
}
autoplay: {
delay: 5000 - getDelay().value
}
autoplay: {
delay: 5000 - getDelay().value
}
Inicializamos también la anchura de cada diapositiva indicando que ocupan toda la ventana:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
s.options = {
width: window.innerWidth,
...
};
s.options = {
width: window.innerWidth,
...
};
s.options = {
width: window.innerWidth,
...
};
Al cambiar de pestaña
Cada vez que cambiemos de pestaña, pararemos la reproducción:
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:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<body onload="onLoad()">
...
functiononLoad(){
window.addEventListener('resize', init);
...
init();
}
...
functioninit(){
getSlides().forEach(function(s){
...
});
}
<body onload="onLoad()">
...
function onLoad() {
window.addEventListener('resize', init);
...
init();
}
...
function init() {
getSlides().forEach(function(s) {
...
});
}
<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();
});
}
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.