Dion Almaer y Pamela Fox, Google
Junio de 2007
Nota del editor: La API de Google Gears ya no está disponible.
- Introducción
- Conoce la app
- Uso de feeds de la API de Google Base
- Cómo agregar Google Gears a la app
- Cómo depurar la app sin conexión
- Conclusión
Introducción
Combinando Google Base con Google Gears, demostramos cómo crear una aplicación que se puede usar sin conexión. Después de leer este artículo, conocerás mejor la API de Google Base y comprenderás cómo usar Google Gears para almacenar y acceder a las preferencias y los datos del usuario.
Información sobre la app
Para comprender esta app, primero debes conocer Google Base, que es básicamente una gran base de datos de elementos que abarcan varias categorías, como productos, opiniones, recetas, eventos y mucho más.
Cada elemento se anota con un título, una descripción, un vínculo a la fuente original de los datos (si existe) y atributos adicionales que varían según el tipo de categoría. Google Base aprovecha el hecho de que los elementos de la misma categoría comparten un conjunto común de atributos; por ejemplo, todas las recetas tienen ingredientes. Incluso, en ocasiones, los elementos de Google Base aparecerán en los resultados de la búsqueda web de Google o en la búsqueda de productos de Google.
Nuestra app de demostración, Base with Gears, te permite almacenar y mostrar búsquedas comunes que podrías realizar en Google Base, como encontrar recetas con "chocolate" (¡qué rico!) o anuncios personales con "paseos por la playa" (¡qué romántico!). Puedes considerarlo como un "lector de Google Base" que te permite suscribirte a búsquedas y ver los resultados actualizados cuando vuelves a visitar la app o cuando esta busca feeds actualizados cada 15 minutos.
Los desarrolladores que deseen extender la app podrían agregar más funciones, como alertar visualmente al usuario cuando los resultados de la búsqueda contengan resultados nuevos, permitir que el usuario marque como favoritos (con una estrella) los elementos favoritos (sin conexión y en línea) y permitir que el usuario realice búsquedas de atributos específicos de la categoría, como Google Base.
Uso de feeds de la API de Google Base Data
Se puede consultar Google Base de forma programática con la API de Google Base Data, que cumple con el framework de las APIs de Google Data. El protocolo de la API de Google Data proporciona un protocolo simple para leer y escribir en la Web, y lo usan muchos productos de Google: Picasa, Hojas de cálculo, Blogger, Calendario, Notebook y muchos más.
El formato de la API de Google Data se basa en XML y en el protocolo de publicación Atom, por lo que la mayoría de las interacciones de lectura y escritura se realizan en XML.
Un ejemplo de un feed de Google Base basado en la API de Google Data es el siguiente:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera
El tipo de feed snippets
nos proporciona el feed de elementos disponible públicamente. El -/products
nos permite restringir el feed a la categoría de productos. Además, el parámetro bq=
nos permite restringir aún más el feed, solo a los resultados que contienen la palabra clave "cámara digital". Si ves este feed en el navegador, verás XML que contiene nodos <entry>
con resultados coincidentes. Cada entrada contiene los elementos típicos de autor, título, contenido y vínculo, pero también incluye atributos adicionales específicos de la categoría (como "precio" para los elementos de la categoría de productos).
Debido a la restricción de dominio múltiple de XMLHttpRequest en el navegador, no podemos leer directamente un feed XML de www.google.com en nuestro código JavaScript. Podríamos configurar un proxy del servidor para leer el XML y devolverlo en una ubicación del mismo dominio que nuestra app, pero preferimos evitar la programación del servidor por completo. Por suerte, hay una alternativa.
Al igual que las otras APIs de datos de Google, la API de datos de Google Base tiene una opción de salida JSON, además del XML estándar. El resultado del feed que vimos anteriormente en formato JSON estaría en esta URL:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera&alt=json
JSON es un formato de intercambio ligero que permite el anidamiento jerárquico y varios tipos de datos. Sin embargo, lo que es más importante, el resultado JSON es código JavaScript nativo, por lo que se puede cargar en tu página web con solo hacer referencia a él en una etiqueta de secuencia de comandos, lo que evita la restricción de dominio cruzado.
Las APIs de datos de Google también te permiten especificar una salida "json-in-script" con una función de devolución de llamada para ejecutar una vez que se cargue el JSON. Esto hace que sea aún más fácil trabajar con el resultado JSON, ya que podemos agregar etiquetas de secuencia de comandos de forma dinámica a la página y especificar diferentes funciones de devolución de llamada para cada una.
Por lo tanto, para cargar de forma dinámica un feed JSON de la API de Base en la página, podríamos usar la siguiente función que crea una etiqueta de secuencia de comandos con la URL del feed (a la que se agregan los valores de alt
callback
) y la agrega a la página.
function getJSON() { var script = document.createElement('script'); var url = "http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera"; script.setAttribute('src', url + "&alt=json-in-script&callback=listResults"); script.setAttribute('type', 'text/JavaScript'); document.documentElement.firstChild.appendChild(script); }
Por lo tanto, nuestra función de devolución de llamada listResults
ahora puede iterar el JSON que se pasó como el único parámetro y mostrar información sobre cada entrada que se encuentra en una lista con viñetas.
function listTasks(root) { var feed = root.feed; var html = ['']; html.push('<ul>'); for (var i = 0; i < feed.entry.length; ++i) { var entry = feed.entry[i]; var title = entry.title.$t; var content = entry.content.$t; html.push('<li>', title, ' (', content, ')</li>'); } html.push('</ul>'); document.getElementById("agenda").innerHTML = html.join(""); }
Cómo agregar Google Gears
Ahora que tenemos una aplicación que puede comunicarse con Google Base a través de la API de Google Data, queremos habilitar esta aplicación para que se ejecute sin conexión. Aquí es donde entra en juego Google Gears.
Existen varias opciones de arquitectura cuando se trata de escribir una aplicación que puede funcionar sin conexión. Te harás preguntas sobre cómo debería funcionar la aplicación en línea y sin conexión (p. ej., ¿funciona exactamente igual? ¿Hay algunas funciones inhabilitadas, como la búsqueda? ¿Cómo manejarás la sincronización?
En nuestro caso, queríamos asegurarnos de que los usuarios de navegadores sin Gears pudieran seguir usando la app y, al mismo tiempo, ofrecerles a los usuarios que sí tienen el complemento los beneficios del uso sin conexión y una IU más responsiva.
Nuestra arquitectura se ve así:
- Tenemos un objeto JavaScript que se encarga de almacenar tus búsquedas y devolver los resultados de estas.
- Si tienes instalado Google Gears, obtendrás una versión de Gears que almacena todo en la base de datos local.
- Si no tienes instalado Google Gears, obtendrás una versión que almacena las búsquedas en una cookie y no almacena los resultados completos (por lo que la capacidad de respuesta es un poco más lenta), ya que los resultados son demasiado grandes para almacenarse en una cookie.
if (online) {}
en todas partes. En cambio, la aplicación tiene una verificación de Gears y, luego, se usa el adaptador correcto.
Cómo usar una base de datos local de Gears
Uno de los componentes de Gears es la base de datos local SQLite que está incorporada y lista para que la uses. Hay una API de base de datos simple que te resultará familiar si ya usaste APIs para bases de datos del servidor, como MySQL o Oracle.
Los pasos para usar una base de datos local son bastante sencillos:
- Inicializa los objetos de Google Gears
- Obtén un objeto de fábrica de bases de datos y abre una base de datos
- Comienza a ejecutar solicitudes de SQL
Veamos estos pasos rápidamente.
Inicializa los objetos de Google Gears
Tu aplicación debe leer el contenido de /gears/samples/gears_init.js
directamente o pegando el código en tu propio archivo JavaScript. Una vez que <script src="..../gears_init.js" type="text/JavaScript"></script>
esté en funcionamiento, tendrás acceso al espacio de nombres google.gears.
Cómo obtener un objeto de Database Factory y abrir una base de datos
var db = google.gears.factory.create('beta.database', '1.0'); db.open('testdb');
Esta única llamada te proporcionará un objeto de base de datos que te permitirá abrir un esquema de base de datos. Cuando abres bases de datos, se definen sus alcances a través de las mismas reglas de política de mismo origen, por lo que tu "testdb" no entrará en conflicto con mi "testdb".
Cómo comenzar a ejecutar solicitudes de SQL
Ahora, ya podemos enviar solicitudes de SQL a la base de datos. Cuando enviamos solicitudes de "selección", recibimos un conjunto de resultados que podemos iterar para obtener los datos deseados:
var rs = db.execute('select * from foo where name = ?', [ name ]);
Puedes operar en el conjunto de resultados devuelto con los siguientes métodos:
boolean | isValidRow() |
void | next() |
void | close() |
int | fieldCount() |
string | fieldName(int fieldIndex) |
variant | field(int fieldIndex) |
variant | fieldByName(string fieldname) |
Para obtener más detalles, consulta la documentación de la API del módulo de la base de datos. (Nota del editor: La API de Google Gears ya no está disponible).
Cómo usar GearsDB para encapsular la API de bajo nivel
Queríamos encapsular y hacer más convenientes algunas de las tareas comunes de la base de datos. Por ejemplo:
- Queríamos tener una buena forma de registrar el SQL que se generaba cuando depurábamos la aplicación.
- Queríamos controlar las excepciones en un solo lugar en lugar de tener que
try{}catch(){}
en todas partes. - Queríamos trabajar con objetos JavaScript en lugar de conjuntos de resultados cuando leíamos o escribíamos datos.
Para controlar estos problemas de forma genérica, creamos GearsDB, una biblioteca de código abierto que encapsula el objeto Database. Ahora mostraremos cómo usar GearsDB.
Configuración inicial
En nuestro código window.onload, debemos asegurarnos de que las tablas de la base de datos en las que confiamos estén en su lugar. Si el usuario tiene instalado Gears cuando se ejecuta el siguiente código, creará un objeto GearsBaseContent
:
content = hasGears() ? new GearsBaseContent() : new CookieBaseContent();
A continuación, abrimos la base de datos y creamos tablas si aún no existen:
db = new GearsDB('gears-base'); // db is defined as a global for reuse later! if (db) { db.run('create table if not exists BaseQueries' + ' (Phrase varchar(255), Itemtype varchar(100))'); db.run('create table if not exists BaseFeeds' + ' (id varchar(255), JSON text)'); }
En este punto, nos aseguramos de que tenemos una tabla para almacenar las búsquedas y los feeds. El código new GearsDB(name)
encapsulará la apertura de una base de datos con el nombre determinado. El método run
encapsula el método execute
de nivel inferior, pero también controla la salida de depuración a una consola y las excepciones de captura.
Cómo agregar un término de búsqueda
Cuando ejecutes la app por primera vez, no tendrás ninguna búsqueda. Si intentas buscar una Nintendo Wii en los productos, guardaremos este término de búsqueda en la tabla BaseQueries.
La versión de Gears del método addQuery
hace esto tomando la entrada y guardándola a través de insertRow
:
var searchterm = { Phrase: phrase, Itemtype: itemtype }; db.insertRow('BaseQueries', searchterm);
insertRow
toma un objeto JavaScript (searchterm
) y controla la inserción en la tabla por ti. También te permite definir restricciones (por ejemplo, la inserción de bloques de unicidad de más de un "Bob"). Sin embargo, la mayoría de las veces, controlarás estas restricciones en la propia base de datos.
Cómo obtener todos los términos de búsqueda
Para completar tu lista de búsquedas anteriores, usamos un selector agradable llamado selectAll
:
GearsBaseContent.prototype.getQueries = function() { return this.db.selectAll('select * from BaseQueries'); }
Se mostrará un array de objetos JavaScript que coinciden con las filas de la base de datos (p.ej., [ { Phrase: 'Nintendo Wii', Itemtype: 'product' }, { ... }, ...]
).
En este caso, puedes devolver la lista completa. Sin embargo, si tienes muchos datos, probablemente quieras usar una devolución de llamada en la llamada de selección para poder operar en cada fila devuelta a medida que llega:
db.selectAll('select * from BaseQueries where Itemtype = ?', ['product'], function(row) { ... do something with this row ... });
A continuación, se incluyen otros métodos de selección útiles en GearsDB:
selectOne(sql, args) | Devuelve el primer objeto JavaScript coincidente |
selectRow(table, where, args, select) | Se usa normalmente en casos simples para ignorar SQL. |
selectRows(table, where, args, callback, select) | Es igual que selectRow, pero para varios resultados. |
Cómo cargar un feed
Cuando recibimos el feed de resultados de Google Base, debemos guardarlo en la base de datos:
content.setFeed({ id: id, JSON: json.toJSONString() }); ... which calls ... GearsBaseContent.prototype.setFeed = function(feed) { this.db.forceRow('BaseFeeds', feed); }
Primero, tomamos el feed JSON y lo devolvemos como una cadena con el método toJSONString
. Luego, creamos el objeto feed
y lo pasamos al método forceRow
. forceRow
INSERTARÁ una entrada si aún no existe una o ACTUALIZARÁ una existente.
Visualización de los resultados de la búsqueda
Nuestra app muestra los resultados de una búsqueda determinada en el panel derecho de la página. A continuación, te mostramos cómo recuperamos el feed asociado al término de búsqueda:
GearsBaseContent.prototype.getFeed = function(url) { var row = this.db.selectRow('BaseFeeds', 'id = ?', [ url ]); return row.JSON; }
Ahora que tenemos el JSON de una fila, podemos eval()
para recuperar los objetos:
eval("var json = " + jsonString + ";");
Ya podemos comenzar a insertar contenido de JSON en nuestra página con innerHTML.
Cómo usar un Resource Store para el acceso sin conexión
Como obtenemos contenido de una base de datos local, esta app también debería funcionar sin conexión, ¿verdad?
Bueno, no. El problema es que, para iniciar esta app, debes cargar sus recursos web, como JavaScript, CSS, HTML e imágenes. Actualmente, si el usuario siguió los siguientes pasos, es posible que la app siga funcionando: iniciar en línea, realizar algunas búsquedas, no cerrar el navegador, pasar a sin conexión. Esto podría funcionar, ya que los elementos seguirían en la caché del navegador. Pero ¿qué sucede si no es así? Queremos que nuestros usuarios puedan acceder a la app desde cero, después de un reinicio, etc.
Para ello, usamos el componente LocalServer y capturamos nuestros recursos. Cuando capturas un recurso (como el código HTML y JavaScript necesarios para ejecutar la aplicación), Gears guarda estos elementos y también intercepta las solicitudes del navegador para devolverlos. El servidor local actuará como un policía de tránsito y devolverá el contenido guardado de la tienda.
También usamos el componente ResourceStore, que requiere que le indiques manualmente al sistema qué archivos deseas capturar. En muchas situaciones, querrás crear versiones de tu aplicación y permitir actualizaciones de forma transaccional. Un conjunto de recursos define una versión, y cuando lances un nuevo conjunto de recursos, querrás que tus usuarios tengan una actualización sin problemas de los archivos. Si ese es tu modelo, usarás la API de ManagedResourceStore.
Para capturar nuestros recursos, el objeto GearsBaseContent hará lo siguiente:
- Configura un array de archivos que se deben capturar
- Crea un LocalServer
- Abre o crea un nuevo ResourceStore
- Llamar para capturar las páginas en la tienda
// Step 1 this.storeName = 'gears-base'; this.pageFiles = [ location.pathname, 'gears_base.js', '../scripts/gears_db.js', '../scripts/firebug/firebug.js', '../scripts/firebug/firebug.html', '../scripts/firebug/firebug.css', '../scripts/json_util.js', 'style.css', 'capture.gif' ]; // Step 2 try { this.localServer = google.gears.factory.create('beta.localserver', '1.0'); } catch (e) { alert('Could not create local server: ' + e.message); return; } // Step 3 this.store = this.localServer.openStore(this.storeName) || this.localServer.createStore(this.storeName); // Step 4 this.capturePageFiles(); ... which calls ... GearsBaseContent.prototype.capturePageFiles = function() { this.store.capture(this.pageFiles, function(url, success, captureId) { console.log(url + ' capture ' + (success ? 'succeeded' : 'failed')); }); }
Es importante tener en cuenta que solo puedes capturar recursos en tu propio dominio. Nos encontramos con esta limitación cuando intentamos acceder al archivo JavaScript de GearsDB directamente desde el archivo "gears_db.js" original en su rama SVN. Por supuesto, la solución es simple: debes descargar los recursos externos y colocarlos en tu dominio. Ten en cuenta que los redireccionamientos 302 o 301 no funcionarán, ya que LocalServer solo acepta los códigos de servidor 200 (correcto) o 304 (sin modificar).
Esto tiene implicaciones. Si colocas tus imágenes en images.yourdomain.com, no podrás capturarlas. www1 y www2 no pueden verse entre sí. Podrías configurar proxies del servidor, pero eso anularía el propósito de dividir tu aplicación en varios dominios.
Depura la aplicación sin conexión
Depurar una aplicación sin conexión es un poco más complicado. Ahora hay más situaciones para probar:
- Estoy en línea y la app se ejecuta por completo en la caché.
- Estoy en línea, pero no accedí a la app y no hay nada en la caché.
- Estoy sin conexión, pero accedí a la app
- Estoy sin conexión y nunca accedí a la app (¡no es un buen lugar para estar!).
Para facilitar el proceso, usamos el siguiente patrón:
- Inhabilitamos la caché en Firefox (o en el navegador que elijas) cuando necesitamos asegurarnos de que el navegador no esté tomando algo de la caché.
- Depuramos con Firebug (y Firebug Lite para probar en otros navegadores); usamos
console.log()
en todas partes y detectamos la consola por si acaso. - Agregamos código JavaScript auxiliar a lo siguiente:
- nos permite borrar la base de datos y comenzar de cero
- Quita los archivos capturados para que, cuando vuelvas a cargar la página, se conecte a Internet y los obtenga de nuevo (esto es útil cuando iteras en el desarrollo ;)).
El widget de depuración aparece en el lado izquierdo de la página solo si tienes instalado Gears. Tiene indicadores para limpiar el código:
GearsBaseContent.prototype.clearServer = function() { if (this.localServer.openStore(this.storeName)) { this.localServer.removeStore(this.storeName); this.store = null; } } GearsBaseContent.prototype.clearTables = function() { if (this.db) { this.db.run('delete from BaseQueries'); this.db.run('delete from BaseFeeds'); } displayQueries(); }
Conclusión
Como puedes ver, trabajar con Google Gears es bastante simple. Usamos GearsDB para que el componente de la base de datos sea aún más fácil y usamos el ResourceStore manual, que funcionó bien para nuestro ejemplo.
El área en la que pasas la mayor parte del tiempo es la definición de la estrategia para obtener datos en línea y cómo almacenarlos sin conexión. Es importante dedicar tiempo a definir el esquema de la base de datos. Si necesitas cambiar el esquema en el futuro, deberás administrar ese cambio, ya que tus usuarios actuales ya tendrán una versión de la base de datos. Esto significa que deberás enviar código de secuencia de comandos con cualquier actualización de la base de datos. Obviamente, es mejor minimizar esto, y puedes probar GearShift, una pequeña biblioteca que puede ayudarte a administrar las revisiones.
También podríamos haber usado ManagedResourceStore para hacer un seguimiento de nuestros archivos, con las siguientes consecuencias:
- Seríamos buenos ciudadanos y versionaríamos nuestros archivos para permitir futuras actualizaciones limpias.
- Hay una función de ManagedResourceStore que te permite crear un alias de una URL para otro contenido. Una opción de arquitectura válida sería tener gears_base.js como una versión que no es de Gears y crear un alias para que Gears descargue gears_base_withgears.js, que tendría toda la compatibilidad sin conexión.
Esperamos que hayas disfrutado de la preparación de aplicaciones y que te haya resultado fácil. Únete a nuestro foro de Google Gears si tienes preguntas o una app para compartir.