OpenSocial / Google Gadgets API: Teil 2 – Persistent speichern
Gadgets und Applikationen in Social Networks wie StudiVZ, Xing und Lokalisten.de sind das nächste große Ding im Web 2.0. Im Selbstversuch erlerne ich die Google Gadget und OpenSocial-API und alles drum und dran. Hier möchte ich zeigen, wie ich auf meinem Weg zum Gadget Developer vorankomme und wie man solche Gadgets überhaupt baut.
Nachdem ich in Teil 1 gezeigt habe, wie man einfache Get-Requests benutzt, um sein Gadget mit Inhalt zu versorgen, werde ich heute zum ersten Mal auf die Persistenzschicht zugreifen, die die API zur Verfügung stellt. Als Beispiel dient dabei ein “Pastebin”, also ein Textfeld, in das ich munter Sachen schreibe oder paste, die mir dann überall auf meiner iGoogle-Seite zur Verfügung stehen. Den fertigen Code für selbstversteher gibts natürlich auch.
1. Grundgerüst mit HTML
Wir fangen wieder an mit dem Grundgerüst. Das einzige, was wir hier an HTML brauchen, ist eine Textarea, das eine ID enthält. Diese wird mit ein wenig CSS-Code schön gemacht.
<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs title="Pastemap" height="65">
<Require feature="opensocial-0.8"/>
</ModulePrefs>
<Content type="html">
<![CDATA[
<style type="text/css">
#pastearea {
width: 100%; font-family: Arial, sans-serif;
font-size: 12px; height: 60px;
}
</style>
<textarea id="pastearea"></textarea>
]]>
</Content>
</Module>
Wie ihr seht verlangt dieses Gadget wieder, dass die opensocial-0.8 API bereitgestellt wird. Außerdem habe ich dieses Mal eine Höhe von 65px vorgegeben, damit sich die Textarea wunderbar in den Container schmiegt.
Nun müssen wir als erstes zusehen, dass unsere Änderungen im Textfeld auch auf Googles Festplatten ankommt (ein Kollege sagte zynisch: “Interessanter wäre, wie man das verhindert!”). Versehen wir unsere Textarea also mit einem onchange-Event.
<textarea id="pastearea" onchange="updatePasteData()"></textarea>
2. Speichern
Wenn wir Änderungen im Textfeld vornehmen, wird also immer die Javascript-Funktion “updatePasteData()” aufgerufen. Da diese noch nicht existiert, fügen wir, wie im ersten Teil, einen Script-Block am Anfang des Content-Knotens ein, der die folgende Funktion enthält:
function updatePasteData() {
var pasteContent = document.getElementById("pastearea").value;
var req = opensocial.newDataRequest();
req.add(
req.newUpdatePersonAppDataRequest("VIEWER",
"pasteContent", pasteContent),
"setPasteData");
req.send();
}
Super, oder?
Als erstes wird eine Variable deklariert, die den aktuellen Inhalt der Textarea (der wir ja die ID #pastearea zugewiesen haben) enthält.
Danach erfolgt ein DataRequest nach der Form, wie alle möglichen Anfragen innerhalb der API vonstatten geht: Man erstellt sich ein generisches DataRequest-Object, fügt spezifische Requests hinzu (in meinem Falle einen AppData Request), und sendet ihn ab. Die ganze req.add-Geschichte läuft dabei wie folgt:
Die Methode ‘add’ der DataRequest-Klasse verlangt zwei Argumente, nämlich den eigentlichen, spezifischen Request, sowie einen Namen, unter dem man das Ergebnis des Requests in seinem DataResult-Object ansprechen möchte. Da wir in diesem Falle aber auf ein Result-Handling verzichten, schreibe ich dazu später mehr.
Interessant ist aber das spezifische Request-Object, welches ich als erstes Attribut übergebe. Dies ist nämlich vom Typen “UpdatePersonAppData”. Das tut – oh Wunder – nichts anderes, als Daten, die sowohl Personen- als auch Anwendungsbezogen sind, in die Persistenzschicht der API zu schreiben / aktualisieren. Das erste Argument gibt dabei die ID der Person, auf die diese Daten bezogen werden, an. Eine von zwei Spezial-Personen-IDs ist “VIEWER”, also ein abstrakte Bezeichnung für die Person, die gerade mit dem Gadget interagiert (die andere Spezial-ID ist “OWNER”, aber dazu an anderer Stelle mehr)
Als Zweites übergeben wir einen String als Namen für das Feld. Den brauchen wir, um später die Inhalte vernünftig zuordnen zu können. Stellt es euch vor, wie einen Key in einem benamten Array; oder, simpler aber falscher, als einer Art Variablennamen.
Der Dritter Parameter ist der Inhalt, der gespeichert werden soll. Das ist in diesem Fall ein String (der Inhalt der Textarea), darf aber auch z.B. ein Array oder gar ein komplexes Objekt sein.
Nachdem wir unseren Request hinzugefügt haben, schicken wir ihn einfach mit der Methode “send()” ab, und schon weiß Google, was wir gerade in unsere Textarea geschrieben haben.
3. Lesen
So ein Pastebin wäre natürlich unglaublich sinnlos, wenn man seine Inhalte nicht auch wieder auslesen könnte.
Ich will, dass beim Laden des Gadgets meine Inhalte vom Google-Server ausgelesen und in die Textarea eingefügt werden. Der erste Teil ist also, eine Aktion beim Laden des Gadgets anzustoßen. Wie das geht, haben wir schon im ersten Teil gesehen, nämlich:
gadgets.util.registerOnLoadHandler(getPersistantPasteData);
Diese Zeile kommt wie gehabt einfach ans Ende unseres Script-Blocks.
Was kann aber diese Funktion, getPersistantPasteData?
function getPersistantPasteData() {
var idspec = opensocial.newIdSpec({ "userId" : "VIEWER",
"groupId" : "SELF" });
var req = opensocial.newDataRequest();
req.add(
req.newFetchPersonRequest("VIEWER"),
"viewer");
req.add(
req.newFetchPersonAppDataRequest(idspec, "pasteContent"),
"pastedata");
req.send( pasteDataRetreiveHandler );
}
Was an dieser Funktion jetzt wirklich neu ist, ist das sogenannte ID-Spektrum. Damit gebe ich einen Bereich vor, mit welchen IDs ich später arbeiten möchte. Da ich nur für mich selbst die Daten erfragen will, setze ich die User-ID auf die Pseudo-ID “VIEWER”, und die Gruppen-ID auf die Pseudo-ID “SELF”. Alternativ könnte man als zweiten Parameter auch dinge wie “FRIENDS” angeben, womit man die Daten aller Freunde des Viewers abfragen würde. Das ist mir aber momentan noch zu unheimlich.
Dass ich jetzt zwei mal mit der Add-Methode einen Request hinzufüge, hat gleich mehrere Gründe: Zum einen brauchen wir beide Informationen; zum anderen will ich für beide Requests nur einmal das Ergebnis behandeln müssen.
Warum brauchen wir beide Infos?
Der zweite Request ist wahrscheinlich einfacher erklärt: Er liefert uns die Daten, die wir in der Persistenzschicht unter dem Schlüssel “pasteContent” eingelagert haben, und zwar für alle im ID-Spektrum definierten User (Also derzeit nur der Viewer). In unserem Result-Objekt wird uns dieser Datensatz unter dem Schlüssel “pastedata” zur verfügung stellen. Soweit noch klar?
Der erste Request, der FetchPersonRequest nämlich, brauchen wir, damit wir endlich die echte ID des VIEWERs erfahren. Die Pseudo-ID wird nämlich unbrauchbar, wenn man sich mal die Datenstruktur ansieht, die so ein FetchPersonAppDataRequest zurückgibt:
0000123456789 = Object {
pasteData = 'Hallo, ich bin dein persönlicher Paste-Inhalt'
}
Allem voran geht also ein Object, das nach unserer ID benannt ist, welches wir also gleich in unserem Result-Handler ansprechen müssen. Und dazu brauchen wir die Viewer-ID. Aye?
Der Parameter, den ich der send()-Methode mitgebe, ist übrigens der Name der Result Handler Funktion.
4. Ergebnisbehandlung
Und eben dieser Result-Handler fehlt uns noch. Was müssen wir machen?
- Wir müssen gucken, ob der Request erfolgreich war oder nicht.
- Wir müssen an unsere Viewer-ID kommen
- Wir müssen wissen, welche Paste-Daten für diesen User gespeichert sind.
var viewerObject = null;
function pasteDataRetreiveHandler(response) {
if (response.hadError()) {
/* the fetch failed ... insert code to handle the error */
} else {
var viewer = response.get("viewer");
viewerObject = viewer.getData();
var data = response.get("pastedata").getData();
var pasteData = data[viewerObject.getId()];
document.getElementById("pastearea").innerHTML = pasteData.pasteContent;
}
}
Argh. Viel Zeug.
Nun gut. Weil ich nicht bei jedem verdammten Request meinen Viewer abfragen möchte, deklariere ich eine Variable “viewerObject”, die uns zur ganzen Laufzeit zur Verfügung stehen soll. Dann beginnt die eigentliche Funktion.
Wie auch im HTTP-Request-Result-Handling aus Teil 1 wird der Result-Handler automatisch mit dem Response-Object gefüttert. Das überprüfe ich zunächst auf einen Fehler (z.B. wenn ich noch keine Daten gespeichert habe o.ä.). Ein Error Handling erspare ich uns allen an dieser Stelle.
Interessant wird jetzt aber, wie man mit den beiden unterschiedlichen, im gleichen Schritt ausgeführten Request-Typen umgeht: Die in Schritt 3 (funktion getPersistantPasteData) definierten Namen für die Requests kann ich jetzt nutzen, um gezielt die Informationen aus meinem Response-Object zu bekommen – und zwar mit einem einfachen get(“viewer”) für den “viewer”-Request. Das dadurch erstellte Objekt ‘viewer’ ist aber auch nochmal ein wenig verschachtelt und zeigt uns erst mit Aufruf der Methode getData() seinen wahren Kern – ein Objekt vom Typen opensocial.Person, das Kernobjekt, das Alles-Monster des Social Networkings.
Um an den Inhalt des Ergebnisses meines zweiten Requests, “pastedata”, zu kommen, kürze ich die zwei Schritte ab und rufe direkt die getData() Methode auf, ohne vorher noch ein Hilfsobjekt zu behelligen.
Weiter Oben habe ich die Datenstruktur dieses Objekts beschrieben. Danach lassen sich auch die folgenden Zeilen erklären: ‘data’ ist das Objekt, viewerObject.getId() gibt mir den benötigten Schlüssel. Und auf der nächsten Ebene steht mein String mit dem Bezeichner ‘pasteContent’, den ich schließlich in meine Textarea pumpe.
Fertig. Doch recht einfach, oder?
Ach ja, und hier nochmal das fertige Listing.

Hi, I am Lukas, a 22-year-old Developer and passionate musician based in Cologne, Germany.