Wie schreibe ich eine Komponente¶
Eine Komponente ist eine einfach Javascript-Datei die für die Komponenten alle notwendigen Resourcen (Css, Javascript, Templates, ...) enthält und über ein Script-Tag in die Konsole eingebunden wird.
Es gibt zwei Arten von Komponenten: Funktionskomponenten 2 die eine bestimmte Funktionalität innerhalb der Konsole bereitstellen - zum Beispiel Tastaturkürzel - und Kontextkomponenten 1, die eine Ausgabe habe und über die Navigation angesteuert werden - zum Beispiel ein Dashboard.
Der Basisaufbau beider Typen ist identisch. Kontextkomponenten1 verfügen zusätzlich über einen View.
Vorbereitung¶
Als erstes muss man eine Verzeichnisstruktur anlegen.
Komponente
┣━━ assets ⤑ Assets wie Bilder, PDF, etc.
┣━━ css ⤑ CSS-Dateien
┣━━ javascript ⤑ Javascriptdateien
┣━━ resource ⤑ Ressourcen wie Sprachdateien
┗━━ vendor ⤑ Herstellerdateien
Der grundlegende Aufbau einer Komponente besteht aus einem einfachen JS-Gerüst.
try {
(function() {
var globalContext = Function('return this')()||(42, eval)('this');
// Hier Code einfügen
return globalContext;
})();
} catch(e) {
// Fehlerdialog anzeigen
try {
Alvine.Package.Console.Host.showNothingWorksAnymoreDialog("%%FILENAME%%", e);
} catch(nothing) {
}
// Event senden
try {
if(typeof jQuery!=='undefined') {
jQuery(window).trigger('exception.ALVINE', e);
}
} catch(nothing) {
}
// Ausgabe in der Konsole
try {
Alvine.Util.Logger.logError("%%FILENAME%%", e);
} catch(nothing) {
console.error(e);
}
}
Wir speichern nun diese Datei als HTML-Datei in unser Komponentenverzeichnis.
Komponente
┗━━ component.js ⤑ Komponentendatei
Nun haben wir eine Komponente und können diese je nach Typ über die Konfiguration in die Konsole einbinden.
Beispiel für das Einbinden eine Funktionskomponente 2
"module": [
{
"component": "example.MyComponent",
"location": "http://www.example.com/component.js"
}, ...
],
Und ein Beispiel für die Konfiguration einer Kontextkomponente 1
"components": {
"component": [
{
"module": {
"prototype": "MyComponent",
"location": "http://www.example.com/component.js"
},
"handler": "mycomponent",
"arguments": {
"key": 'value
}
}, ...
Die Kontextkomponente verfügt in der Konfiguration über einen Schlüssel zur Angabe des Handlers und einen Schlüssel zur Angabe von zusätzlichen Argumenten. Dadurch lässt sich eine Komponente mehrfach nutzen.
Komponenten in Javascript definieren¶
Jede Komponente wird über das Modulsystem vom Host eingebunden.
Innerhalb der Komponente legen wir ein zentrales Komponenten-Objekt
an. Dies wird in der JS-Datei definiert. Hierzu wird unser Beispiel um ein script
-Bereich erweitert.
var component = Alvine.Package.Factory.createComponent('MyComponent');
Bei einer Kontextkomponenten1 wird beim Verlassen des Kontextes die Methode MyComponent.cleanUp()
aufgerufen. In dieser
Methode sollten alle Ressourcen (Registry, Datenbank, ..) die von der Komponente verwendet werden, geschlossen und
gelöscht werden.
component.cleanUp = function() {
// Aufräumarbeiten
};
Kontextkomponente definieren¶
Eine Kontextkomponete stellt die Schnittstelle zwischen dem System und dem Benutzer her. Dafür benötigt die Komponente einen View (Darstellung der Steuerelemente). Für den View wird eine Struktur benötigt, die wir über ein Template in die Komponente einbinden.
Die Definition des Templates erfolgt über das templates
-Schlüsselwort in den startComponent
-Optionen.
Das Template muss das Attribut data-container
enthalten. Innerhalb dieser Node wird die Ausgabe der Komponente eingefügt.
<template id="template">
<div data-container></div>
</template>
Über die Methoden Alvine.Package.Factory.createComponent(componentName, componentVersion, requirements)
und Component.createView()
lässt sich die Komponente anschliessend initialisieren. Leichter und einfacher geht es aber über die Methode Alvine.Package.Factory.startComponent(componentName, componentVersion, requirements)
.
Die Methode Alvine.Package.Factory.startComponent(componentName, componentVersion, requirements)
fügt alle Bausteiner zusammen und erlaubt so eine schnelle
und sichere Umsetzung einer Komponente. Alvine.Package.Factory.startComponent(componentName, componentVersion, requirements)
erwartet neben dem
Namen der Komponente auch die Version und optional Abhängigkeiten.
var namespace = "example";
var componentName = "MyComponent";
var componentVersion = "1.0.0";
Alvine.Package.Console.Host.startComponent(namespace+'.'+componentName, componentVersion, {
/** Notwendige Versionsnummern des Alvine-Framework und jQuery */
libraries: {
alvine: '1.9.0',
jquery: '2.3.0'
},
/** Javascript-Bibliotheken die geladen werden müssen */
externals: [
"/app/console/js/mylib.js",
"/app/console/css/mylib.css",
],
/** Notwenige Funktionskomponenten die eingebunden sein müssen */
modules: {
'Alvine.Package.UI.Dialog.Bootstrap': '1.0.0',
'Alvine.Package.i18n.Globalize': '1.0.0'
},
/** URL in der die Sprachdateien liegen */
locales: [
"resource/"
],
/** Templates */
templates: [
"<template></template>"
],
component: {
/** Wird beim Verlassen des Kontextes aufgerufen */
cleanUp: () => {
// do something
}
},
/** Initialisierung des Views */
view: {
init: initUI
activate: activateUI,
template: '#template'
}
}).catch(error => {
Alvine.Util.Logger.logError(namespace+'.'+componentName, componentVersion, 'startComponent', error);
Alvine.Package.Console.Host.notifyDanger('i18n:error.somethingwentwrong');
});
function activateUI(configuration) {
}
Die beiden Funktionen initUI
und activateUI
werden nacheinander aufgerufen. Während des Aufrufs
von initUI
werden alle Controls
und Dialoge erstellt und nach dem Aufruf der Funktion ins DOM eingebunden.
function initUI(configuration, doneCallback) {
var view = $this;
/** do something */
doneCallback();
}
doneCallback
dient dazu, die Ablaufkontrolle im init-Teil anzuhalten, bis alle externen Ereignisse (wie z.B. das
Nachladen von Daten) erfolgt sind. Der doneCallback
-Parameter ist optional. Wird die Methode ohne zweite Parameter
definiert initUI(configuration)
so wartet der Host nicht auf den Aufruf und fährt sofort mit dem Aufruf von activateUI()
fort.
Best practice ist es alle Promises von externen Anfragen in einen Iterator zu sammeln und anschliessend über Promise.all()
den
doneCallback()
aufzurufen.
var promises = []; //iterator
// Externe Daten abfragen
promises.push(Alvine.Package.Console.Host.fetch(dataURL1, {});
// Weitere Daten ....
promises.push(Alvine.Package.Console.Host.fetch(dataURL2, {});
/** do something */
Promise.all(promises).then(() => {
doneCallback();
});
Nachdem das DOM erstellt wurde, wird vom Host der Callback activateUI
aufgerufen. Hier können
Eventhandler und Änderungen am DOM durchgeführt werden.
function activateUI(configuration) {
var view = $this;
/** do something */
}
Der Ablauf der Initialisierung ist hier nochmal bildlich dargestellt:
Über den View können Events einfach zentral Abgefangen werden. Dazu definieren wir zum Beispiel:
function activateUI(configuration) {
var view = $this;
view.on('click', 'button', function(event) {
/** do something */
});
}
Die Methode view.on(event, selector, callback)
erwartet den gewünschten Event-Typ (click, change, ...), einen Selektor
zur identifizierung des Elements und eine Callback-Funktion.
Bilder und CSS-Dateien¶
Bilder werden im Verzeichnis asset
und CSS-Dateien im Verzeichnis css
der Komponente gespeichert. Die
Dateien können relativ zur Komponente referenziert werden.
Javascript¶
Dateien von externen Projekten werden entweder direkt über ein CDN eingebunden oder im vendor-Verzeichnis der Komponente gespeichert.
Laden der lokalisierten Sprachdatei¶
Alle Übersetzungen der Komponente werden in einer einfachen JSON-Datei gespeichert. Der Aufbau der Datei ist sehr einfach. Die Schlüssel entsprechen den in der Komponente verwendeten Zeichenketten und der Wert ist jeweils die Übersetzung in der betreffenden Landessprache.
{
"i18n:ok": "OK",
"i18n:cancel": "Abbruch",
}
Hinweis
Die Schlüssel von lokalisierten Texten soll immer mit dem Prefix i18n:
beginnen.
Die Lokale-Datei kann absolut oder relativ zur Komponente - am besten im Verzeichnis resource
gespeichert werden. Der Dateiname ist der Name der Lokale, zum Beispiel: de-DE.json oder nur de.json.
resource
┣━━ de.json ⤑ Deutsche Sprachdateien
┗━━ en.json ⤑ Englische Sprachdateien
Wichtig
Einfache Ausdrücke wir die folgenden sind bereits über die Bootstrap-Komponente verfügbar und dürfen nicht neu definiert werden.
"i18n:ok": "OK",
"i18n:cancel": "Abbruch",
"i18n:submit": "absenden",
"i18n:delete": "löschen",
"i18n:add": "hinzufügen",
"i18n:remove": "entfernen"
Wichtig
Alle Schlüssel werden in einer zentralen Datenbank des Host gespeichert. Die eigenen Schlüssel müssen deshalb immer im
Namensraum der Komponente definiert werden, damit diese keine anderen Schlüssel überschreiben. Statt i18n:mykey
sollte
i18n:mycomponent.mykey
definiert werden.
Die Sprachdatei kann entweder direkt über startComponent
oder manuell über folgende Anweisung geladen werden:
var baseurl = component.getModule().getBaseURL();
var resourcePath = 'resource';
var promise = Alvine.Package.Console.Host.loadLocale(baseurl+resourcePath);
promise
.then(function(result) {
// Lokale wurden geladen
})
.catch(function(result) {
// Fehler
});
Das von Host.loadLocale zurückgegebene Promise transportiert als ersten Wert ein Objekt mit dem Status der Queue. Im Erfolgsfall
wird {state:'done'}
und im Fehlerfall {state:'error'} zurückgegeben.
Siehe hierzu auch die Anleitung im Alvine Frontend Framework
Zugriff auf die Settings¶
Speichert die Komponente Werte im Settingsbereich, so kann die Komponente auf diese über die Registry zugreifen.
Alvine.Registry.getValueFromPath('console.settings.my');