In der Frontendentwicklung ist es nicht unüblich, eine Anwendung in verschiedener Art in Erscheinung treten zu lassen. Ob für die Abgrenzung mehrerer Brands voneinander, den Verkauf einer Software mit spezifischen Designanpassungen für den Käufer (etwa für einen Online-Shop), oder zur Durchführung von A/B-Tests mit unterschiedlich gestylten Frontendkomponenten – es gibt eine Vielzahl von Gelegenheiten, bei denen es Sinn macht, eine Anwendung mit unterschiedlichen Gestaltungen in anderen Gewändern zu zeigen.
Hier möchte man selbstverständlich in den wenigsten Fällen das komplette Frontend neu schreiben. Dabei können bereits kleinere Änderungen, etwa eine Anpassung der verwendeten Farben, das Aussehen der Anwendung grundlegend ändern.
Im Folgenden möchte ich zeigen, wie man durch den Einsatz von Themes die eigene Anwendung mit relativ geringem Aufwand in neuem Glanz präsentieren kann.
Besonders gut eignet sich das komponentenbasierte JavaScript-Framework ExtJS, das neben einer Handvoll fertiger Themes die Möglichkeit bietet, eigene Sets zu erstellen. Der Aufwand wird dabei durch den komponentenbasierten Ansatz reduziert.
Ähnlich wie beim Bauen komplexer Gebilde aus Lego bietet auch ExtJS grundlegende Komponenten, die sich zu komplexen Anwendungen zusammensetzen lassen. So besteht beispielsweise ein Dialog aus einem Window, das einen Container für den Inhalt und eine Titelleiste mit Schließen-Icon bereitstellt, dem eigentlichen Content, wie einer Fehlermeldung, sowie diversen Buttons, etwa zum Bestätigen oder Abbrechen einer Aktion. Diese Unterteilung komplexer Komponenten in kleinere Subkomponenten kommt insbesondere dem Theming zu Gute, da nur die Einzelbestandteile jeweils einmal gestylt werden müssen. Die Komponenten können dann aus diese Grundlage zurückgreifen.
Neben dem geringeren Aufwand, verglichen zum Einzelstyling jeder einzelnen Komponente, profitiert das Frontend so auch von einer hohen Konsistenz, da eine Titelleiste immer als Titelleiste wiederzuerkennen ist.
Wer nun um die damit eingeschränkte Flexibilität bangt, der sei beruhigt. Auch hier bietet ExtJS eine Möglichkeit, die Flexibilität wiederzuerlangen, was ich weiter unten im Text unter “Mehr Flexibilität durch eigene Komponenten-Styles” noch erläutere.
Integration in SASS/Compass
Nun ist die Unterteilung in Komponenten erst die halbe Miete; ExtJS bietet über die Integration des Compass-Frameworks noch eine weitere Unterstützung im Theming-Prozess.
Compass ist ein auf SASS basierendes Framework, das eine Abstraktionsschicht über CSS bildet und – ähnlich wie Less oder stylus – CSS durch einen Präprozessor um Funktionen (im SASS-Kontext Mixins genannt) und Variablen erweitert. Zusätzlich bietet das System u.a. eine Abstraktion für CSS3-Attribute, was Kompatibilitätsprobleme ausräumt und Schreibarbeit spart.
Für ein ausführlicheres Tutorial zum Arbeiten mit SASS möchte ich an dieser Stelle auf einen bereits bestehenden Blogpost, sowie die SASS-Dokumentation verweisen. Voraussetzung für das Arbeiten mit SASS und damit auch mit Compass ist eine lauffähige Ruby-Umgebung. Nicht zwingend, jedoch sehr hilfreich ist auch die Sencha Commandline für das Erstellen einer Anwendung und das Theming. Zur Installation unter Mac/Windows/Linux sei auf die entsprechende Dokumentation verwiesen.
Erzeugen eines Themes
Sobald die Sencha CMD-Tools laufen, kann man per sencha generate theme themename
ein neues Theme erzeugen. Damit wird im Workspace-Verzeichnis unter packages ein neuer Ordner mit dem entsprechenden Themenamen erstellt. Sollte es hier zu einem Fehler kommen ist muss eventuell noch ein Workspace angelegt werden.
Dabei gilt es, zuerst in der package.json anzugeben, von welchem ExtJS Theme abgeleitet werden soll. Der Standardwert zeigt hier auf das Classic Theme, das jedoch kaum Mixins anbietet. Eher empfiehlt sich daher das aktuellere Neptune, das sehr schlicht und flach gehalten ist, jedoch an vielen Stellen die Möglichkeit bietet, mit Farbverläufen 3D-Effekte zu erzeugen, was dem eigenen Theme mehr Raumtiefe gibt.
Um auf Neptune zugreifen zu können, muss man in der package.json folgende Änderung vornehmen:
<code>-"extend": "ext-theme-classic"; +"extend": "ext-theme-neptune"; </code>
Anschließend ist das neue Theme noch in der Anwendung zu registrieren, durch folgende Zeile innerhalb von AppVerzeichnis/.sencha/app/sencha.cfg:
<code>-app.theme=ext-theme-classic +app.theme=my-custom-theme </code>
Danach kann im eigentlichen Anwendungsverzeichnis das Theme mit sencha app refresh
geupdatet werden. Dies setzt eine bereits bestehende Anwendung voraus, siehe hierzu Developing Applications. Die eigentliche Arbeit, aus SASS-Code das Theme zu erstellen, erledigt dann ein sencha package build
innerhalb des Themeverzeichnisses.
Anatomie eines Themes
Das zuvor erstellte Theme enthält verschiedene Ordner, die je eine bestimmte Funktion erfüllen:
- sass
- etc (Hilfsmixins, z.B.: Berechnungsfunktionen)
- example (wird für das Slicing benötigt)
- var (Variablendefinitionen)
- src (eigene Mixins)
- ressourcen
- images (Bilddaten)
- overrides
- .sencha
- package (Buildkonfigurationen)
Die Unterordner unter sass sollten spätestens am Ende dieses Beitrages selbsterklärend sein, einzig das etc-Verzeichnis könnte Verwirrung stiften. Hierbei handelt es sich um einen Ort, wo Hilfsfunktionen in Form von Mixins abgelegt werden können. Die müssen dann innerhalb der ebenfalls dort befindlichen all.scss-Datei geladen werden. Zu sehen ist das u.a. im Neptune-Theme innerhalb des ExtJS-Frameworks.
Im Ordner ressources/images verweilen alle Bilder, die die jeweiligen Komponenten zum Stylen benötigen. Danben liegen im Ordner overrides JavaScript-Klassen, die bereits vorhandene ExtJS-Komponenten überschreiben können, wenn es für das Theming notwendig ist.
Der letzte Ordner in der Liste ist der am wenigsten genutzte – hier liegen nur Einstellungsdateien für den Buildprozess des Themes. Die einzige Stelle, die uns hier interessieren sollte, ist in der sencha.cfg die Zeile:
package.sass.namespace=…
Sie sollte erst einmal leer gelassen werden, da sie den Autoloading-Mechanismus beeinflusst.
Autloading der SASS-Dateien
ExtJS bringt einen Autoloading-Mechanismus mit der zu jeder Komponente die entsprechenden SASS-/Bilddateien importiert. Dazu reicht es aus, wenn die Ordnerstruktur die Struktur des JavaScript Namespaces widerspiegelt, in dem sich die Komponente befindet. Wenn zum Beispiel eine Komponente im Namespace MyApp.window.MessageBox liegt, so würde folgende Struktur die entsprechenden SASS-/Bilddaten importieren:
- sass
- var
- MyApp
- window
- MessageBox.scss
- window
- MyApp
- src
- MyApp
- window
- MessageBox.scss
- window
- MyApp
- var
- ressourcen
- images
- MyApp
- window
- close.png
- window
- MyApp
- images
Dabei ist die Groß-/Kleinschreibung zu beachten.
Dialogstyling
Nun genug der grauen Theorie und Butter bei die Fische: stylen wir unseren ersten Dialog.
Wie bereits anhand des Namespaces MyApp.window.MessageBox zu erahnen, nutzt man als Ausgangsbasis eine abgeleitete Komponente, die Ext.window.MessageBox beerbt. Hierfür gilt folgender Code:
Ext.define('MyApp.window.MessageBox', {
extend: 'Ext.window.MessageBox',
componentCls: 'my-dialog',
makeButton: function(btnIdx) {
var me = this,
button = me.callParent(arguments);
if(btnIdx === 1) {
button.ui = 'primary';
} else if(btnIdx === 2) {
button.ui = 'secondary';
}
return button;
}
});
Damit erweitert man die Erzeugung der Buttons: Der erste Button bekommt ein zusätzliches UI-Attribut, das man im späteren Verlauf noch benötigen wird, ebenso der zweiten Button – nur das dieser ein anderes UI-Attribut erhält.
Variablen
Da die MyApp.window.MessageBox von Ext.window.MessageBox abgeleitet ist, setzt sie sich ebenfalls aus einem Ext.window.Window und mehreren Ext.button.Button-Komponenten zusammen. Den Inhalt blenden wir an dieser Stelle aus, es genügt zunächst, nur die entsprechenden Variablen zu setzten um den Hintergrund zu rot und die Schrift des Titels zu weiss zu ändern.
Da wir hier eine Komponente des ExtJS Namespaces stylen, muss auch unsere SASS-Datei entsprechend in diesem Namespace, ergo im richtigen Ordner liegen. Daher legen wir die Datei sass/var/Ext/window/Window.scss an, in der wir die entsprechenden Variablen des Eltern-Themes überschreiben können: Das sind $window-header-background-color und $window-header-color.
Unsere Window.scss sieht nun also wie folgt aus:
$window-header-background-color: #FF0000 !default;
$window-header-color: #FFFFFF !default;
Das !default wird gesetzt, um anzugeben, dass die Variable von einem erbendem Theme überschrieben werden kann. Es ist obsolet, wenn sicher ist, dass das Theme für sich existiert und nie beerbt wird. Da sie aber keine Auswirkung hat, wenn das Theme nicht beerbt wird, rate ich dazu, sie immer zu setzten – es sei denn, es handelt sich um Variablen, die nur zur interen Verwendung des Themes genutzt werden und bei denen ein Überschreiben zu gefährlichen Seiteneffekten im Theme führt.
Nach diesem Schema lässt sich auch jede andere Komponente stylen. Die Dokumentation bietet einen hervorragenden Überblick zu den verfügbaren Variablen für jede Komponente.
Globale Variablen
Wer sich von vornherein viel Arbeit sparen möchte, sollte mit den globalen Variablen spielen, die im Eltern-Theme unter sass/var/Component.scss zu finden sind. Oft lässt sich durch Setzten der $base-color und der $neutral-light-color und der $neutral-dark-color bereits erstaunlich viel erreichen. Im Neptun-Theme werden gerade sie häufig als Standard für Komponenten verwendet oder daraus hellere/dunklere Farben für eine Komponente erzeugt.
Ebenfalls global setzten lassen sich etwa die $font-size oder die $font-family.
Mehr Flexibilität durch eigene Komponenten-Styles
Neben dem einfachen Stylen durch Setzten von Variablen, bietet ExtJS für viele Komponenten Mixins an, um unterschiedliche Styles zu realisieren. So wurde beispielsweise im genannten Code für den ersten und den zweiten Button der MyApp.window.MessageBox-Komponente das UI-Attribut vergeben, mit dem sich vordefinierte Styles auf die Komponente anwenden lassen. So wird sehr elegant das Problem umgangen, dass eine Komponente nur einmal gestylt werden kann, denn aus Sicht des Themes wird die Komponente dupliziert, wenn das Attribut gesetzt wird.
So können wir nun zwei neue Buttonstyles erstellen, einen mit einen grünen und einen mit einem gelben Hintergrund. Da wir an dieser Stelle ein Mixin verwenden müssen, ist unser Code am besten in sass/src/Ext/button/Button.scss aufgehoben, ebenso werden die notwendigen Variablen für die Hintergrundfarben in sass/var/Ext/button/Button.scss abgelegt.
Das Mixin für den Button rufen wir mit einen @include auf und übergeben ihm als erstes Argument das UI-Attribut, aus dem es sich zusammensetzt. Das UI-Attribut für Buttons hat dabei eine Besonderheit – es setzt sich nämlich wiederum zusammen aus dem gesetzten UI-Attribut und dem Scale-Attribut der Komponente. Wenn nicht anders angegeben, ist letzteres im Standardfall small. Damit kann ein Button neben seinem UI-Style auch für verschiedene Größen entworfen und berechnet werden.
Die meisten Argumente setzten wir erst gar nicht, so dass die Standardvariablen des Eltern-Theme zum Tragen kommen. Das spart im folgenden Beispiel Platz und erhöht damit die Lesbarkeit:
//Primary Button Styling
@include extjs-button-ui(
$ui: 'primary-small',
$background-color: $button-primary-background-color,
$background-color-over: $button-primary-background-color-over,
$background-color-focus: $button-primary-background-color-focus,
$background-color-pressed: $button-primary-background-color-pressed,
$background-color-disabled: $button-default-background-color-disabled,
$background-gradient: $button-primary-background-gradient,
$background-gradient-over: $button-primary-background-gradient-over,
$background-gradient-focus: $button-primary-background-gradient-focus,
$background-gradient-pressed: $button-primary-background-gradient-pressed,
$background-gradient-disabled: $button-default-background-gradient-disabled,
$color: $button-primary-color,
$color-over: $button-primary-color-over,
$color-focus: $button-primary-color-focus,
$color-pressed: $button-primary-color-pressed,
$color-disabled: $button-primary-color-disabled,
);
//Secondary Button Styling
@include extjs-button-ui(
$ui: 'secondary-small',
$background-color: $button-primary-background-color,
$background-color-over: $button-primary-background-color-over,
$background-color-focus: $button-primary-background-color-focus,
$background-color-pressed: $button-primary-background-color-pressed,
$background-color-disabled: $button-default-background-color-disabled,
$background-gradient: $button-primary-background-gradient,
$background-gradient-over: $button-primary-background-gradient-over,
$background-gradient-focus: $button-primary-background-gradient-focus,
$background-gradient-pressed: $button-primary-background-gradient-pressed,
$background-gradient-disabled: $button-default-background-gradient-disabled,
$color: $button-primary-color,
$color-over: $button-primary-color-over,
$color-focus: $button-primary-color-focus,
$color-pressed: $button-primary-color-pressed,
$color-disabled: $button-primary-color-disabled,
);
Die zugehörige Datei mit den Variablen unter sass/var/Ext/button/Button.scss sieht damit so aus:
$button-primary-background-color: #73b51e !default;
$button-primary-background-color-over: #73b51e !default;
$button-primary-background-color-focus: #73b51e !default;
$button-primary-background-color-pressed: #73b51e !default;
$button-primary-color: $neutral-light-color !default;
$button-primary-color-over: $neutral-light-color !default;
$button-primary-color-focus: $neutral-light-color !default;
$button-primary-color-pressed: $neutral-light-color !default;
$button-primary-color-disabled: $neutral-light-color !default;
$button-secondary-background-color: #FFFF00 !default;
$button-secondary-background-color-over: #FFFF00 !default;
$button-secondary-background-color-focus: #FFFF00 !default;
$button-secondary-background-color-pressed: #FFFF00 !default;
$button-secondary-color: $neutral-dark-color !default;
$button-secondary-color-over: $neutral-dark-color !default;
$button-secondary-color-focus: $neutral-dark-color !default;
$button-secondary-color-pressed: $neutral-dark-color !default;
$button-secondary-color-disabled: $neutral-dark-color !default;
Im Beispiel sind noch keine unterschiedlichen Farbnuancen für die verschiedenen Button-Zustände gesetzt. Anhand der Variablennamen sollte die jeweilige Funktion jedoch hervorgehen, so dass dies nur eine Fingerübung darstellt. Das Mixin selber kann bereits mit den verschiedenen Button-Zuständen umgehen.
Fortgeschrittene Techniken
Das im Artikel Gezeigte ist nur ein Bruchteil (wenn auch der am häufigsten Verwendete) dessen, was mit dem ExtJS Theming möglich ist. Er sollte für die einen Einstieg in das erste eigene Theme ausreichen.
Wer mehr zum Theming erfahren möchte – wie dem Umgang mit Legacy-Browsern oder Möglichkeiten, wie in einem ExtJS Theme Symbole, etwa für ein Close-Icon, skaliert und verändert werden können ohne dafür einzelne Bilder zu erzeugen zu müssen – für den lohnt es sich, nächste Woche erneut im Mayflower-Blog vorbeizuschauen.
Lesenswert: ExtJS Theming: Eine Anwendung, 100 Gesichter http://t.co/wxXrBsGHWa
Danke für den Lesetipp! Dein Beitrag hat mir echt Appetit auf das Werk gemacht. Wird angeschafft!
Neues aus dem Mayflower Blog: ExtJS Theming: http://t.co/Vxby96bKGi ^LR