Google Base und Google Gears für eine leistungsstarke Offline-Nutzung

Erster Artikel in der Reihe „Bessere Ajax-Anwendungen mit Google-APIs erstellen“.

Dion Almaer und Pamela Fox, Google
Juni 2007

Hinweis der Redaktion:Die Google Gears API ist nicht mehr verfügbar.

Einführung

In diesem Beispiel wird gezeigt, wie Sie Google Base mit Google Gears kombinieren, um eine Anwendung zu erstellen, die offline verwendet werden kann. Nachdem Sie diesen Artikel gelesen haben, kennen Sie die Google Base API besser und wissen, wie Sie Google Gears zum Speichern und Abrufen von Nutzereinstellungen und ‑daten verwenden.

Informationen zur App

Um diese App zu verstehen, sollten Sie sich zuerst mit Google Base vertraut machen. Dabei handelt es sich im Grunde um eine große Datenbank mit Elementen aus verschiedenen Kategorien wie Produkte, Rezensionen, Rezepte und Veranstaltungen.

Jeder Artikel ist mit einem Titel, einer Beschreibung, einem Link zur Originalquelle der Daten (falls vorhanden) sowie zusätzlichen Attributen versehen, die je nach Kategorie variieren. Google Base nutzt die Tatsache, dass Artikel in derselben Kategorie eine gemeinsame Gruppe von Attributen haben. Alle Rezepte haben beispielsweise Zutaten. Google Base-Artikel werden gelegentlich sogar in den Suchergebnissen der Google Websuche oder der Google Produktsuche angezeigt.

Mit unserer Demo-App Base with Gears können Sie häufige Suchanfragen speichern und anzeigen lassen, die Sie in Google Base ausführen könnten, z. B. Rezepte mit „Schokolade“ (lecker) oder Kontaktanzeigen mit „Spaziergänge am Strand“ (romantisch). Sie können sich die App als „Google Base Reader“ vorstellen, mit dem Sie Suchanfragen abonnieren und die aktualisierten Ergebnisse sehen können, wenn Sie die App wieder aufrufen oder wenn die App alle 15 Minuten nach aktualisierten Feeds sucht.

Entwickler, die die App erweitern möchten, könnten weitere Funktionen hinzufügen, z. B. den Nutzer visuell benachrichtigen, wenn die Suchergebnisse neue Ergebnisse enthalten, dem Nutzer ermöglichen, Favoriten (offline + online) mit einem Lesezeichen zu versehen (mit einem Stern zu markieren), und dem Nutzer ermöglichen, kategoriespezifische Attributsuchen wie Google Base durchzuführen.

Google Base Data API-Feeds verwenden

Google Base kann programmatisch mit der Google Base Data API abgefragt werden, die dem Google Data API-Framework entspricht. Das Google Data API-Protokoll bietet ein einfaches Protokoll zum Lesen und Schreiben im Web und wird von vielen Google-Produkten verwendet, darunter Picasa, Tabellen, Blogger, Kalender und Notebook.

Das Google Data API-Format basiert auf XML und dem Atom Publishing Protocol. Die meisten Lese-/Schreibvorgänge erfolgen daher in XML.

Ein Beispiel für einen Google Base-Feed, der auf der Google Data API basiert, ist:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera

Der Feedtyp snippets enthält den öffentlich verfügbaren Feed mit Artikeln. Mit -/products können wir den Feed auf die Produktkategorie beschränken. Mit dem Parameter bq= können wir den Feed weiter einschränken, sodass nur Ergebnisse mit dem Keyword „Digitalkamera“ angezeigt werden. Wenn Sie diesen Feed im Browser aufrufen, sehen Sie XML-Code mit <entry>-Knoten, die passende Ergebnisse enthalten. Jeder Eintrag enthält die typischen Elemente für Autor, Titel, Inhalt und Link, aber auch zusätzliche kategoriespezifische Attribute wie „Preis“ für Artikel in der Kategorie „Produkte“.

Aufgrund der domainübergreifenden Einschränkung von XMLHttpRequest im Browser dürfen wir in unserem JavaScript-Code keinen XML-Feed von www.google.com direkt einlesen. Wir könnten einen serverseitigen Proxy einrichten, um das XML einzulesen und an einem Ort in derselben Domain wie unsere App wieder auszugeben, möchten aber serverseitige Programmierung ganz vermeiden. Zum Glück gibt es eine Alternative.

Wie die anderen Google Data APIs bietet auch die Google Base Data API neben dem Standard-XML eine JSON-Ausgabeoption. Die Ausgabe für den Feed, den wir uns zuvor angesehen haben, wäre im JSON-Format unter dieser URL verfügbar:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera&alt=json

JSON ist ein einfaches Austauschformat, das hierarchische Verschachtelung sowie verschiedene Datentypen ermöglicht. Noch wichtiger ist jedoch, dass die JSON-Ausgabe selbst nativer JavaScript-Code ist. Sie kann also in Ihre Webseite geladen werden, indem Sie einfach in einem Script-Tag darauf verweisen. So wird die domänenübergreifende Einschränkung umgangen.

Mit den Google Data APIs können Sie auch eine „json-in-script“-Ausgabe mit einer Callback-Funktion angeben, die ausgeführt wird, sobald das JSON geladen ist. Dadurch wird die Arbeit mit der JSON-Ausgabe noch einfacher, da wir Script-Tags dynamisch an die Seite anhängen und für jedes Tag unterschiedliche Callback-Funktionen angeben können.

Um einen JSON-Feed der Base API dynamisch auf die Seite zu laden, können wir die folgende Funktion verwenden, die ein Script-Tag mit der Feed-URL (angehängt mit altcallback-Werten) erstellt und an die Seite anhängt.

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);
}

Unsere Callback-Funktion listResults kann jetzt also den als einzigen Parameter übergebenen JSON-Code durchlaufen und Informationen zu jedem gefundenen Eintrag in einer Aufzählungsliste anzeigen.

  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("");
  }

Google Gears hinzufügen

Nachdem wir nun eine Anwendung haben, die über die Google Data API mit Google Base kommunizieren kann, möchten wir diese Anwendung für die Offlineausführung aktivieren. Hier kommt Google Gears ins Spiel.

Es gibt verschiedene Architekturmöglichkeiten, wenn es darum geht, eine Anwendung zu schreiben, die offline verwendet werden kann. Sie werden sich fragen, wie die Anwendung online und offline funktionieren soll (z. B.ob sie genau gleich funktioniert). Sind einige Funktionen deaktiviert, z. B. die Suche? Wie gehen Sie mit der Synchronisierung um?)

In unserem Fall wollten wir sicherstellen, dass Nutzer in Browsern ohne Gears die App weiterhin verwenden können, während Nutzer mit dem Plug-in die Vorteile der Offline-Nutzung und einer reaktionsschnelleren Benutzeroberfläche nutzen können.

Unsere Architektur sieht so aus:

  • Wir haben ein JavaScript-Objekt, das für das Speichern Ihrer Suchanfragen und das Zurückgeben von Ergebnissen für diese Anfragen zuständig ist.
  • Wenn Sie Google Gears installiert haben, erhalten Sie eine Gears-Version, in der alles in der lokalen Datenbank gespeichert wird.
  • Wenn Sie Google Gears nicht installiert haben, erhalten Sie eine Version, in der die Anfragen in einem Cookie gespeichert werden. Die vollständigen Ergebnisse werden nicht gespeichert, da sie zu groß sind, um in einem Cookie gespeichert zu werden. Daher ist die Reaktionszeit etwas langsamer.
Der Vorteil dieser Architektur ist, dass Sie nicht überall nach if (online) {} suchen müssen. Stattdessen hat die Anwendung einen Gears-Check und dann wird der richtige Adapter verwendet.


Lokale Gears-Datenbank verwenden

Eine der Komponenten von Gears ist die lokale SQLite-Datenbank, die eingebettet und einsatzbereit ist. Es gibt eine einfache Datenbank-API, die Ihnen vertraut sein dürfte, wenn Sie schon einmal APIs für serverseitige Datenbanken wie MySQL oder Oracle verwendet haben.

Die Schritte zur Verwendung einer lokalen Datenbank sind ganz einfach:

  • Google Gears-Objekte initialisieren
  • Datenbank-Factory-Objekt abrufen und Datenbank öffnen
  • SQL-Anfragen ausführen

Sehen wir uns diese kurz an.


Google Gears-Objekte initialisieren

Ihre Anwendung sollte den Inhalt von /gears/samples/gears_init.js entweder direkt lesen oder den Code in Ihre eigene JavaScript-Datei einfügen. Sobald Sie <script src="..../gears_init.js" type="text/JavaScript"></script> eingerichtet haben, können Sie auf den Namespace google.gears zugreifen.


Database Factory-Objekt abrufen und Datenbank öffnen
var db = google.gears.factory.create('beta.database', '1.0');
db.open('testdb');

Mit diesem Aufruf erhalten Sie ein Datenbankobjekt, mit dem Sie ein Datenbankschema öffnen können. Wenn Sie Datenbanken öffnen, werden sie über dieselben Regeln für die Herkunftsbeschränkung festgelegt. Ihre „testdb“ kollidiert also nicht mit meiner „testdb“.


SQL-Anfragen ausführen

Jetzt können wir SQL-Anfragen an die Datenbank senden. Wenn wir „select“-Anfragen senden, erhalten wir ein Resultset zurück, das wir nach den gewünschten Daten durchlaufen können:

var rs = db.execute('select * from foo where name = ?', [ name ]);

Sie können das zurückgegebene Ergebnis-Set mit den folgenden Methoden bearbeiten:

booleanisValidRow()
voidnext()
voidclose()
intfieldCount()
stringfieldName(int fieldIndex)
variantfield(int fieldIndex)
variantfieldByName(string fieldname)

Weitere Informationen finden Sie in der API-Dokumentation zum Datenbankmodul. (Anmerkung des Editors: Die Google Gears API ist nicht mehr verfügbar.)


GearsDB zum Kapseln der Low-Level-API verwenden

Wir wollten einige der häufigsten Datenbankaufgaben zusammenfassen und vereinfachen. Beispiel:

  • Wir wollten eine gute Möglichkeit haben, den SQL-Code zu protokollieren, der beim Debuggen der Anwendung generiert wurde.
  • Wir wollten Ausnahmen an einem Ort behandeln, anstatt try{}catch(){} überall zu verwenden.
  • Wir wollten beim Lesen oder Schreiben von Daten mit JavaScript-Objekten anstelle von Ergebnismengen arbeiten.

Um diese Probleme auf generische Weise zu beheben, haben wir GearsDB erstellt, eine Open-Source-Bibliothek, die das Database-Objekt umschließt. Wir zeigen Ihnen nun, wie Sie GearsDB verwenden.

Ersteinrichtung

Im window.onload-Code müssen wir dafür sorgen, dass die Datenbanktabellen, auf die wir uns verlassen, vorhanden sind. Wenn der Nutzer Gears installiert hat, wenn der folgende Code ausgeführt wird, wird ein GearsBaseContent-Objekt erstellt:

content = hasGears() ? new GearsBaseContent() : new CookieBaseContent();

Als Nächstes öffnen wir die Datenbank und erstellen Tabellen, falls sie noch nicht vorhanden sind:

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)');
}

Wir haben jetzt eine Tabelle, in der die Abfragen und Feeds gespeichert werden können. Der Code new GearsDB(name) kapselt das Öffnen einer Datenbank mit dem angegebenen Namen. Die Methode run umschließt die Methode execute auf niedrigerer Ebene, verarbeitet aber auch die Debugging-Ausgabe in einer Konsole und fängt Ausnahmen ab.


Suchbegriff hinzufügen

Wenn Sie die App zum ersten Mal ausführen, sind noch keine Suchanfragen vorhanden. Wenn Sie in „Produkte“ nach einer Nintendo Wii suchen, wird dieser Suchbegriff in der Tabelle „BaseQueries“ gespeichert.

In der Gears-Version der Methode addQuery wird dies erreicht, indem die Eingabe übernommen und über insertRow gespeichert wird:

var searchterm = { Phrase: phrase, Itemtype: itemtype };
db.insertRow('BaseQueries', searchterm); 

insertRow nimmt ein JavaScript-Objekt (searchterm) entgegen und fügt es in die Tabelle ein. Außerdem können Sie Einschränkungen definieren, z. B. dass nicht mehr als ein Block mit dem Namen „Bob“ eingefügt werden darf. In den meisten Fällen werden Sie diese Einschränkungen jedoch in der Datenbank selbst verarbeiten.


Alle Suchbegriffe abrufen

Um die Liste der bisherigen Suchanfragen zu füllen, verwenden wir einen schönen Select-Wrapper namens selectAll:

GearsBaseContent.prototype.getQueries = function() {
  return this.db.selectAll('select * from BaseQueries');
}

Dadurch wird ein Array von JavaScript-Objekten zurückgegeben, die den Zeilen in der Datenbank entsprechen (z.B. [ { Phrase: 'Nintendo Wii', Itemtype: 'product' }, { ... }, ...]).

In diesem Fall können Sie die vollständige Liste zurückgeben. Wenn Sie jedoch viele Daten haben, sollten Sie wahrscheinlich einen Callback im Select-Aufruf verwenden, damit Sie jede zurückgegebene Zeile verarbeiten können, sobald sie eingeht:

 db.selectAll('select * from BaseQueries where Itemtype = ?', ['product'], function(row) {
  ... do something with this row ...
});

Hier sind einige weitere hilfreiche Auswahlmethoden in GearsDB:

selectOne(sql, args)Erstes/ein passendes JavaScript-Objekt zurückgeben
selectRow(table, where, args, select)Wird normalerweise in einfachen Fällen verwendet, um SQL zu ignorieren
selectRows(table, where, args, callback, select)Wie „selectRow“, aber für mehrere Ergebnisse.

Feed laden

Wenn wir den Ergebnissen-Feed von Google Base erhalten, müssen wir ihn in der Datenbank speichern:

content.setFeed({ id: id, JSON: json.toJSONString() });

... which calls ...

GearsBaseContent.prototype.setFeed = function(feed) {
  this.db.forceRow('BaseFeeds', feed);
}

Zuerst nehmen wir den JSON-Feed und geben ihn mit der Methode toJSONString als String zurück. Anschließend erstellen wir das feed-Objekt und übergeben es an die Methode forceRow. Mit forceRow wird ein Eintrag eingefügt, wenn noch keiner vorhanden ist, oder ein vorhandener Eintrag aktualisiert.


Suchergebnisse anzeigen

In unserer App werden die Ergebnisse für eine bestimmte Suche im rechten Bereich der Seite angezeigt. So rufen wir den Feed ab, der mit dem Suchbegriff verknüpft ist:

GearsBaseContent.prototype.getFeed = function(url) {
  var row = this.db.selectRow('BaseFeeds', 'id = ?', [ url ]);
  return row.JSON;
}

Nachdem wir das JSON für eine Zeile haben, können wir es eval(), um die Objekte zurückzugeben:

eval("var json = " + jsonString + ";");

Wir können loslegen und Inhalte aus JSON in unsere Seite einfügen.


Ressourcenspeicher für Offlinezugriff verwenden

Da wir Inhalte aus einer lokalen Datenbank abrufen, sollte diese App auch offline funktionieren, oder?

Nein. Das Problem ist, dass Sie zum Starten dieser App ihre Webressourcen wie JavaScript, CSS, HTML und Bilder laden müssen. Derzeit funktioniert die App möglicherweise noch, wenn der Nutzer die folgenden Schritte ausgeführt hat: Online starten, einige Suchanfragen ausführen, den Browser nicht schließen, offline gehen. Das könnte funktionieren, da sich die Elemente noch im Cache des Browsers befinden. Was aber, wenn das nicht der Fall ist? Wir möchten, dass unsere Nutzer die App von Anfang an aufrufen können, z. B. nach einem Neustart.

Dazu verwenden wir die LocalServer-Komponente und erfassen unsere Ressourcen. Wenn Sie eine Ressource erfassen (z. B. das HTML und JavaScript, das zum Ausführen der Anwendung erforderlich ist), speichert Gears diese Elemente und fängt auch Anfragen des Browsers ab, um sie zurückzugeben. Der lokale Server fungiert als Verkehrspolizist und gibt die gespeicherten Inhalte aus dem Store zurück.

Wir verwenden auch die ResourceStore-Komponente, bei der Sie dem System manuell mitteilen müssen, welche Dateien er erfassen soll. In vielen Szenarien möchten Sie Ihre Anwendung versionieren und Upgrades auf transaktionale Weise ermöglichen. Eine Version wird durch eine Reihe von Ressourcen definiert. Wenn Sie eine neue Reihe von Ressourcen veröffentlichen, sollten Ihre Nutzer die Dateien nahtlos aktualisieren können. Wenn das Ihr Modell ist, verwenden Sie die ManagedResourceStore API.

Um unsere Ressourcen zu erfassen, wird das GearsBaseContent-Objekt:

  1. Ein Array von Dateien einrichten, die erfasst werden müssen
  2. LocalServer erstellen
  3. ResourceStore öffnen oder erstellen
  4. Seiten im Geschäft erfassen
// 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'));
  });
}

Wichtig ist, dass Sie nur Ressourcen in Ihrer eigenen Domain erfassen können. Diese Einschränkung ist aufgetreten, als wir versucht haben, direkt über den SVN-Trunk auf die GearsDB-JavaScript-Datei zuzugreifen. Die Lösung ist natürlich ganz einfach: Sie müssen alle externen Ressourcen herunterladen und in Ihrer Domain platzieren. 302- oder 301-Weiterleitungen funktionieren nicht, da LocalServer nur die Servercodes 200 (Erfolg) oder 304 (Nicht geändert) akzeptiert.

Das hat Auswirkungen. Wenn Sie Ihre Bilder auf images.yourdomain.com platzieren, können Sie sie nicht erfassen. www1 und www2 können sich nicht gegenseitig sehen. Sie könnten serverseitige Proxys einrichten, aber das würde den Zweck der Aufteilung Ihrer Anwendung auf mehrere Domains zunichtemachen.

Offline-Anwendung debuggen

Das Debuggen einer Offlineanwendung ist etwas komplizierter. Es gibt jetzt mehr Szenarien zum Testen:

  • Ich bin online und die App wird vollständig im Cache ausgeführt.
  • Ich bin online, habe aber nicht auf die App zugegriffen und es ist nichts im Cache.
  • Ich bin offline, habe aber auf die App zugegriffen.
  • Ich bin offline und habe noch nie auf die App zugegriffen (keine gute Situation!).

Um die Sache zu vereinfachen, haben wir das folgende Muster verwendet:

  • Wir deaktivieren den Cache in Firefox (oder Ihrem bevorzugten Browser), wenn wir sichergehen müssen, dass der Browser nicht einfach etwas aus dem Cache abruft.
  • Wir verwenden Firebug zum Debuggen (und Firebug Lite zum Testen in anderen Browsern). Wir verwenden console.log() überall und erkennen die Konsole für den Fall, dass sie vorhanden ist.
  • Wir fügen Hilfs-JavaScript-Code hinzu, um:
    • damit wir die Datenbank bereinigen und neu beginnen können.
    • Entfernen Sie die erfassten Dateien, damit sie beim Neuladen wieder aus dem Internet abgerufen werden (nützlich, wenn Sie die Entwicklung durchlaufen).

Das Debugging-Widget wird nur dann auf der linken Seite der Seite angezeigt, wenn Sie Gears installiert haben. Es enthält Callouts zum Bereinigen von Code:

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();
}

Fazit

Wie Sie sehen, ist die Arbeit mit Google Gears recht einfach. Wir haben GearsDB verwendet, um die Datenbankkomponente noch einfacher zu gestalten, und den manuellen ResourceStore, der für unser Beispiel gut funktioniert hat.

Der Bereich, in dem Sie die meiste Zeit verbringen, bestimmt die Strategie für das Online-Abrufen von Daten und das Offline-Speichern. Es ist wichtig, sich Zeit für die Definition des Datenbankschemas zu nehmen. Wenn Sie das Schema in Zukunft ändern müssen, müssen Sie diese Änderung verwalten, da Ihre aktuellen Nutzer bereits eine Version der Datenbank haben. Das bedeutet, dass Sie bei jedem Datenbank-Upgrade Skriptcode bereitstellen müssen. Es ist natürlich hilfreich, dies zu minimieren. Möglicherweise möchten Sie GearShift ausprobieren, eine kleine Bibliothek, die Ihnen bei der Verwaltung von Überarbeitungen helfen kann.

Wir hätten auch ManagedResourceStore verwenden können, um unsere Dateien im Blick zu behalten. Das hätte folgende Auswirkungen gehabt:

  • Wir würden uns vorbildlich verhalten und unsere Dateien versionieren, um zukünftige Upgrades zu ermöglichen.
  • Mit einer Funktion des ManagedResourceStore können Sie eine URL mit einem Alias für andere Inhalte versehen. Eine gültige Architektur wäre, gears_base.js als Nicht-Gears-Version zu verwenden und einen Alias dafür zu erstellen, sodass Gears selbst gears_base_withgears.js herunterladen würde, die alle Offline-Unterstützung enthält.
Für unsere App war es unserer Meinung nach einfacher, nur eine Schnittstelle zu haben und diese Schnittstelle auf zwei Arten zu implementieren.

Wir hoffen, dass dir die Vorbereitung von Anwendungen Spaß gemacht hat und du sie einfach fandest. Wenn Sie Fragen haben oder eine App teilen möchten, besuchen Sie bitte das Google Gears-Forum.