HTML5 Offline-Reader Web-App mit LocalStorage und Appcache

Dieses Projekt liegt jetzt schon bestimmt 4-5 Monate fast fertig bei mir rum, heute Abend hab ichs dann mal finalisiert. Es geht darum mittels HTML5 Elementen eine Web-App zu schreiben, die das offline lesen von Artikeln ermöglicht. Verwendete Techniken sind der LocalStorage des Browsers und der Appcache, also die Möglichkeit mittels Manifest-Dateien mehrere Dateien anzugeben, die der Browser offline zwischenspeichern soll.

Das Szenario aus User-Sicht

Der User besucht einmalig eine Seite eines Blogs, News-Seite, etc., die als Offline Reader fungiert. Ist er mit einem Smartphone auf dieser Seite fügt er sie evtl. sogar zu seinem Homescreen hinzu und bekommt sie fortan mit schönem Icon angezeigt. In Zukunft besucht er das Blog,die News-Seite, etc. von der der Offline Reader stammt und kann dort auf der Übersichtsseite mit den Anrissen ein kleines „M“-Icon oben rechts anklicken, das daraufhin von einer transparenten Darstellung wechselt zu einer ohne Transparenz – dieser Artikel ist damit zum offline lesen im Offline Reader markiert. Wechselt er nun zum Offline Reader können dort die zuvor im Anriss markierten Inhalte vollständig gelesen werden, auch ohne Internet-Verbindung. Um die gelesenen Artikel vom Offline Reader zu entfernen, wird einfach auf das „x“ oben rechts beim jeweiligen Artikel geklickt.

Vorstellbar ist dieses Szenario z.B. vor einer Zugfahrt. Den Offline Reader hat der User zuvor schon mal zu seinem Homescreen hinzugefügt oder tut dies nun. Er surft nun noch beim Blog, der News-Seite, etc. vorbei und sucht sich anhand der Anrisstexte auf der Übersichtsseite 3-4 Artikel raus, die er gleich im Zug in Ruhe lesen möchte. Er tut dies, da er weiß, dass die Internet-Verbindung bei der Zugfahrt schlecht sein wird und jedes Aufrufen der Seite lange dauert.
Nachdem er sich ein paar Artikel markiert hat kommt der Zug, er steckt das Smartphone weg, setzt sich im Zug auf seinen Platz und um Akku zu sparen schaltet er die Internet-Verbindung des Smartphones aus. Nun ruft er von seinem Homescreen aus den Offline Reader auf und kann dort die zuvor markierten Artikel lesen. Wenn er einen Artikel gelesen hat löscht er ihn aus der Liste mittels Klick aufs „x“.

Wie geht das nun?

LocalStorage

Fangen wir beim Markieren der Artikel für das spätere Lesen an. Durch Klick auf das „M“ wird ein AJAX-Request im Hintergrund ausgelöst. Es wird die ID des Beitrags an den Server übertragen und dieser liefert den Text an das Script zurück. Das Script speichert diesen nun im LocalStorage des Browsers (in meinem Beispiel verwende ich das zuvor im Blog schon mal vorgestellte PlugIn jStorage). Klickt der User noch mal auf das „M“ wird der Eintrag aus dem LocalStorage wieder entfernt.

Appcache

Die Inhalte sind nun schon im Prinzip lokal im Browser gespeichert, jedoch braucht es noch eine Seite um die Inhalte auch anzuzeigen, wenn der User offline ist. Dazu dient in meinem Beispiel die einfache Datei index.html mit folgendem Inhalt:

<!DOCTYPE HTML>
<html lang="de-DE" manifest="cache-manifest.appcache">
<head>
  <title>Offline Reader</title>
  <meta charset="UTF-8">

  <link rel="apple-touch-icon-precomposed" href="apple-touch-icon-57x57-precomposed.png" />
  <link rel="apple-touch-icon-precomposed" sizes="114x114" href="apple-touch-icon-114x114-precomposed.png" />
  <link rel="apple-touch-icon-precomposed" sizes="72x72" href="apple-touch-icon-72x72-precomposed.png" />

  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

  <link rel="stylesheet" type="text/css" href="../standard.css" />
  <script type="text/javascript" src="../scripts/jquery-1.6.4.min.js"></script>
  <script type="text/javascript" src="../scripts/jquery.json-2.3.min.js"></script>
  <script type="text/javascript" src="../scripts/jstorage.js"></script>

  <script type="text/javascript" src="../scripts/scripts.js"></script>
</head>
<body>
  <h1>Meine Artikel</h1>
  <ul id="my_articles">

  </ul>
</body>
</html>

Schnell fällt auf, viel Inhalt hat die Seite erstmal noch nicht. Interessant ist vor allem der Eintrag in Zeile 2

manifest="cache-manifest.appcache"

In *.appcache-Dateien gibt man Dateien an, die der Browser offline verfügbar machen soll. In diesem Fall sieht die Datei so aus:

CACHE MANIFEST
# v8
CACHE:
index.html
../standard.css
../scripts/jquery-1.6.4.min.js
../scripts/jquery.json-2.3.min.js
../scripts/jstorage.js
../scripts/scripts.js

Die Datei muss unbedingt mit CACHE MANIFEST beginnen. Der Kommentar anschließend ist dazu da, um dem Browser mitzuteilen, dass sich an den unten aufgeführten Dateien etwas geändert hat. Es ist nämlich so: Besucht der User eine Website, die eine Manifest-Datei referenziert, liest der Browser die Datei und macht die darin aufgelisteten Dateien offline verfügbar. Wird jetzt eine dieser Dateien irgendwo für die Website benötigt, fragt der Browser nicht mehr beim Server nach, sondern nimmt die Datei aus seinem Speicher (dem sogenannten Appcache). Wenn sich also an einer der Dateien auf dem Server etwas ändert (neue Features, Bugfixes,…) bekommt der Browser nichts davon mit. Erst wenn sich an der Manifest-Datei etwas ändert fragt der Browser die aufgelisteten Dateien neu vom Server ab. Sehr wichtig ist, dass man die Manifest-Datei selbst nicht auch auflistet, denn sonst steuert man den User in eine schlimme Lage. Der Browser speichert dann auch die einzige Datei zwischen, an der er eigentlich Veränderungen der zwischengespeicherten Dateien erkennen kann – er wird also vermutlich nie mehr Änderungen der zwischengespeicherten Dateien auf dem Server mitbekommen. Das lässt sich nur beheben, wenn im Browser selbst manuell der Appcache geleert wird.

Weiter zum Inhalt der Datei.
Der Block „CACHE:“ bedeutet, das nun Dateien folgen, die zwischengespeichert werden soll. Pro Zeile wird eine Datei angegeben. Man kann komplette URLs angeben, zum Server absolute Pfade oder Pfade, die relativ zur Manifest-Datei sind, wie im Beispiel.
Es gibt noch die Blöcke „NETWORK:“ und „FALLBACK:“. Ressourcen die nach der NETWORK-Anweisung aufgelistet werden, werden immer vom Server abgerufen, das ist quasi eine Online-Whitelist. FALLBACK spricht im Prinzip für sich. Hier kann man z.B. einfach nur eine Datei angeben, die dem User mitteilt, dass die Web-App gerade nicht zur Verfügung steht, da der User keine Internet-Verbindung hat. Man kann allerdings auch für Bereiche einen Fall definieren.

FALLBACK:
/ offline.html

bedeutet, dass für alle Anfragen auf / und darunter (also alle auf diesen Server), die nicht verfügbar sind, die Datei offline.html angezeigt wird.

FALLBACK:
/api/logs/ api_offline.html

bedeutet jedoch, dass lediglich Anfragen an /api/logs/ und darunter auf diesem Server mit api_offline.html beantwortet werden.

Damit die Manifest-Datei funktioniert, muss man sie mit dem Mime-Type text/cache-manifest vom Server ausliefern lassen. Da geht z.B. mit folgendem Eintrag in einer .htaccess-Datei:

AddType text/cache-manifest .appcache

Damit die Manifest-Datei selbst nicht vom normalen Browser-Cache erfasst wird, sollte man sie mit einem Expire-Header ausliefern, der ihr nur kurze Gültigkeit zugesteht.

Web-App

Damit das ganze ein bisschen App-ähnlicher wird, stattet man die index.html einfach noch mit Icons aus, falls die Seite zum Homescreen eines iPhones oder Android-Smartphones hinzugefügt wird und nutzt weitere Möglichkeiten, um Web-Apps mehr wie native Apps erscheinen zu lassen.

Das Szenario aus Entwickler-Sicht

Der User besucht einmalig die als Offline Reader fungierende Website. Der Browser erkennt, dass eine Manifest-Datei hinterlegt ist, liest diese und macht die angegebenen Ressourcen offline verfügbar. Ist der User mit einem Smartphone auf dieser Seite fügt er sie evtl. zu seinem Homescreen hinzu. Da im Kopf-Bereich der Seite Anweisung für die Darstellung als Web-App hinterlegt sind, wird im Safari-Browser die Menüleiste „schöner“ dargestellt, sodass es mehr wie eine native App anstatt wie eine Website aussieht. Außerdem sind Icons in drei unterschiedlichen Auflösungen hinterlegt – der User bekommt das für sein Endgerät passendste Icon auf dem Homescreen angezeigt. Der Offline Reader ist nun fertig eingerichtet beim User.

In Zukunft kann der User der Website auf der Übersichtsseite mit den Anrissen ein kleines „M“-Icon oben rechts anklicken. Im Hintergrund läuft ein AJAX-Request, der den gesamten Inhalt des Artikels vom Server abruft und im LocalStorage des Browsers des Users speichert. Ruft der User nun den Offline Reader auf (der Browser öffnet die Seite aus dem Appcache, sofern sich die Manifest-Datei nicht geändert hat), prüft ein JavaScript, ob sich im LocalStorage Artikel befinden. Ist dies der Fall werden sie in den DOM des Offline Readers eingehangen und können so offline vom User gelesen werden ohne dass eine Internet-Verbindung benötigt wird. Hat der User einen Artikel gelesen und klickt auf das „x“ oben rechts bei einem Artikel, entfernt das JavaScript den Artikel aus dem DOM der Seite und löscht ihn im Appcache des Browsers.

Statistik und Beispiel

Meine diesem Beispiel zugrunde liegende Anwendung ist online verfügbar. Die Übersichtsseite und der Offline Reader. Für mein einfaches Beispiel ist im Hintergrund eine XML-Datei, die die Titel, Anrisse und vollständigen Artikel (Anrisse+Lorem Ipsum) bereit stellt. Der Einfachheit halber umfasst die Web-App jQuery und das PlugIn jStorage. Das JavaScript, CSS und die HTML-Datei werden alle in den App-Cache geladen, sodass die App sozusagen eine Dateigröße von insgesamt knapp 109 Kbyte umfasst.

Weiterführend kann ich noch die Mozilla Developer-Seite zum Thema empfehlen: Using the Application Cache.

Veröffentlicht von

Hilko

2006 machte ich mein Hobby zum Beruf, startete mit einer Ausbildung zum Mediengestalter und arbeite inzwischen in Hannover als Frontend Web-Developer. Neben der Leidenschaft für semantisches Markup und den Möglichkeiten der aktuellen Frontend-Techniken, interessiere ich mich für das Reisen, fremde Kulturen und die Kunst der Fotografie.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.