Anzeige
Anzeige
UX & Design

Webentwicklung für das iPhone – Teil 4: Offline-Datenbank für die eigene Webapplikation

Spricht man im Zusammenhang von Websites oder Webapplikationen über Datenbanken, sind fast immer serverseitige Datenbanksysteme wie MySQL oder Oracle gemeint. Dabei gibt es viele Szenarien, in denen es sinnvoll ist, Daten nicht nur auf einem Server zu speichern, sondern sie auch lokal vorzuhalten – teilweise sogar ausschließlich. Hier kommen Offline-Storages ins Spiel. Wir zeigen, wie man sie für seine eigene iPhone-WebApp nutzt.

7 Min.
Artikel merken
Anzeige
Anzeige

Offline-Storages sind Bestandteil von HTML5 und bieten die Möglichkeit, Daten auf dem Client in einer SQLite-Datenbank zu speichern. Diese Daten werden persistent vorgehalten und unterliegen keinem Verfallsdatum. Daher eignen sie sich gut zum Speichern von Benutzereinstellungen, zum Zwischenspeichern von Daten vor dem Senden an einen Server und um Informationen nur einmal vom Server zu übertragen und vorzuhalten, wenn sich der Inhalt serverseitig selten ändert. Da die Datenbank lokal abgelegt ist, wird keine Internetverbindung benötigt, um darauf zuzugreifen.

Anzeige
Anzeige

Als Beispiel dient in diesem Artikel eine fiktive Videodatenbank. Diese verwendet folgenden HTML-Code als Grundgerüst:

HTML-Grundgerüst

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3c.org/1999/xhtml">
<head>
	<title>Videobestand</title>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
	<meta name="viewport" content="width=320; initial-scale=1.0; user-scalable=0;"/>
	<style type="text/css" media="screen">
		@import "lib/iUI/iui.css";
	</style>
	<style type="text/css" media="screen">
		@import "css/videobestand.css";
	</style>
	<script type="application/x-javascript" xsrc="js/videobestand.js"></script>
	<script type="application/x-javascript" xsrc="lib/iUI/iui.js"></script>
</head>
<body style="overflow:hidden;" onLoad="initDB();">
<div class="toolbar">
	<h1 id="pageTitle"></h1>
	<a id="backButton" class="button" href="#"></a>
</div>
<ul id="home" title="Videobestand" selected="true">
</ul>
</body>
</html>

Listing 1

Das Grundgerüst basiert auf dem iPhone-Userinterface-Framework iUI, das im Verlauf der Serie schon mehrfach zum Einsatz kam. Das eigentliche JavaScript ist in eine externe Datei ausgelagert. Im Body-Bereich befinden sich nur der DIV-Container für die Toolbar am Anfang der Seite und eine ungeordnete leere Liste. Diese Liste soll mit JavaScript gefüllt werden.

Anzeige
Anzeige

Initialisieren der Datenbank

Als erstes müssen Sie die Datenbank initialisieren. Dabei testet das Skript in Listing 2, ob der Browser Offline-Datenbanken unterstützt, indem es versucht, eine Datenbank zu öffnen. Tritt dabei ein Fehler auf, weil die entsprechende Funktion nicht existiert, fängt das Skript den Fehler ab, gibt eine Fehlermeldung aus und bricht an dieser Stelle ab. Im Erfolgsfall wird die Datenbank initialisiert.

Anzeige
Anzeige

Initialisieren der Datenbank

function initDB() {

try {
	if (!window.openDatabase) {
		alert('JavaScript-Datenbanken werden von Ihrem Client nicht unterstützt!');
	} else {
		var shortName = 'videodb';
		var version = '1.0';
		var displayName = 'Videobestand';
		var maxSize = 65536; // max. Größe der Datenbank in bytes
		// Im Erfolgsfall enthält mydb eine Instanz des Datenbank-Objekts
		var mydb = openDatabase(shortName, version, displayName, maxSize);

			createTables(mydb);
		queryDB(mydb);
	}
} catch(e) {
	// Hierher kommt der Code für die Fehlerbehandlung (Error Handling)
	if (e == INVALID_STATE_ERR) {
		// Falsche Datenbank-Version.
		alert("Ungültige Datenbank-Version!");
	} else {
		alert("Unbekannter Fehler "+e+".");
	}
	// return;
}
}

Listing 2

Die Initialisierung beginnt mit dem Erzeugen der Datenbank. Das Skript versucht, die Datenbank zu öffnen. Scheitert dies, weil sie noch nicht existiert, wird sie neu angelegt und geöffnet. Der weitere Zugriff erfolgt dann über das Datenbankobjekt und seine Methoden.

Der „shortName“ identifiziert die Datenbank im System und dient als eindeutiger Bezeichner. Der „displayName“ wird vom Browser angezeigt, wenn es zu Benutzerinteraktionen kommt. Die Variante „version“ spielt im Moment noch keine Rolle und wird erst interessant, wenn die geplante Update-Funktion implementiert ist.

Anzeige
Anzeige

Bis es soweit ist, belassen Sie den Wert am besten auf „1.0“. Der Wert „maxSize“ gibt die zu erwartende Größe der Datenbank an und ermöglicht dem System so eine Ressourcenplanung. Wird mehr Speicher erwartet, gibt das System eine Alert-Box aus, die der Anwender bestätigen muss.

Tabelle anlegen und befüllen

Um auf die Datenbank zuzugreifen, bedienen Sie sich SQL-Queries, die Sie auch von anderen relationalen Datenbanken wie MySQL kennen. Die Funktion, mit der die Queries an die Datenbank geschickt werden, heißt „executeSql“ und ist Teil der Transaktion des Datenbankobjekts.

SQL-Befehle absetzen

function createTables(db) {
	db.transaction(
		function (transaction) {
			transaction.executeSql('SQL-Statement', [], nullDataHandler, errorHandler);
		}
	}
}

Listing 3

Statt SQL-Statement setzen Sie hier das gewünschte SQL-Kommando ein. „NullDataHandler“ ist eine Funktion, die nichts macht. Erwarten Sie einen Rückgabewert, beispielsweise das Ergebnis einer Abfrage, steht hier die Funktion, die das Ergebnis annehmen soll. Die Funktion „errorHandler“ wird nur aufgerufen, wenn ein Fehler bei der Abfrage auftritt.

Anzeige
Anzeige

Anlegen und Befüllen der Tabelle

function createTables(db) {
	db.transaction(
		function (transaction) {
			transaction.executeSql('DROP TABLE IF EXISTS videos;',
															[], nullDataHandler, errorHandler);
				transaction.executeSql('CREATE TABLE IF NOT EXISTS videos (VideoID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , Name TEXT NOT NULL, Genre TEXT NOT NULL, gesamt INT NOT NULL, verliehen TEXT NULL );',[], nullDataHandler, errorHandler);
				transaction.executeSql('INSERT INTO videos (VideoID, Name, Genre, gesamt, verliehen) VALUES (NULL, "Alien", "SciFi", "12", "11");', [], nullDataHandler, errorHandler);
         transaction.executeSql('INSERT INTO videos (VideoID, Name, Genre, gesamt, verliehen) VALUES (NULL, "Fight Club", "Action", "24", "14");', [], nullDataHandler, errorHandler);
				transaction.executeSql('INSERT INTO videos (VideoID, Name, Genre, gesamt, verliehen) VALUES (NULL, "Keinohrhasen", "Liebeskomödie", "20", "18");', [], nullDataHandler, errorHandler);
				transaction.executeSql('INSERT INTO videos (VideoID, Name, Genre, gesamt, verliehen) VALUES (NULL, "Monster", "Drama", "3", "3");', [], nullDataHandler, errorHandler);
				transaction.executeSql('INSERT INTO videos (VideoID, Name, Genre, gesamt, verliehen) VALUES (NULL, "Serenity", "SciFi", "8", "8");', [], nullDataHandler, errorHandler);
		}
	);
}

function nullDataHandler(transaction, results) { } 

function errorHandler(transaction, error) {
	// error enthält die Fehlermeldung
	alert('ACHTUNG! Es ist ein Fehler aufgetreten: '+error.message+' (Code '+error.code+')');
	return true;
}

Listing 4

Da die Tabelle nun befüllt ist, können Sie sie auch abfragen. Mittels DOM-Scripting werden die Ergebnisse in die Seite eingebaut. Der zugehörige Code sieht wie folgt aus:

Abfrage

function queryDB(db) {
	function dataHandler(transaction, results) {
		var panel = "";
			for (var i=0; i<results.rows.length; i++) {
				var row = results.rows.item(i);
				listElem = document.createElement("li");
				anchorElem = document.createElement("a");
				anchorElem.setAttribute("href","'#"+row['VideoID']+"'");
				anchorElem.appendChild(document.createTextNode(row['Name']));
				listElem.appendChild(anchorElem);
				document.getElementById("home").appendChild(listElem);
					panel = document.createElement("div");
					panel.setAttribute("id",row['VideoID']);
					panel.setAttribute("class","panel");
					panel.setAttribute("title",row['Name']);
					headline = document.createElement("h2");
					headline.appendChild(document.createTextNode("Lokation: " + row['Name']));
					panel.appendChild(headline);
					fieldset = document.createElement("fieldset");
					ptag = document.createElement("p");
					ptag.setAttribute("class","ptext");
					ptag.appendChild(document.createTextNode("Verliehene Videos: " + row['verliehen'] + " von " + row['gesamt']));
			fieldset.appendChild(ptag);
			panel.appendChild(fieldset);
			document.getElementsByTagName("body")[0].appendChild(panel);
		}

	}
	db.transaction(
		function (transaction) {
			transaction.executeSql("SELECT * FROM videos ORDER BY Name;", [], dataHandler, errorHandler);
		}
	);
}

Listing 5

Die abgefragten Daten werden als <li>-Elemente in die Liste eingefügt. Für jedes Element wird ein neuer Container mit der ID des betreffenden Videos angelegt. iUI kümmert sich um die Verknüpfung.

Offline-Applikation

Neben Daten lassen sich auch ganze Webapplikationen offline halten. Damit werden die Nachteile einer möglicherweise fehlenden oder eingeschränkten Internetverbindung umgangen. Das iPhone oder der iPod touch müssen die WebApp dann nur einmal laden und sie steht anschließend jederzeit zur Verfügung. Möglich macht das ein zukünftiger, aber seit iPhone OS 2.1 schon verfügbarer Standard: der „HTML5 Offline Application Cache“. Um ihn zu nutzen, werden ein so genanntes Cache-Manifest und ein zugehöriges JavaScript-Interface benötigt.

Anzeige
Anzeige

Das Cache-Manifest

Das Cache-Manifest ist eine Textdatei, die alle Ressourcen auflistet, beispielsweise Bilder, CSS- oder JavaScript-Dateien. Der Browser muss so für das Caching also nicht jede einzelne Seite aufrufen, sondern lädt beim Öffnen der Seite sämtliche Dateien, die Sie angegeben haben. Anschließend lädt der Browser keine Daten mehr vom Server, sondern greift nur noch auf den Cache zu. Die Dateien bleiben auch beim Schließen des Browsers erhalten. Folgende Eigenschaften besitzt das Manifest:

  • Der Mime-Type lautet text/cache-manifest.
  • Die erste Zeile lautet immer CACHE MANIFEST.
  • Jede Zeile enthält entweder die URL zu einer Ressource oder einen Kommentar. Kommentare sind einzeilig und werden mit dem Hash-Symbol (#) eingeleitet.
  • URLs können absolut oder relativ sein.
  • Die HTML-Datei, die das Cache-Manifest referenziert, muss nicht enthalten sein.

Ein Beispiel für das Cache-Manifest könnte wie folgt aussehen:

Cache-Manifest

CACHE MANIFEST

# HTML-Dateien
produkte.html
service.html
kontakt.html
# JavaScript-Dateien
lib/iUI/iui.js
lib/main.js
#CSS-Dateien
lib/iUI/iui.css
lib/main.css
# Bilder
img/logo.png
http://example.com/images/brand.gif

Listing 6

Nun müssen Sie das Cache-Manifest referenzieren, indem Sie das HTML-Tag auf der Startseite erweitern: „<html manifest=“filename.manifest“>“. Dabei können Sie einen absoluten oder einen relativen Pfad angeben.

Anzeige
Anzeige

Offline-Application-Cache updaten

Würden nun Änderungen an der Website vorgenommen, würden diese nicht angezeigt werden, da die Seiten ja schon im Cache vorgehalten werden. Also muss der Cache aktualisiert werden. Zum einen geschieht das automatisch, sobald Sie den Inhalt des Cache-Manifests ändern – das Ändern des Dateidatums bringt also noch nichts. Ändert sich an der Verzeichnisstruktur nichts, ändert sich auch das Cache-Manifest nicht. Änderungen innerhalb einer Datei werden also nicht übernommen.

Sie müssen also etwas nachhelfen. Mit JavaScript können Sie eine Aktualisierung manuell anstoßen. Das DOMWindow-Objekt besitzt dazu die Eigenschaft „DOMApplicationCache“, deren Instanz jeweils den Application-Cache eines Dokuments repräsentiert.

Cache aktualisieren

// Cache-Objekt ermitteln
cache = window.applicationCache;

// Wenn der Cache mit dem Update fertig ist ...
if (cache.status == cache.UPDATEREADY) ...

// Update manuell anstoßen. Update wird in Zwischenspeicher geschrieben
cache.update();

// War das Update erfolgreich, Zwischenspeicher mit Cache austauschen
cache.swapCache();

// Weiterarbeiten, wenn Cache wieder in UPDATEREADY-Zustand ist
if (cache.status == cache.UPDATEREADY) ...

Listing 7

Außer „UPDATEREADY“ gibt es noch weitere Zustände des Caches:

Anzeige
Anzeige
  • UNCACHED = 0
  • IDLE = 1
  • CHECKING = 3
  • DOWNLOADING = 4
  • UPDATEREADY = 5
  • OBSOLETE = 6

Nach dem Update sollten Sie die Seite noch einmal laden, da das Update erst greift, wenn eine neue Seite aufgerufen wird. Es ist aktuell nicht möglich, dem Cache einzelne Dateien hinzuzufügen oder sie zu entfernen. Dieses Feature soll aber in einer zukünftigen Version des Standards implementiert werden.

Cache Events

Auch der Cache kennt JavaScript-Events, die ausgelöst werden, wenn sich der Application Cache ändert oder beim Update ein Fehler auftritt.

Cache aktualisieren

// Cache-Objekt ermitteln
cache = window.applicationCache;

// Der Eventlistener ruft die Funktion 'cacheUpdatereadyListener' auf, wenn das Update erfolgreich war.
cache.addEventListener('updateready', cacheUpdatereadyListener, false);

// Der Eventlistener ruft die Funktion 'cacheErrorListener' auf, wenn das Update fehlgeschlagen ist.
cache.addEventListener('updateready', cacheErrorListener, false);

Listing 8

Weitere Events sind „onchecking“, „onerror“, „onnoupdate“, „ondownloading“, „onprogress“, „onupdateready“, „oncached“, „onobsolet“.

Anzeige
Anzeige

Fazit

Mit den hier vorgestellten Techniken ist es möglich, Webapplikationen zu bauen, die sich fast nicht mehr von richtigen Applikationen unterscheiden. Mit dem Offline Application Cache und dem Offline Data Storage fallen zudem die Nachteile normaler WebApps wie die zwingende Verfügbarkeit eines Netzzugangs oder die Latenz durch das Laden der nächsten Seite weg.

Alle Artikel dieser Serie:
Teil 1 (t3n Nr. 13) Websites für das iPhone optimieren
Teil 2 (t3n Nr. 14) Eine iPhone-Applikation entwickeln (Design und iUI)
Teil 3 (t3n Nr. 16) iPhone-spezifische Features nutzen (CSS Transitions/Transformations, GPS)
Teil 4 (t3n Nr. 17) Offline-Datenbankenanbindung (mit SQLite)
Mehr zu diesem Thema
Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Anzeige
Anzeige
Schreib den ersten Kommentar!
Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.

Bitte schalte deinen Adblocker für t3n.de aus!
Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Deine t3n-Crew

Anleitung zur Deaktivierung
Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder
Auf Mastodon teilen

Gib die URL deiner Mastodon-Instanz ein, um den Artikel zu teilen.

Anzeige
Anzeige