STYLESHEETS OPTIMAL Organisieren

Bei dem als Schmetterlingseffekt bekannten Phänomen geht es darum, dass bestimmte Systeme sehr empfindlich auf kleine Abweichungen in den Anfangsbedingungen reagieren. Ob das beim Wetter so ist, sei dahingestellt. Aber eines ist klar: Auf Stylesheets passt es - hier kann eine kleine Änderung an einer Stelle auf eine ganz andere Stelle Auswirkungen haben.

Schuld daran ist das C in CSS, das heißt, die in ihren Auswirkungen sehr weitreichende Kaskade (Cascading). Dieses Verhalten von CSS erschwert die Arbeit, vor allem dann, wenn mehrere Personen an einem Stylesheet arbeiten. Um diese Situation zu verbessern, gibt es CSS-Methoden oder auch CSS-Strategien. Im Unterschied zu Frameworks beinhalten Methoden nicht vorgefertigte, direkt einsetzbare Styles, sondern Überlegungen, wie Stylesheets zu erstellen sind.

Das Ziel von Strategien sind wartbare, modifizierbare, und lesbare Stylesheets. Wichtig ist außerdem die Vorhersehbarkeit und Unabhängigkeit.

Vorhersehbarkeit: Der Aufbau von Stylesheets soll vorhersehbar sein, genauso auch die Benennungen und die Art der Selektoren.

Unabhängigkeit: Einzelne Bestandteile sollen unabhängig voneinander sein. Das gilt in zweierlei Hinsicht: Zum einen sollen die CSS-Teile nicht zu sehr voneinander abhängen (sonst haben wir den Schmetterlingseffekt), und zum anderen soll der CSS-Code nicht zu eng mit dem HTML-Code verknüpft sein. Wenn zum Beispiel eine UI-Komponente an einer anderen Stelle platziert wird, soll das ohne weitere Änderungen möglich sein.

Ein weiterer Vorteil bei der Verwendung einer der Methoden ist, dass damit auch neuen Teammitgliedern einfach kommuniziert werden kann, wie Stylesheets zu erstellen sind.

Im Folgenden werden bekannte Methoden vorgestellt, nämlich SMACSS, OOCSS, BEM, SUIT CSS und Atomic CSS.

SMACSS

Die Abkürzung SMACSS steht für »Scalable and Modular Architecture for CSS« (Bild 1). Erfinder dieser Strategie ist Jonathan Snook. Als Entwickler arbeitete Jonathan Snook lange als Einzelkämpfer und wechselte dann in ein größeres Team. Da wurde ihm klar, dass es Strategien braucht, wenn mehrere Kollegen gleichzeitig, effektiv und ohne sich in die Quere zu kommen am CSS-Code arbeiten sollen. Das war die Geburtsstunde von SMACSS.

Eine wichtige Komponente von SMACSS ist die Kategori-sierung von CSS-Regeln. Unterschieden werden die Typen Base, Layout, Module und Submodule, State und Theme.
Sehen wir uns die Typen nun genauer an: Die Basisregeln

- Base - definieren die Default-Styles für Elemente. Hierfür kommen Typselektoren wie body oder a zum Einsatz. Basisregeln sind zum Beispiel die grundlegenden body-Formatie-rungen für die Schriftart oder die Hintergrundfarbe.

Die Layoutformatierungen (Layout) betreffen die großen Bereiche der Site wie den Header, die Sidebar, den Inhaltsbereich und den Footer. Diese Bereiche können über IDs angesprochen werden, es kann aber auch Nachfahrenkombinatoren geben, wenn das Layout Modifikationen erhält. Im folgenden Beispiel haben wir eine Standard-Sidebar #sidebar und eine modifizierte Sidebar mit fester Breite:

#sidebar { width: 20%; float: right;

}

.l-fixed #sidebar {

width: 200px;

}

Die Layoutregeln betreffen die grobe Struktur der Seite, bei den Modulen (Module) handelt es sich hingegen um kleinere Komponenten. Dazu gehören Navigationsleisten, Dialogfenster, Widgets und Ähnliches. Der Code für Module sollte so geschrieben sein, dass die Module als selbstständige Komponenten existieren können. Für Module verwendet man typischerweise Klassenselektoren wie etwa .module; Typselektoren sollten vermieden werden.

Wenn Module in unterschiedlichen Kontexten verschieden aussehen, haben wir sogenannte Submodule. Anstatt diese durch Nachfahrenkombinatoren wie .special .pod anzuspre-
So präsentiert sich die Website von SMACSS (Bild 1)
STYLESHEETS OPTIMAL Organisieren
Praktikable Styles

BEM, OOCSS, SUIT CSS und Co. bieten Strategien für wiederverwendbare Stylesheets.


chen, sollten zusätzliche Klassen definiert werden. So wäre beispielsweise für das Modul .pod das Submodul .pod-con_ strained denkbar. Beim Submodul stehen dann beide Klassen, wie der HTML-Ausschnitt zeigt:

<div class="pod pod-constrained">...</div>

Eine weitere Kategorie sind die Zustände (States). Ein Accordion hat beispielsweise den Zustand aus- oder eingeklappt. Zustände werden üblicherweise zu Elementen hinzugefügt, für die bereits Layout- oder Modulregeln gelten. So könnte eine Hinweiskomponente den Zustand is-error haben:

<div class="msg is-error">

Ein Fehler ist aufgetreten!

</div>
Zustände sind üblicherweise mit JavaScript verbunden. Die Verwendung von !important ist bei ihnen gestattet, weil Zustände die anderen Angaben überschreiben sollen.

Die letzte Kategorie von Regeln sind Themes, die Farben und Hintergründe betreffen. Wenn diese separat notiert werden, lassen sie sich bei alternativen Themes leichter überschreiben. Ein Projekt kann aber auch ganz ohne Themes auskommen.

Depth of Applicability

Ein wichtiges Ziel von SMACSS ist es, die Depth of Applicability (Anwendbarkeitstiefe) zu verringern: Bei vielen Projekten sind Selektoren wie die Folgenden üblich:

#sidebar div ul {}

Das Problem daran ist, dass detaillierte Annahmen über den HTML-Code gemacht werden und dass für die Funktionsweise somit genau diese HTML-Struktur notwendig ist. Komponenten lassen sich dann nicht verschieben; will man eine Komponente an anderer Stelle platzieren, muss man die Regeln doppeln. Die Lösung besteht darin, die Tiefe der Verschachtelung zu reduzieren und von dem Wurzelelement der Komponente selbst auszugehen. SMACSS bietet eine klare Strukturierung von Stylesheets, erlaubt aber verschiedene Arten von Selektoren - ID-Selektoren oder Nachfahrenselektoren sind nicht verpönt. Das ist in den meisten neueren CSS-Herangehensweisen anders, bei denen durchweg auf Klassen gesetzt wird - wie etwa bei OOCSS.

OOCSS

Das Konzept OOCSS steht für Object Oriented CSS (Bild 2) und stammt von Nicole Sullivan. In Programmiersprachen wie Java und PHP arbeitet man mit Klassen und Objekten, und dieses Konzept wird nun auf HTML/CSS übertragen. Ein Objekt besteht aus vier Bestandteilen:

HTML, das heißt, ein oder mehrere DOM-Knoten,

CSS-Deklarationen für die Styles der Knoten, die mit dem Klassennamen des umfassenden Knotens beginnen,

Komponenten wie Hintergrundbilder,
Präsentation

von Nicole Sullivan zu OOCSS (Bild 2)
JavaScript-Verhalten, Listeners und Methoden, die mit dem Objekt in Zusammenhang stehen.

Ein Objekt kann zum Beispiel folgendermaßen aussehen:
<div class="mod">

<div class="inner">

<div class="hd">Block Head</div>

<div class="bd">Block Body</div>

<div class="ft">Block Foot</div>

</div>

</div>

Das Objekt ist in diesem Fall ein Modul, erkenntlich am Namen mod. Es beinhaltet vier Knoten, die nicht unabhängig vom Modul existieren können.

In OOCSS gelten zwei wichtige Grundprinzipien. Beide sprechen dafür, einfache Klassenselektoren einzusetzen:

Struktur von Aussehen trennen (Separate structure and skin): Damit Formatierungen wie background- und border-Anga-ben sich problemlos wiederverwenden lassen, sollten sie über CSS-Klassen hinzugefügt werden und nicht über HTML-Elemente/Typselektoren.

Container und Inhalte voneinander trennen (Separate container and content): Ein Objekt soll immer gleich aussehen, unabhängig davon, wo es platziert wird. Wenn es um die Formatierung einer h2-Überschrift geht, ist ein Nachfahrenkombinator wie .myObject h2 ungünstig. Besser ist es, der h2-Überschrift selbst eine Klasse zu geben, über die sie angesprochen werden kann, also beispielsweise <h2 class= “category“>.

Bei der objektorientierten Programmierung ist das Konzept der Vererbung wichtig. Eine Kindklasse erweitert die Elternklasse durch weitere Eigenschaften beziehungsweise Methoden. Auch in OOCSS können Komponenten erweitert werden, das heißt, dass sie zusätzliche oder andere Formatierungen erhalten. Das geschieht durch Hinzufügen einer zusätzlichen Klasse.

Von der Benutzung von IDs als CSS-Selektoren wird abgeraten, aber natürlich benötigt man ID-Attribute für Verlinkungen und eventuell auch zur Auswahl mit JavaScript. Falls man Nachfolgeselektoren einsetzt, sollten sie nicht zu weit oben im DOM-Baum auftauchen. Erlaubt sind sie, wenn ein Element Teil eines größeren Objekts ist und nicht unab- hängig existieren kann. Folgt man diesen Prinzipien, so arbeitet man in den meisten Fällen mit einfachen Klassenselektoren und in ausgewählten Fällen mit Kombinatoren.

BEM

Das Konzept BEM (Block Element Modifier) (Bild 3) wurde zuerst von Yandex eingeführt. Block, Element und Modifier stehen dabei für die drei Kategorien von Klassennamen, die unterschieden werden.

Auf einer Webseite ist beispielsweise das Logo ein Block (eigenständige Einheit), ebenso das Suchfeld oder auch das Menü. Die Menüpunkte sind hingegen nicht eigenständig, sondern Teil des Menüblocks. Menüpunkte sind damit Elemente (Bild 4).

Neben Blocks und Elementen gibt es die Modifizierer. Mo-difizierer können die Elemente in verschiedener Hinsicht verändern, etwa für Hervorhebungen oder beim Theming. Ein Beispiel sind Buttons, die je nach Funktion in unterschiedlichen Farben oder Größen dargestellt werden.

Sehen wir uns das Grundprinzip von BEM konkret an. Blocks erhalten einfache Klassennamen wie block.

<div class="block"></div>
ment deutlich, ohne dass diese - wie sonst oft - durch Nachfahrenselektoren abgebildet wird. An dieser Stelle Nachfahrenselektoren wie .block .block_element {} zu verwenden ist bei BEM nicht erwünscht. Andernfalls würde die Spezifität erhöht werden, was wiederum Änderungen bei der Formatierung solcher Elemente schwierig macht. Aus demselben Grund sollte der Name des HTML-Elements nicht vor den Klassennamen geschrieben werden:

/* empfohlene Auswahl von Elementen */

.block_element {}

/* nicht empfohlen hingegen */

p.block_element { }

.block .block__element {}
Beispiel für Blöcke und Elemente bei BEM (Bild 4)
Für die Auswahl per CSS schreiben Sie den Klassenselektor .block { }. Elemente sind, wie erwähnt, nicht eigenständig, sondern Teil eines Blocks. Das zeigt sich auch bei der Benennung; Elemente erhalten nämlich als Präfix den Namen des Blocks, gefolgt von zwei Unterstrichen__und dem Elementnamen. Ein Element können also zum Beispiel block__element heißen.

<div class="block">

<p class="block_element">...</p>

</div>

Angesprochen werden die Elemente in CSS nur über ihren

Klassennamen, das heißt, im Beispiel .block_element {}

Dadurch, dass der Name des Blocks bei einem Element am Anfang steht, wird die Beziehung zwischen Block und Ele-
Kommen wir nun zu den Modifizierern. Auch ihr Name besteht aus dem Elementpräfix, diesmal gefolgt von zwei Minuszeichen (-- ) und dem Modifizierernamen.

.block--mod {}

In diesem Beispiel haben wir einen Blockmodifizierer. Im HTML-Code - und das ist entscheidend - werden dann mindestens zwei Klassen vergeben; die eine für den Block und die andere für den Blockmodifizierer:

<div class="block block--mod">...</div>

Es sind natürlich auch mehrere Modifiziererklassen möglich.

Da immer nur Klassenselektoren eingesetzt werden, die dieselbe Spezifikation haben, müssen die Modifizierer nach den Element-/Blockklassen im Stylesheet definiert werden:
.block { }

.block--mod {}

Normalerweise wird von der Verwendung von Kombinatoren abgeraten. Aber hier gilt eine Ausnahme. Wenn sich ein Element aufgrund der Modifizierung des Blocks ändert, sollte man das auch mit dem Selektor abbilden.

BEM-Prinzipien

Ein wichtiges Prinzip bei BEM ist die Blockunabhängigkeit: Man kann einen Block irgendwo auf der Seite platzieren und
BEM: Block Element Modifier (Bild 3)


sicher sein, dass er nicht von der Umgebung beeinflusst wird. Dies bedeutet, dass die Verwendung eines Normalize-Style-sheets dem Grundprinzip von BEM widerspricht, weil damit das Aussehen eines Blocks von einem Drittanbieter-Code abhängt.

Bisher hatten wir nur einfache Strukturen, ein HTML-Element kann aber auch zu zwei Blöcken gehören:

<div class="block1 block2">

Und ein HTML-Element kann gleichzeitig Block und Element sein:

<div class="block1_element block2">

Außerdem können Blöcke verschachtelte Elemente beinhalten:

<div class="block">

<div class="block_elem1">

<div class="block_elem2">

<div class="block_elem3"></div>

</div>

</div>

</div>
Aber auch in diesem Fall bleiben die Selektoren einfach; dadurch kann die Struktur jederzeit geändert werden:

.block {}

.block_elem1 {}

.block_elem2 {}

.block_elem3 {}

Das strenge formale Benennungsschema bei BEM macht es einfach, einzelne Typen hervorzuheben und zu überprüfen, ob die Namen konform sind. Dafür eignen sich die folgenden Attributselektoren:

/* Klassen kennzeichnen */

[class] { outline: 5px solid red;

}

/* BEM-Elemente */

[class*="_"] {

outline: 5px solid yellow;

}

/* BEM-Modifizierer */

[class*="--"] { outline: 5px solid grey;

}

Das Prinzip von BEM lässt sich auch noch erweitern. So können Sie beispielsweise - wie Harry Roberts zeigt - Namensräume einsetzen. Harry Roberts schlägt c für Components, o
für Objects und u für Utilities (Helferklassen) vor, sowie is-und has- für Zustände.

<div class="o-media c-user c-user--premium">

<img src="" alt="" class="o-media_img c-user_photo

c-avatar" />

<p class="o-media_body c-user_bio">...</p>

</div>

BEM setzt auf Präfixe am Anfang der Klassennamen. Man kann jetzt zusätzlich mit Suffixen arbeiten und damit am Ende eines Klassennamens kennzeichnen, was eine Klasse unter bestimmten Umständen - zum Beispiel beim Ausdruck oder bei einer bestimmten Viewport-Breite - macht.

o-media@md bedeutet, dass das Element ein Media-Objekt bei der Viewport-Größe Medium (md) ist. Sinnvoll kann auch eine Klasse wie u-hidden@print sein, um zu kennzeichnen, dass etwas nicht ausgedruckt werden soll. Im Stylesheet muss das @-Zeichen allerdings mit einem \ maskiert werden:

@media print {

.u-hidden\@print { display: none;

}

}
Noch ein Tipp: Wer sich an die Verschachtelung von Selektoren in Sass gewohnt hat, muss bei BEM umlernen, da diese Verschachtelungen und die dadurch erzeugten Nachfahrenselektoren nicht erwünscht sind. Wer diese Verschachtelungen als Strukturierung beibehalten möchte, kann auf folgende Syntax zurückgreifen:

.block {

@at-root #{&}__element { background: black;

}

@at-root #{&}--modifier { color: red;

}

}

Eingesetzt wird dabei das in Sass 3.3 eingeführte @at-root, wodurch die Angabe auf der obersten Ebene angesiedelt und nicht verschachtelt wird. Durch #(&) wird der angegebene Blockname als Präfix genutzt. Das erzeugt dann den folgenden Code:

.block__element { background: black;

}

.block--modifier { color: red;

}

Es gibt eine Reihe von Tools für BEM, wie beispielsweise den BEM-Constructor von Daniel Guillan. Er hilft bei der Ver-  wendung von BEM mit Sass. Bei Bedarf kann man auch andere Trennzeichen definieren als die doppelten Unter- oder Bindestriche.

SUIT CSS

SUIT CSS (Bild 5) nennt sich »Style tools for UI components«, das heißt, die Ausrichtung ist die komponentenbasierte UI-Entwicklung. Es stammt von Nicolas Gallagher. Neben der Vorstellung einer Herangehensweise beinhaltet SUIT CSS auch Pakete und Build-Tools wie CSS-Präprozessoren.

Ein wichtiger Bestandteil von SUIT CSS ist die Namenskonvention, die an BEM erinnert, aber in Details davon abweicht. Das Grundprinzip von SUIT CSS ist, dass es keine unsemantischen Bindestriche gibt. Das heißt, wenn ein Klassennamen aus mehreren Wörtern besteht, werden diese in der CamelCase-Schreibweise geschrieben. Außerdem gibt es Präfixe wie u für Helferklassen (utility). Das folgende Beispiel

Webseite von

SUIT CSS (Bild 5)
SUIT CSS

Style tools for Ul components

css preprocessor • encapsulation testing style foundation • style utilities • style components
zeigt Helferklassen und die Verwendung der CamelCase-Schreibweise:

<div class="u-cf">

<div class="u-sizeFit">...</div>

<div class="u-sizeFill">...</div>

</div>

Außerdem gibt es responsive Helferklassen. Bei diesen wird nach dem u die Viewport-Größe geschrieben, für die sie gelten, wie beispielsweise u-sm-<name> oder u-md-<name>.

Komponenten können ein Namensraumpräfix haben oder auch nicht, sie werden in PascalCase-Schreibung angegeben. Im Gegensatz zur CamelCase-Schreibung beginnen die Bezeichner bei PascalCase mit einem Großbuchstaben. Ein Beispiel für einen PascalCase-Komponentennamen wäre My-Component.

Die Namen für Modifizierer beinhalten den Namen der Komponente, gefolgt von zwei Bindestrichen (wie bei BEM) und dem Modifizierernamen, beispielsweise also .Button--de-fault {}. Im HTML-Code werden beide Klasse verwendet.

<button class="Button Button--default" type="button">... </button>

Nichtselbstständige Bestandteile von Komponenten heißen bei SUIT CSS Descendents - sie entsprechen den Elementen
von BEM. Die Namen von Descendents werden in Camel-Case-Schreibweise angegeben, sie bestehen aus dem Namen der Komponente, gefolgt von einem Bindestrich und dem Namen des Descendents. Im folgenden Beispiel sind Tweet-hea-der und Tweet-bodyText Descendents.

<article class="Tweet">

<header class="Tweet-header">

</header>

<div class="Tweet-bodyText">

</div>

</article>

Außerdem gibt es Zustände, die mit is beginnen. Diese Klassen sollten im Stylesheet nicht alleine stehen, sondern immer nur mit dem zugehörigen Komponentennamen.

.Tweet { /* ... */ }

.Tweet.is-expanded { /* ... */ }

Im HTML-Code werden beide Klassen angegeben:

<article class="Tweet is-expanded">

</article>

Die Klasse is-expanded wäre ebenfalls nützlich bei einem Accordion, allerdings muss jede Komponente ihre eigenen Styles für den Zustand definieren.

.Accordion { /* ... */ }

.Accordion.is-expanded { /* ... */ }

Normalerweise werden bei SUIT CSS einfache Selektoren eingesetzt, bei den Zuständen sind es hingegen doppelte Klassenselektoren. Diese Abweichung vom allgemeinen Prinzip ist jedoch beabsichtigt. Der Vorteil besteht darin, dass diese doppelten Klassen sich gegenüber den einfachen durchsetzen. .Accordion.is-expanded hat eine höhere Spezifität als .Accordion und gilt damit immer - unabhängig von der Reihenfolge der Regeln im Stylesheet.

Neben den Benennungsregeln bietet SUIT CSS eine Reihe von Komponenten, die man bei Bedarf einsetzen kann. Das wären beispielsweise Überprüfungs-Tools, um die Konformität des Codes zu testen.

Eine weitere Komponente ist SUIT CSS Base, die ein paar Ergänzungen zu Normalize bietet.

SUIT CSS Utilities schließlich sind Helferklassen für die Ausrichtung, die Darstellungsart, fürs Layout, für Links, Texte, Größen und mehr. Zusätzlich erlaubt suitcss-preprocessor das Schreiben von Code, wie er teilweise in Spezifikationen vorgesehen, aber noch nicht in Browsern implementiert ist.

Atomic CSS

Eine Klarstellung vorweg: Die nun vorgestellte CSS-Strategie Atomic CSS hat nichts zu tun mit dem Atomic Design genannten Design-Ansatz von Brad Frost.

Zurück zur Methode Atomic CSS (Bild 6). Wie viele Klassen sind sinnvoll und notwendig? Zu den Anfangszeiten von CSS war man da eher sparsam, inzwischen dürfen es gerne auch mal mehr sein. Ein Extremfall ist in dieser Hinsicht Atomic CSS, wo es CSS-Klassen für alle denkbaren CSS-Formatie-rungen gibt:

<div class="Bgc(#0280ae) C(#fff) P(20px)">

Lorem ipsum </div>

Dieser Code ist direkt verständlich. Es werden eine Hintergrundfarbe, eine Schriftfarbe sowie ein padding definiert. Das heißt, bei Atomic CSS schreibt man keinen CSS-Code, sondern arbeitet mit Klassen im HTML-Code zur Formatierung. Da manche Zeichen in Klassennamen nicht erlaubt sind, muss man sie in CSS escapen; dies wirkt auf den ersten Blick recht kryptisch:

.P\(20px\) { padding: 20px;

}

.C\(\#fff\) { color: #fff;

}

.Bgc\(\#0280ae\) { background-color: #0280ae;

}

Der Ansatz von Atomic CSS ist auf jeden Fall gewöhnungsbedürftig und es erscheint nicht so weit von Inline-Styles entfernt. Warum dann nicht gleich mit dem Folgenden arbeiten:

Links zum Thema

SMACSS https://smacss.com https://www.smashingmagazine.com/2012/04/ decoupling-html-from-css

OOCSS

https://github.com/stubbornella/oocss/wiki/faq www.slideshare.net/stubbornella/object-oriented-css

BEM

http://getbem.com/introduction https://en.bem.info https://github.com/danielguillan/bem-constructor http://csswizardry.com/2015/08/bemit-taking-the-bem-naming-convention-a-step-further • SUIT CSS https://suitcss.github.io https://philipwalton.com/articles/introducing-html-inspector

Atomic CSS https://acss.io
Atomic CSS
Get Started
CSS is painful Style with class

CSS is a critical piece of the frontend toolkit, but it's hard to Most approaches to styling inside components rely on manage, especially in large projects. Styles are written in a inline styles, which are limiting. Atomic CSS, like inline global scope, which is narrowed through complex styles, offers single-purpose units of style, but applied via

Atomic CSS: Kleinste CSS-Komponenten (Bild 6)

<div style="background-color: #0280ae; color: #fff; padding: 20px">

Lorem ipsum </div>

Gegenüber Inline-Styles hat Atomic CSS zwei eindeutige Vorteile: Es ist sowohl die Verwendung von Pseudoklassen wie hover als auch von Media Queries möglich, was bei Inline-Styles nicht geht.

Atomic CSS verfolgt einen grundlegend anderen Ansatz als die bisher vorgestellten Herangehensweisen und wird durchaus nicht von allen Entwicklern positiv aufgenommen. Ob Atomic CSS tatsächlich für alle Sorten von Projekten geeignet ist, sei dahingestellt. Aber es gibt zweifellos Fälle, in denen diese Herangehensweise praktisch sein kann; beispielsweise für WYSIWYG-Editoren, welche die Formatierungen der Benutzer umsetzen müssen, oder wenn man einen Prototyp erstellt. In solchen Fällen kann es durchaus hilfreich sein, wenn HTML-Code und Formatierung an derselben Stelle stehen.

Fazit

Bei größeren Projekten oder bei der Arbeit im Team kommt man heute nicht mehr darum herum, sich Gedanken über die Strategie für das CSS zu machen. An sich gibt es zwei Möglichkeiten: Entweder verwendet man eine bereits existierende, bekannte Strategie, was den Vorteil hat, dass diese bereits dokumentiert ist. Oder man modifiziert eine Strategie und entwickelt daraus einen eigenen Ansatz. So oder so: Die Zeiten, in denen man ohne Methodik einfach so lange am Stylesheet gebastelt hat, bis die Website das gewünschte Aussehen zeigte, sind vorbei.
Dr. Florence Maurice

ist Autorin, Trainerin und Programmiererin in München. Sie schreibt Bücher zu PHP und CSS3 und gibt Trainings per Video. Außerdem bloggt sie zu Webthemen unter: http://maurice-web.de/blog
Atomic CSS Docs Reference Support Q
I— assert@1.3.0 tamhan@TAMHAN14:~/mercuryworkspace$ npm install

Das eigentliche Verpacken des Quellcodes lässt sich in Brow-serify folgendermaßen durchführen: Mit -o übergeben Sie den Namen der anzulegenden Konvolutdatei, die das Resultat der Auflösung aller require^-Direktiven aufnimmt:

tamhan@TAMHAN14:~/mercuryworkspace$ browserify index.js -o out.js

Nach Abarbeitung dieses Kommandos sehen Sie eine neue Datei namens out.js, die ein von Abhängigkeiten befreites Kompilat der Applikation darstellt. Für die Ausführung des Programms ist sodann ein Testharnisch erforderlich, der sich so aufbauen lässt:
wollen wir uns das betreffende Modul von Mercury näher ansehen. Öffnen Sie dazu die Datei readme.md, die im Projektverzeichnis /node_modules/virtual-dom bereitsteht.

Die hier verwendete Markdown-Syntax mag nicht jedermanns Sache sein; mit etwas gutem Willen erschließt sich der Inhalt des Dokuments allerdings ohne Probleme. Im in Bild 2 gezeigten Abschnitt der Dokumentation findet sich eine Passage, die auf eine eigene Readme-Datei hinweist: Sie ist ausschließlich für die h-Funktion und ihre Helfer vorgesehen.

Das in MD-Dateien enthaltene Markdown ist ein im Jahr 2004 entwickeltes Beschreibungsformat, das eine Art »HTML light« darstellt. Seine Autoren haben es unter anderem für Dokumentationen vorgesehen, die sich bei Bedarf einfach in HTML umwandeln lassen. Im Fall unserer Readme-Dateien würde sich dazu beispielsweise das als Web-App verfügbare Werkzeug Dillinger (http://dillinger.io) anbieten.
<html>

<body>

<script src="out.js"></script>

</body>

</html>

Der nicht sonderlich komplizierte Code der hier als worker. htm bezeichneten Webseite beschränkt sich darauf, das Ergebnis der Kompilation über ein script-Tag einzubinden und ein <body>-Tag bereitzustellen, in dem unser Beispiel nach Belieben herumfuhrwerken kann.

Webserver starten

Zu guter Letzt müssen wir noch einen Webserver starten, über den wir die Inhalte des Verzeichnisses für Browser zugänglich machen. Unter Ubuntu bietet sich hierbei der in Python enthaltene SimpleHTTP-Server an, der sich in jedem Konsolenfenster aufrufen lässt und den Inhalt des gerade aktiven Arbeitsverzeichnisses über Port 8000 bereitstellt:

tamhan@TAMHAN14:~/mercuryworkspace$ python -m SimpleHTTPServer

Serving HTTP on 0.0.0.0 port 8000 ...

127.0.0.1 - - [30/Dec/2016 19:06:41] "GET /worker.htm HTTP/1.1" 200 -

Laden Sie die Webseite über den URL http://localhost:8000/ worker.htm und klicken Sie den Button einige Male an, um die Inkrementierung des angezeigten Wertes zu erhalten.

Wer den Quellcode der geladenen Webseite in Firefox öffnet, sieht nur den Testharnisch. Der von Mercury generierte Code kommt nur dann auf den Bildschirm, wenn Sie den Debugger öffnen und in den Ins-pector-Tab wechseln (Bild 1).
Markdown-Syntax

Damit ist erwiesen, dass unser Programm problemlos funktioniert. Zum Verständnis dessen, was hinter den Kulissen passiert,
HyperScript-Programmierframework

Jake Verbaten orientierte sich bei der Entwicklung am von Dominic Tarr erdachten HyperScript-Programmierframe-work. Es handelt sich dabei um eine Bibliothek, die Entwicklern das Erzeugen von HTML-Markup aus einer Gruppe von Funktionen und Parameterobjekten ermöglicht. Der Name der Funktion ist damit übrigens auch erklärbar - er steht für Hypertext beziehungsweise HyperScript.

Um HyperScript hat sich eine Gruppe von Werkzeugen entwickelt, die beispielsweise HTML-Inhalte in eine Abfolge von Skriptfunktionen konvertieren können. Zudem findet sich auf der Webseite des Produkts eine umfangreiche Dokumentation. Wer mit der h()-Funktion von Mercury Probleme hat, sollte unter 0 Console D Debugger O Style Ed... Perfor.
<div class— counter >
The state

<code>clickCaunt</code> has value: 12.

<input class="button" value="Click me!" type="button"> </div>

</body>

</html>
Die Kombination aus Browserify und Mercury generiert zur Laufzeit vergleichsweise komplexes Markup (Bild 1)
Gl +

<htnl>

<headx/head>

▼ <body>

<script src-"out.js"></script>
O Inspector
## Documentation

You can find the documentation for the seperate components in their READMEs
For 'create-element.js‘ see the [vdom README!(vdon/README,nd)

For 'diff.js' see the [vtree README1(vtree/README.nd)

For 'h.js' see the [virtual-hyperscript README!(virtual-hyper script/README, pid) For 'patch.js‘ see the [vdom README1(vdon/README.nd)
Die etwas seltsam aussehende abfolge von Klammern beschreibt in Markdown einen Hyperlink (Bild 2)
html/css/javascript
Framework
O Inspector
□ Console > Debugger
<headx/head>

▼ <body>

ocript src="out. 1s"x/5criDt>

,*’<div id="page">

<div class=l'coijnterl,>Erstes Div!</div> <nnng class='’ningzwei">Zweites Div!</nmg> </div>

</body>
Die Erkennung von Nicht-Tags funktioniert mit Mercury nicht sonderlich zuverlässig (Bild 3)
<div id="page">

<nmg class="nmgzwei">Zweites Div!</nmg>

</div>

Mit diesem Wissen können wir unseren Harnisch wieder in den Ursprungszustand zurückversetzen. Diesmal wollen wir statt eines Knopfes gleich mehrere Buttons anlegen:

App.render = function render(state) { return h('div#page',[ h('input.button#btn1', {

type: 'button', value: 'Knopf 1!',

'ev-click': hg.send (state.channels.clicks) }), h('br'),

h('input.button#btn2', {

type: 'button', value: 'Knopf 2!',

'ev-click': hg.send (state.channels.clicks) })
]);
};
Interessant ist, dass wir die Aufrufe von h() nun mit einem vollwertigen Properties-Array ausstatten. Neben der Be- ►
Das br-Tag kommt ohne Parameter und Kinder aus, weshalb seine Erzeugung primitiv ist (Bild 4)
Der Selektor ist dabei ein nach dem Schema name.class1.class2#id aufgebauter String. Der erste Teil, also der Wert bis zum ersten Punkt, beschreibt dabei den Namen des anzulegenden Tags. Wird hier beispielsweise a übergeben, so entsteht ein Hyperlink.

Eventuell folgende Strings beschreiben sodann eine oder mehrere Klassen, die dem neu anzulegenden Objekt zugewiesen werden. Hinter dem Rautezeichen folgt sodann eine ID, die das individuelle Objekt beschreibt.

Berücksichtigen Sie, dass das Übergeben von ID und Klasse nicht unbedingt erforderlich ist. Es wäre beispielsweise auch in Ordnung, einfach a#nmg zu übergeben, um ein <a>-Tag mit der ID NMG zu generieren.

In der Dokumentation findet sich zudem ein Verweis auf eine kleine Sonderintelligenz: Wird der name-Tag nicht in Form eines gültigen HTML-Objekts übergeben, erzeugt HyperScript - so zumindest die Spezifikation - ein <div>-Tag und betrachtet auch den ersten Teil des Strings als Klassendeklaration. Zum Testen dieses Verhaltens und zur Vorführung von HyperScript passen wir unser Programm ein wenig an:

App.render = function render(state) { return h('div#page',

[h('div.counter',"Erstes Div!"), h('nmg.nmgzwei',{}, "Zweites Div!")]);

};

Hinter der - absichtlich etwas eigenwilligen - Formatierung verbirgt sich ein nicht sonderlich komplexes DOM-Element: Wir liefern das Resultat einer h()-Funktion, die ein <div> erstellt, das die ID page aufweist.

Der Aufruf von h() bekommt neben dem Namen div und der anzulegenden ID auch ein Array übergeben, das die im Tag anzulegenden Kinder beschreibt. Wir legen hierbei zwei Kinder an: einerseits ein <div> mit der Klasse counter und zweitens ein NMG-Element mit der Klasse nmgzwei. Beide Aufrufe von h() bekommen zudem einen String übergeben, der den zur Laufzeit darzustellenden Inhalt anliefert. Im Fall von nmgzwei übergeben wir aus didaktischen Gründen noch ein leeres JSON-Objekt, das anzeigt, dass keine Properties vorhanden sind.

Reihenfolge beachten

Bei Betrachtung dieser Funktionen fällt auf, dass die h-Funk-tion sehr flexibel ist: Sie erkennt automatisch, ob ein Parameter Kinder (Typ: Array) oder Properties (Typ: Objekt) anliefert. Wichtig ist hierbei nur, dass die in der Syntax angegebene Reihenfolge nicht vertauscht wird.
Mit diesem Wissen können wir uns an die Syntax der h-Funktion heranwagen, die im Normalaufbau zwei oder drei Parameter entgegennimmt:

h(selector, properties, children)
Nach einem abermaligen Aufruf von Browserify ist das Programm ausführungsbereit. Bild 3 zeigt, was auf der Workstation des Autors im Firefox-Brow-ser auftauchte.

Interessant ist hier vor allem, dass Mercury es mit der in HyperScript spezifizierten Erkennung von Nicht-HTML-Tags nicht sonderlich genau nimmt. Unser Programm erzeugt ein <nmg>-Tag, das im HTML5-Standatd nicht vorkommt, aber immerhin die Klasse nmg-zwei aufweist:
stimmung des Typs des anzulegenden Knopfes legen wir einen Event-Handler fest. Die verwendete Syntax ist etwas eigenwillig, wird sich aber im nächsten Abschnitt erschließen. Sie können den Code zu diesem Zeitpunkt gern in Firefox und Co. ausführen, um sich davon zu überzeugen, dass die beiden Buttons komplett übereinander erscheinen. Wer h() mit nur einem Parameter aufruft, bekommt ein normales Element ohne Eigenschaften und Kinder (Bild 4).

Eventorientierte Architekturen

Die Eventverarbeitung ist traditionell eine der wichtigsten Aufgaben eines JavaScript-Frameworks. Das in Mercury implementierte Ereignisverwaltungssystem kommt von Haus aus mit rund einem Dutzend Ereignisse, die in der Datei dom-delegator/index.js deklariert sind. Insbesondere befindet sich darin folgendes Array:

var commonEvents = [

"blur", "change", "click", "contextmenu", "dblclick", "error","focus", "focusin", "focusout", "input", "keydown", "keypress", "keyup", "load", "mousedown", "mouseup", "resize", "select", "submit",

"touchcancel", "touchend", "touchstart", "unload"

]

Ein kurzer Blick auf die im Feld commonEvents enthaltenen Strings zeigt, dass unser Klick-Event von Mercury unterstützt und somit automatisch geroutet wird. Diese auf den ersten Blick unbedeutend erscheinende Unterteilung ist insofern von Relevanz, als alle Ereignisse in einem als DOM-Delega-tor bezeichneten Modul zusammenlaufen. Es handelt sich dabei um ein unter node_modules/dom-delegator/dom-delega-tor.js deklariertes eigenständiges Modul, das bei Bedarf durch Inklusion unabhängig vom Rest von Mercury in eigene Projekte eingebunden werden kann.

Jedes der in commonEvents genannten Ereignisse wird zur Laufzeit über einen Kanal präsentiert. Im Fall des clicked-Er-eignisses heißt dieser auf Seiten des Benutzerinterfaces ev-click. Das gleichnamige Attribut erlaubt uns nun das Einschreiben von mehr oder weniger beliebigen Methoden, die im Ereignisfall aktiviert werden. Im Moment sieht der dazugehörende Code so aus:

h('input.button#btn2', { type: 'button', value: 'Knopf 2!',

'ev-click': hg.send(state.channels.clicks)

})

ev-click wird hierbei mit der Methode hg.send verbunden, die für das Abfeuern von Ereignissen im Framework verantwortlich ist. Der übergebene Parameter beschreibt dabei ein Element des Channels-Felds, das im globalen state-Objekt untergebracht ist.

Das Channels-Feld ist eine Besonderheit von Mercury. Das Framework arbeitet intern mit einer weitere Gruppe von Kanälen, die im Rahmen der App-Methode angelegt werden
und systemweit zur Verfügung stehen. Wir wollen den im Beispiel schon vorhandenen clicks-Kanal nun weiterverwenden. Die von ihm aufgerufene Methode muss um einen Aufruf von console.log erweitert werden, um bei Aktivierung eine Nachricht in die Debugger-Konsole zu schreiben:

function incrementCounter(state) { console.log("Hallo!");

}

Wer das vorliegende Programm in einem Browser ausführt, stellt fest, dass das Anklicken der Knöpfe zu einer Anpassung der Ausgabe in der Debugger-Konsole führt. Das Ereignis ev clicks ist übrigens nicht auf das Absetzen von Nachrichten an Channels beschränkt; Sie können beliebige andere Funktionen aufrufen.

Direktes Einschreiben von Ereignissen ist hilfreich, wenn es um die Implementierung bestimmter Komponentenverhalten geht. Falls Sie Ereignisse auf systemweite Art und Weise abpassen wollen, können Sie direkt mit dem Delegator zusammenarbeiten. Als Beispiel dafür wollen wir eine Methode anlegen, die bei Klicks auf beliebige Steuerelemente aktiviert wird. Dazu müssen Sie im ersten Schritt die Deklaration der Buttons anpassen, um keine direkten Event-Handler mehr anzulegen:

App.render = function render(state) { return h('div#page',[

h('input.button#btn1', { type: 'button', value: 'Knopf 1!'

}),
Als Erstes müssen wir das delegator-Objekt unter Nutzung von require laden. Die hier verwendete Syntax entspricht im Großen und Ganzen dem, was man von Node.js und Co. kennt:

var delegator = require("dom-delegator")

Der eigentliche Aufruf von Elementen des Delegators ist in zweierlei Hinsicht seltsam: Erstens liefert require eine Art Blaupause zurück, die wie eine Methode aufgerufen werden muss, um eine verwendbare Instanz zu bekommen.

Eventnamen ohne Präfix

Zweitens sind die zu übergeben Eventnamen ohne das Präfix ev- anzuschreiben - wer addGlobalEventListener einen String der Bauart ev-* übergeben würde, bekommt zur Laufzeit keine Ereignisinformationen angeliefert.

Als zweiten Parameter bekommt die Funktionen einen Verweis auf den Event-Handler, der beim Auftreten des Events zu aktivieren ist:

function App() { delegator().addGlobalEventListener('click', runMe);
return hg.state({

Im nächsten Schritt legen wir eine Handler-Funktion an. Das an die Methode übergebene Objekt - der Autor nennt es hier aus Gründen der Bequemlichkeit a - ist ein klassisches Java Script-Informationsträ-gerelement, das unter anderem Daten über das Steuerelement anliefert, das für die Auslösung des Ereignisses zuständig war:

function runMe(a){ console.log(a);

}

Bild 5 zeigt, wie sich das Objekt in der Debugger-Konsole von Firefox präsentiert.

JavaScript-Entwickler aller Couleur leiden seit Jahr und Tag unter einer Design-Entscheidung des Spracherfinders: Er vergaß, die Sprache um eine Möglichkeit zum Abfeuern von Notifications zu ergänzen, die von der Runtime beim Zugriff auf bestimmte Variablen ausgelöst werden. Es gibt kaum ein Framework, das hier keine eigene Implementierung mitbringt. Im Fall von Mercury ist dies insofern besonders interessant, als das betreffende Modul klein und leicht weiterverwendbar ist.

Gruppe von Modulen

Zur Begründung der folgenden Schritte wollen wir einen Blick hinter die Kulissen werfen. In der Datei index.js findet sich unter anderem eine Struktur, die zeigt, dass das Mercu-ry-Hauptframework aus einer Gruppe von Modulen zusammengestellt ist. Auffällig ist auch, dass die Observable-Logik selbst modular ist. Es ist erlaubt und legitim, nur bestimmte Elemente zu laden:

var mercury = module.exports = {

array: require('observ-array'), struct: require('observ-struct'), varhash: require('observ-varhash'), value: require('observ'), state: state,

Wir wollten in den folgenden Schritten einige der Observables verwenden, um unseren Testharnisch um einige Aspekte einer Data-Binding-Applikation zu bereichern. Als ersten Akt müssen wir hierbei das Modul ObservableArray laden und die Zustandsvariable der gesamten Mercury-Applika-
tion um ein diesbezügliches Feld erweitern, dem sogleich einige Werte eingeschrieben werden:

var ObservArray = require("observ-array") function App()

{

delegator().addGlobalEventListener ('click', runMe); return hg.state({ myField:ObservArray(["A", "B", "C"]), channels: { ...

Als Nächstes ist eine Anpassung in der Methode render erforderlich. Sie muss für jedes Element im Observable-Array ein neues Element in den DOM-Baum einfügen, um am Ende eine Liste aller Elemente zu erzeugen.

Dieses Problem lässt sich am Einfachsten über ein leeres Array lösen, das nach vollständiger Bevölkerung in das finale DOM-Element eingebaut wird. Ein Weg zur Implementierung sieht so aus:

App.render = function render(state)

{

var objCache=[];

state.myField.forEach(function (what, index) { objCache.push(h('br')); objCache.push(h('div',{},what));

});

Interessant ist in diesem Zusammenhang, dass das in Mercury mitgelieferte Observable-Array dual ist. Das bedeutet, dass eine Instanz zur Laufzeit sowohl durch ein Observable als auch durch ein normales JavaScript-Array repräsentiert wird. Diese Vorgehensweise erleichtert den Zugriff auf die im Container befindlichen Informationen immens.

Wir nutzen diese Dualität zur Realisierung einer for-Schlei-fe, die die weiter oben erwähnte /or-Schleife mit diversen Elementen bevölkert. Für jedes Feld entsteht ein neues div-Tag und ein neues <br>.

Das so entstehende Feld wird im nächsten Schritt in das finale DOM-Element eingebunden, das von return als Rückgabewert in Richtung von Mercury gesendet wird. Hier fällt die enorme Flexibilität von HyperScript auf. Das Framework erkennt automatisch, dass eines der Elemente des Kinder-Ar-rays ein weiteres Array ist, das durch Rekursion abgearbeitet wird:

return h('div#page',[ h('input.button#btn1', { type: 'button', value: 'Knopf 1!'

}),

objCache,

h('input.button#btn2', { type: 'button', value: 'Knopf 2!'

}) ►
T Object

rawEvent: MouseEvent dick altKey: False

bubbles: true button: 0 buttons: 0 cancelable: true clientX: 55 clientY: 45 ctrlKey: false

currentTarget: <html> eventPhase: 1 layerX: 55

layerY: 45 metaKey: false offsetX: 41 offsetY: 3 pageX: 55 pageY:45

relatedTarget: null N

screenX: 2040 screenY: 142 shiftKey: false

target: <input#btn2.button>

timestamp: 76226672

to Element: undefined N

type: "click"

1 view: Window^ worker.htm which: 1 1 _proto_: Object
Der Delegator liefert Informationen, die die Korrelation von Ereignissen erleichtern (Bild 5)


]);

};
Wer das Programm in der vorliegenden Form ausführt, wird mit dem in Bild 6 gezeigten Verhalten belohnt.

Unsere nächste Aufgabe besteht in der Realisierung eines Event-Handlers, der das Feld bei jedem Anklicken um ein weiteres Element ergänzt. Im Idealfall - also bei korrektem Funktionieren von Mercury -würde dies eine Aktualisierung des DOM-Baums hervorrufen. Der am Bildschirm angezeigte Inhalt des Arrays müsste also immer mit dem Zustand des Feldes d'accord gehen.

Beginnen wir unsere Arbeiten mit dem Einschreiben eines Event-Handlers in btn2, der über einen neuen Mercury-Ka-nal mit dem Rest der Applikationslogik kommuniziert:

h('input.button#btn2', { type: 'button', value: 'Knopf 2!',

'ev-click': hg.send (state.channels.updateField)

})

Diese auf den ersten Blick umständlich wirkende Vorgehensweise ist erforderlich, weil wir so am bequemsten auf eine Instanz des globalen stage-Objekts zugreifen können. Zur Deklaration des neuen Kanals müssen wir das an App übergebene Feld ein wenig anpassen:

function App() {

channels: {

clicks: incrementCounter, updateField: fieldWorker

}

});

Die eigentliche Intelligenz zur Anpassung des Arrays findet sich nur in der Hilfsmethode fieldWorker. Sie ruft die PushMethode von myField auf, um das neue Element ins Array zu schieben und ein Aktualisierungs-Event abzufeuern:

function fieldWorker(state){

state.myField.push("Neuer Wert");

}

Wer das vorliegende Programm ausführt und den Button einige Male anklickt, stellt fest, dass sich die Struktur des Formulars permanent verändert. Damit ist bewiesen, dass unser Observable auf Basis von Mercury problemlos funktioniert.

Auch wenn HyperScript das eine oder andere Komfortfeature anbietet: Lange Verkettung von h-Aufrufen werden bald unübersichtlich. Wenn man davon ausgeht, dass das
durchschnittliche DOM-Objekt aus einigen Hundert Elementen besteht, bekommt man Kopfschmerzen.

Erfreulicherweise bietet Mercury mit dem KomponentenSubsystem eine komfortable Möglichkeit zur Generierung von Widgets an, die als Ganzes in den DOM-Tree eingefügt werden können und das Hantieren mit den einzelnen h()-Aufrufen abstrahieren.

Wir wollen uns als erstes Beispiel einem vom Entwicklerteam als Einstiegsaufgabe empfohlenen Widget zuwenden, das ein mehr oder weniger beliebiges DOM-Objekt einpackt. Es enthält in der vorgegebenen Variante zudem einen kleinen Fehler, der uns das Hantieren mit den Debugging-Möglichkeiten von Browserify und Co. ermöglicht.

Dazu müssen Sie eine neue Datei namens WrapperWidget. js erzeugen, die mit folgendem Code ausgestattet wird:

var createElement = require ('virtual-dom/create-element.js'); var diff = require('virtual-dom/diff'); var patch = require('virtual-dom/patch'); var document = require('global/document');

module.exports = PureWrapperWidget;

An erster Stelle finden sich in der Datei die üblichen Inklusionen. Beachten Sie, dass das Erzeugen eines Widgets keine Einbindung von Mercury als Ganzes voraussetzt. Stattdes-sen reicht es, einige DOM-Hilfsobjekte zu laden und das Wid-get-Objekt über die Export-Funktionalität zu exponieren.

Widgets entstehen nach den von Osmani und Co. eingeführten Regeln der objektorientierten Programmierung in JavaScript. Als Erstes beschaffen wir deshalb einen Verweis auf den Prototyp der Objektfamilie, der im nächsten Schritt ein Type-Wert eingeschrieben wird. Es ist von eminenter Bedeutung, dass das Feld den Wert Widget annimmt. Mercury erkennt die Komponenten zur Laufzeit anhand eines StringVergleichs gegen ebendieses Memberfeld:

var proto = PureWrapperWidget.prototype; proto.type = 'Widget';

Die Spezifikation von Mercury-Widgets schreibt zudem vor; dass zwei Funktionen vorhanden sein müssen. Als Erstes findet sich die Funktion Init, die für die Erzeugung einer neuen Instanz des jeweiligen Steuerelements zuständig ist und folgendermaßen aussieht:

proto.init = function init() {

var elem = createElement(this.currVnode); var container = document.createElement('div'); container.appendChild(elem); return container;

};

Aktualisierungen der Struktur haben in der Methode update zu erfolgen. Der Code beschränkt sich darauf, eingehende DOM-Elemente in den Container weiterzuschreiben.
CD I localhost:8D00/worker.htm Knopf 1!

A

B

C_

KnopFZ!

Der Inhalt des Arrays wird zwischen den beiden Knöpfen ausgegeben (Bild 6)


Im Interesse der Geschwindigkeit der gesamten Applikation prüfen wir hier noch, ob eine Aktualisierung wirklich notwendig ist. Die Methode replaceChild wird nur dann aufgerufen, wenn Änderungen anstehen:
require in das Hauptprogramm einbinden. Im lokalen Verzeichnis liegende Module werden in Browserify durch das Voranstellen des Strings ./ angesprochen - der Punkt steht in Unix für das lokale Verzeichnis:
_|_ü Style Ed. * Security
O Inspec...

* Net • CSS

Initing widget!

Running init!

► TypeError: Argument 1 of Node.appendChild is not an object. [Learn Morel

The character encoding of the HTML document was not declared. The document will render with garbled text inworker.htm some browser configurations if the document contains characters from outside the US-ASCII range. The character encoding of the page must be declared in the document or in the transfer protocol.

Use of getPreventDefault{) is deprecated. Use defaultPrevented instead.

Vom Entwickler vorgegebener Beispielcode muss nicht unbedingt perfekt sein (Bild 7)
I D Debug.

s JS
. © Perfor. Logging
. O Mem., ► Server
Netw... Eb 0 I v Filter output
out.js:4621;5 out.js:4628:5 out.j5:4631:5
worker.htm
proto.update = function update(prev, elem) { var prevVnode = prev.currVnode; var currVnode = this.currVnode; var patches = diff(prevVnode, currVnode); var rootNode = elem.childNodes[0]; var newNode = patch(rootNode, patches); if (newNode !== elem.childNodes[0]) {

elem.replaceChild(newNode, elem.childNodes[0]);

}

};

Damit fehlt uns der Konstruktor des Widgets, der in der offiziellen Dokumentation nicht korrekt beschrieben wird. Eine funktionierende Version des Codes sieht so aus:

function PureWrapperWidget(vnode) {

if (!(this instanceof PureWrapperWidget)) { return new PureWrapperWidget(vnode);

}

this.currVnode = vnode;

}

Der this-Parameter muss im Rahmen jedes Durchlaufs des Konstruktors gegen den Typ der Klasse verglichen werden. Schlägt dieser Vergleich fehl, so müssen Sie von Hand eine neue Instanz des Widgets anlegen und diese retournieren. Es handelt sich dabei um eine kleine Besonderheit des Mercu-ry-Frameworks, auf die man nicht auf den ersten Blick kommt. Unterbleiben Prüfung und Return, so wird init() zur Laufzeit nicht aufgerufen und das Widget erscheint nicht am Bildschirm.

Falls Sie dieses seltsame Verhalten von Hand nachprüfen möchten, ergänzen Sie init einfach um einen Aufruf von con-sole.log:

proto.init = function init() { console.log("Running init!");

};

Damit ist die Arbeit am Korpus des Widgets abgeschlossen. Sein Code lässt sich durch einen spezialisierten Aufruf von
var OurWrapper = require("./wrapper")

Damit ist unser Widget einsatzbereit. Unsere nächste Handlung besteht darin, die render-Funktion anzupassen, um eine Instanz des Steuerelements auf den Bildschirm zu bekommen:

App.render = function render(state) {

var btn = document.createElement("BUTTON"); var t = document.createTextNode("Inhalt"); btn.appendChild(t);

return h('div#page',[

OurWrapper(btn),

h('b',"Test!")

]);

};

Als primäres Hindernis zeigt sich hier die Erstellung eines passenden Inhalts für das Widget. Wir wollen bei diesem kleinen Beispiel ohne jQuery und Co. auskommen, weshalb die DOM-APIs des Browsers zum Einsatz kommen.

In der Theorie ist unser Programm an dieser Stelle einsatzbereit. Wer den Code im Browser lädt, findet sich jedoch statt-dessen mit der in Bild 7 gezeigten Fehlermeldung konfrontiert. Die Frage lautet nun, wieso dies passiert.

Fehlersuche mit Browserify

Unser Programmbeispiel besteht aus einigen Dutzend Zeilen. Nach der Bearbeitung mit Browserify wächst es durch die Inklusion diverser Module und Dateien auf mehrere Tausend Zeilen Code an. Zur Lösung dieses Problems bietet sich die Nutzung von Source Maps an: Es handelt sich dabei um Hilfsdateien, die Debuggern eine Korrelation zwischen dem generierten Code und den Quelldateien ermöglichen.

Die Generierung ebendieser Files erfolgt durch Übergeben des Parameters --debug an Browserify. Berücksichtigen Sie, dass die Präambel aus zwei Minuszeichen besteht:

tamhan@TAMHAN14:~/mercuryworkspace$ browserify index.js -o out.js -debug

Browserify unterscheidet sich insofern von anderen Systemen, als es die Mappinginformatio-nen direkt in die Ausgabedatei schreibt. Dies lässt sich durch einen Vergleich der Dateigrößen bestätigen. Bei aktivierter Debuggerunterstützung verdreifacht sich die Länge der relevanten Datei.
Dank der in out.js eingebetteten Sourcemap zeigt Chrome die Stelle des Fehlers direkt an (Bild 8)
Weil eine seperate .map-Datei fehlt, stößt Firefox hier an seine Grenzen. Als Workaround bietet sich die Nutzung von Google Chrome an, wo sich der Fehler wie in Bild 8 gezeigt präsentiert. Besonders nett ist die Möglichkeit der Platzierung von Breakpoints auf Ebene der Originaldateien. Der Debugger zeigt sogar die Variablenwerte korrekt an.

Wer unbedingt mit Firefox debuggen möchte, kann das unter https://www.npmjs.com/package/exorcist bereitstehende Werkzeug exorcist zur Generierung der benötigten .map-Dateien einsetzen. Mit diesem Wissen lässt sich die Fehlerquelle auf die Zeile

var elem = createElement (this.currVnode);

zurückführen: elem ist nach Abarbeitung der Zuweisung null, was auf fehlerhafte Parameter in createElement zurückzuführen ist. Wer den Code der Methode createElement weiterverfolgt, endet bei der Methode isVirtualNode. Ihr Aufbau präsentiert sich folgendermaßen:

var version = require("./version") module.exports = isVirtualNode function isVirtualNode(x) {

return x && x.type === "VirtualNode" && x.version === version

}

Das gesamte vdom-Modul von Mercury kann nur mit jenen Nodes zusammenarbeiten, die Teil des virtuellen DOM sind. Aus diesem Grund müssen wir index.js um die Inklusion der notwendigen Generatorfunktion erweitern.
Im nächsten Schritt wird der Code von render() angepasst,

um statt einem DOM-Element ein vdom-Element anzuliefern:

App.render = function render(state) { var btn = new vnode('input', { type: 'button', value: 'Knopf!'

});

return h('div#page',[

OurWrapper(btn),

h('b',"Test!")

]);

};

Damit ist unser kleines hauseigenes Widget einsatzbereit.
geval
build error I npm package 2.2.0 I coverage 100% 1 dependencies none
Jedes Modul von Mercury bringt seine eigenen Kompatibilitätsanforderungen mit - im Fall von geval ist IE8 ausgenommen (Bild 9)
In Webapplikationen findet man immer wieder archaische Event-Frameworks wie Radio.js. Das zugegebenermaßen nicht besonders große Modul mag ein grundlegendes Handling von Ereignissen ermöglichen, bietet aber kaum Komfort. Dies ist insofern schade, weil Mercury ein wesentlich umfangreicheres Event-Subsystem mitbringt.

Als nächstes Beispiel wollen wir uns diesem System zuwenden. Das Modul hört auf den Namen geval. Als Erstes sei an dieser Stelle angemerkt, dass geval mit dem Internet Explorer 8 nicht funktioniert (Bild 9). In diesem Artikel wollen wir ein kleines Eventsystem auf Basis von geval realisieren. Entwickler haben dabei die Wahl zwischen dem einfacheren Event und dem komplexeren Event-Emitter, der das eine oder andere Komfortfeature bietet. Im Fall des Event-Emitters beginnt der Code mit dem Laden der relevanten Klasse:

var EventEmitter = require('events').EventEmitter

Als Nächstes müssen wir eine Methode anlegen, die von Mercury im Fall des Auftretens des Ereignisses aufgerufen werden soll. Wir wollen hier auf console.log zurückgreifen:

function onNMG(){

console.log("Ereignis ist aufgetreten!");

}

Aus Gründen der Bequemlichkeit nutzen wir den weiter oben angelegten Delegator weiter. Er bietet einen einfachen Weg zum Abgreifen von Klickereignissen. Als Nächstes legen wir ein neues Ereignis an. Die on-Methode nimmt sowohl einen String als auch eine Methode entgegen, die beim Auftreten des betreffenden Events aufzurufen ist:

function App() {

delegator().addGlobalEventListener('click', runMe); window.eventStream=new EventEmitter(); window.eventStream.on('nmg', onNMG);

geval unterscheidet sich von anderen Eventsystemen insofern, als die Steuerung über ein Ereignisobjekt erfolgt. Wir legen die von EventEmitter zurückgelieferte Instanz in window ab, um sie aus anderen Methoden aus aufrufen zu können. Der Variablenname Stream ist hierbei eine Konvention in Mercury. Event-Verwaltungsobjekte werden von Jake Verbaten stets als Datenstrom betrachtet und haben deshalb so gut wie immer einen auf Stream endenden Namen.

Für die Aktivierung greifen wir auf die aus dem vorhergehenden Beispiel bekannte Methode runMe zurück, die bei beliebigen Klicks in der Webseite aktiviert wird:

function runMe(a){ window.eventStream.emit('nmg');

}

Die Dokumentation der EventEmitter-Klasse ist denkbar schlecht. Wer mehr über den inneren Aufbau erfahren möch-
22 function App() {

23 delegator().addGlobalEventListener('click', runMe)

24 _

25 Iwindow.eventStreai^new EventEmitter()i

EventEaiitter Kit retui) .. .

► events: Object

maxListeners: undefined

▼_proto__: Object

^ I events: undefined

jnaxListeners: undefined

addListener: (type, listener)

361 > I ► constructor: EventEmitterf)

emit: (type)

listenerCount: (type)

listeners: (type)

on: (type, listener)

once: (type, listener)

removeAHListeners: (type)

removeListener: (type, listener)

setHaxListeners: (n)
te, sollte einen Breakpoint platzieren und das Objekt als Ganzes reflektieren. Auf der Workstation des Autors präsentierte sich als Ergebnis davon unter Chrome der in Bild 10 gezeigte Aufbau.

Wer die vergleichsweise methodenlastige Syntax des Event-Emitters nicht goutiert, kann als Alternative auch auf das klassische Event-Element zurückgreifen. Die Inklusion und die Deklaration des Objekts erfolgen folgendermaßen:

var Event = require('geval')

var stream = { ondata: Event(function () { ... }), onend: Event(function () { ... })

}

Das Stream-Objekt lässt sich sodann durch Aufrufen der angelegten Felder aktivieren. Ein großer Vorteil dieser Vorgehensweise besteht darin, dass Nutzer des Stream-Objekts nur jene Ereignisse auslösen können, die im Rahmen der Initialisierung angelegt wurden.

Fazit

Mercury ist ein Framework, das im Vergleich zu seinen Mitbewerbern durch einen extrem schlanken und sehr modularen Aufbau punkten kann. Leider ist die Dokumentation -auch im Vergleich zu im Allgemeinen schlecht dokumentierten Web-Frameworks - wirklich mangelhaft.

Lehrreich ist ein Blick in den sehr interessant aufgebauten Korpus des Frameworks auf jeden Fall - und dass sich das eine oder andere Modul auch außerhalb von Mercury gut macht, steht ebenfalls außer Frage.
Tam Hanna

ist Autor, Trainer und Berater mit den Schwerpunkten Webentwicklung und Webtechnologien. Er lebt in der Slowakei und leitet dort die Firma Tamoggemon Holding k.s.

Er bloggt sporadisch unter: www.tamoggemon.com
EventEmitter

stellen diverse Hilfsfunktionen zur Verfügung (Bild 10)
38 I function

39 conso

40 >

41

42 I function

43 state

44 >

45

46 IApp.rende

4 7_return n;'div#page'. |



You may also like

Keine Kommentare:

Blog-Archiv

Powered by Blogger.