Aus Linux-Magazin 10/2012

Offline-fähige Anwendungen mit HTML 5

© Oksana Byelikova, 123RF.com

Ein Offlinespeicher im Browser und ein Schuss HTML-5-Akrobatik ergeben interaktive Webanwendungen, die auch dann noch funktionieren, wenn die Internetverbindung abreißt. Als Kür zaubert das folgende Beispiel ein GUI, das auf Desktops wie Smartphones eine gute Figur macht.

Webbrowser waren ursprünglich reine Viewer für statische HTML-Dokumente aus dem WWW. Seit HTML 2 können sie auch simple Benutzereingaben mittels Formularen erfassen und zum Speichern an eine Datenbank im Internet senden, doch ist dafür die Verfügbarkeit des Netzes Voraussetzung. Bei jeder Netzwerkstörung verliert der genervte Benutzer seine Arbeit komplett.

Offline – na und?

HTML 5 [1] bietet deshalb verschiedene Techniken, um Webanwendungen offline-fähig zu gestalten. Dafür bedarf es zunächst eines Application Cache, der die Bestandteile der Anwendung selbst speichert, etwa HTML- und Javascript-Dateien. Für die veränderlichen Sitzungsdaten stehen gleich drei verschiedene Speicher-Implementierungen zur Verfügung: Local Storage speichert Schlüssel-Wert-Paare, Web SQL speichert Daten in einer SQL-, Indexed DB in einer No-SQL-Datenbank.

Zusammenspiel vieler Technologien

In Geotracker [2], der Beispielanwendung zu diesem Artikel, wirken viele Webtechnologien zusammen (Abbildung 1):

Abbildung 1: Die Beispielanwendung Geotracker, hier unter Chrome auf einem Android-Smartphone, vereint viele Webtechnologien in sich.

Abbildung 1: Die Beispielanwendung Geotracker, hier unter Chrome auf einem Android-Smartphone, vereint viele Webtechnologien in sich.

Local Storage, Web SQL und Indexed DB sorgen dafür, dass die Anwendungsdaten auch offline verfügbar sind. Das Geolocation-API ortet das Mobilgerät des Benutzers, die Bibliothek Open Layers zeigt den Standort auf einer Karte an. Jquery Mobile zeichnet eine grafische Benutzeroberfläche, die auf Webbrowsern für den Desktop ebenso funktioniert wie auf Smartphones und Tablets. Couch DB schließlich speichert die Standorte Server-seitig, damit sie mehreren Anwendern zur Verfügung stehen.

Der Artikel stellt diese Technologien zunächst im Detail vor. Danach kommen sie in einer Beispielanwendung zum Einsatz, die ein offline-fähiges Tool zum Speichern von Geokoordinaten (Geolocations) umsetzt. Dank HTML 5 läuft diese App auf der größtmöglichen Zahl von Geräten – überall da, wo Firefox (aktuell 14) , Opera (12), Chrome (21), Safari und Internet Explorer (9) zu finden sind.

Webbrowser versuchen die Zahl der Netzwerkzugriffe möglichst gering zu halten. Dateien, die sie bereits einmal vom Webserver geholt haben, bewahren sie zur Wiederverwendung im Cache auf. Der Webserver unterstützt den Browser dabei, beispielsweise indem er im Kopf der HTTP-Antwort den Status 304 sendet. Dies signalisiert dem Webbrowser, dass sich die erneut angefragte Datei nicht geändert hat. Dieser Cache ist jedoch nicht für den Offline-Einsatz geeignet.

Application Cache

Um eine Webanwendung dauerhaft im Webbrowser zu speichern, etabliert HTML 5 den Application Cache [3]. Dessen Steuerdatei ist ein Cache Manifest, das auf dem Webserver liegt. Das Manifest in Listing 1 veranlasst den Browser dazu, alle Dateien im Application Cache zu speichern, die nach dem Schlüsselwort »CACHE« aufgeführt sind. Im Cache Manifest lassen sich URLs, relative oder absolute Pfade verwenden.

Listing 1

Cache Manifest

01 CACHE MANIFEST
02
03 CACHE:
04 index.html
05 js/jquery-1.7.1.min.js
06 js/script.js
07 js/jquery.mobile-1.1.0.min.css
08 js/jquery.mobile-1.1.0.min.js
09 js/OpenLayers.js
10
11 NETWORK:
12 /couchdb/
13 *

Bevor der Browser die Dateien speichern darf, muss der Benutzer zustimmen. Falls der Browser nicht nachfragt, kann das der Anwender etwa aus dem Firefox-Kontextmenü unter »Seiteninformationen | Berechtigungen« einstellen. In der untersten Zeile von Abbildung 2 hat der Benutzer der Verwendung des Application Cache, dem Speichern der Daten, in der vorletzten Zeile der Ortung per Geolocation zugestimmt.

Abbildung 2: Unter den seitenspezifischen Einstellungen von Firefox setzt der Benutzer die Berechtigungen für die Geolocation und den Offlinespeicher.

Abbildung 2: Unter den seitenspezifischen Einstellungen von Firefox setzt der Benutzer die Berechtigungen für die Geolocation und den Offlinespeicher.

Die URLs aus dem Abschnitt »NETWORK« im Cache Manifest (Listing 1) landen nicht im Application Cache, der Browser erfragt sie jedes Mal vom Webserver. Der Platzhalter »*« erlaubt es, auf jede URL im WWW zuzugreifen. Das Cache Manifest verknüpft der Entwickler durch Angabe eines Pfades im Attribut »manifest« mit einer HTML-Datei (Listing 2). Diese Pfadangabe ist relativ zur HTML-Datei. Beim erstmaligen Aufruf liest der Browser das Cache Manifest asynchron und speichert die Dateien aus dem Abschnitt »CACHE« im Application Cache. Bei jedem folgenden Aufruf der HTML-Datei überprüft der Webbrowser die Gültigkeit des Manifests. Ist es nach wie vor gültig, lädt der Webbrowser die Dateien aus dem Application Cache – beispielsweise wenn der Webbrowser im Offlinemodus läuft oder gerade keine Netzwerkverbindung besteht.

Listing 2

Manifest einbinden

01 <!DOCTYPE HTML>
02 <html manifest="geotrack.appcache">
03 [...]
04 </html>

Ändert sich das Manifest auch nur um ein Bit, liest der Webbrowser es neu ein und lädt die Dateien aus dem Abschnitt »CACHE« abermals in den Application Cache. Chrome gewährt Einblick in die Verarbeitung des Cache-Manifests (Abbildung 3). Damit der Webbrowser das Manifest erkennt, ist im Apache-Webserver der passende MIME-Typ mit »AddType cache-manifest .appcache« einzurichten.

Abbildung 3: Auskunftsfreudig: Chrome meldet die Verarbeitungsschritte des Cache-Manifests in seiner Konsole für Webentwickler.

Abbildung 3: Auskunftsfreudig: Chrome meldet die Verarbeitungsschritte des Cache-Manifests in seiner Konsole für Webentwickler.

Web Storage

Sitzungsbezogene Daten lassen sich in HTTP-Cookies dauerhaft im Webbrowser speichern. Sie vereinfachen es, eine Session über das verbindungslose HTTP-Protokoll zu halten, sind aber als dauerhafter Speicher von Anwendungsdaten im Webbrowser ungeeignet. Aus diesem Grund hat das W3C im Jahr 2009 den so genannten Web Storage [4] geschaffen. Im Gegensatz zu den Alternativen Web SQL und Indexed DB ist diese Speichermöglichkeit in den aktuellen Versionen aller Webbrowser zu finden.

Web Storage bietet zwei Datenspeicher: Der Session Storage speichert Daten pro Tab, der Local Storage pro URL. Beide hinterlegen Schlüssel-Wert-Paare, jedoch bleiben nur die Daten des Local Storage dauerhaft gespeichert. Das Objekt »window.LocalStorage« realisiert Local Storage mittels Javascript (Listing 3).

Listing 3

Local Storage

01 window.LocalStorage = {
02  storage: window.localStorage,
03
04  save : function(obj) {
05   var pos = this._unpack('pos');
06   pos[obj.key] = obj;
07   this.storage.setItem('pos', JSON.stringify(pos));
08  },
09
10  read : function(key, cbk) {
11   cbk(this._unpack('pos')[key]);
12  },
13
14  _unpack : function(key) {
15   var data = this.storage.getItem(key)
16   return (data)?jQuery.parseJSON(data):{};
17  }
18 };

Das Attribut »storage« in Zeile 2 verweist auf den Local Storage. Die Methode »save« aus Zeile 4 speichert Objekte in einem Feld. Dieses schreibt Zeile 7 mit der Methode »setItem()« in den Local Storage. Um einen Wert zu erhalten, übersetzt »stringify()« das Feld zuvor in eine Zeichenkette im Json-Format.

Das Lesen eines Objekts erfolgt mittels »read« (Zeile 10). Wie »save« entpackt auch diese Methode zunächst das Feld der Objekte mit »unpack()« . Unpack liest mit »getItem()« das serialisierte Feld aus dem Local Storage (Zeile 15). Abschließend rekonstruiert »parseJSON()« das Feld. Die Methode »read()« ruft mit dem Ergebnis der Lese-Operation die benutzerdefinierte Callbackfunktion aus der Parameterliste in Zeile 10 auf.

Der Local Storage ist zum dauerhaften Speichern von Schlüssel-Wert-Paaren völlig ausreichend, nicht aber zum Aufbewahren strukturierter Daten. Die Laufzeit der Methode »stringify()« aus Listing 3 wächst mit der Größe des zu serialisierenden Objekts rasch an (Abbildung 4). Unter anderem deshalb schuf das W3C bald mit Web SQL [5] die Möglichkeit, über Javascript auf eine SQL-Datenbank im Webbrowser zuzugreifen. Das ist allerdings umstritten und die Weiterentwicklung des Standards wurde Ende 2010 eingestellt. Web SQL findet sich in den aktuellen Versionen der Webbrowser Opera, Chrome und Safari.

Abbildung 4: Benchmark-Resultat des Local Storage: Beim Speichern großer Felder steigt die Laufzeit steil an.

Abbildung 4: Benchmark-Resultat des Local Storage: Beim Speichern großer Felder steigt die Laufzeit steil an.

Ein Beispiel für Web SQL zeigt Listing 4. Vor den SQL-Anweisungen muss Zeile 3 erst einmal die Datenbank öffnen. Die Methode »openDatabase()« nimmt den Namen der Datenbank, die Version, einen Anzeigenamen und die mutmaßliche Größe der Datenbank in Byte entgegen. Eine leere Versionsnummer öffnet immer die jüngste Version.

Listing 4

Wrapper-Objekt für Web SQL

01 window.WebSQL = {
02  _open : function() {
03   return window.openDatabase('geos', '', 'myDB', 50000000);
04  },
05
06  reinstall : function(vers) {
07   var db = this._open();
08   db.changeVersion(db.version, vers, function(t) {
09     t.executeSql('CREATE TABLE pos (id, lat float, lon float)');
10   });
11  },
12
13  read : function (key, cbk) {
14    this._open().readTransaction(function(t) {
15     t.executeSql('SELECT * FROM pos WHERE id = ?', [key],
16       function(t, d) {d.rows.length?cbk(d.rows.item(0)):''},
17       function(t, e) {console.log(e.message)});
18    });
19  },
20
21  save : function(obj) {
22   this._open().transaction(function(t,r) {
23    t.executeSql('INSERT INTO pos (id, lon, lat) VALUES (?, ?, ?)',
24     [obj.key, obj.lon, obj.lat])
25   });
26  }
27 };

Zum Lesen von Daten dient »readTransaction()« in Zeile 14, zum Schreiben »transaction()« (Zeile 22). Beide Methoden laufen asynchron ab (siehe Kasten “Asynchroner Funktionsaufruf”). Die Methode »executeSql()« führt in den Zeilen 9, 15 und 23 SQL-Anweisungen aus. Die Fragezeichen in den SQL-Statements sind Platzhalter und werden positionsgetreu durch Elemente des folgenden Felds ersetzt.

Asynchroner Funktionsaufruf

Einen asynchronen Funktionsaufruf nimmt die Javascript-Engine aus dem sonst sequenziellen Programmablauf heraus und führt ihn baldmöglichst aus. So kann eine Anwendung weiterlaufen, während sie beispielsweise auf eine Eingabe wartet. War der Aufruf erfolgreich, kommt ein Success-Callback zur Ausführung, ansonsten ein Error-Callback. Beides sind Funktionen, die der asynchrone Aufruf als Parameter erhält.

In Abbildung 5 kommt der asynchrone Funktionsaufruf P2 nach dem im Programmablauf nächsten Schritt P3 zur Ausführung. Der auf P2 folgende Schritt ergibt sich aus der Parameterliste seines Aufrufs. In Webbrowsern läuft Javascript gewöhnlich in einem einzigen Thread ab.

Abbildung 5: Beim asynchronen Funktionsaufruf ist P2 baldmöglichst an der Reihe. Der Erfolg entscheidet darüber, wie es weitergeht.

Abbildung 5: Beim asynchronen Funktionsaufruf ist P2 baldmöglichst an der Reihe. Der Erfolg entscheidet darüber, wie es weitergeht.

Der Success-Callback von »executeSql()« in Zeile 16 ruft für die erste Zeile der Ergebnismenge – »data.rows.item(0)« – die benutzerdefinierte Callbackfunktion »cbk« auf. Der Error-Callback in Zeile 17 schreibt eine Fehlermeldung auf die Konsole. Das Datenbankschema lässt sich mit der Methode »changeVersion()« (Zeile 8) ändern. Diese Funktion erfordert die alte und die neue Versionsnummer. Die Anweisungen zur Schema-Manipulation werden im Success-Callback übergeben.

Object Store

Mozilla lehnt es ab, Web SQL in Firefox zu implementieren. Zudem ist das Abbilden von Objekten auf die Spalten einer Datenbanktabelle (Objekt-relationales Mapping) aufwändig. Als einfacher gilt, einen Object Store zu verwenden, wie es der Nachfolgestandard Indexed DB [6] vorschlägt. Indexed DB speichert die Objekte zu einem Schlüssel ab und bringt mit Transaktionen und Indizes wichtige Aspekte einer hochperformanten und mehrbenutzertauglichen Datenbank mit. Indexed DB ist partiell in den aktuellen Versionen von Firefox und Chrome umgesetzt sowie testweise in Microsofts kommendem Internet Explorer 10.

Indexed DB

Listing 5 zeigt Indexed DB im Einsatz: Zeile 2 referenziert das Datenbankobjekt unter Berücksichtigung der Hersteller-Präfixe mehrerer Browser. In Zeile 7 öffnet der Javascript-Code die Datenbank und startet in Zeile 13 eine Transaktion. Der erste Parameter der Methode Transaction übergibt eine Liste zu bearbeitender Object Stores. Der zweite markiert mit 0 lesende und mit 1 schreibende Transaktionen, Firefox verwendet ab Version 13 standardgetreu »readonly« und »readwrite« . Erst die folgende Methode »objectStore()« gewährt Zugriff auf die Daten im Object Store »pos« .

Listing 5

Wrapper-Objekt für Indexed DB

01 window.IndexedDB = {
02  indexedDB : window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB,
03
04  storeParams: {keyPath:'key', autoIncrement:false},
05
06 open : function(cbk, mod) {
07    var orq = this.indexedDB.open('geos');
08    orq.onsuccess = function(e) {
09     if (window.mozIndexedDB) {
10      try {
11       cbk(e.target.result.transaction(['pos'], mod).objectStore('pos'));
12      } catch(l) {
13       cbk(e.target.result.transaction(['pos'], ((mode === 0)?'readonly':'readwrite')).objectStore('pos'));
14      }
15     } else {
16      cbk(e.target.result.transaction(['pos'], mod).objectStore('pos'));
17     }
18 }},
19
20  reinstall : function() {
21   this.indexedDB.deleteDatabase('geos');
22   var orq = this.indexedDB.open('geos');
23   orq.onupgradeneeded = function(e) {
24    e.target.result.createObjectStore('pos', IndexedDB.storeParams);
25   };
26   orq.onsuccess = function(e) {
27    if (e.target.result.setVersion) { //chromium
28     var crq = e.target.result.setVersion(2);
29     crq.onsuccess = function(e) {
30      e.target.source.createObjectStore('pos', IndexedDB.storeParams);
31   }}};
32  },
33
34  read : function(key, cbk) {
35   this.open(function(obst) {
36    obst.get(key).onsuccess = function(e) {cbk(e.target.result);}
37  }, 0);
38 },
39
40  save : function(obj) {
41   this.open(function(obst) {obst.put(obj);}, 1);
42  }
43 };

In Zeile 36 nutzt »read« die Methode »get()« , um das Objekt mit dem Schlüssel »key« asynchron zu lesen. Der Success-Callback ruft die benutzerdefinierte Callbackfunktion »cbk()« für das Ergebnis der Lese-Operation auf. Save in Zeile 40 wendet die Methode »put()« zum Speichern eines Objekts an. Den Schlüssel extrahiert Indexed DB aus der Angabe »keyPath: ‘key’« in Zeile 4. Diese Vereinbarung erklärt Indexed DB den Pfad zu dem Schlüssel in einem Objekt.

Die Eigenschaft übergibt der Code (Zeilen 24 und 30) beim Erstellen des Object Store »pos« der Methode »createObjectStore()« . Nach dem Löschen der Datenbank »pos« in Zeile 21 überlässt der Code das Neuanlegen dem Event »onupgradeneeded« . Dies tritt auch beim erstmaligen Öffnen einer Datenbank ein. Für den Browser Chrome kommt im Success-Callback der öffnenden Anfrage die Methode »setVersion()« zum Zuge. Dazu gibt Zeile 28 eine höhere Versionsnummer an.

Einheitliches API

Die drei Wrapper-Objekte aus den Listings 3 bis 5 bieten ein einheitliches API zum Speichern von Objekten. Die Auswahl der am besten geeigneten Schnittstelle übernimmt die Methode Autoconnect aus dem Objekt »DataConnector« in Listing 6. Ist keine Speichermethode geeignet, erzeugt »throw« einen Fehler.

Listing 6

Initialisieren des Offlinespeichers

01 window.DataConnector = {
02  autoConnect : function() {
03   if (IndexedDB.indexedDB) {
04    return IndexedDB;
05   } else if (window.openDatabase) {
06    return WebSQL;
07   } else if (window.localStorage) {
08    return LocalStorage;
09   }
10   throw new Error({'message': 'no storage class available!'});
11  }
12 };

Mit dem Application Cache und den beschriebenen Speichermöglichkeiten für die Anwendungsdaten lassen sich offline-fähige Apps entwickeln. Abbildung 6 zeigt die Beispielanwendung, deren Programmierung der Rest dieses Beitrag erläutert. Mit einem Klick auf »Neuer Eintrag« kann der Anwender einen neuen Positionspunkt in die Karte eintragen. Der aktuelle Standort ist voreingestellt. Nach dem Speichern überträgt die App die Position zunächst in einen der drei lokalen Datenspeicher und fügt dann eine rote Markierung in die Karte ein. Zudem landet die Position in einer Liste unterhalb der Kartenansicht.

Abbildung 6: Die offline-fähige Beispielanwendung speichert die geografischen Koordinaten von Orten und zeigt deren Position in einer Karte.

Abbildung 6: Die offline-fähige Beispielanwendung speichert die geografischen Koordinaten von Orten und zeigt deren Position in einer Karte.

Wie ein Neustart des Browsers zeigt, bleiben die gespeicherten Daten erhalten. Dabei startet die Anwendung selbst dann, wenn die Netzwerkverbindung getrennt ist. Wer sich dafür interessiert, was unter der Haube vorgeht, sollte die jeweils neuesten Versionen der Webbrowser Firefox, Opera oder Chrome zum Testen verwenden. Chrome unterstützt alle hier beschriebenen Technologien. Zudem bietet Googles Webbrowser die übersichtlichste Konsole zum Inspizieren der Webanwendung. Sie lässt sich mit dem Tastenkürzel [Strg]+[Shift]+[I] öffnen.

Den aktuelle Standort eines mobilen Geräts kann der Webbrowser mit dem Geolocation-API [7] ausfindig machen. Firefox [8] und Chrome ermitteln die Position durch eine Anfrage bei Google, die die Kennung benachbarter Wifi-Netzwerke und die IP-Adresse auswertet. Das Geolocation-API verpackt die Standortdaten in ein Objekt. Es enthält Längen- (»latitude« ) und Breitengrad (»longitude« ) sowie deren Abweichung (»accuracy« ) in Grad, die Höhe über dem Meeresspiegel (»altitude« ) sowie deren Abweichung (»altitudeAccuracy« ) in Metern, die aktuelle Geschwindigkeit entlang der Erdoberfläche (»velocity« ) in Meter pro Sekunde und die Richtung der Bewegung (»heading« ) in Grad – bezogen auf 0° Nord und im Uhrzeigersinn wachsend.

Standortbestimmung

Das Geolocation-API ist in allen aktuellen Versionen der Webbrowser Firefox, Opera, Chrome, Safari und Internet Explorer implementiert, funktioniert in Firefox unter Linux aber leider nicht. Eine alternative Möglichkeit zur Bestimmung der Geolocation mittels Google ist das Skript Geolocation Shim [9]. Auf jeden Fall sollte beim Betrieb die Wifi-Schnittstelle eingeschaltet sein.

Listing 7 zeigt, wie der Programmierer das Geolocation-API einsetzt. Die Funktion »getCurrentPosition()« in den Zeilen 3 bis 7 ermittelt den Standort asynchron. Der Success-Callback findet sich im ersten, der Error-Callback im zweiten Parameter der Funktion.

Listing 7

Wrapper-Objekt für Geolocation-API

01 window.Geolocation = {
02  current : function() {
03   navigator.geolocation.getCurrentPosition(
04    function(pos) {Geolocation.success(pos)},
05    function(err) {console.log(err.code);},
06    {highAccuracy:true, maximumAge:0, timeout:1000}
07   );
08  },
09
10  watch : function() {
11   navigator.geolocation.watchPosition(
12    function(pos) {Geolocation.success(pos)}
13   );
14  },
15
16  success : function(pos) {
17   Geolocation.latitude = pos.coords.latitude;
18   Geolocation.longitude = pos.coords.longitude;
19  }
20 };

Das Objekt im dritten Parameter konfiguriert den Aufruf: »highAccuracy« ermittelt, unter Umständen unter größerem Energieverbrauch, den Standpunkt möglichst genau, »maximumAge« erlaubt die Verwendung von gespeicherten Geolocations, die nicht älter als angegeben sind, und »timeout« bestimmt die Zeit, nach der die Ortung abbricht. Beide Angaben verwenden Millisekunden als Einheit. Die Angabe »maximumAge=0« verzichtet auf gespeicherte Geolocations. Die Funktion »watchPosition()« beobachtet die aktuelle Position dauerhaft. Falls sich die Position merklich ändert, führt der Code den Success-Callback aus. Dieser speichert ab Zeile 18 den aktuellen Breiten- und Längengrad.

Open Layers

Geolocations lassen sich mit Hilfe der freien Javascript-Bibliothek Open Layers [10] in einer Karte anzeigen, etwa von Openstreetmap oder Google Maps. Aktuell liegt Open Layers in Version 2.11 vor. Listing 8 zeigt, wie das »script« -Element in Zeile 5 die Bibliothek in ein HTML-Dokument einbindet. Der »div« -Container in Zeile 9 nimmt die Karte auf.

Listing 8

Open Layers

01 <!DOCTYPE html>
02 <html>
03 <head>
04  <meta charset="UTF-8"/>
05  <script src="js/OpenLayers.js"></script>
06  <script src="listing-12.js"></script>
07 </head>
08 <body>
09    <div id="osmap" style="width:512px;height:256px"></div>
10 </body>
11 </html>

Das Anzeigen der Standorte mit Javascript demonstriert Listing 9. Zeile 6 initialisiert den Kartenbereich, Zeile 7 fügt ihm eine Schicht mit der Openstreetmap-Karte hinzu, Zeile 9 die Schicht für die Markierungen. Die Methode »addMarker()« in Zeile 15 zeigt eine Geolocation, indem sie einen Klon des Bildobjekts aus Zeile 12 herstellt und an den durch »coords« (Zeile 20) transformierten Koordinaten einfügt. Abschließend macht das Skript die neue Markierung zum Mittelpunkt des Kartenausschnitts.

Listing 9

Wrapper-Objekt für Open Layers

01 window.Map = {
02  map: undefined,
03  ipath : 'img/marker.png',
04
05  init : function() {
06   this.map = new OpenLayers.Map({div:"osmap", zoom:12});
07   this.map.addLayer(new OpenLayers.Layer.OSM());
08   this.marker = new OpenLayers.Layer.Markers();
09   this.map.addLayer(this.marker);
10   var size = new OpenLayers.Size(21,25);
11   var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
12   this.icon = new OpenLayers.Icon(this.ipath, size, offset);
13  },
14
15  addMarker : function(lon, lat) {
16   this.marker.addMarker(new OpenLayers.Marker(this.coords(lon, lat), this.icon.clone()));
17   this.map.setCenter(this.coords(lon, lat));
18  },
19
20  coords : function (lon, lat) {
21   return new OpenLayers.LonLat(lon, lat).transform(
22    new OpenLayers.Projection("EPSG:4326"),
23    new OpenLayers.Projection("EPSG:900913")
24   );
25  }
26 };

Jquery Mobile

Eine grafische Benutzerschnittstelle für Webbrowser in Smartphones und Tablets lässt sich mit dem Open-Source-Framework Jquery Mobile ([11], aktuell ist 1.1.0) einfach und effizient erstellen. Es bringt viele vorkonfigurierte Knöpfe, Listen, Dialoge und Suchfilter mit, die Entwickler sonst mühevoll selbst anlegen und testen müssten. Jquery Mobile basiert auf HTML 5 und der Javascript-Bibliothek Jquery. Es eignet sich für eine stetig wachsende Zahl von Umgebungen, etwa Apple I-OS 3.2 bis 5.0, Android 4.0, Windows Phone 7 bis 7.5, Blackberry 7, Firefox Desktop 4 bis 9, Firefox Mobile (10 Beta), Opera Desktop 4 bis 5, Chrome Desktop 11 bis 17, Safari Desktop sowie Internet Explorer 7 bis 9.

Die vorkonfigurierten Elemente aus Jquery Mobile lassen sich mit benutzerdefinierten Data-Attributen [12] anwenden (Listing 10). Nachdem die Zeilen 6 bis 8 zunächst die Dateien für Jquery Mobile einbinden, wählt das Data-Attribut »data-theme« in Zeile 11 das so genannte Corporate Design »c« für die Seite aus. Eigene Designs kann der Programmierer mit dem Tool Theme Roller [13] kreieren. Das Attribut »data-role« weist den Container in Zeile 12 als eine einzelne Seite, den in Zeile 13 als Kopfbereich dieser Seite aus. Zeile 16 definiert den Content-Bereich und macht das »a« -Element in Zeile 17 zum Button für das Hinzufügen neuer Standorte.

Listing 10

Data-Attribute für Jquery Mobile

01 <!DOCTYPE HTML>
02 <html manifest="geotrack.appcache">
03 <head>
04  <meta charset="UTF-8"/>
05  <title>Geo Tracker</title>
06  <link rel="stylesheet" href="css/jquery.mobile-1.1.0.min.css"/>
07  <script src="js/jquery-1.7.1.min.js"></script>
08  <script src="js/jquery.mobile-1.1.0.min.js"></script>
09  <script src="listing-9.js"></script>
10 </head>
11 <body data-theme="c">
12  <div data-role="page" id="overview">
13   <div data-role="header">
14    <h1>Geotracker</h1>
15   </div>
16   <div data-role="content">
17    <a data-role="button" data-rel="dialog" href="#addDetail">Neuer Eintrag</a>
18    <h2>Einträge</h2>
19    <div id="listcontainer">
20     <ul id="list" data-role="listview"></ul>
21    </div>
22   </div>
23  </div>
24 </body>
25 </html>

Der Knopf »Neuer Eintrag« in Zeile 17 aus Listing 10 bindet einen Dialog ein, in dem er das Element mit der ID »addDetails« im Attribut »data-rel=”dialog”« referenziert. Listing 11 zeigt den Quellcode des Elements – ein Formular zur Eingabe des Standorts. Listing 12 beschreibt das Verhalten der Anwendung bei unterschiedlichen Ereignissen (Events) mit Hilfe von Javascript. Das erste Laden des Elements mit der ID »addDetail« löst das Event »pageinit« in Zeile 1 aus.

Listing 11

Dialog mit Jquery Mobile

01 <div data-role="page" id="addDetail">
02  <div data-role="content">
03   <form id="detail">
04    <div data-role="fieldcontain">
05     <label for="lat">Schlüssel</label>
06     <input type="text" name="key" id="key">
07     <label for="lat">Breitengrad</label>
08     <input type="text" name="lat" id="latitude">
09     <label for="long">Längengrad</label>
10     <input type="text" name="long" id="longitude">
11     <fieldset class="ui-grid-a">
12      <div class="ui-block-a">
13       <input id="addCancel" data-rel="back" type="button" value="Abbrechen"/>
14      </div>
15      <div class="ui-block-b">
16       <input data-rel="back" type="submit" value="Speichern"/>
17      </div>
18     </fieldset>
19    </div>
20   </form>
21 </div>

Listing 12

Events

01 $('#addDetail').live('pageinit', function() {
02  $('#detail').submit(function() { return add();});
03  $('#addCancel').click(close);
04 });
05
06 $('#addDetail').live('pagebeforeshow', function() {
07  $('#latitude').val(Geolocation.latitude);
08  $('#longitude').val(Geolocation.longitude);
09 });

Es definiert Folgendes: Das Absenden des Formulars – Event »submit« (Zeile 2) – ruft die Funktion »add()« auf. Ein Klick auf den Knopf zum Abbrechen – Event »click« – schließt den Dialog (Zeile 3). Bevor der Dialog auf das Event »pagebeforeshow« (Zeile 6) hin erscheint, schreibt Javascript die aktuellen Werte für die geografische Länge und Breite in die Textfelder. Die Beispielanwendung speichert die gesammelten Daten in einer Couch-DB-Datenbank, um sie mit anderen Benutzern teilen zu können.

Daten online

Couch DB [14] ist eine No-SQL-Datenbank, die Dokumente im Json-Format unter einem Schlüssel abspeichert. Sie verfügt über ein REST-API und lässt sich mit Jquery einfach bedienen. Der Couch-DB-Server läuft nach der Installation unter dem Port 5984. Ein reverser Proxy holt die Couch DB in die Domain der Webanwendung. Listing 13 zeigt die Konfiguration eines Apache-Webservers zu diesem Zweck.

Listing 13

Proxy für Couch DB

01 <VirtualHost *:80>
02  DocumentRoot /home/pa/www
03
04  ProxyPass /couchdb http://localhost:5984
05  ProxyPassReverse /couchdb http://localhost:5984
06 </VirtualHost>

Die Definition des Proxys steht in Zeile 4. Diese Datei speichert man unter »/etc/ apache2/sites-available/offline« und verlinkt sie in »/etc/apache2/sites-enabled« analog zum Default-Eintrag. Vor dem Neustart des Apache-Servers mit »apache2ctl restart« ist in der Default-Konfiguration der Port von »:80« auf zum Beispiel »:8080« zu ändern.

Die folgende HTTP-Anfrage erstellt eine leere Datenbank namens »geos« :

curl -X PUT http://localhost/couchdb/geos

Listing 14 zeigt die Synchronisation des Offline-Datenspeichers mit der Couch-DB-Datenbank. Die Methode »toSync()« in Zeile 4 serialisiert das Objekt mittels »stringify« zu einem Json-Dokument und sendet es per HTTP-Put an die Datenbank »geos« . Den Schlüssel des Dokuments bekommt die URL gemäß dem REST-API hinten angehängt. Der Content-Type der HTTP-Anfrage ist »application/json« . HTML 5 bringt mit dem Application Cache und den Datenspeichern für Anwendungsdaten (Local Storage, Web SQL und Indexed DB) alles mit, um Webanwendungen offline-fähig zu machen. Damit sind die Apps jederzeit verfügbar und nicht mehr von Netzwerkstörungen betroffen.

Listing 14

Synchronisation mit Datenbank

01 window.Sync = {
02  syncurl : '/couchdb/geos',
03
04  toSync : function(obj) {
05   $.ajax({
06    url : Sync.syncurl+'/'+obj.key,
07    contentType : 'appcliation/json',
08    type : 'PUT',
09    data : JSON.stringify(obj)
10   });
11  }
12 };

Mittels Jquery Mobile lassen sich universelle Apps für den Desktop und mobile Betriebssysteme wie Android oder Apples I-OS erstellen. So entfällt die kostenintensive Entwicklung unterschiedlicher Anwendungen für die verschiedenen Plattformen. (mhu)

Der Autor

Dipl.-Phys. Andreas Möller http://pamoller.com beschäftigt sich seit zehn Jahren mit der Entwicklung Internet-basierter Software. Dazu zählen Datenbankanwendungen, Webanwendungen sowie Systeme für das Single Source Publishing. Zurzeit ist er als Berater und freier Autor tätig.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 7 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben