Calculadora con estilo personalizable utilizando IONIC 5

¿Cómo podemos utilizar IONIC 5?

Si deseamos utilizar IONIC 5, sólo necesitaremos enlazar los archivos JavaScript y CSS, tal como se especifica en la documentación y en github:

...
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/[email protected]/dist/ionic/ionic.esm.js"></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/[email protected]/dist/ionic/ionic.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/[email protected]/css/ionic.bundle.css"/>
...

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
123+
456
789*
0/
AC.=

En el ejercicio anterior hemos utilizado el siguiente código HTML:

<table>
  <tr>
    <td colspan="4">0</td>
  </tr>
  <tr>
    <td>1</td>
    <td>2</td>
    <td>3</td>
    <td>+</td>
  </tr>
  <tr>
    <td>4</td>
    <td>5</td>
    <td>6</td>
    <td>-</td>
  </tr>
  <tr>
    <td>7</td>
    <td>8</td>
    <td>9</td>
    <td>*</td>
  </tr>
  <tr>
    <td colspan="3">0</td>
    <td>/</td>          
  </tr>
  <tr>
    <td colspan="2">AC</td>
    <td>.</td>
    <td>=</td>            
  </tr>
</table>

Si utilizamos los nuevos elementos que nos proporciona IONIC 5, obtendríamos el mismo resultado con el siguiente código:

<ion-grid>
  <ion-row>
    <ion-col>0</ion-col>
  </ion-row>
  <ion-row>
    <ion-col>1</ion-col>
    <ion-col>2</ion-col>
    <ion-col>3</ion-col>
    <ion-col>+</ion-col>
  </ion-row>
  <ion-row>
    <ion-col>4</ion-col>
    <ion-col>5</ion-col>
    <ion-col>6</ion-col>
    <ion-col>-</ion-col>
  </ion-row>
  <ion-row>
    <ion-col>7</ion-col>
    <ion-col>8</ion-col>
    <ion-col>9</ion-col>
    <ion-col>*</ion-col>
  </ion-row>
  <ion-row>
    <ion-col size="9">0</ion-col>
    <ion-col>/</ion-col>
  </ion-row>
  <ion-row>
    <ion-col size="6">AC</ion-col>
    <ion-col>.</ion-col>
    <ion-col>=</ion-col>
  </ion-row>
</ion-grid>

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:

...
<ion-row>
  <ion-col size="9">0</ion-col>
  <ion-col>/</ion-col>
</ion-row>
<ion-row>
  <ion-col size="6">AC</ion-col>
  <ion-col>.</ion-col>
  <ion-col>=</ion-col>
</ion-row>
...

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:

<ion-button color="danger" expand="block" onclick="del()">AC</ion-button>

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:

<ion-list>
  <ion-item>
    <ion-label>Tamaño botones</ion-label>
    <ion-select value="default" id="size">
      <ion-select-option value="small">Pequeños</ion-select-option>
      <ion-select-option value="default">Medianos</ion-select-option>
      <ion-select-option value="large">Grandes</ion-select-option>
    </ion-select>
  </ion-item>
  <ion-item>
    <ion-label>Tipo botones</ion-label>
    <ion-select value="solid" id="type">
      <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>
  </ion-item>
</ion-list>

¿Estilo IOS o Android?

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á:

<ion-header>
  <ion-toolbar>
    <ion-title>Calculator</ion-title>
    <ion-buttons slot="secondary">
      <ion-button href="index.html?ionic:mode=md">Android</ion-button>
    </ion-buttons>
    <ion-buttons slot="primary">
      <ion-button href="index.html?ionic:mode=ios">IOS</ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

Cuando iniciemos la aplicación, ésta se mostrará automáticamente utilizando el estilo de la plataforma en la que se esté ejecutando.

¿Código JavaScript adicional?

Pues la verdad es que muy poco:

function onLoad() {
    document.addEventListener("ionChange", setStyle);
    setStyle();
}

function setStyle() {
    document.querySelectorAll("ion-content ion-button").forEach(function(b) {
        b.expand = "block";
        b.strong = "true";
        b.fill = document.getElementById("type").value;
        b.size = document.getElementById("size").value;
    });
}

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 resumen…

El fichero index.html

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

  <script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/[email protected]/dist/ionic/ionic.esm.js"></script>
  <script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/[email protected]/dist/ionic/ionic.js"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/[email protected]/css/ionic.bundle.css"/>

  <script src="calculator.js"></script>

  <title>Calculator!</title>
</head>

<body onload="onLoad()">
  <ion-app>
    <ion-header>
      <ion-toolbar>
        <ion-title>Calculator</ion-title>
        <ion-buttons slot="secondary">
          <ion-button href="index.html?ionic:mode=md">Android</ion-button>
        </ion-buttons>
        <ion-buttons slot="primary">
          <ion-button href="index.html?ionic:mode=ios">IOS</ion-button>
        </ion-buttons>
      </ion-toolbar>
    </ion-header>

    <ion-content>
      <ion-list>
        <ion-item>
          <ion-label>Tamaño botones</ion-label>
          <ion-select value="default" id="size">
            <ion-select-option value="small">Pequeños</ion-select-option>
            <ion-select-option value="default">Medianos</ion-select-option>
            <ion-select-option value="large">Grandes</ion-select-option>
          </ion-select>
        </ion-item>
        <ion-item>
          <ion-label>Tipo botones</ion-label>
          <ion-select value="solid" id="type">
            <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>
        </ion-item>
      </ion-list>
      <ion-grid>
        <ion-row>
          <ion-col><ion-button color="dark"><span id="result">0</span></ion-button></ion-col>
        </ion-row>
        <ion-row>
          <ion-col><ion-button color="primary" onclick="add('1')">1</ion-button></ion-col>
          <ion-col><ion-button color="primary" onclick="add('2')">2</ion-button></ion-col>
          <ion-col><ion-button color="primary" onclick="add('3')">3</ion-button></ion-col>
          <ion-col><ion-button color="tertiary" onclick="add('+')">+</ion-button></ion-col>
        </ion-row>
        <ion-row>
          <ion-col><ion-button color="primary" onclick="add('4')">4</ion-button></ion-col>
          <ion-col><ion-button color="primary" onclick="add('5')">5</ion-button></ion-col>
          <ion-col><ion-button color="primary" onclick="add('6')">6</ion-button></ion-col>
          <ion-col><ion-button color="tertiary" onclick="add('-')">-</ion-button></ion-col>
        </ion-row>
        <ion-row>
          <ion-col><ion-button color="primary" onclick="add('7')">7</ion-button></ion-col>
          <ion-col><ion-button color="primary" onclick="add('8')">8</ion-button></ion-col>
          <ion-col><ion-button color="primary" onclick="add('9')">9</ion-button></ion-col>
          <ion-col><ion-button color="tertiary" onclick="add('*')">*</ion-button></ion-col>
        </ion-row>
        <ion-row>
          <ion-col size="9"><ion-button color="primary" onclick="add('0')">0</ion-button></ion-col>
          <ion-col><ion-button color="tertiary" onclick="add('/')">/</ion-button></ion-col>
        </ion-row>
        <ion-row>
          <ion-col size="6"><ion-button color="danger" onclick="del()">AC</ion-button></ion-col>
          <ion-col><ion-button color="primary" onclick="add('.')">.</ion-button></ion-col>
          <ion-col><ion-button color="tertiary" onclick="calc()">=</ion-button></ion-col>
        </ion-row>
      </ion-grid>
    </ion-content>
  </ion-app>
</body>
</html>

El fichero calculator.js

function onLoad() {
    document.addEventListener("ionChange", setStyle);
    setStyle();
}

function setStyle() {
    document.querySelectorAll("ion-content ion-button").forEach(function(b) {
        b.expand = "block";
        b.strong = "true";
        b.fill = document.getElementById("type").value;
        b.size = document.getElementById("size").value;
    });
}

function setResult(value) {
    document.getElementById("result").innerHTML = value;
}

function getResult() {
    return(document.getElementById("result").innerHTML);
}

function add(key) {
    var result = getResult();
    if (result!="0" || isNaN(key)) setResult(result + key);
    else setResult(key);
}

function calc() {
    var result = eval(getResult());
    setResult(result);
}

function del() {
    setResult(0);
}

El resultado

Puedes hacer clic aquí para observar el aspecto que tiene la calculadora y probar la funcionalidad resultante utilizando el código especificado.

Cuentos para ser recordados

Diez relatos recomendados para todos los públicos

Relatos infantiles inspirados en numerosos cuentos populares orientales con el objetivo de lograr incluir esas grandes enseñanzas que se llevan transmitiendo desde hace tantos años, y que conviene seguir recordando. Es un libro recomendado para pequeños y mayores, y por ello se ha utilizado un lenguaje asequible para los niños, sin olvidar que también es importante conseguir emocionar a los adultos.

Historias que invitan a reflexionar y ensalzan el valor de la amistad y la familia

Se abordan situaciones que se pueden resolver con un poco de tolerancia, compromiso, disciplina, esfuerzo y trabajo en equipo. Además, se intentan transmitir valores que contribuyan a potenciar nuestras capacidades: amistad, superación, valentía, sabiduría, respeto, deportividad, diversidad e integración.

  • Aunque sea de noche y haga un frío que pela: Jugar al fútbol con tus amigos es un excelente motivo para levantarse pronto un sábado por la mañana. Un buen ambiente en el terreno de juego amenizará todas las situaciones, por complicadas que sean.
  • ¡Shhhhhhh!: A veces es importante guardar silencio. Podemos mejorar nuestras habilidades si cumplimos las normas y nos comprometemos a dar lo mejor de nosotros mismos.
  • Cajas de cartón: Todos tenemos alguna habilidad que nos hace únicos y especiales, y no hay mayor satisfacción que ver una cara feliz como resultado de nuestro trabajo.
  • No lo puedo evitar: Cuando tu hermano pequeño te hace rabiar, piensa en algo para intentar evitar pelearte con él. Enfadarse no conduce a nada, y a veces los problemas se solucionan simplemente con un poco de paciencia.
  • Alumnos tranquilos: Un profesor inquieto y unos alumnos muy tranquilos. Con iniciativa y creatividad podemos adelantarnos para abordar y superar con éxito cualquier situación.
  • El cinturón negro: El color del cinturón infunde confianza y respeto, pero sólo los mejores maestros son merecedores de la máxima distinción. Detrás de cada victoria o logro personal hay un trabajo de esfuerzo y sacrificio que merece la pena.
  • La primera lección: Es importante ser humilde y reconocer cuánto nos queda por aprender.
  • Escuchando a todos para entender a cada uno: Debemos ponernos en el lugar de los demás para lograr entender los problemas específicos que se planteen en cada momento.
  • Hasta que aprendáis de una escoba: Un padre en apuros intenta que sus hijos consigan gestionar su tiempo libre para encontrar el equilibrio adecuado entre sus obligaciones y sus devociones.
  • La búsqueda del tesoro solidario: Unos jovencitos con mucha iniciativa se enfrentarán a diversos retos y utilizarán hábilmente su ingenio para intentar salir airosos. El trabajo en equipo es uno de los pilares fundamentales para superar cualquier desafío. Hacer piña con nuestros compañeros nos permite conseguir los mejores resultados.

Otros libros del autor

Todos los beneficios son para Alianza española de familias de VHL

Comprando el libro aportamos un granito de arena para ayudar a la asociación de enfermos de von Hippel-Lindau (alianzavhl.org). Por ello, no sólo pretendemos regalar ilusión a quien lo lea, sino a todos aquellos enfermos y familiares que están esperando que les echemos una mano, ya que todos los beneficios de la venta del libro van destinados íntegramente a ellos.

La contraseña

Nuestro protagonista intentará abrir una caja misteriosa con un martillo… ¿lo conseguirá?

Un fantástico reto que conducirá al protagonista a resolver diversos acertijos con ayuda de sus padres.

Bilingüe

Texto en castellano y en inglés. Incluye ilustraciones relacionadas con el cuento para aprender el abecedario, repartidas en tres secciones:

  • Learn the alphabet
  • Letter … is for …
  • Spell it out

Nueva edición con letra caligráfica

Ideal para aprender a leer y escribir, en castellano y en inglés.

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:

  1. El médico que no creía en los besitos
  2. Tú me dictas y yo escribo
  3. Para que coman los gatos
  4. Mi almuerzo
  5. El cuentacuentos
  6. Las hadas de las profesiones
  7. Déjalo como está
  8. No te entiendo
  9. ¡¡¡Grrrrrrr!!!
  10. Leo, mi mejor amigo
  11. La princesa y el viajero
  12. La contraseña

Alianza VHL

Todos los beneficios son para Alianza española de familias de VHL (alianzavhl.org).

La princesa y el viajero

¿Alguna vez has mirado a lo lejos esperando ver a alguien?

A veces encuentras tu destino en el camino menos esperado… Pero hay que ser muy perseverante y seguir caminando hasta que encuentras lo que estás buscando…

Bilingüe

Texto en castellano y en inglés. Incluye ilustraciones relacionadas con el cuento para aprender el abecedario, repartidas en tres secciones:

  • Learn the alphabet
  • Letter … is for …
  • Spell it out

Nueva edición con letra caligráfica

Ideal para aprender a leer y escribir, en castellano y en inglés.

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:

  1. El médico que no creía en los besitos
  2. Tú me dictas y yo escribo
  3. Para que coman los gatos
  4. Mi almuerzo
  5. El cuentacuentos
  6. Las hadas de las profesiones
  7. Déjalo como está
  8. No te entiendo
  9. ¡¡¡Grrrrrrr!!!
  10. Leo, mi mejor amigo
  11. La princesa y el viajero
  12. La contraseña

Alianza VHL

Todos los beneficios son para Alianza española de familias de VHL (alianzavhl.org).

Leo, mi mejor amigo

¿Por qué el cielo está gris y nublado?

Una conmovedora historia dedicada a esos amigos incondicionales que siempre están a nuestro lado, pase lo que pase, sin pedir nunca nada a cambio.

Bilingüe

Texto en castellano y en inglés. Incluye ilustraciones relacionadas con el cuento para aprender el abecedario, repartidas en tres secciones:

  • Learn the alphabet
  • Letter … is for …
  • Spell it out

Nueva edición con letra caligráfica

Ideal para aprender a leer y escribir, en castellano y en inglés.

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:

  1. El médico que no creía en los besitos
  2. Tú me dictas y yo escribo
  3. Para que coman los gatos
  4. Mi almuerzo
  5. El cuentacuentos
  6. Las hadas de las profesiones
  7. Déjalo como está
  8. No te entiendo
  9. ¡¡¡Grrrrrrr!!!
  10. Leo, mi mejor amigo
  11. La princesa y el viajero
  12. La contraseña

Alianza VHL

Todos los beneficios son para Alianza española de familias de VHL (alianzavhl.org).

¡¡¡Grrrrrrr!!!

¿Oyes eso?

¿Quién está gruñendo? ¿Es el ruido del viento? ¿Es un animal peligroso?. Esta historia te pondrá los pelos de punta…

Bilingüe

Texto en castellano y en inglés. Incluye ilustraciones relacionadas con el cuento para aprender el abecedario, repartidas en tres secciones:

  • Learn the alphabet
  • Letter … is for …
  • Spell it out

Nueva edición con letra caligráfica

Ideal para aprender a leer y escribir, en castellano y en inglés.

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:

  1. El médico que no creía en los besitos
  2. Tú me dictas y yo escribo
  3. Para que coman los gatos
  4. Mi almuerzo
  5. El cuentacuentos
  6. Las hadas de las profesiones
  7. Déjalo como está
  8. No te entiendo
  9. ¡¡¡Grrrrrrr!!!
  10. Leo, mi mejor amigo
  11. La princesa y el viajero
  12. La contraseña

Alianza VHL

Todos los beneficios son para Alianza española de familias de VHL (alianzavhl.org).

No te entiendo

¿Podemos hablar sin palabras? ¿Podemos entendernos sin hablar?

En esta didáctica historia será una niña muy perseverante quien nos enseñe lo fácil que puede resultar llegar a entablar conversación con alguien que parece que nos ignora.

Bilingüe

Texto en castellano y en inglés. Incluye ilustraciones relacionadas con el cuento para aprender el abecedario, repartidas en tres secciones:

  • Learn the alphabet
  • Letter … is for …
  • Spell it out

Nueva edición con letra caligráfica

Ideal para aprender a leer y escribir, en castellano y en inglés.

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:

  1. El médico que no creía en los besitos
  2. Tú me dictas y yo escribo
  3. Para que coman los gatos
  4. Mi almuerzo
  5. El cuentacuentos
  6. Las hadas de las profesiones
  7. Déjalo como está
  8. No te entiendo
  9. ¡¡¡Grrrrrrr!!!
  10. Leo, mi mejor amigo
  11. La princesa y el viajero
  12. La contraseña

Alianza VHL

Todos los beneficios son para Alianza española de familias de VHL (alianzavhl.org).

Déjalo como está

¿Qué hay que hacer con las cosas que se rompen?

La acumulación de basura puede llegar a convertirse en un gran problema.

Bilingüe

Texto en castellano y en inglés. Incluye ilustraciones relacionadas con el cuento para aprender el abecedario, repartidas en tres secciones:

  • Learn the alphabet
  • Letter … is for …
  • Spell it out

Nueva edición con letra caligráfica

Ideal para aprender a leer y escribir, en castellano y en inglés.

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:

  1. El médico que no creía en los besitos
  2. Tú me dictas y yo escribo
  3. Para que coman los gatos
  4. Mi almuerzo
  5. El cuentacuentos
  6. Las hadas de las profesiones
  7. Déjalo como está
  8. No te entiendo
  9. ¡¡¡Grrrrrrr!!!
  10. Leo, mi mejor amigo
  11. La princesa y el viajero
  12. La contraseña

Alianza VHL

Todos los beneficios son para Alianza española de familias de VHL (alianzavhl.org).

Las hadas de las profesiones

¿Qué quieres ser de mayor?

¿Alguna vez te han dicho que tienes la cabeza llena de pájaros? ¿Alguna vez has querido ser inventor?

Bilingüe

Texto en castellano y en inglés. Incluye ilustraciones relacionadas con el cuento para aprender el abecedario, repartidas en tres secciones:

  • Learn the alphabet
  • Letter … is for …
  • Spell it out

Nueva edición con letra caligráfica

Ideal para aprender a leer y escribir, en castellano y en inglés.

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:

  1. El médico que no creía en los besitos
  2. Tú me dictas y yo escribo
  3. Para que coman los gatos
  4. Mi almuerzo
  5. El cuentacuentos
  6. Las hadas de las profesiones
  7. Déjalo como está
  8. No te entiendo
  9. ¡¡¡Grrrrrrr!!!
  10. Leo, mi mejor amigo
  11. La princesa y el viajero
  12. La contraseña

Alianza VHL

Todos los beneficios son para Alianza española de familias de VHL (alianzavhl.org).

Una app muy sencilla para guardar sitios interesantes con ubicaciones y fotografías con IONIC 4+Angular

El objetivo: Guardar ubicaciones e imágenes

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:

ionic start lugares blank --type=angular --no-git --no-link

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:

  1. src/app/home/home.page.html: Código HTML.
  2. 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:

<ion-header>
  ...
  <ion-title>Places!</ion-title>
  ...
  <ion-button (click)="reorder=!reorder">
  ...    
</ion-header>

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:

<ion-item>
  <ion-input placeholder="Enter description" [(ngModel)]="text"></ion-input>
</ion-item>
<ion-item *ngIf="text.length">
  <input type="file" accept="image/*" (change)="add($event)" />
  <ion-icon slot="end" name="camera" (click)="add()"></ion-icon>
</ion-item>

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>):

<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" (click)="delete(i)">
    <ion-icon slot="icon-only" name="trash"></ion-icon>
  </ion-item-option>
</ion-item-options>

Mostrando todos los elementos

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.

El fichero «home.page.html» completo

<ion-header>
  <ion-toolbar color="primary">
    <ion-title>Places!</ion-title>
    <ion-buttons slot="primary">
      <ion-button (click)="reorder=!reorder">
        <ion-icon slot="icon-only" name="reorder"></ion-icon>
      </ion-button>
    </ion-buttons>
  </ion-toolbar>       
</ion-header>
<ion-content>
  <ion-list lines="full">
    <ion-item color="light">
      <ion-input placeholder="Enter description" [(ngModel)]="text"></ion-input>
    </ion-item>
    <ion-item color="light" *ngIf="text.length">
      <input type="file" accept="image/*" (change)="add($event)" />
      <ion-icon slot="end" name="camera" (click)="add()"></ion-icon>
    </ion-item>    
    <ion-reorder-group [disabled]="!reorder" (ionItemReorder)="move($event.detail)">
      <ion-item-sliding *ngFor="let item of list; let i=index">
        <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" (click)="delete(i)">
            <ion-icon slot="icon-only" name="trash"></ion-icon>
          </ion-item-option>
        </ion-item-options>
      </ion-item-sliding>
    </ion-reorder-group>
  </ion-list>
</ion-content>

Código TypeScript

Los atributos

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í):

async add(event) {
  let loading = await this.loadingController.create();
  loading.present();
  ...
  loading.dismiss();
}

Obteniendo la imagen

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í):

...
<input type="file" (change)="add($event)" />
...
<img [src]="item.img" />
...
async add(event) {
  let reader = new FileReader();
  reader.onload = (data:any) => {
    item.img = data.target.result;
    ...
  }
  reader.readAsDataURL(event.target.files[0]);
}

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):

...
  navigator.camera.getPicture(function success(data) {
    list.unshift({ name:this.text, img:"data:image/jpeg;base64,"+data, url:url });
    save();     
  }, function error(msg) {
  }, { targetWidth:100, navigator.camera.DestinationType.DATA_URL });
...

El fichero «home.page.ts» completo

import { Component } from '@angular/core';
import { LoadingController } from '@ionic/angular';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})

export class HomePage {
  reorder: boolean;
  list: any;
  text: string;

  constructor(public loadingController:LoadingController) {
    this.reorder = false;
    this.text = "";
    this.list = localStorage.getItem('places-list');
    if (this.list!=='undefined' && this.list!==null) this.list = JSON.parse(this.list);
    else this.list = [];
  }

  async add(event?) {
    let loading = await this.loadingController.create({duration:30000});
    loading.present();

    let text = this.text, list = this.list, save = this.save;
    this.text = "";

    navigator.geolocation.getCurrentPosition(pos => {
      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:any) => {
          list.unshift({ name:text, img:data.target.result, url:url });
          loading.dismiss();
          save(list);
        }
        reader.readAsDataURL(event.target.files[0]);
      }
      else {
        (<any>navigator).camera.getPicture(function success(data) { 
          list.unshift({ name:text, img:"data:image/jpeg;base64,"+data, url:url });
          loading.dismiss();
          save(list);
        }, function error(msg) {
          loading.dismiss();
        }, { targetWidth:100, destinationType:(<any>navigator).camera.DestinationType.DATA_URL });
      }
    });
  }

  save(list) {
    localStorage.setItem('places-list', JSON.stringify(list));
    location.href = "index.html";
  }

  delete(item) {
    this.list.splice(item, 1);
    this.save(this.list);
  }

  move(indexes) {
    let item = this.list[indexes.from];
    this.list.splice(indexes.from, 1);
    this.list.splice(indexes.to, 0, item);
    this.save(this.list);
    indexes.complete();
  }
}

Compilando la aplicación

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í):

...
    <plugin name="cordova-plugin-geolocation" />
    <plugin name="cordova-plugin-camera" />
...

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):

...
<body>
  <app-root></app-root>
  <script type="text/javascript" src="cordova.js"></script>
</body>
...

El resultado

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.