Apache Cordova | Añadir aviso de carga de datos “loading” animado

En este artículo quiero compartir un nuevo tip sencillo para Apps desarrolladas con Apache Cordova o PhoneGap y no es otro que añadir un aviso de que se están cargando datos o un mensaje de loading para que el usuario esté al tanto de que se está realizando dicha operación mientras debe esperar. Esto además, nos puede servir para otras acciones como actualizaciones por cálculos, etc.

¿Por qué es tan importante un aviso de carga de datos?

poster Muchos desarrolladores obvian la inclusión de mensajes de aviso de carga de datos en sus Apps y, peor aún tampoco lo hacen en aplicaciones web o en las clásicas de escritorio. Esto es un enorme error de cara a la experiencia de usuario ya que provoca múltiples problemas derivados de los que enumero algunos a continuación:

  • El usuario cree que la aplicación se ha colgado y la cierra con la consiguiente mala reputación que nos ganaremos.
  • El usuario cree que la acción que ha realizado no se está ejecutando y vuelve a intentar realizarla. Si es una consulta no será tan drástica salvo que se volverá a generar el tráfico de red que corresponda pero, ¿si es una actualización o una creación de un registro? Puede que no hayamos controlado que el usuario le pueda dar varias veces y que nos genere datos erróneos, errores, … con la consiguiente insatisfacción por el usuario que se traduce en mala reputación.
  • Las Apps DEBEN ser fluidas y de respuesta inmediata. Recordemos que estamos desarrollando Apps y que el usuario espera que las acciones se ejecuten instantáneamente, es decir, no admite tener que esperar como hacía antiguamente en un PC 386 donde tener que esperar unos segundos era la tónica general y por tanto se aceptaba. Además, debemos recordar que puede que sea un usuario de un dispositivo de gama alta donde tiene la idea de que todo debe ser “ipso facto”.
  • Por simple buena experiencia de usuario UX. El usuario debe saber en todo momento qué ocurre con la aplicación en su dispositivo y esto no es negociable aunque el mensaje sólo aparezca, si todo va bien, unos milisegundos.
  • Algunos casos más que no he considerado necesarios nombrar.

¿Cómo lo implementamos?

Hay mil y una formas de implementar un aviso de carga, con CSS, JavaScript, GiF, … pero os voy a dejar unas directrices que sirven en cualquier caso y que yo sigo por buenas prácticas basadas en mi experiencia:

  • Debe ser claro y conciso, nada de mensajes como: “Estamos cargando los datos de la tabla de super héroes, calculando los índices, parseando el JSON y redimensionando las imágenes. Espere unos instantes, disculpe las molestias”. Un usuario no necesita saber tanto y, de hecho, probablemente nadie se lea tanto detalle. El usuario generalmente desea saber únicamente que se están cargando datos y, en caso de necesitar algo más de información podemos añadir algunas palabras pero sin pasarnos como “Cargando datos de superhéroes”.
  • Se puede dar el caso en el que la carga requiera de varios procesos, en ese caso, intercambiaremos unos mensajes por otros en función del estado del proceso como por ejemplo, “Cargando datos”, “Calculándo índices”, “Redimensionando imágenes”, de forma que el usuario sólo verá en pantalla el mensaje concreto y, si el proceso se detuviera un instante en alguno de los subprocesos, siempre sabría qué es lo que está ralentizando la carga.
  • Se DEBEN bloquear los elementos que generen la misma acción u otras que entren en conflicto. Lo más sencillo es bloquear la pantalla por completo con una capa que impida al usuario ejecutar cualquier acción. Puede darse el caso que por ejemplo, mientras se esté buscando queramos permitir al usuario ejecutar otras acciones en pantalla y, en este caso, sólo deberíamos mantener activos aquellos elementos que queramos que puedan ser accionados en ese caso. Si es una simple carga de datos incluso puede que no necesitemos bloquear ninguna acción, pero estudiad entonces muy bien esos casos “for if the flies”.
  • Se debe ver claramente salvo que no queramos bloquear la pantalla como en el caso anterior. Por ejemplo, en cargas en background podemos permitirnos el lujo de usar algo muy sutil como los puntitos que aparecen y desaparecen en Windows Phone.

Con estas directrices, que podéis saltaros, ignorar, completar, reducir,… ya estamos listos para pensar cómo queremos que sea nuestro aviso de carga. A mi en concreto me gusta ser un poco original y no limitarme a las típicas rueditas girando acompañadas con el mensaje “Cargando…” así que os compartiré mi idea para mi aplicación de Super Héroes realizada con Cordova.

Como se puede observar he puesto un mensaje claro de carga que habla un poco de la aplicación usando para ello una posible situación de ciencia ficción, además de añadir la silueta de un superhéroe volando por la pantalla. Está claro que se podría hacer más atractivo pero esto no es más que un ejemplo. Ahora veamos cómo lo he realizado.

Para hacer la animación simplemente he usado las características que nos proporciona CSS3 y he puesto un desplazamiento de derecha a izquierda ascendente como podéis ver en el siguiente código y que os explico ahora para ahorraros verlo.

  1. Añado un bloque oculto que contiene el texto y una imagen que será la que se mueva por la pantalla
  2. Les establezco posición absoluta y que el contenedor ocupe toda la pantalla con fondo negro translúcido
  3. Añado la animación mediante CSS de tal forma que vaya de derecha a izquierda en forma ascendente y que se repita infinitamente
  4. En el código JavaScript, muestro el bloque con el mensaje de carga de datos cuando se esté realizando la carga y lo oculto cuando se haya acabado.
  5. Las funciones JavaScript que he creado nos permiten indicar si se ha de mostrar o no, además del elemento que mostramos/ocultamos.

HTML

<div class="loading" style="display:none;">

    <div class="loading-text">Un momento, estamos salvando la galaxia</div>

    <img class="loading-img" src="images/layout/superhero-flying.png" />

</div>

 

CSS

body {

    margin: 0;

    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;

}

 

.loading {

    position: absolute;

    top: 0;

    left: 0;

    height: 100%;

    width: 100%;

    background: rgba(0, 0, 0, 0.75);

    overflow: hidden;

}

 

    .loading .loading-text {

        position: relative;

        top: 50%;

        width: 100%;

        text-align: center;

        color: #ffde00;

        font-size: 17px;

    }

 

    .loading .loading-img {

        position: absolute;

        -moz-animation: loading 3s infinite;

        -o-animation: loading 3s infinite;

        -webkit-animation: loading 3s infinite;

        animation: loading 3s infinite;

    }

 

@-moz-keyframes loading {

    from {

        top: 60%;

        left: -60px;

    }

 

    to {

        top: 40%;

        left: 110%;

    }

}

 

@-webkit-keyframes loading {

    from {

        top: 60%;

        left: -60px;

    }

 

    to {

        top: 40%;

        left: 110%;

    }

}

 

@keyframes loading {

    from {

        top: 60%;

        left: -60px;

    }

 

    to {

        top: 40%;

        left: 110%;

    }

}

 

JavaScript

function onDeviceReady() {

    // Handle the Cordova pause and resume events

    document.addEventListener('pause', onPause.bind(this), false);

    document.addEventListener('resume', onResume.bind(this), false);

 

    // TODO: Cordova has been loaded. Perform any initialization that requires Cordova here.

    //checkConnection();

    loading(true);

 

    amscontext = new WindowsAzure.MobileServiceClient(

            "URL_SERVICIO_MÓVIL",

            "APPLICATION_KEY");

 

    getSuperHeros();

};

 

function getSuperHeros() {

    var superHeroTable = amscontext.getTable('superhero');

 

    superHeroTable.read().then(function (items) {

        displaySuperHeros(items);

        loading(false);

    });

}

 

...

...

...

 

function loading(visible, loadingSelector) {

    var selector = '.loading';

    if (loadingSelector != undefined)

        selector = loadingSelector;

 

    if (selector.indexOf('#') == 0) {

        var loadingElement = document.getElementById(selector.substring(1));

        DisplayElement(loadingElement, visible);

    }

    else if (selector.indexOf('.') == 0) {

        var loadingElements = document.getElementsByClassName(selector.substring(1));

        for (var i = 0; i < loadingElements.length; i++) {

            var loadingElement = loadingElements[i];

            DisplayElement(loadingElement, visible);

        }

    }

}

 

function DisplayElement(element, visible) {

    if (visible)

        element.style.display = 'block';

    else

        element.style.display = 'none';

}

 

 

 

Have fun & enjoy coding!!