Appcache Manifest erstellen und in einer angularJS App integrieren

Wer eine ausgewachsene App entwickelt, die aus rein statischen Dateien (*js, *.css, *.html) besteht und die dynamischen Daten ausschließlich über einen REST-Server nachlädt, sollte sich in jedem Fall mal mit dem HTML5 Appcache beschäftigen. In so einem Fall kann man die (z.B. bei alistapart etwas übertriebenen beschriebenen) Nachteile nämlich ruhig ignorieren.

Der Vorteil für den Nutzer die kompletten statischen Daten immer sofort auf dem Gerät parat zu haben und außer dem REST-Request überhaupt keine HTTP-Requests zum Laden der App zu benötigen, ist speziell für mobile Anwendungen ein ungeheurer Performancegewinn.

Wie ein Appcache-Manifest aufgebaut ist, wird z.B. auf HTML5 Rocks beschrieben.

Nun will man die Datei aber natürlich möglichst automatisch erstellen. Wir haben uns – da wir grunt als Buildtool verwenden – für grunt-contrib-manifest entschieden. Für mich ein weiterer Grund sehr zufrieden mit unserer Entscheidung zu sein, grunt zu verwenden. Es gibt für praktisch alle Anforderungen im Bereich Webentwicklung bzw. Frontend-Entwicklung bereits ein Plugin. [highlight1]”Da gibts ein Plugin für”[/highlight1] statt “Da gibt’s eine App für” sozusagen 🙂

Die Konfiguration ist simpel und selbsterklärend, da muss ich nicht weiter drauf eingehen. Und funktioniert bisher problemlos.

Was etwas mehr nachdenken erfordert hat, war, wie man auf das Event “updateready” reagieren soll, das der Browser erzeugt, wenn er die Dateien im Appcache vollständig aktualisiert hat und die neue Version der App geladen werden muss. Dass eine nicht wegklickbare Info-Box erscheinen sollte, die der Nutzer nur mit einem Klick auf “Neu laden” schließen können sollte, war recht schnell klar. Das eigentliche “Problem” war, das solche DOM-Manipulationen nicht in einen angularJS Controller gehören. Schließlich sollen die auf jeden Fall auch ohne DOM unittest-bar bleiben.

Also war eine Direktive notwendig.

Wir haben es momentan mit dieser Direktive gelöst. Das Popup benutzt jQuery Mobile.

function AppcacheUpdated() {
	var elementScope;

	window.applicationCache.addEventListener('updateready', function() {
		if (window.applicationCache.status === window.applicationCache.UPDATEREADY) {
			window.applicationCache.swapCache();
			elementScope.show = true;
			if (!elementScope.$$phase) {
				elementScope.apply(elementScope.show);
			}
		}
	});

	return {
		restrict:'E',
		template:" <div data-role='popup' class='appcachePopup ui-content' data-theme='e'>" +
			" <p><translate>Neue Version</translate></p><br />" +
			"<a ng-click='reload()' data-role='button'>Neu laden</a> </div>",
		scope:{
		},
		controller: function ($scope){
			$scope.$watch("show", function(value) {
				if (value) {
					$(".appcachePopup").popup("open", {dismissible:false});
				}
			});

			$scope.reload = function() {
				window.location.reload();
			};

			$scope.show = false;
		},
		link: function (scope) {
			elementScope = scope;
		}
	};
}

angularModule.directive("appcacheUpdated", AppcacheUpdated);

Die Direktive zu verwenden ist simpel, einfach in jede HTML-Seite, auf der sie verwendet werden soll, irgendwo einbauen:

<appcache-updated></appcache-updated>

Das ist so ziemlich meine erste Direktive. Mal schauen ob sie auch weiterhin so funktionieren wird, wie sie es momentan tut. Oder ob es dort noch Fallen gibt, über die ich bisher nicht gestolpert bin. Vor allem bin ich mir momentan noch etwas unsicher, ob es günstig ist, sich den “element.$scope” in einer Closure zu merken.

Die Komplexität um angularJS-Direktiven zu schreiben, ist aber in jedem Fall deutlich geringer als JSF-Komponenten zu schreiben. Ich bin immer mehr der Überzeugung, dass JSF für mich keine Zukunft mehr hat, wenn es um Frontend-Entwicklung geht. Der Ansatz auf dem Server das HTML zu rendern und den Client darstellen zu lassen, hat sich in den letzten Jahren einfach überlebt.

Die Entwicklung mit angularJS ist erstens deutlich einfacher und zweitens … macht es einfach Spass. 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

*