Web Components erklärt, Teil 3: native HTML-Elemente erweitern

Die bisherigen Teile dieser Serie haben gezeigt, was Web Components sind und wie man mit ihnen eigene HTML-Elemente erfinden kann. Seither sind Web Components auch ganz gut im allgemeinen Bewusstsein der Frontend-Entwickler angekommen, was man vor allem an der auf Twitter aufkommenden Kritik merkt. Eine besonders häufig formulierte Befürchtung ist, dass Web Components jedes etablierte HTML-Element durch eine unsemantische Eigenkonstruktion ersetzen könnten. In diesem Szenario würde z.B. aus <a href="https://www.google.de"> ohne guten Grund ein Monstrum der Marke <polymer-anchor data-href="https://www.google.de">, das in wichtigen Details (z.B. Barrierefreiheit oder Browserunterstützung) nicht an das Original heranreicht.

Es ist in der Tat nicht auszuschließen, dass demnächst ohne komplett überzeugenden Grund zusätzliche Varianten von <a> erfunden werden, aber es ist definitiv nicht nötig, dabei das Rad stets komplett neu zu erfinden und auf die Fähigkeiten der Originale zu verzichten. Web Components beinhalten einen Mechanismus, der es erlaubt vorhandene Elemente mit wenig Aufwand um neuen Fähigkeiten zu ergänzen. Mit normalem DOM-Code ist das etwas knifflig, mit Polymer jedoch ein Kinderspiel.

Der Extend-Mechanismus

Wie wir im ersten Teil der Serie gelernt haben, ist eine der neuen Technologien rund um Web Components die Funktion document.registerElement(). Mit ihr kann man beim Browser komplett selbst erfundene Elemente anmelden:

document.registerElement('x-foo', {
  prototype: Object.create(HTMLElement.prototype, {
    methodBar:{
      value: function(){
        console.log('Hallo Welt!');
      }
    }
  })
});

An document.registerElement() übergibt man neben dem Namen des neuen Elements (hier x-foo) auch ein Konfigurationsobjekt, das unter anderem den Prototypen für das neue Element festlegt (hier ein neues Objekt, das seinerseits HTMLElement.prototype als Prototypen hat). Als Prototyp kann jedes HTML-Element fungieren, so dass man sein selbstgebautes Element zu z.B. einem Sonderfall von <a> könnte, einfach indem man HTMLAnchorElement.prototype statt HTMLElement.prototype verwendet. Außer einer veränderten Prototypen-Kette bringt das allein aber nicht viel und <x-foo> wird dadurch nicht zu einem anklickbaren Link.

Um wirklich (ohne das Rad neu zu erfinden) eine Abwandlung von <a> zu erstellen, muss man einen etwas anderen Weg gehen. Zum Einen muss man bei der Element-Anmeldung dem Konfigurationsobjekt eine extends-Eigenschaft mitgeben, die den Tag des Elements angibt, von dem wir einen Sonderfall anlegen möchten:

document.registerElement('x-foo', {
  prototype: Object.create(HTMLAnchorElement.prototype, {
    methodBar:{
      value: function(){
        console.log('Hallo Welt!');
      }
    }
  }),
  extends: 'a'
});

Wichtig ist zum Anderen, dass der Prototyp des Elements in extend auch der Prototyp des Objekts in prototype ist. Die Benutzung des neuen Elements erfolgt dann nicht über einen neuen Tag <x-foo>, sondern das Element wird im is-Attribut eines normalen <a>-Elements angegeben:

<a is="x-foo" href="https://www.google.de">Google</a>

Dieses Element ist ein fast ganz normaler Link, nur ergänzt durch unsere Erweiterung in Form der JavaScript-Funktion methodBar(). Die Spezifikationen für Custom Elements nennen solche Konstruktionen type extensions. An sich funktioniert unsere Erweiterung jetzt auch, aber richtig toll ist das Ergebnis noch nicht:

  1. Die rohe API von document.registerElement() ist äußerst unbequem, v.A. das Setup der Prototypen-Kette ist ausgesprochen mühsam
  2. Alles, was über den Einbau einer kleinen JS-Extramethode herausgeht (z.B. Shadow DOM) würde richtig anstrengend werden
  3. Mangels Polyfills funktioniert der bisher gezeigte Code nur in den allerneuesten Browser (d.h. aktuellem Chrome)

Zum Glück ist das Erweitern von Elementen mit Polymer ein Kinderspiel. Es ist sogar so einfach, dass wir uns für den nächsten Schritt direkt vornehmen können, eine durchaus realitäsnahe Erweiterung für <a>-Elemente zu schreiben.

Type Extensions mit Polymer

QR-Codes (nicht lachen!) sind nichts anderes als Links in Bildform. Also macht es Sinn, sie in HTML wie folgt einzubinden:

<a is="qr-code" href="https://www.google.de">Google</a>

Ein solcher Link sollte statt des Link-Texts einen QR-Code anzeigen. Ergänzend sollte man, da es sich ja um Bilder handelt, optionale height- und width-Attribute angeben können. Wenn sich eins der Größen-Attribute oder das href-Attribut ändert, sollte der Code neu berechnet werden. Davon abgesehen sollte sich das Element wie ein herkömmlicher Link verhalten: es muss anklickbar sein, sich per Tastatur bedienen lassen und generell keine schwerwiegenden Nachteile gegenüber einem herkömmlichen Link haben.

Eine gut funktionierende QR-Code-Library ist auf Github schnell gefunden und wird zusammen mit dem üblichen Boilerplate-Code für ein neues Polymer-Element in eine HTML-Datei geschrieben:

<script src="qrcode.js"></script>
<link rel="import" href="bower_components/polymer/polymer.html">

<polymer-element name="qr-code">

  <template>
    <span id="Code"></span>
  </template>

  <script>
    Polymer('qr-code', {
    });
  </script>

</polymer-element>

Das „Element“ qr-code, das wir hier neu anlegen, wird hinterher als Wert im is-Attribut von <a>-Elementen fungieren. Das <span>-Element im Shadow-DOM-Template nimmt den erzeugten QR-Code auf.

Der Prototyp unseres qr-code-Elements braucht als erstes eine Methode zum erstellen neuer QR-Codes. Diese Objekte, erstellt durch die QR-Library, werden in der qrcode-Eigenschaft der Element-Instanz gespeichert (this.qrcode). Eingesetzt wird diese Methode wenn das Element bereit ist, d.h. der ready-Callback feuert:

Polymer('qr-code', {

  createCode: function(){
    return this.qrcode = new QRCode(this.$.Code, {
      text: this.getAttribute('href'),
      width: this.getAttribute('width') || 128,
      height: this.getAttribute('height') || 128
    });
  },

  ready: function(){
    this.createCode();
  }

});

Um auf Attribut-Änderungen reagieren zu können, bedienen wir uns des attributeChanged-Events. Wenn sich nur das href-Attribut ändert, können wir die makeCode()-Methode von QRCode-Objekten nutzen; nur bei Änderungen der Maße muss eine komplett neue QRCode-Instanz erstellt werden:

Polymer('qr-code', {

  ...

  attributeChanged: function(attr, oldVal, newVal){
    if(attr === 'href'){
      this.qrcode.makeCode(newVal);
    }
    if(attr === 'width' || attr === 'height'){
      this.createCode();
    }
  }

});

Stand jetzt ist unser Werk immer noch ein normales Custom Element. Und wie machen wir jetzt eine <a>-Erweiterung daraus? Es könnte einfacher nicht sein:

<polymer-element name="qr-code" extends="a">
  ...
</polymer-element>

Das ganze komplizierte Prototypen-Setup übernimmt Polymer und unsere Element-Erweiterung funktioniert einfach!

Selbstgebaute Custom Elements lassen sich in Polymer auch erweitern, wenn auch auf etwas andere Art und Weise. Was eigentlich gar nicht geht, sind Mehrfacherweiterungen.

Mixins statt Mehrfacherweiterungen

Mehrfacherweiterungen nach dem Muster <a is="foo bar baz"> gibt es nicht. Das ist auch einigermaßen nachvollziehbar, denn letztlich laufen Erweiterungen immer noch auf Prototypen-Ketten hinaus und da ist klar, dass ein Element in der Kette nicht mit mehr als einem anderen verbunden sein kann. Aber das als zweites Argument in die Polymer()-Funktion gesteckte Konfigurationsobjekt kann natürlich ein aus mehreren Objekten zusammengesetztes Objekt sein – Mixins sind hier die beste Lösung.

Nehmen wir einmal an, wir wollten ein Set von Social-Media-Buttons bauen. Jeder Social-Media-Button muss eine URL kennen, für die er Tweet-Links, Like-Schaltflächen o.Ä. bereitstellen soll. Es macht Sinn, diese Funktionalität in eine entsprechende Social-Media-Button-„Basisklasse“ auszulagern:

<polymer-element name="social-media-button" attributes="url">
<script>
  Polymer('social-media-button', {
    url: null,
    ready: function(){
      this.url = this.url || window.location.href;
    }
  });
</script>
</polymer-element>

Erweiterungen dieses allgemeinen Social-Media-Buttons könnten dann konkrete Facebook- und Twitter-Buttons sein. Diese sind keine Type Extensions sondern eigenständige Elemente, da die Custom-Elements-Spezifikation Type Extensions nur für native Elemente vorsieht. Polymer behilft sich, indem aus selbstdefinierten Elementen abgeleitete selbstdefinierte Elemente einfach per Prototypen-Kette hintereinandergeschaltet werden und so komplett neue Elemente entstehen, die die gleichen Fähigkeiten wie ihre „Basisklassen“ haben.

<polymer-element name="twitter-button" extends="social-media-button" attributes="via">
  <template>
    <iframe allowtransparency="true" frameborder="0" scrolling="no"
            src="https://platform.twitter.com/widgets/tweet_button.html?url={{encodedUrl}}&amp;via={{via}}"
            style="width:130px; height:20px;"></iframe>
  </template>
  <script>
    Polymer('twitter-button', {
      via: 'sir_pepe',
      encodedUrl: null,
      observe: {
        url: 'encode'
      },
      encode: function(){
        this.encodedUrl = encodeURIComponent(this.url);
      }
    });
  </script>
</polymer-element>


<polymer-element name="facebook-button" extends="social-media-button" attributes="share">
  <template>
    <iframe allowTransparency="true" frameborder="0" scrolling="no"
            src="//www.facebook.com/plugins/like.php?href={{encodedUrl}}&amp;width&amp;layout=standard&amp;action=like&amp;show_faces=false&amp;share={{share}}&amp;height=35&amp;appId=263047413871308" style="border:none; overflow:hidden; height:35px;" ></iframe>
  </template>
  <script>
    Polymer('facebook-button', {
      share: true,
      encodedUrl: null,
      observe: {
        url: 'encode'
      },
      encode: function(){
        this.encodedUrl = encodeURIComponent(this.url);
      }
    });
  </script>
</polymer-element>

Beide Elemente haben große Unterschiede im Shadow DOM und dezent andere Features (via-Attribut beim Twitter-Button, share-Attribut beim Facebook-Widget) aber sie haben auch ein gemeinsames Feature: beide erzeugen (und verwenden) eine encodedUrl-Eigenschaft. Wenn nicht alle Social-Media-Buttons diese Eigenschaft benötigen, hat sie in der „Basisklasse“ nichts verloren, andererseits ist es auch etwas blöd, diesen Code doppelt vorliegen zu haben. Die Lösung besteht darin, die gemeinsamen Teile des Konfigurations-Objekts in ein eigenes Objekt auszulagern …

var encodedUrlMixin = {
  encodedUrl: null,
  observe: {
    url: 'encode'
  },
  encode: function(){
    this.encodedUrl = encodeURIComponent(this.url);
  }
};

… und dieses Objekt mit den individuellen Konfigurations-Objekten der Buttons zu kombinieren. Hierfür kann jede beliebige Mixin-Funktion verwendet werden; Polymer bringt in Form von Platform.mixin() auch eine mit, die so gut wie jede andere ist:

<polymer-element name="twitter-button" extends="social-media-button" attributes="via">
  <template>
    ...
  </template>
  <script>
    Polymer('twitter-button', Platform.mixin({
      via: 'sir_pepe',
    }, encodedUrlMixin));
  </script>
</polymer-element>


<polymer-element name="facebook-button" extends="social-media-button" attributes="share">
  <template>
    ...
  <script>
    Polymer('facebook-button', Platform.mixin({
      share: true
    }, encodedUrlMixin));
  </script>
</polymer-element>

Und schon funktionieren beide Buttons! Der Schlüssel zu zwischen Komponeten geteilter Funktionalität ist also die Kombination von normaler „Vererbung“ und flexiblen Mixins.

Fazit

Werden dank Web Components viele komische neue Dinge als zweifelhafte HTML-Element umgesetzt werden? Vermutlich. Als unsemantische Div-Suppe muss das Ganze allerdings nicht enden, da, wie wir gesehen haben, sich auch einfach native Elemente erweitern lassen. Und für komplexere Funktionalitäts-Transplantationen stehen Mixins bereit, die ein ganz eigener Teil der Web-Component-Welt werden können. Es ist also alles halb so wild.

Sieben ausgewählte JSConf-Videos

In letzter Zeit sind sehr viele neue Videos aus dem JSConf-Umfeld bei Youtube aufgeschlagen. Obwohl die Talks meist vergleichsweise kurz ausfallen (lobenswerterweise!) dürfte kein normaler Mensch die Zeit haben, sich alles anzusehen. Zum Glück bin ich nicht normal und habe in den letzten Wochen wirklich extrem viel Zeit in der Eisenbahn verbracht. Und ich habe mir währenddessen alles angesehen.

Die folgenden 7 Videos sind die, die mir am besten gefallen haben und aus denen ich am meisten für den Alltag mitgenommen habe. Das ist notwendigerweise ziemlich subjektiv (abgefahrene ES6-Experimente finde ich persönlich sehr viel spannender als enterprisige Scaling-Probleme) aber ich versichere euch, dass ihr mit dem Konsum der folgenden Talks nichts falsch macht. Die Videos sind nach meiner persönlichen Bewertung sortiert – was weiter oben steht, fand ich besser. Und sie sind alle kurz genug, um bequem in eine Mittagspause zu passen.

Guy Bedford: Package Management for ES6 Modules

Noch ein Package Manager? Vade retro satana! Aber Guy Bedford verheiratet mit JSPM die Gegenwart von JavaScript-Modulen (AMD, CommonJS, Globals) mit der Zukunft (ES6-Module) auf entzückend elegante Art und Weise. Egal wie das Modul verfasst ist, mit JSPM funktioniert es in jedem Fall. Ein Polyfill bringt der JS-Umgebung ES6-Module bei und der Moduloader ist qua ES6-Standard sowieso in der Lage angepasst zu werden, was in diesem Fall für all die verschiedenen DIY-Formate passiert. Ein Package Manager für Zukunft und Gegenwart.

James Long: Unshackling JavaScript with Macros

SweetJS stattet JavaScript mit Makros aus. So weit, so altbekannt; dass man SweetJS als eine Art Traceur Light verwenden kann, war zumindest mir nicht gegenwärtig. Das Package es6-macros implementiert die meiner Meinung nach wichtigsten ES6-Features (Destructuring, Klassen, Arrow Functions) ohne dass man mit dem Monstrum Traceur (und seinen heillos kaputten Grunt-Plugins) ringen müsste. Und natürlich kann man mit SweetJS noch mehr anstellen, was James Long in epischer Breite erklärt.

Neil Green: Writing Custom DSLs

Eine eigene DSL bauen? „Brauche ich nicht, kann ich nicht“ war mein erster Gedanke. Doch Neil Green zeigt sehr schön auf, wie falsch dieser Gedanke ist. Vom Nutzen von DSLs über die Planung mit dem Endnutzer bis hin zur konkreten Umsetzung wird kein Schritt auf dem Weg ausgelassen, so dass man sofort seine eigene kleine Sprache bauen möchte.

John-David Dalton: Fearless Browser Testing

Der Autor von Lo-Dash erzählt aus seiner Test-Praxis – und die hat es in sich. Er hat den Anspruch, dass seine Library auch in den ollsten Browsern und ungewöhnlichsten Umgebungen funktioniert und führt zu diesem Zwecke allerlei Tricks und Tools zu Felde. Wenn man vielleicht auch nicht jedes seiner Probleme selbst jederzeit hat (wandernde Timer in alten IE z.B.) ist seine Test-Infrastruktur doch sehr beeindruckend und inspirierend.

Nick Bray: Native Code on the Web

Wenn ein Google-Mitarbeiter über „Native Code on the Web“ erzählt, befürchtet man zunächst eine Werbeveranstaltung für Native Client. Tatsächlich ist dieser Talk jedoch ein schön nerdiger Rundumschlag, der auch ASM.js und der konkreten Umsetzung von Webapps mit nativem Kern Zeit widmet. Was Nick Bray erzählt und zeigt ist für die tägliche Arbeit von Otto Normalentwickler vermutlich nicht direkt relevant, ist aber unter dem Gesichtspunkt Nerd-Sport sehr beeindruckend.

Christoph Burgmer: Hacking a HTML renderer in plain browser-side JS

Und nochmal Frontend-Extremsport in seiner feinsten Form. Christoph Burgmer kombiniert über 9000 verschiedene Hacks um in seinem Browser einen Browser zu bauen. Yo dawg.

Ryan Florence: Embularactymerbone

Ein kurzer Vergleich zwischen Ember, Angular, React, Polymer, und Backbone. Ich weiß nicht was Polymer in dieser Reihe zu suchen hat, aber trotzdem: schneller als in diesem 30-Minuten-Video bekommt man nicht die Unterschiede zwischen all diesen Frameworks erklärt. Kann man sich gut ansehen, wenn man sich mit den einzelnen Tools noch nicht befasst hat.

Erklärbär-Termine für August, September und Oktober 2014

Im Herbst geht es rund! Neben den üblichen Münchner HTML5- und CSS3-Terminen habe ich versucht, auf jeder Konferenz derer ich habhaft werden konnte, einen Workshop zu Web Components unterzubringen. Falls eure Konferenz in der folgenden Liste fehlt durfte ich entweder den Termin noch nicht veröffentlichen oder sie fehlt wirklich. Schickt dann den Veranstalter zu mir, denn die Welt braucht mehr Web Components!

  • 18. - 20. August in München: HTML5-Schulung bei der Open Source School. Mein bewährtes drei­tä­gi­ges HTML5-Standardprogramm stattet die Teilnehmer im Druckbetankungsverfahren mit so gut wie allem aus, was man zu HTML5 wissen muss. Von semantischem Markup bis hin zu Canvas-Frameworks ist alles dabei. Einen großer Praxisanteil mit überschaubaren Arbeitsgruppen stehen auf dem Plan und ein Exemplar des HTML5-Buchs gibt es obendrein.
  • 21. und 22. August in München: CSS3 bei der Open Source School. Mein bewährtes zweitä­gi­ges CSS3-Standardprogramm katapultiert die Teilnehmer in das CSS3-Zeitalter, in dem Webfonts, Animationen und Farbverläufe fließen. Geboten wird auch hier ein großer Praxisanteil, kleine Arbeitsgruppen und mindestens ein CSS3-E-Book gibt es als Bonus.
  • 13. - 15. Oktober in Berlin: HTML5/JavaScript-Days in Berlin. Gleich drei Workshops gibt es von mir: Web Components, HTML5-Formulare und Techniken für asynchrones JavaScript
  • 26. Oktober in München: WebTech Conference. Auf der Agenda steht mein bewährter Workshop neue Techniken für Responsive Design, der sich mit Element Queries, Flexbox und den neuen Webstandards für Responsive Images befasst.

Termine unpassend, Orte alle zu weit weg und Programme nicht genehm? Ich komme auch gerne mit einem maßgeschneiderten Talk oder Workshop vorbei – mich kann man mieten!

Responsive Web Components

Ich habe mich die letzte Zeit intensiv mit Web Components (via Polymer) und aktuellen Webstandards rund um Responsive Design befasst. Eigentlich, so denkt man zunächst, bräuchte es auch für Web Components neue Responsive-Design-Webstandards. Das heutige Arbeitspferd für Responsive Design sind Media Queries, die (neben vielen anderen Nachteilen) den Haken haben, sich nur auf die Maße der gesamten Webseite bzw. des gesamten Devices beziehen zu können. Der reinen Lehre nach sollte eine Komponente aber komplett unabhängig vom Rest der Webseite sein, d.h. selbst entscheiden wann sie sich für weniger oder mehr Platz umbaut. Das ist allerdings nicht ganz einfach – nicht zuletzt, weil das eigentlich überhaupt gar nicht geht. Und ich glaube auch nicht, dass man das haben will.

Was eine wirklich komplett unabhängige Komponente bräuchte, wären nicht herkömmliche Media Queries zur Abfrage der Gesamtbreite von Device oder Webseite, sondern etwas zur Abfrage von der Breite eines bestimmten Elements (d.h. von sich selbst). Ein solches Feature hieße dann Element Query und trägt in sich das kleine Problem, dass es nicht funktioniert. Tab Atkins legt die Gründe hier und hier dar. Es gibt eine Vielzahl an konzeptionellen Problemen zu überwinden und wenn Element Queries auch nicht völlig unmöglich sind, so sind sie doch selbst im besten Fall noch viele Jahre von der Einsatzreife entfernt. Nicht nur gibt es keine Browserunterstützung, es gibt noch nicht mal einen konkreten Plan wie ein solches Feature überhaupt aussehen könnte.

Dessen ungeachtet gibt es diverse Prollyfills, die mit dem Element-Query-Konzept experimentieren. Wirklich ausgereift scheint mir keins dieser Scripts zu sein und insbesondere in Web Components konnte ich keinen der Kandidaten überzeugend zum Funktionieren bewegen. Und während ich noch damit herumspielte kam mir der Gedanke, dass man vielleicht gar keine Element Queries in Web Components haben möchte.

Zum einen stellt sich die Frage, ob man eine Responsive-Funktionalität, die so ausgefeilt wie Media Queries ist, überhaupt in seiner Komponente braucht. Dinge wie die Schriftgröße werden ohnehin von der umgebenden Webseite auf die Komponente vererbt – schrumpft die Schriftgröße via Media Query auf der Seite, so schrumpft sie auch in der Komponente. Sind Layout-Maße mit schriftgrößen-relativen Einheiten festgelegt, reduzieren auch sie sich gleich mit. Und baut man das Komponenten-Layout mit Flexbox, so kann man zumindest einfache Layout-Umbauten (z.B. den Wechsel zwischen einem Zwei- und Einspalten-Design) nur über Mindestmaße und flex-wrap, also ganz ohne Media Queries abbilden. Außerdem könnte ein Webdesigner mit entsprechenden Pseudoklassen direkt ins Shadow DOM hineingreifen um vor Ort und Stelle punktuelle CSS-Änderungen anzubringen. Häufig könnte das allein schon reichen aber selbst wenn nicht, weiß ich nicht, ob man Komponenten mit jeweils eigenen Element Queries haben will.

Wenn Komponenten gemäß der reinen Lehre sich komplett selbst managen und sie auch selbst via Element Query entscheiden, wann sie sich responsive rearrangieren, geht dem Webdesigner ein großes Stück Kontrolle verloren. Wo die Breakpoints für das Design der Webseite sitzen, würden dann plötzlich die Entwickler der Komponenten entscheiden! Das entsprecht aber eigentlich auch der reinen Lehre – wo kommen wir denn hin, wenn angeblich komplett in sich gekapselte Komponenten über das Aussehen der gesamten Webseite entscheiden? Ohnehin: so lange es keine nativen Element Queries gibt, müsste jede Komponente seine eigene Responsive-Logik in JS mitschleppen. Jeder Teil der Webseite hätte dann sein eigenes kleines Gehirn (inklusive eigener Bugs) und sein eigenes kleines Design und alles sieht aus (und resized sich) wie Kraut und Rüben. Das kann eigentlich nicht die Lösung sein.

Aktuell erscheint es mir am Sinnvollsten, dass (mit JavaScript) auf der Webseite eine zentrale Responsive-Steuerung eingerichtet wird. Die Komponenten haben mehrere Layouts an Bord, entscheiden aber nicht selbst, wann welches Aussehen verwendet wird. Das regelt die zentrale Steuerung per Attributvergabe an die Komponenten. So bleibt alles inklusive der Komponenten schön responsive, aber die Breakpoints werden zentral gesteuert. Das, kombiniert mit den schon erwähnten Responsive-Automatismen (Flexbox, Schriftgröße) könnte ein schönes gemeinsames Bild ergeben. Und nicht zuletzt funktioniert eine solche Konstruktion ohne Wenn und Aber in modernen Browsern.

Zur Umsetzung dieses Prinzips muss eine Komponente (gebaut mit Polymer oder anderweitig) nur ein entsprechendes layout-Attribut unterstützen und sein Aussehen in Abhängigkeit davon regeln. Das ist zumindest in Polymer-Elementen recht unkompliziert:

<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="x-layouts" attributes="layout" noscript>
  <template>
    <style>
      :host {
        display: block;
      }
      :host([layout="large"]) {
        color: red;
      }
      :host([layout="medium"]) {
        color: green;
      }
      :host([layout="small"]) {
        color: blue;
      }
    </style>
    <p>
      Hallo Welt!
    </p>
  </template>
</polymer-element>

Das Element unterstützt ein layout-Attribut mit den drei Werten small, medium und large und passt sein Aussehen mit dem Element-eigenen CSS an. Das Setzen dieses Attributs übernimmt dann einfach die Webseite, die das Element einbindet:

<!doctype html>
<title>Responsive Komponente</title>
<meta charset="utf-8">
<script src="bower_components/platform/platform.js"></script>
<link rel="import" href="x-layouts.html">
<x-layouts class="responsiveComponent"></x-layouts>
<script>

  var components = document.querySelectorAll('.responsiveComponent');

  var isLarge = window.matchMedia('(min-width: 600px)');
  var isMedium = window.matchMedia('(min-width: 400px)');
  var isSmall = window.matchMedia('(max-width: 400px)');

  function resize(){
    var layout = (isLarge.matches) ? 'large' :
                 (isMedium.matches) ? 'medium' : 'small';
    for(var i = 0; i < components.length; i++){
      components[i].setAttribute('layout', layout);
    }
  }

  window.addEventListener('resize', resize);
  window.addEventListener('load', resize);

</script>

Und das war es schon! Wenn man einen Haufen selbstentwickelter Komponenten hat, die alle das layout-Attribut unterstützen, braucht man keine einzige Code-Zeile mehr als das. Das einzig etwas abgefahrenere Feature in dieser Konstruktion ist window.matchMedia(), was aber außer in alten IE überall funktioniert und für die IE gibt es bekanntlich einen Polyfill. Für umfassende Unterstützung ist also gesorgt.

Zusammengefasst vereint diese Art von responsiven Komponenten mehrere Vorteile: sie funktionieren (im Gegensatz zu Element Queries) in jedem modernen Browser, erlauben die Zentralisierung der Breakpoint-Steuerung (im Gegensatz zu autonom entscheidenden Komponenten) und sind, wenn die Grundfunktion einmal implementiert ist, sehr einfach zu benutzen – vor allem wenn man bedenkt, dass man Polymer-Elemente von anderen Elementen erben lassen kann. Der Haken an der Geschichte ist, dass natürlich nie alle Komponenten auf dieser Welt eine einheitliche layout-Attribut-Unterstützung haben werden, sondern dass man damit vornehmlich eigene Komponenten wird ausrüsten können. Aber wenn man bedenkt, dass sich heutzutage die meisten Komponenten überhaupt gar nicht um das Responsive-Thema kümmern, ist das eigentlich kein wirklicher Rückschritt.

Folgt mir!

Kauft mein Zeug!

Cover der HTML5-DVD
Infos auf html5-dvd.de, kaufen bei Amazon

Cover des HTML5-Buchs
Infos auf html5-buch.de, kaufen bei Amazon

Cover des CSS3-Buchs
Infos auf css3-buch.de, kaufen bei Amazon