Lade...
 

WebWidgets

WebWidgets

 

WebWidget Entwicklung mit AngularJS

Für das Erstellen von AngularJS-WebWidgets wird ein Grundwissen zu AngularJS vorausgesetzt. Im Folgenden wird nur beschrieben, wie ein WebWidget als AngularJS-Controller definiert werden kann. Die Variante mit der Direktive ist mächtiger und vielseitiger, der Funktionsumfang einer Direktive wird aber nur selten benötigt. WebWidgets werden in widgetPath angelegt (falls es generische Widgets sind) oder in widgetPath/(Kunde | Projekt) falls es sich um kunden-/projektspezifische Widget handelt.

Ein WebWidget, besteht aus folgenden Teilen:

  • Einer .html-Datei (dem Einstiegspunkt), die in dem APP-Flag angegeben wird und den Controller des Widgets referenziert und eine Art Template darstellt.
  • Einer .js-Datei mit dem Schema (*-directive.js), die optimaler Weise einen ähnlichen Namen hat, wie die .html-Datei. Hier kommt der JavaScript-Code für den Controller rein, der in der .html-Datei referenziert wird. Die Datei wird im gleichen Verzeichnis abgelegt, wie die .html-Datei.
  • Beliebig vielen .css-Dateien, die Style-Definitionen enthalten, die zur Darstellung des WebWidgets notwendig sind. Style-Definitionen sollten nicht in der .html Datei aufgeführt (style-Tag) oder refrenziert (link-Tag) werden, da sie ansonsten mehrfach und zum falschen Zeitpunkt eingebunden werden. Die .css-Dateien werden im Verzeichnis dependencies/ abgelegt, welches relativ zur .html-Datei angelegt wird. Falls eine .css nur Styles für ein bestimmtes WebWidget definiert, dann sollte die Datei ähnlich benannt werden. Gehört die .css zu einer eingebundenen Bibliothek, dann sollte der Name beibehalten werden. CSS-Selektoren, die sich auf ein Widget beziehen, sollten möglichst spezifisch sein, damit sich zwei unterschiedliche Widgets nicht beeinflussen.
  • Beliebig vielen .js-Dateien, in die JavaScript-Abhängigkeiten enthalten, die das WebWidget benötigt, um zu funktionieren. Einige geläufige Bibliotheken, wie JQuery, JQuery-UI, JSTree und lodash sind immer verfügbar und müssen nicht als Abhängigkeit abgelegt werden, da sie auch von MorphIT benutzt werden. JavaScript-Abhängigkeiten sollten ebenfalls in dem Ordner dependencies/ abgelegt werden, relativ zur .html-Datei.

 

Eine Verzeichnisstruktur kann dann beispielsweise wie folgt aussehen:

widgetPath/
    myWidget.html
    myWidget-directive.js
    dependencies/
        myWidget.css
        fancy-lib.css
        fancy-lib.js

 

Anhand dieser Struktur wird im folgenden ein WebWidget entworfen, welches zwei Zahlen (a&b) an ClassiX schickt, und das Ergebnis (Produkt) darstellt.

Die WebWidget-Definition in InstantView würde wie folgt aussehen:

Web(web, APP("myWidget.html"), 0,0,100,100)
[
  Msg(MULTIPLY_SOCKET) //Websocket-Message deklarieren (wird ausgelöst über websocketCommunication.send('multiply'))
  MULTIPLY_SOCKET: // Auf dem Stack liegt das, was an send übergeben wurde {a:(factor), b:(factor)}
  { LocalVar(json, result) -> json
    json Copy(a) json Copy(b) * -> result    //Werte multiplizieren
    result "product" Widget Call(PushSocket) //Ergebnis per PushSocket über den Websocket an das HTML-Widget senden
  }
]

 

Das HTML-Template zu dem Widget könnte wie folgt aussehen: (myWidget.html)

<div ng-controller="myWidgetController"> <! Controller referenzieren -->
  <div ng-if="initialized" class="success">Result: {{result}}</div> <! Ergebnis darstellen, wenn es verfügbar ist (Klasse wird in .css definiert) -->
  <div ng-if="!initialized">Not yet initialized</div>  <! Ansonsten standardmeldung anzeigen -->
</div>

Das dazugehörige myWidget.css könnte wie folgt aussehen:

div[ng-controller="myWidgetController"] .success {
  color: green;
}

 

Der dazugehörige Controller (die Logik des Widgets) würde wie folgt aussehen: (myWidget-directive.js):

morphIT.controller('myWidgetController', ['$scope', '$rootScope', 'webwidgetCommunication', function($scope, $rootScope, webwidgetCommunication) {
  var id = $scope.id;  //<- die Widget-ID ist immer über $scope.id erreichbar
  var url = $rootScope.widget[id].url; //<- die vollständige URL aus APP(...) inklusive eventueller Query-Parameter

  //Scope initialisieren
  $scope.initialized = false; 

  //Callback registrieren, der ausgelöst wird, wenn ClassiX Daten sendet
  webwidgetCommunication.registerWidget(id, function(type, data) {
    if (type === 'product') {
      $scope.initialized = true;
      $scope.result = data;  //<- empfangene Daten zuweisen
    }
  });

  //Message ('multiply',{a:21,b:2}) an ClassiX senden... ClassiX sollte mit ('product', 42) antworten
  webwidgetCommunication.send(id, 'multiply', {a:21, b:2});
  

  //Widget beim Löschen des Scopes wieder deregistrieren, um Ressourcen freizugeben
  $scope.$on('$destroy', function() { webwidgetCommunication.deregisterWidget(id); });
}]);

 

Auf diese Weise lässt sich mit relativ wenig Code ein gänzlich neues Widget definieren, welches hoch interaktiv ist. Zu beachten ist die Konvention, dass die Nachricht, die das WebWidget sendet durch Umwandeln in Großbuchstaben und anfügen eines _SOCKET zum Namen der ausgelösten Message wird. (multiply -> MULTIPLY_SOCKET). Für das Empfangen und Senden von asynchronen Nachrichten an die verbundene ClassiX-Instanz bietet MorphIT den service webwidgetCommunication an, der wie im Codebeispiel gezeigt verwendet werden kann. Der Service bietet ab MorphIT 3.16.1 bietet der Service zusätzlich zu dem alten Interface ein vereinfachtes Interface (Channels), das wie folgt benutzt wird:

//...

//Widget registrieren
var channel = webwidgetCommunication.registerWidget(id);

//Callback function(data,type) am channel für Nachrichten vom Typ 'product' registrieren
//Der Typ '*' kann angegeben werden, um auf alle Nachrichtentypen zu reagieren
channel.on('product', function(data) {
  $scope.initialized = true;
  $scope.result = data;  //<- empfangene Daten zuweisen
});

//Message ('multiply',{a:21,b:2}) an ClassiX senden... ClassiX sollte mit ('product', 42) antworten
channel.send('multiply', {a:21, b:2});


//Widget beim Löschen des Scopes wieder deregistrieren, um Ressourcen freizugeben
$scope.$on('$destroy', function() { channel.close(); });

 

ClassiX kann prinzipiell zu jedem Zeitpunkt über Widget Call(PushSocket) eine Nachricht an das HTML-WebWidget schicken. Es kann aber sein, dass das WebWidget im Browser zu dem Zeitpunkt noch nicht initialisiert oder gar nicht offen ist und die Nachricht somit nie ankommt. Als Best-Practice sollte sich also das HTML-WebWidget immer zuerst melden. Es ist nicht gefordert, dass ClassiX auf eine Message vom Browser-Widget eine Antwort schickt.

 

Testen

Zur Unterstützung bei der Entwicklung und beim Testen von WebWidgets gibt es (aktuell nur in QM) das WebWidget QM/test.html, welches ein einfaches Interface definiert, um dem verbundenen nativen WebWidget beliebige Nachrichten zu schicken. So kann vor der Entwicklung des HTML-WebWidgets sichergestellt werden, dass das WebWidget auf InstantView-Seite korrekt implementiert ist. So können auch auf einfache Weise Fehler in der Implementierung gefunden werden.

 

WebWidgets mit statischem MorphIT

Der MorphIT-Server bietet die Möglichkeit an, die Clients ohne gebundene ClassiX-Instanz bis zu einem gewissen Grad (vor Login) direkt aus vorher exportierten statischen Views zu bedienen. WebWidgets können im eingeschränkten Umfang auch auf diesen statischen Views verwendet werden.

Erkennt der Client, dass er keine stehende Verbindung zu einer ClassiX-Instanz hat, schaltet das der Service webwidgetCommunication intern auf HTTP-Anfragen um. Daraus ergeben sich direkt einige Einschränkungen und notwendige Anpassungen für das WebWidget. Gut geschriebene WebWidgets können sowohl im statischen, als auch im dynamischen Modus verwendet werden.

 

WebSerivce-Interface: Die HTTP-Anfragen der WebWidgets werden vom MorphIT-Server einer beliebigen verfügbaren WebService-ClassiX-Instanz zugewiesen und an deren WebService-Interface weitergeleitet. Die Nachrichten gehen also nicht direkt beim Widget ein (..._SOCKET), sondern werden als WebService-Message (..._POST) in das System gebroadcasted. Anstatt eines JSON-Objekts oder analogem InstantView-Typ liegt auf dem Stack ein CX_HTTP_REQUEST-Objekt, welches im Body einen JSON-String enthält, der mit dem CX_JSON_PARSER geladen werden kann.
Die Antwort kann wie im WebService-Interface üblich als CX_JSON_OBJECT mit ReturnStack zurückgegeben werden. Falls manuell ein CX_HTTP_RESPONSE-Objekt zurückgegeben wird, muss darauf geachtet werden, dass der Body ein gültiges JSON enthalten, ansonsten wird die Antwort verworfen.

 

Stateless: Da die Anfragen aller vorhandenen statischen MorphIT-Clients von einer (oder einigen wenigen) ClassiX-Instanzen beantwortet werden, dürfen die Anfragen nicht voraussetzen, dass sich ClassiX Informationen aus vorherigen Anfragen merkt und auf diese in nachfolgenden Anfragen zurückgreift. Jeder Aufruf für sich muss also in sich abgeschlossen beantwortbar sein.

 

Request-Reply: Da die Kommunikation auf HTTP-Anfragen vom Client an ClassiX reduziert ist, muss für ein kompatibles WebWidget auf die Vorteile der WebSockets verzichtet werden. So kann das WebWidget immer nur dann eine Nachricht von ClassiX erhalten, wenn es vorher eine Nachricht an ClassiX geschickt hat. Im Gegensatz zur WebSocket-Verbindung ist ClassiX im statischen Fall auch dazu gezwungen auf jede Nachricht mit einer Nachricht zu antworten.

 

Antwort-Typ: Falls ClassiX mit einem JSON-Objekt antwortet, dann wird der Typ aus dem Feld type übernommen (falls dieser gesetzt ist). Sollte dies nicht der Fall sein, dann ist der Typ der Antwort gleich dem Typ der Anfrage. In unserem Beispiel wäre die Antwort auf multiply also ebenfalls vom Typ multiply, weil ClassiX kein JSON-Objekt zurückgibt und damit den Typ nicht anders setzen kann.

 

Error-Typ: Der MorphIT-Server oder ClassiX können, um Fehler zu signalisieren (Bsp: keine WebSerivce-Instanz verfügbar) auf eine HTTP-Anfrage mit einem JSON-Objekt vom Typ error antworten. Solche Antworten werden ebenfalls an das WebWidget geschickt. Falls das WebWidget keinen expliziten 'error'-Handler am Channel registriert hat, dann wird die Fehlermeldung in der Konsole und über dem WebWidget in rot ausgegeben.

Das Objekt ist wie folgt definiert:

Feld Typ Beschreibung
type string Enthält den Wert "error"
errorId string Optional - Falls es eine mehrsprachige Beschreibung des Fehlers gibt (de.json, en.json), dann enthält dieses Feld die Übersetzungs-Id
error string Enthält die Fehlermeldung in englischer Sprache

InstantView Scriptsprache