Programování gadgetu pro Google Wave
od aichi
Před nedávnem zveřejnila česká pobočka Google soutěž o nejlepší gadget pro Google Wave, který by uměl zobrazit pozici uživatelů pomocí geolokace IP adresy a nalezení sobě nejbližšího a zobrazení všech pomocí Google Maps. Soutěže jsem se zůčasnil a chci se s vámi podělit o zkušenosti z víkendového kódování gadgetu.
Pokračování:
Den první - sobota
Kuchařka
První co musíte mít, je přístup do Google Wave, nebo alespoň do Sandboxu, jinak vám vývoj pujde těžce. Do obojího potřebujete pozvánku. Já jsem pracoval v Google Wave. První co potřebujete pro práci s gadgety je Extension Installer. Na odkazované stránce je popis, jak ho nainstalovat a používat.
Dále budeme potřebovat dokumentaci. Pro wave najdeme dokumentaci na Google Code, pokud se proklikáte hlouběji, najdtete stránku s wave API. Dále budeme potřebovat API pro Google Maps, nejlépe nyní ve verzi 3, protože ta nevyžaduje generování API key pro budoucí použití.
Základní Hello World gaget
Dle zadání gadget musí umět v základní verzi geologaci uživatele, který gadget vkládá do vlny dle jeho IP a zobrazení na mapě.
Nejprve musíme vytvořit kostru gadgetu:
<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="Zobraz pozici">
<Require feature="wave" />
</ModulePrefs>
<Content type="html">
<![CDATA[
<h1>gadget pro wave</h1>
]]>
</Content>
</Module>
Kód uložíme jako XML soubor na veřejně dostupný server a pomocí Extension Manageru ve Wave ho přidáme do vlny. Po chvilce načítání by se měl objevit nadpis gadget pro wave.
Přísady - co je třeba znát pro základní gadget
Wave API je velice malé, skládá se z 12 základních metod a 3 tříd. Důležitá je třída Participant, což je účastník vlny (wave). U každého účastníka můžeme zjistit jeho jméno, id a obrázek. Velice důležité je odlišení účastníků ve vlně dle role. Jeden z účastníků je ten, kdo gadget do vlny přidal, říká se mu Host. Dalším důležitým účastníkem je Viewer, tedy ten, kdo si vlnu právě prohlíží.
Před vlastním kódováním potřebujeme ještě znát mechanismus, jak se náš gadget dozví o změnách ve vlně. Pro informování gadgetu se využívá callback mechanismu, svojí metodu registrujeme jako posluchače pomocí volání wave.setStateCallback().
Postup vaření - princip gadgetu
První co je třeba, je vytvořit "třídu" a z ní instanci. Také potřebujeme nějakou metodu, která bude zavolaná, až bude Wave prostředí načteno - obdoba document.load události v HTML DOM. Wave poskytuje metodu gadgets.util.registerOnLoadHandler(), které se předává callback metoda. Náš kód může vypadat takto:
<script type="text/javascript">
/*bind funkce pro uzavreni scope, tedy this=obj uvnitr volane funkce */
bind = function(obj,fnc){
return function() {
return fnc.apply(obj,arguments);
}
};
/*konstruktor*/
function Application() {
this.init = bind(this, this.init);
}
/*callback metoda pro registerOnLoadHandler*/
Application.prototype.init = function() {
alert('gadget onload');
}
/*zaregistrovani handleru na onLoad*/
var app = new Application();
gadgets.util.registerOnLoadHandler(app.init);
</script">
Uzel skript umístíme do námi vytvořeného XML do uzlu Content, místo původního nadpisu.
Náš gadget potřebuje znát změnu stavu vlny, první změna stavu je po načtení všech dat. Zde by mohlo dojít k mílce, stav neznamená stav tvořený uživateli (změna obsahu), ale stav, který si ukládá náš gadget. Stav (state), je tedy úložiště dat ve vlně, které je dostupné našemu gadgetu. Pokud jeden uživatel změní stav gadgetu (a ten ho uloží), instance gadgetu u ostatních uživatelů v jejich prohlížečích o tom dostanou po chvíli zprávu a aktualizují se. Proto je potřeba změnu stavu sledovat. Do metody init() dopíšeme navěšení callbacku:
function Application() {
this.init = bind(this, this.init);
this.stateUpdated = bind(this, this.stateUpdated);
}
/*callback metoda pro registerOnLoadHandler*/
Application.prototype.init = function() {
if (wave && wave.isInWaveContainer()) {
wave.setStateCallback(this.stateUpdated);
}
}
/*callback metoda pro stateCallback*/
Application.prototype.stateUpdated = function(){
alert('state update');
}
Tvorba omáčky
Náš gadget potřebuje vzhled, tlačítko pro přidání polohy a prostor pro mapu. Proto upravíme HTML v našem XML:
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="http://code.google.com/intl/cs-CZ/apis/gears/gears_init.js"></script>
<style type="text/css" media="screen">
#map {height: 400px;}
#title {margin: 10px 0 5px 0;}
</style>
</head>
<body>
<div id="content_div"></div>
<p id="title">Zobrazení polohy uživatele: <strong id="titleName">
</strong>
</p>
<div id="controls">
<button id="addViewerPosition"
onclick="app.addViewerPosition();">Přidej moji polohu</button>
</div>
<div id="messages"></div>
<div id="map"></div>
<script type="text/javascript">
/*obsah viz výše*/
</script>
</body>
Jelikož budeme zobrazovat mapu, načteme si Google API na řádku 3. Dále budeme zjišťovat pozici uživatele pomocí geolokace. Tu umí buď Firefox 3.5 a nebo prohlížeče s Google gears (IE, Safari, Chrome), které linkujeme na řádku 4. Poslední zvýrazněný řádek naznačuje, že potřebujeme ovladač stisku tlačítka:
function Application() {
...
this.addViewerPosition = bind(this, this.addViewerPosition);
}
// udalost na tlacitku
Application.prototype.addViewerPosition = function() {}
Nyní nám již nic nebrání v zobrazení mapy, kterou si uložíme do instanční vlastnosti map:
function Application() {
...
this.map = null;
}
Application.prototype.init = function() {
...
//praha
var lat = 50.083;
var lon = 14.467;
var latlng = new google.maps.LatLng(lat, lon);
var myOptions = {
zoom: 8,
center: latlng,
mapTypeControl: true,
navigationControl: false,
scaleControl: false,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
this.map = new google.maps.Map(document.getElementById("map"),myOptions);
}
Příloha a obloha
Máme vzhled, máme mapu, máme prázdné metody pro stisk tlačítka a změnu stavu. Nyní si je třeba povědět něco o práci se stavem aplikace, který získáme voláním wave.State().
Stav (State) je vlastně key:value databáze. Můžeme do ní ukládat páry klíč:hodnota pomocí metody submitValue(klic,hodnota), nebou můžeme uložít více změn najednou pomocí asynchrnoní metody submitDelta(). Dále potřebujeme číst data. Pokud známe klíč, čteme data pro tento klíč pomocí metody key(klic). Získání všech klíčů je možné pomocí metody getKeys().
Nyní je nutné udělat designové rozhodnutí o struktuře dat při ukládání. Pokud by našim cílem byla jen aplikace pro jednodušší část zadání, stačilo by uložit jen zeměpisnou šířku a délku a id uživatele, který gadget do vlny vložil. Nicméně já jsem chtěl kostru aplikace využít i pro druhou část úkolu, proto ukládáme jeden serializovaný literálový objekt, kde pro každý id uživatele jsou uloženy jeho souřadnice.
Nyní je čas implementovat ovladač tlačítka. Je potřeba zjistit zda browser umí geolokaci a získat souřadnice:
Application.prototype.addViewerPosition = function() {
if (navigator.geolocation) {
/* firefox geolocation is available */
navigator.geolocation.getCurrentPosition(this.setHostPositionState);
} else if (google && google.gears) {
/* gears geolocation is aviable */
var geo = google.gears.factory.create('beta.geolocation');
geo.getCurrentPosition(this.setHostPositionState, this._showGeoLocationError);
} else {
/*geolocation is not supported*/
this._showGeoLocationError();
}
}
Získání souřadnic je jak ve Firefoxu tak při využití Google Gears asynchronní, tudíž musíme předat callback metodu setHostPositionState() na řádcích 4 a 8. Dále Google Gears umožňují informovat, aplikaci pokud se geolokace nepovede (řádek 8) a je také dobré informovat, že prohlížeč nepodporuje geolokaci (např. Opera), viz. řádek 11.
Nyní budeme implementovat obě metody. Metoda pro zobrazení chyby je jednoduchá, protože pouze zobrazí text do připraveného DIVu s id="messages":
function Application() {
...
this._showGeoLocationError = bind(this, this._showGeoLocationError);
/*zasobnik na DOM elementy*/
this.dom = {};
this.dom.msg = document.getElementById('messages');
}
Application.prototype._showGeoLocationError = function() {
this.dom.msg.innerHTML = 'Geolokace se nezdařila.';
}
Nyní nás čeká ukládání získaných souřadnic, nicméně musíme si připravit živnou půdu. Jak jsem psal výše, vše ukládáme do stavu jako JSON, který musíme serializovat a při získání ze stavu deserializovat. Klíč tohoto objektu je uložen v konstruktoru. Metoda pro získání stavu se jmenuje getPositionState().
function Application () {
...
this.stateName = 'aichiStateHolder';
}
// ziskani stavu vsech pozic
Application.prototype.getPositionState = function() {
var value = wave.getState().get(this.stateName, '{}');
eval('var state = '+value+';');
return state;
}
Nyní již můžeme přistoupit k implementaci metody setHostPositionState(), kterou zpracujeme získané souřadnice z geolokace. Hned na začátku musíme ošetřit různý vstup, neboť Firefox zasílá rovnou objekt se souřadnícem, ale Gears posílají bohatší data, která nevyužijeme. Dále musíme získat ID uživatele v roli Viewer, tedy toho kdo má zobrazen gadget. Nakonec získáme stav, vložíme do něj souřadnice a znovu ho uložíme, pomocí metody submitDelta().
// ulozeni souradnic toho kdo to zadal
Application.prototype.setHostPositionState = function(position) {
var position = position.coords || position;
//ziskani stavu
var state = this.getPositionState();
//ziskani objektu Participant role Viewer
var viewer = wave.getViewer();
state[this.createName(viewer.getId())] = {latitude: position.latitude, longitude: position.longitude};
//ulozeni noveho stavu
var serializedState = wave.util.printJson(state)+'';
var delta = {};
delta[this.stateName] = serializedState;
wave.getState().submitDelta(delta);
}
Na 8. řádku používáme metodu createName(), kterou upravujeme ID uživatele. To je z toho důvodu, že ID jsou ve tvaru emailové adresy a tečka nemůže být použita v názvu vlastnosti. Proto musíme ID pozměnit:
// vytvoreni klice z ID uzivatele
Application.prototype.createName = function(name) {
return name.replace(/[^a-z]/gi, '');
}
Servírování
Poslední metoda, která nám chybí v implementaci je stateUpdated(), metoda reagující na změnu stavu. V této metodě musíme jednak schovat button pro všechny kromě uživatele v roli Host a druhak musíme sledovat, zda do stavu byla uložena souřadnice a tu případně zobrazit na mapě:
function Application(){
...
this.host = null; //participant v roli Host
this.viewer = null; //participant v roli Viewer
this.inited = false; //po prve jsme prosli metodou stateUpdated
}
// volano pokazde, kdyz se zmeni stav ve vlne
Application.prototype.stateUpdated = function() {
this.host = wave.getHost();
this.viewer = wave.getViewer();
//spusteno poprve
if (!this.inited) {
//schovani cudliku pro update mapy
if (this.host.getId() != this.viewer.getId()) {
var elm = document.getElementById('addViewerPosition').disabled = true;
}
//upraveni nadpisu gadgetu
document.getElementById('titleName').innerHTML += this.host.getDisplayName();
}
var state = this.getPositionState();
//mame souradnice, muzeme je zobrazit
if (state[this.createName(this.host.getId())]) {
var pos = state[this.createName(this.host.getId())];
this._showHostPosition(pos);
}
//prvni prubeh
this.inited = true;
}
A nyní vlastní metoda _showHostPosition() pro zobrazení polohy na mapě:
//zobrazeni pozice na mape
Application.prototype._showHostPosition = function(position) {
//nastaveni pozice na mape
var p = new google.maps.LatLng(position.latitude, position.longitude);
this.map.setCenter(p);
this.map.setZoom(13);
//zobrazeni ikony cloveka na mape
var participant = this.host;
var m = new google.maps.Marker({
position: p,
visible: true,
title: participant.getDisplayName(),
icon: new google.maps.MarkerImage(
participant.getThumbnailUrl(),
new google.maps.Size(96,96,'px', 'px'),
new google.maps.Point(0,0),
new google.maps.Point(48, 96)
),
map: this.map
});
//zobrazeni zeleneho puntiku
var m1 = new google.maps.Marker({
position: p,
visible: true,
title: participant.getDisplayName(),
icon: new google.maps.MarkerImage(
'http://www.czechdesign.cz/open/gb.png',
new google.maps.Size(11,11,'px', 'px'),
new google.maps.Point(0,0),
new google.maps.Point(6, 6)
),
map: this.map
});
//naveseni vizitky na zeleny puntik
var i = new google.maps.InfoWindow({
content: '<h3>'+participant.getDisplayName()+'</h3><br /><img src="'+participant.getThumbnailUrl()+'" />'
});
google.maps.event.addListener(m1, 'click', function() {
i.open(this.map, m1);
});
}
Ve zkratce na začátku metody je napolohování mapy a přizoomování. Dále je potřeba vytvořit na mapě značku (Marker) a ke značce vytvořit vizitku ve které zobrazíme jmého a obrázek člověka, který přidal gadget do vlny.
Obrázky které používáme v HTML i v JS je nutné odkazovat absolutně, protože gadget běží v iframe, který je dynamicky vytvořen a má adresu wave.google.com/... (řádek 29).
Závěr
Gadget za jeden den odpovídá základnímu zadání a bylo ho možné přihlásit do soutěže. Nicméně mým cílem bylo udělat gadget s plnou funkčností a proto očekávejte druhý díl. Také si můžete vyzkoušet plně funkční gadget přidáním pomocí Extension Manageru.
Adresy zpětných odkazů pro tento příspěvek:
Trackback URL (right click and copy shortcut/link location)
09. 01. 10 19.00:39, 

