Zum Inhalt

Wie schreibe ich eine Komponente

Eine Komponente ist eine einfach HTML-Datei die für die Komponenten alle notwendigen Resourcen (Css, Javascript, Templates, ...) enthält und über einen Dokumenten-Import 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 legen wir folgende Verzeichnisstruktur an.

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 HTML-Gerüst.

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Meine Komponenten</title>
  </head>
  <body>
  </body>
</html>

Wir speichern nun diese Datei als HTML-Datei in unser Komponentenverzeichnis.

Komponente
   ┗━━ component.html ⤑ 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": "MyComponent",
        "location": "http://www.example.com/component.html"
}, ...
],

Und ein Beispiel für die Konfiguration einer Kontextkomponente 1

"components": {
    "component": [
        {
            "module": {
                "prototype": "MyComponent",
                "location": "http://www.example.com/component.html"
            },
            "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 HTML-Datei definiert. Hierzu wird unser Beispiel um ein script-Bereich erweitert.

<script>
var component = Alvine.Package.Factory.createComponent('MyComponent');
</script>

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 den 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 erfolgt im <head> Bereich der HTML-Datei der Komponente. 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.

Alvine.Package.Console.Host.startComponent(namespace+'.'+componentName, componentVersion, {
    libraries: {
        alvine: DEFAULTS.REQUIREDALVINEFRAMEWORKVERSION,
        jquery: DEFAULTS.REQUIREDJQUERYVERSION
    },
    externals: [
        /**           "//cdnjs.cloudflare.com/ajax/libs/dexie/2.0.4/dexie.js",
         "/app/console/js/platform.js",
         "/app/console/js/platform/workingcopy.js",
         "/app/console/js/platform/locale.js",
         "/app/console/js/platform/net.js",
         "/app/console/js/platform/lists.js",
         "/app/console/js/platform/maps.js",
         "/app/console/js/platform/registry.js",
         "/app/console/js/platform/lists.js",
         "/app/console/js/platform/form.js",
         "/app/console/js/platform/persistence/index.js",
         "/app/console/js/platform/persistence/indexeddb/dexie/objectstore.js",
         "/app/console/js/platform/persistence/indexeddb/dexie/dataobject.js",
         "/app/console/js/platform/persistence/indexeddb/dexie/datasource.js",
         "/app/console/js/platform/persistence/indexeddb/dexie/query.js",
         "/app/console/js/platform/persistence/indexeddb/dexie/queryparser.js",
         "/app/console/js/platform/form/dialog.js",
         "/app/console/js/platform/form/detailform.js",
         "/app/console/js/waiterfy/listdialog.js",

         "/app/console/js/platform/model.js",
         "/app/console/js/platform/model/commerce/item/catalog/category.js",
         "/app/console/js/platform/datamanager.js",
         "/app/console/js/platform/storagemanager.js",
         "/app/console/js/platform/queryparser.js",*/
    ],
    modules: {
        'Alvine.Package.UI.Dialog.Bootstrap': '1.0.0',
        'Alvine.Package.UI.Dialog.Widgets': '1.0.0',
        'Alvine.Package.i18n.Globalize': '1.0.0'
    },
    locales: [
        DEFAULTS.RESOURCEPATH+"/"
    ],
    component: {
        cleanUp: (a) => {
            if(view.dialog) {
                view.dialog.cleanUp();
            }
        }
    },
    view: {
        init: initUI,
        activate: activateUI,
        template: '#template'
    }
}).then(data => {
    view = data[1];
}).catch(error => {
    Alvine.Package.Console.Host.showNothingWorksAnymoreDialog(namespace, 'Alvine.Platform.Console.Order.List.startComponent', error);
});

Die beiden Funktionen initUI und activateUI werden nacheinander aufgerufen. Während des Aufrufs von initUI werden alle Controls und Dialoge erstellt und nach aufruf der Funktion ins DOM eingebunden.

Nachdem das DOM erstellt wurde, wird von createView der Callback activateUI aufgerufen. Hier können Eventhandler und Änderungen am DOM durchgeführt werden.

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.

<link rel="stylesheet" href="css/my.css">

4d3c02eb-8976-4388-9406-06b447f19890

<img src="asset/image/my.png">

Javascript

Javascript kann direkt in der HTML-Datei der Komponente definiert werde. Alternativ kann es in eigenen Dateien ausgegliedert und relativ eingebunden werden.

<script src="javascript/my.js"></script>

Externe Bibliotheken

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 sollten 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

Einfache Ausdrücke wir die folgenden sind bereits über die Bootstrap-Komponente verfügbar und müssen 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 sollten 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 in der Komponente ü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');

  1. Kontextkomponenten verfügen, anders als Funktionskomponenten 2 über einen View und lassen sich über die Navigation ansteuern. 

  2. Funktionskomponenten werden zentral eingebunden und stellen eine Funktionalität ohne expliziten Userview zur Verfügung.