Zukunft der Webentwicklung: Webkomponenten und Progressive Web Apps, Teil 1

Web als Plattform

Keine Scheu, die Plattform zu nutzen

Bei Polymer kümmern sich die App-Elemente <app-route> und <app-location> um das clientseitige Routing. Sie sind ein schönes Beispiel für die Zusammenarbeit von Webkomponenten, die eigentlich einfache Aufgaben erledigen, aber im Zusammenspiel mit weiteren Elementen den Job erledigen.

Das Folgende ist gleichzeitig ein Beispiel für Datenbindung von Polymer, die die Kommunikation zwischen den Elementen erleichtert. Den Seitenquelltext erzeugte im Wesentlichen Polymer CLI:

<!doctype html>
<html>
<head>
<title>demo</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="manifest" href="/manifest.json">
<script src="bower_components/webcomponentsjs/
webcomponents-lite.js"></script>
<link rel="import" href="src/demo-app/demo-app.html">
</head>
<body>
<demo-app></demo-app>
</body>
</html>

Als Erstes fällt das Einbinden der Manifest-Datei auf. Sie folgt der W3C-Spezifikation für Web-App-Manifest-Dateien und enthält Metadaten zur Anwendung, wie sie für Googles Progressive-Web-Apps-Ambitionen benötigt werden. Dazu in einem künftigen Artikel mehr.

Als Nächstes wird webcomponents-lite.js geladen, es enthält die Browserlücken stopfenden Polyfills. Der Ordner "bower_components" stammt von der Verwendung des Paketmanagers Bower. Das Node.js-Modul vereinfacht den Download der JavaScript-Bibliotheken. In erster Linie löst es Abhängigkeiten auf, was sonst vor allem bei neuen Versionen problematisch werden kann.

Schließlich wird demo-app.html importiert und im Body der Seite eingesetzt:

<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/app-route/
app-location.html">
<link rel="import" href="../../bower_components/demo-location/
demo-location.html">
<dom-module id="demo-app">
<template>
<app-location route="{{route}}"></app-location>
<demo-location route="[[route]]"></demo-location>
</template>

<script>
Polymer({
is: 'demo-app',
});
</script>
</dom-module>

Hier laden Entwickler zunächst alles, was sie für die App brauchen: Polymer selbst, das bei app-route untergebrachte app-location-Element und ein eigenes Element demo-location. In <template> steht das HTML, das <demo-app> einbindet. Wenn Polymer es analysiert, werden die Klammern im Wert des route-Attributs entdeckt. Sie zeigen an, dass Data Binding gewünscht ist: im Falle der geschweiften Klammern in beide Richtungen, bei den eckigen nur in eine. Im <script>-Element steht, was Polymer minimal benötigt. Hier wird das Element nur benannt. Wie einfach das Data Binding
funktioniert, verdeutlicht das demo-location-Element:

<link rel="import" href="../../bower_components/polymer/polymer.html">

<dom-module id="demo-location">
<template>
<p><span>location:</span> <span>{{route.path}}</span></p>
</template>

<script>
Polymer({
is: 'demo-location',
properties: {
route: {
type: Object
}
}
});
</script>
</dom-module>

Polymer sind die Eigenschaften bekannt zu machen, auf die es beim Element achten soll. Das geschieht durch Erstellen des Objekts properties. Minimum ist eine Typdeklaration. Da die route-Property der location-Komponente vom Typ Object ist, ist das hier anzugeben. Das Objekt hat eine Eigenschaft "path", die das Template verwendet.

Bei Annahme, dass die App durch http://localhost/polymer-demo/index.html im Browser aufgerufen würde, zeigt die Seite nun "location: /polymer-demo/index.html an. Die vom location-Element erzeugte Eigenschaft "path" wurde also in die Seite eingefügt, ohne dass Entwickler hierfür Code schreiben mussten. Bei Polymer geschieht das automatisch. Das funktioniert aber nur, solange man den Kontext einer mit Polymer erzeugten Komponente nicht verlässt. Würden die Elemente <app-location> und <demo-location> direkt im Light DOM auf der Seite sitzen, käme keine Bindung zustande.

Fähig für Lazy Loading

Polymer erlaubt es, Elemente erst dann zu laden, wenn sie tatsächlich gebraucht werden (Lazy Loading). Hierzu ist es erforderlich, in den Routing-Prozess einzugreifen. Man unterbricht das Laden neuer Inhalte, um sicherzustellen, dass die benötigten Elemente schon vorhanden sind. Wenn nicht, geht es erst weiter, wenn sie mit importHref geladen wurden. Im nächsten Schritt wird <demo-location> durch <app-route> ersetzt, so wie von den Polymer-Schöpfern vorgesehen.

<app-route
route="{{route}}"
pattern="/:page"
data="{{path}}"
tail="{{tail}}">
</app-route>

Man kann das Element wie eine Funktion ansehen, wobei die Attribute route und pattern die Eingabe darstellen, path und tail die Ausgabe. Das Element wird aktiv, wenn sich über die Datenbindung zu
<app-location> der Wert von route ändert, also wenn sich in der Adressbox des Browsers etwas tut. Mit dem Pattern lässt sich angeben, welche Teile des Pfads wie benannt werden sollen.

Das Vorgehen erinnert etwas an die extract-Funktion von PHP, die die Werte eines assoziativen Arrays zu einzelnen Variablen überträgt. Der obige Pfad mündet in ein Objekt mit der Eigenschaft "page", die den Wert "polymer-demo" hat, und dieses Objekt wird über das data-Attribut anderen Elementen bekannt gemacht. Was durch das Pattern nicht abgedeckt ist, landet im tail-Attribut, in diesem Fall also "index.html". Man ahnt, wie es weitergeht: Ein weiteres Element verbindet sich mit app-route und nutzt dessen Werte zur Darstellung von Inhalten.

Im Falle des Shops ist das iron-pages. Unter "Iron" (Fe) hat Google Komponenten grundsätzlicher Natur versammelt. Sie macht kaum mehr, als dafür zu sorgen, dass immer nur eines der Kindelemente sichtbar ist. Die Entwickler sprechen in diesem Zusammenhang von Views. Der Shop kennt fünf davon, und jede Ansicht ist wieder über eine eigene Komponente umgesetzt, <shop-home> für die Startseite, <shop-cart> für den Warenkorb et cetera.

<iron-pages selected="[[path.page]]" attr-for-selected="name" 
selected-attribute="visible">
<div name="one">1</div>
<div name="two">2</div>
<div name="three">3</div>
</iron-pages>

Der Wert des Attributs selected entscheidet, welcher <div>/i] sichtbar ist. Standardmäßig lässt sich hier der Index angeben. Der Autor folgt aber dem Shop und gibt den Elementen einen Namen. [i]<iron-pages> muss nun wissen, was es verwenden soll. Das geschieht über dessen Attribut attr-for-selected. Stände im selected-Attribut "two", würde im Beispiel also die Zahl 2 angezeigt. Der Shop verwendet außerdem selected-attribute. Hierüber setzen Entwickler ein Attribut beim gerade sichtbaren Element. Im Chrome-Inspector können Entwickler beobachten, dass bei der jeweiligen Komponente das visible-Attribut vorhanden ist. Über sogenannte Observer kann die Komponente nun darauf reagieren, ob sie gerade zu sehen ist oder nicht. Details lässt der Artikel außen vor, es soll nur deutlich werden, wie hier das DOM genutzt wird.

Im eigentlichen Shop übernimmt die übergeordnete Komponente <shop-app> ihre Rolle als Vermittler und reagiert selbst auf Änderungen bei <app-route>. Der Wert wird dann auf das eigene page-Attribut übertragen. Deswegen steht dort bei selected schlicht "[[page]]". Das ermöglicht zusätzliches Scripting, zum Beispiel soll zum Seitenanfang gescrollt werden.