Una de las características más interesantes en HTML5 es la posibilidad de almacenar datos localmente y permitir que la aplicación se ejecute offline. Hay tres APIs diferentes que tratan con esas características y elegir una depende de que quieres hacer exactamente con los datos que estas planeando almacenar localmente:
- Almacenamiento Web: Para almacenamiento local básico con pares clave/valor.
- Almacenamiento Offline: Usa un manifiesto para almacenar archivos completos para su uso oflline.
- Base de Datos Web: Para almacenamiento en bases de datos relacionales.
API de almacenamiento web
La implementación más básica para almacenar datos localmente en la máquina del usuario es con la API de almacenamiento web. Esta API utiliza pares clave/valor para permitir a los desarrolladores almacenar información básica y variables que pueden ser utilizadas por la aplicación web. Un caso de uso ideal para esto es el almacenamiento simple de datos que necesitan permanecer persistentes después de que el usuario ha navegado fuera de nuestra aplicación o ha cerrado el navegador. Guardando el estado de un juego, guardando una ubicación de navegación, o almacenando información específica que se quiere utilizar a través de la aplicación web (como el nombre de usuario) pero para lo cual no se quiere utilizar cookies. Se puede utilizar un API similar para almacenar datos en una sesión individual. Los datos se borran automáticamente después de que el usuario ha navegado fuera de la aplicación o ha cerrado el navegador.
Se necesita considerar lo siguiente cuando se utilice el API de almacenamiento web:
- El almacenamiento es para todo el dominio, así que cuando la aplicación guarda algo utilizando el almacenamiento web, los datos solamente son accesibles por otros sitios de ese dominio.
- Las restricciones de almacenamiento de datos es de 5 megabytes. Este límite fue sugerido por la W3C pero la especificación proporciona cierto margen para los detalles de implementación, así que el verdadero límite depende en los proveedores de navegadores.
Revisando el soporte de los navegadores
Para utilizar el API de almacenamiento web debemos asegurarnos de que el navegador del usuario lo soporta:
1 2 3 4 5 6 7 |
function checkLocalStorageSupport() {<br /> try {<br /> return 'localStorage' in window && window['localStorage'] !== null;<br /> } catch (e) {<br /> return false;<br /> }<br /> } |
El almacenamiento web utiliza un objeto llamado localStorage, el cual es un objeto de windowclass. El código de arriba revisa si localStorage es un objeto de window y entonces devolverá true, así la aplicación puede aprovechar el API de almacenamiento local.
Agregando y retornando datos
Agregar y retornar datos desde el objeto localStorage es tan fácil como llamar un método getter y setter que son implementados por la especificación de almacenamiento local. Se puede almacenar cualquier tipo de dato utilizando localStorage, pero todo se almacena como string dentro del área de almacenamiento. Esto significa que puede ser necesario convertir los datos antes de enviarlos a localStorage o después de utilizarlo. Por ejemplo, para almacenar un objeto de JavaScript, utilizando JSON y llamando stringify() antes de parse() sería necesario después de haber recuperado los datos. Es lo mismo para convertir variables numéricas una vez que han sido devueltas desde el objeto localStorage.
En este ejemplo, se establece un input sencillo para que cuando el usuario envíe los datos sean almacenados en el área de almacenamiento local. Si los datos ya fueron almacenados, cuando se cargue la página mostrará la información almacenada con un saludo. Esta es la función que se llama cuando el usuario envía los datos:
1 2 3 4 5 6 7 |
function onClick()<br /> {<br /> if(checkLocalStorageSupport)<br /> {<br /> window.localStorage.setItem("name",document.getElementById("name").value);<br /> }<br /> } |
Esta función utiliza el método setItem en el objeto localStorage y usa el valor del form para llenar el almacenamiento local. Cuando se carga la página, la aplicación tiene una función onLoad que revisa si hay datos almacenados y les agrega un saludo.
1 2 3 4 5 6 7 8 9 10 11 12 |
function onLoad()<br /> {<br /> if(checkLocalStorageSupport)<br /> {<br /> var name = window.localStorage.getItem("name");<br /> if(name != null)<br /> {<br /> window.document.getElementById("divName").innerHTML = "Bienvenido " + name; <br /> }<br /> <br /> }<br /> } |
Borrando datos
Mientras que los usuarios pueden borrar los datos en localStorage utilizando su navegador. puede tener más sentido darles la opción de borrar los datos desde la aplicación. localStorage tiene un método clear() que justamente hace eso. El código de abajo se ejecuta cuando el usuario da clic al boton reset en la aplicación. Si se quiere remover un elemento específico, se puede usar el método removeItem() para eliminar una sola llave del almacenamiento local.
1 2 3 4 5 6 7 |
function onReset()<br /> {<br /> if(checkLocalStorageSupport())<br /> {<br /> window.localStorage.clear(); <br /> }<br /> } |
Manejando cambios
La API de almacenamiento local también incluye una forma de escuchar y responder a cambios en el almacenamiento local. Al agregar un event listener y escuchar un evento de storage, la aplicación puede responder a los cambios de localStorage. El dato en el evento incluye el nombre de la llave que cambio, el nuevo valor, el valor anterior (si es aplicable), y la URL de la página que llamó al API. La especificación para el API de localStorage esta implementada de tal manera que la sesión que inicializo el evento no verá la ejecución del evento. Eso es debido a que la especificación dice que el evento debería ser ejecutado únicamente por otras pestañas o sesiones y no en la que cambió el almacenamiento.
Para escuchar los eventos de almacenamiento, al primer paso es agregar el event listener:
1 |
window.addEventListener("storage",onStorageChange); |
Entonces se establece el evento de onStorageChange para manejar el evento de almacenamiento.
1 2 3 4 5 6 |
function onStorageChange(e) {<br /> if(e.key == "name")<br /> {<br /> alert(e.newValue + ' acaba de agregar su nombre al almacenamiento local'); <br /> }<br /> } |
Hay un API similar para crear datos que solamente persisten para la sesión individual. Al utilizar el objeto sessionStorage en vez de localStorage, cualquier dato almacenado será automáticamente eliminado cuando el usuario deje la pagina. De hecho, el API tiene exactamente los mismos metodos, asi que se pueden utilizar al solo reemplazar localStorage con sessionStorage y los datos locales serán almacenados hasta que el usuario cierre el navegador o la pestaña en que se encuentre la aplicación.
Almacenamiento Offline
A veces simplificar unos cuantos datos en la máquina del usuario no será suficiente. En muchos casos, la aplicación completa puede tener que trabajar offline, no solo almacenar datos. Para ese caso de uso, HTML5 incluye la posibilidad de almacenar archivos y recursos en la máquina del usuario a los que el navegador podrá acceder sin una conexión a internet. Eso incluye imágenes, archivos JavaScript, archivos HTML, archivos CSS, casi cualquier cosa que conforme a la aplicación web puede ser almacenada localmente y utilizado incluso sin una conexión a internet. La clave de esto es configurar el manifiesto de almacenamiento.
Utilizando un archivo manifiesto
El archivo manifiesto es parte de un nuevo atributo manifest en la etiqueta raíz de HTML. Es un archivo en el servidor web que lista todos los archivos que el navegador debería descargar y guardar para que puedan ser utilizados después. Tiene la extensión .manifest y el único cambio que hay es que el servidor web debe tener soporte para el mime type .manifest, asegurate de que el servidor web que hospeda tu aplicación puede manejar archivos .manifest correctamente.
El archivo manifiesto tiene una estructura básica. Cada archivo manifiesto empieza con CACHE MANIFEST y ahí están listados todos los archivos que necesita el navegador para el acceso offline. Aquí hay un ejemplo sencillo que almacenará JavaScript, un archivo CSS, un par de imágenes, y la página HTML:
1 2 3 4 5 |
CACHE MANIFEST<br /> style.css<br /> offlinescript.js<br /> images/dreamweaver_logo.png<br /> images/edge_logo.png |
Las rutas son relativas a la página HTML a la cual accede el usuario. Hay otras opciones de las cuales se debe tener cuidado cuando se esta creando el manifiesto. El manifiesto del API ofrece una sección FALLBACK que apunta a una página que será cargada en el caso de uso anterior. Entonces cuando el usuario intente acceder a algo que no fue almacenado, verá un mensaje indicándole que se encuentra offline. Aqui hay un manifiesto de almacenamiento teórico que podría parecer así:
CACHE MANIFEST
1 2 3 4 5 6 7 8 9 10 11 |
NETWORK:<br /> my_dynamic_script.cgi<br /> <br /> FALLBACK:<br /> my_offline_message.html<br /> <br /> CACHE:<br /> style.css<br /> offlinescript.js<br /> images/dreamweaver_logo.png<br /> images/edge_logo.png |
En este ejemplo, se tiene una página HTML con páginas externas de JavaScript y CSS. La página HTML muestra texto describiendo el logo de Adobe y cuando se le da click a la imagen, el JavaScript cambia la imagen y el texto por otro logo. Aqui esta el codigo HTML seguido de la función en JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><br /> <html xmlns="http://www.w3.org/1999/xhtml" manifest="cache.manifest"><br /> <head><br /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><br /> <title>Adobe Logos</title><br /> <script src="offlinescript.js"></script><br /> <link href="style.css" rel="stylesheet" /><br /> </head><br /> <br /> <body><br /> <div id="textContent">Este es el logo de Edge:</div><br /><br /> <img id="logo" name="logo" src="images/edge_logo.png" onclick="onLogoClick();" /><br /> <p class="small">Click en el logo para cambiarlo.</p><br /> </body><br /> </html><br /> <br /> // JavaScript<br /> function onLogoClick(e)<br /> {<br /> var currentContent = window.document.getElementById("textContent").innerHTML;<br /> <br /> if(currentContent == "Este es el logo de Edge:")<br /> {<br /> window.document.getElementById("textContent").innerHTML = "Este es el logo de Dreamweaver:";<br /> window.document.logo.src = "images/dreamweaver_logo.png"; <br /> } else {<br /> window.document.getElementById("textContent").innerHTML = "Este es el logo de Edge:";<br /> window.document.logo.src = "images/edge_logo.png"; <br /> }<br /> } |
La pieza clave para todo es la etiqueta de HTML con el atributo manifest. Esta apuntando al archivo cache.manifest. El archivo manifiesto instruye al navegador a descargar todos los archivos en esa lista. No importa si el usuario carga estos archivos mientras está navegando, el navegador descargará automáticamente todo en el archivo manifiesto. Eso significa que las imágenes serán almacenadas para su uso offline incluso cuando no se ha descargado la página (ya que aun no se ha interactuado con esta) en que están siendo utilizadas. Así que al cargar la página una vez, se puede interactuar completamente offline.
Comprendiendo eventos
La última parte importante del acceso offline son los eventos que ocurren durante el proceso de almacenamiento. Cuando el navegador encuentra el atributo manifest ejecuta una serie de eventos en el objeto window.applicationCache. Lo primero que ocurre es que se dispara el evento checking. Este evento busca lo que se debe hacer con este archivo de almacenamiento en particular. Las herramientas de desarrollo de Google Chrome proporcionan una gran vista de los eventos que ocurren cuando se lleva a cabo el almacenamiento.
Si es la primera vez que el usuario visita el sitio, el evento de descarga será ejecutado y el navegador descargará todos los recursos listados en el archivo manifiesto. Lee el archivo manifiesto, revisa cuantos archivos se tiene que descargar, y entonces devuelve una actualización del progreso de cada archivo. El evento de progreso incluye una variable descargada y una variable total, con la cual podemos determinar cuánto se ha almacenado.
1 2 3 4 5 |
function onProgress(e)<br /> {<br /> var content = window.document.getElementById("loadedInfo").innerHTML;<br /> window.document.getElementById("loadedInfo").innerHTML = content + '<br /> Loaded file ' + e.loaded + ' of ' + e.total;<br /> } |
Una vez que todo ha sido almacenado, el navegador dispara un evento de almacenamiento para notificar al desarrollador que todos los archivos han sido descargados satisfactoriamente.
Las siguientes ocasiones que el usuario visite la página, el navegador revisa si algo ha cambiado en el archivo manifiesto. Si nada ha cambiado, ejecuta el evento noupdate y continua. Si algo ha cambiado, realiza el mismo proceso descrito anteriormente; ejecutará el evento downloading con una serie de eventos de progreso hasta que los archivos hayan sido actualizados. Cuando eso ocurre, en vez de ejecutar el evento cached, el navegador disparará un evento updateready para avisar que los archivos han sido actualizados.
El último evento del cual preocuparse es el evento error que se ejecutará cada vez que algo falle. Puede ser que un archivo no se haya descargado correctamente, que el navegador no pueda acceder al archivo manifiesto, o que uno o más archivos listados no están disponibles. Para identificarlo, simplemente se agrega un event listener para este evento.
1 2 3 4 5 6 |
window.applicationCache.addEventListener("error",onError);<br /> <br /> function onError(e)<br /> {<br /> window.document.getElementById("loadedInfo").innerHTML = "Algo salió mal mientras se guardaban los archivos.";<br /> } |
Base de datos web
Originalmente había una especificación de SQL Web que ya no recibe mantenimiento. Ahora esa energía es enfocada al trabajo de un API de Base de Datos indexada y parece que esta será la forma de almacenar información en bases de datos relacionales. Firefox y Chrome soportan indexedDB pero debido a la especificación y al soporte aún están en curso, eso está más allá del enfoque de este artículo.
A dónde ir desde aquí
En este artículo exploramos dos de las principales formas para almacenar datos localmente. La primera proporciona un almacenamiento básico. Con esta API se pueden guardar nombres de valores pares más allá de las sesiones o una simple sesión. La segunda, la aplicación del archivo manifiesto, que permite a los desarrolladores almacenar archivos completos en la máquina del usuario para su uso offline.
Para más información de las APIs de HTML5 de almacenamiento, revisa los siguientes recursos:
El pasado, el presente y el futuro del almacenamiento local para aplicaciones web
HTML5 Rocks: Almacenamiento Local
Dr. Dobbs: Almacenamiento Web para HTML5
La documentación: Borrador de la W3C: Almacenamiento Web
La versión original de este artículo está publicada en Adobe Devnet bajo licencia Creative Commons, fue traducido y adaptado en nuestro blog por Jesús Macedo.