Category Archives: Browser

AddToHomescreen: pin web apps to the homescreen

add2home-screen1Apples iOS supports since ages a meta tag called “apple-mobile-web-app-capable”, with which a developer can tell iOS that web site doesn’t need navigation and url bars and is handling that itself. So, if this meta tag is set and a web site is called from the iOS homescreen it looks like a normal app for the normal visitor.

The problem is to tell its visitors that this behaviour is supported as most users don’t know that there is even the possibility to add websites to their homescreens.

But like they say – there is an app for that – there is a library for that: Matteo Spinelli has developed the wonderful tool AddToHomescreen. If you embed the code inside your web app, a small tooltip will be visible after page load which asks the user to add the site to their homescreen. The library checks itself that its only visible on compatible iOS devices and changes the little icon what the user should tap on automatically (its different on iPads and iPhones). Additionally you can configure some options like if and how long the tooltip stays or how long the tooltip remembers that the user has closed the tooltip and will not see it again.

After optimizing and deleting not-needed languages its payload is only 6 kB.

Attention if you are using requireJS

AddToHomeScreen uses the onload event to init itself by default, but that event is already gone when loading it via requireJS … at least when using it together with r.js optimizer (which you should!). But there is a (advanced) config option for this:

window.addToHomeConfig = {hookOnload: false}

If this option is set, the library inits itself as soon as its loaded and doesn’t wait for the (already gone) load event.

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. 🙂

iOS 6 cached POST-Requests … und wie man das verhindert

Letzte Woche haben wir uns ziemlich gewundert als uns aufgefallen ist, dass unsere iPhones mit iOS 6 nicht mehr mit unserer Web App funktioniert haben. Da sind wir wohl über ein “Feature” von iOS 6 gestolpert, das ziemlich viel Aufsehen in der Entwicklergemeinde verursacht hat, als es veröffentlicht wurde.

Um dem Benutzer noch das letzte bisschen Performance vorzugaukeln, hat sich Apple nämlich entschieden, POST-Requests (z.B. per Ajax) im Browser zu cachen, wenn dies nicht explizit vom Server deaktiviert wird.

Apple steht damit ziemlich alleine … alle HTTP-Clients machen das aus gutem Grund nicht. In der “Restful HTTP”-Philosophie sind POST-Requests per Definition nicht idempotent, d.h. das Ergebnis mehrerer identischer POST-Requests an dieselbe URL selbst mit derselben Payload haben NICHT immer dasselbe Ergebnis. In SQL entspricht ein POST-Requests einem INSERT. Es werden also im Regelfall für jeden Request neue Datensätze angelegt.

POST-Requests zu cachen verbietet sich also, ausser der Serverbetreiber aktiviert es explizit mittels der üblichen HTTP-Header zum Caching.

Zwar kann ich verstehen, wenn Apple so viele HTTP-Requests sparen möchte wie es geht, damit der Benutzer möglichst performante Webseiten präsentiert bekommt, aber hier übertreiben sie ganz eindeutig.

Wenn man das Problem einmal gefunden und identifiziert hat, lässt es sich zum Glück sehr schnell deaktivieren. Wir haben im Server nun einen ServletFilter (Java) implementiert, der in alle Responses von POST-Requests zur REST-Schnittstelle den HTTP-Header [highlight1]Cache-Control: no-cache[/highlight1] einbaut.

Wenn man über den Server keine Kontrolle hat und das nicht einbauen kann, kann man als Client-Entwickler auch eine zufällige Komponente in die URL einbauen, sodass die URL immer unterschiedlich ist und iOS6 die Requests zwar cached, aber durch die wechselnde URL der Cache ins Leere läuft. Am besten geht das mit einem Timestamp:

url = url + "?" + (new Date()).getTime();

Der URL wird dann die Anzahl der Millisekunden seit 1970 angehangen.