Freie Komponente
Mit Hilfe einer "Freien Komponente" können individuelle Komponenten für den Formular-Renderer entwickelt werden, sollten die Bausteine von Lobster Data Platform / Orchestration nicht mehr genügen oder wenn eigene kreative Komponenten in einem Formular verwirklicht werden sollen.
Der Weg einer solchen Komponente sollte nur beschritten werden, wenn tatsächlich keine andere Onboardlösung mehr gefunden werden kann oder wenn Komponenten von Drittanbietern eingebunden werden sollen.
Achtung: Hierfür sind Programmierkenntnisse und Erfahrung im Umgang mit HTML5 (HTML/CSS/Javascript) die Grundvoraussetzung!
Inhalt
Einführung
Freie Komponenten können, sofern lizenziert, auf zwei Arten entwickelt werden:
Direkt über die "Freie Komponente" in der Portalsektion des Editors. Dann über den "Bearbeiten" Knopf in der entsprechenden Eigenschaftenrubrik.
Vorteil: Direktes Entwickeln der Komponente in der "What you see is what you get" Manier
Nachteil: Keine Wiederverwendbarkeit der entwickelten Komponente
Nachteil: Kein "Single Point of Maintanence". Wird eine Komponente nur von Form zu Form kopiert, können Fehler und Erweiterungen nicht an einem zentralen Punkt abgehandelt werden
Nachteil: Die Komponente wird pro Formularinstanz kompilliert und kann nicht global gecached werdenPersistentes Anlegen von Komponenten über die "Freie Komponenten" Übersicht (Rechte müssen entsprechend vorhanden sein)
Vorteil: Wiederverwendbarkeit der Komponenten. Die Wartung und Weiterentwicklung von Komponenten geschieht von einem Punkt aus
Vorteil: Komponenten tauchen direkt in der Sektion "Freie Komponenten" im Formulareditor auf und können auf die Form gezogen werden
Vorteil: Der Komponentencode kann global gecached werden. Der Kompillierungsschritt muss nicht pro Formularinstanz neu durchgeführt werden
Nachteil: Änderungen an einer Komponente werden nicht direkt sichtbar. Empfehlung: Einen Portaleditor öffnen und durch Wechseln zwischen Test- und Editiermodus die neuesten Änderungen laden
Der Editor für die Konfiguration unterscheidet sich bei beiden Arten nur in kleineren Details und wird daher hier nur einmal beschrieben.
Aufbau einer "Freien Komponente"
Eine freie Komponente besteht grundsätzlich aus drei Teilen:
View (HTML)
Definiert das Grundlayout der Komponente mit HTML CodeStyle (CSS)
Definiert das Aussehen und die Ausgestaltung des Grundlayouts mit CSS Code.
Dieser CSS Code kann über den Reiter "Einstellungen" auch von einer Datei geladen werden (Stylecode von URL laden). In diesem Fall werden vor der Inkludierung des Dateiinhalts ebenfalls proprietäre Schlüsselwörter (wie :host) interpretiert.Controller (Javascript)
Implementiert die Programmlogik und das Verhalten der Komponente mit Javascript und bildet die Schnittstelle zwischen der Komponente und dem Formular-Renderer.
Hinweis: Da Lobster_pro keine älteren Browser (wie z.B. IE11) mehr unterstützt, kann sämtlicher Javascript-Code im ES6 Syntax geschrieben werden.
Daher ist auch der Editor in eben diese drei Teile aufgeteilt.
Editor für "Freie Komponenten" |
Darstellung im Formeditor |
|
|
Tipp: Über den Reiter "Einstellungen" können zusätzliche Einstellungen vorgenommen und Beispiele geladen werden.
Im Reiter Ressourcen können externe Ressourcen (hauptsächlich Sprachverwaltungseinträge) definiert werden, welche im Falle eines "Exports" der Komponente mitexportiert werden. (Für Details siehe Abschnitt Lokalisierung)
Hinweis: In diesem Kapitel wird nicht im Detail auf Fachbegriffe bezüglich der genannten Teile eingegangen, da ein ausgeprägtes Wissen im Bereich der Webapplikationen vorausgesetzt wird. Ebenso werden Code-Kommentare ausschließlich in Englisch angegeben.
Controller
Die Implementierung der Komponentenlogik erfolgt im Controller-Teil des Editors mit Javascript. Als Vereinbarung gilt, dass die Controllerklasse an Lobster Data Platform / Orchestration zurückgegeben werden muss (return Classname). Dies ermöglicht auch die Verwendung eigener Hilfsklassen innerhalb des Controllercodes.
Dem Controller liegt folgendes Interface zugrunde, wobei die Implementierung der einzelnen Methoden optional ist.
interface IFreeComponent {
/**
* Will be called on initialization and once the readonly flag of the element has changed
*
* @param {boolean} readonly True if readonly has changed to true, false if it has changed to false
*/
readOnlyChanged( readonly: boolean ): void;
/**
* Will be called on initialization and once the enabled flag of the element has changed
*
* @param {boolean} disabled True if the component has been disabled, false if it has been enabled
*/
disabledChanged( disabled: boolean ): void;
/**
* Will be called when the element gets initialized, even before the view part of the component is ready ( see onViewInit() ).
* Therefore view based context properties like hostElement are not initialized, yet.
* In case the component has script dependencies defined, initialize() will be called once all dependencies are loaded without an error.
* This method might return a PromiseLike object to do some asynchronous initialization as well (e.g. retrieving data from a web service etc.)
*
* @param {FreeComponentContext} context The predeclared view context
* @returns A PromiseLike object or void
*/
initialize( context: FreeComponentContext ): PromiseLike<void>;
/**
* Will be called once the entire component (controller) is destroyed.
* The controller is NOT destroyed in case of invisibility!
*/
onDestroy(): void;
/**
* Will be called once the element loads data from its data field.
* @param {Object} value The populated value
* @param {boolean} isNew Determines whether the load was due to initialization (in case of true, a default value may be loaded if value is null or undefined)
* @returns True if the value of the element has actually changed, false else
*/
populateValue( value: Object, isNew: boolean ): boolean;
/**
* Will be called once the internal value of the element will be changed.
* This is independend of the elements data field and may occur due to user changes or behaviour actions.
* The changed value is not necessarily the populated value and is to be synchronized once retrieveValue() is called
*
* @param {Object} value The changed value
* @returns True if the value of the element has actually changed, false else
*/
changeValue( value: Object ): boolean;
/**
* Retrieves the value of this element (e.g. to write it into the form data or for validation)
*
* @returns The value of this element
*/
retrieveValue(): Object;
/**
* Retrieves a dummy value of this element. Needed for the data structure export in the form editor
*
* @returns Any dummy value which is of the same type as the elements value
*/
retrieveDummyValue(): Object;
/**
* Returns a list of event names supported by this component.
* Use the context (given to onViewInit()) to dispatch events. (e.g. this.context.dispatchEvent('MY_EVENTNAME', "event value") )
* It is recommended to prefix the event names, if it is not a default event like 'CHANGED', to prevent collisions between event names.
* Warning: Do not dipsatch CHANGED events on retrieveValue(), changeValue() or populateValue()!
* Important: To notify the form renderer about value changes triggered by the view of this component, call this.context.notifyValueChange( value )!
*/
getEventNames(): string[];
/**
* Returns a list of action names which are handled by this component.
* The handling of actions is implemented in the handleAction() method.
*
* @see handleAction()
*/
getActionNames(): string[];
/**
* Returns a list of behaviour names which are handled by this component.
* The handling of behaviours is implemented in the handleBehaviour() method.
*
* @see handleBehaviour()
*/
getBehaviourNames(): string[];
/**
* Will be called once the component needs to handle a specific action.
* @see getActionNames()
*
* @param {FreeComponentAction} a The action which should be handled by this component (action name: a.name, action value: a.value)
*/
handleAction( a: FreeComponentAction ): void;
/**
* Will be called once the component needs to handle a specific behaviour.
* @see getActionNames()
*
* @param {FreeComponentBehaviour} a The behaviour which should be handled by this component (behaviour name: b.name, behaviour value: b.value)
*/
handleBehaviour( b: FreeComponentBehaviour ): BehaviourResult | Promise< BehaviourResult >;
/**
* Will be called once the view part of this component has been inserted into the DOM and is ready for use.
* In case the component has style dependencies defined, onViewInit() will be called once all style sheets are loaded without an error.
*
* @param {FreeComponentContext} context The view context of this component. It offers angular support and access to the host DOM element
*/
onViewInit( context: FreeComponentContext ): void;
/**
* Will be called once the view part of this component is destroyed and removed from the DOM.
* This would also apply if the component is not rendered by the form renderer (e.g. invisible, not displayed because in closed expandable or tab etc.)
* Should disconnect view listeners and release resources.
* onViewInit() will be called once the view has been created again.
*/
onViewDestroy(): void;
/**
* Sets/Removes the focus from the component
*
* @param focus True if the focus should be set, false if it should be removed
*/
setFocus( focus: boolean ): void;
/**
* Will be called once the hint of the element has changed
*
* @param hint The hint text (usually displayed underneith the component as visual text)
*/
hintChanged( hint: string ): void;
/**
* May be used to override the html part of the component.
* Implementing this is not the common case
*/
getViewHTML(): string;
}
interface BehaviourResult {
/**
* The result to indicate if the true or false actions should be executed
*/
result: boolean;
/**
* The value, which should be passed to the actions
*/
value: any;
}
Initialisierung
Sollten Skript-Abhängigkeiten (Reiter: Abhängigkeiten) definiert worden sein, so wird die Controllerklasse erst kompilliert, wenn alle diese Abhängigkeiten fehlerfrei geladen wurden (siehe auch Importieren von externen Bibliotheken und Modulen).
Die erste Initialisierungsstufe "initialize( context )" der Komponente wird gerufen, auch wenn die Komponente selbst nicht sichtbar ist. Hier können zusätzliche Daten beschafft und vorbereitende Schritte für den von der View losgelösten Teil durchgeführt werden. Die Methode kann optional ein Promise zurückliefern, was die Initialisierung der Komponente (und damit auch des Formulars) soweit hinauszögert, bis dieses aufgelöst wird.
Sobald die Komponente sichtbar gezeichnet wird, ruft der Renderer die methode "onViewInit( context )" und übergibt dabei den View-Context (Typ: FreeComponentContext), dessen Struktur nachfolgend dokumentiert ist.
interface FreeComponentContext {
/**
* The element which appends the free component view as inner html, available after onViewInit was called
*/
hostElement: HTMLElement;
/**
* Indicates whether the element is rendered in form editing mode
*/
isEditorMode: boolean;
/**
* The generated view id to resolve the binding between hostElement node and controller instance, available after onViewInit was called
*/
viewId: string;
/**
* Indicates whether the component view is destroyed.
* If this is set to true, the view context is invalidated and must not be used anymore
*/
isDestroyed: boolean =
false
;
/**
* The reference to the style code of the component in the DOM, available after onViewInit was called
*/
internalStyleRef: string;
/**
* Queries the host element for a specific element that matches the query.
* Using this method ensures that only an element within the component view is selected.
*
* @param query A DOM element query (e.g. ".container ~ span")
* @see elements(query)
*/
public element( query: string ): HTMLElement;
/**
* Queries the host element for specific elements that match the query
* Using this method ensures that only elements within the component view are selected.
*
* @param query A DOM element query (e.g. "div > .headline")
* @see element(query)
*/
public elements( query: string ): HTMLElement[];
/**
* Dispatches any event defined by its name and value
*
* @param eventName The name of the event
* @param value The value passed by the event
* @param changeKind (optional) Defines the kind of interaction which triggered the event (default is "MANUAL_CHANGED")
*/
dispatchEvent( eventName: string, value: any, changeKind = CHANGE_KINDS.MANUAL_CHANGED );
/**
* Notifies the form renderer that the value of the component has changed through the view part.
* Hint: Do not call notifyValueChange() in changeValue(), populateValue() or retrieveValue()!
*
* @param value The changed value
* @param changeKind (optional) Defines the kind interaction which triggered the event (default is "MANUAL_CHANGED")
*/
notifyValueChange( value: any, changeKind = CHANGE_KINDS.MANUAL_CHANGED );
/**
* Returns a localized value from the registered template resources
*
* @param resourceIdentifier The identifier of the resource registered in the template resources
* @param defaultValue An optional default value which will be returned if the resource was not found
* @param args (optional) Arguments to replace placeholders in the resource string ({0}, {1}, ...)
* @param localeCode (optional) The locale code (defaults to the current session locale)
*/
public rs( resourceIdentifier: string, defaultValue?: string, args?: any[], localeCode?: string ): string;
/**
* Can be used to bootstrap an isolated angular context.
* This is only recommended if the component is not instanciated a lot per form e.g. in a "repeatable element" container
*
* #### Example
* ```javascript
* context.requestAngularPlatformRef().bootstrapModule( CustomAngularModule );
* ```
*/
public requestAngularPlatformRef(): PlatformRef;
/**
* Marks the host view of the component for checks.
* This is usually not necessary.
*/
public markForCheck(): void;
}
Daten und Werte einer "Freien Komponente" / Verhaltensauslöser "Geändert"
Wenn die Komponente ihre Daten in das eingestellte Datenfeld schreiben und über Verknüpfungen gelesen werden soll, muss zunächst das Dateninterface implementiert werden.
Die Methoden "populateValue()", "changeValue()" und "retrieveValue()" dienen zum Lesen und Schreiben von Formulardaten im Bezug auf das Datenfeld der Komponente. Rein visuelle Komponenten, welche keinen Wert besitzen müssen diese Methoden nicht implementieren.
Methode |
Erklärung |
populateValue( value, isNew ) |
Wird gerufen, wenn der Wert (value) der Komponente über die Elementdaten (Datenfeld) gesetzt wird. |
changeValue( value ) |
Wird gerufen, wenn der Wert (value) der Komponente unabhängig vom Datenfeld gesetzt werden soll. |
retrieveValue( ) |
Liefert den Wert der Komponente zurück |
retrieveDummyValue( ) |
Liefert Dummydaten z.B. für die Strukturexport Funktion, wenn sich der Editor nicht im Testmodus befindet |
Wichtig: Die oben genannten Methoden zum Setzen des Wertes werden ausschließlich vom Formular-Renderer gerufen.
Eine interne Änderung des Wertes (z.B. durch die Eingabe eines Textes durch den Benutzer) wird ausschließlich über die "notifyValueChange( )" Methode des View-Kontexts (FreeComponentContext) signalisiert.
Um als Konfigurator auf den "Geändert" Verhaltensauslöser reagieren zu können, muss das Event "CHANGED" explizit von der Komponente angeboten werden (siehe nachfolgendes Kapitel).
Events, Verhalten und Actions
Abgesehen vom Lesen und Schreiben des Komponentenwertes findet die Kommunikation zwischen anderen Elementen und der "Freien Komponente" ausschließlich über Events, Verhalten und Actions statt.
Welche Ereignisse die Komponente anbietet, wird in der Methode getEventNames( ) definiert. In der Sprachverwaltung können diese auch über das Bundle "de.lobster.fre.model.EventTypeEnum" lokalisiert werden.
Sämtliche angebotenen Ereignisse stehen folglich auch in der Verhaltenskonfiguration im Formulareditor zur Verfügung. Beispiel:
Controller |
Verhaltenskonfiguration |
getEventNames() { return [ 'MY_CUSTOM_EVENT1' , 'MY_CUSTOM_EVENT2' ]; } |
|
Zum Auslösen eines Events kann die Methode "dispatchEvent( )" des View-Kontexts ( siehe onViewInit( context ) ) gerufen werden. Beispiel:
onViewInit( context ) {
this
.context = context;
}
someViewEventHandler() {
if
( !
this
.context )
return
;
this
.context.dispatchEvent(
'MY_CUSTOM_EVENT1'
,
/* some event data */
12345 );
}
Hinweis: Es ist zu empfehlen, proprietäre Events mit einem Prefix zu versehen, damit diese nicht mit nativen Eventnamen kollidieren.
Folgende native Events, welche nicht automatisch angeboten werden, sind bislang definiert:
Eventname |
Beschreibung |
Muss vom Controller gerufen werden? |
CHANGED |
Der Wert der Komponente hat sich geändert. |
Wird über context.notifyValueChange() automatisch ausgelöst |
FOCUS_OUT |
Die Komponente hat den Fokus verloren |
Ja |
CHANGED_AND_FOCUS_OUT |
Die Komponente hat den Fokus verloren und der Wert seit dem letzten Fokussieren hat sich geändert. |
Nein |
CLICKED |
Die Komponente oder eine ihrer Subteile wurde angeklickt |
Ja |
F[1-12]_PRESSED |
Die entsprechende F-Taste wurde gedrückt |
Ja |
ENTER_PRESSED |
Die Entertaste wurde gedrückt |
Ja |
Zusammenfassend steuern Events den Ausgangsweg der Komponentenkommunikation.
Verhalten stellen eine weitere Möglichkeit dar, Daten aus der Kompontente zu lesen. Analog zu den Events legt die Controller-Methode "getBehaviourNames()" fest, welche Verhalten von der Komponente gehandelt werden.
Mit der Verhaltensaktion "Freies Komponentenverhalten" (nur kompatibel mit "Freien Komponenten") kann dann das gewünschte Verhalten der Komponente ausgeführt werden.
Die Daten des Verhaltens stehen dabei im Verhaltenhandler des Controllers zur Verfügung.
Beispiel:
Controller |
Verhaltenskonfiguration |
getBehaviourNames() { return [ 'MY_BEHAVIOUR1' , 'MY_BEHAVIOUR2' ]; } |
|
Tipp: Die aufgelisteten Verhalten können in der Sprachverwaltung über das Bundle freeComponentBehaviours lokalisiert werden.
Sobald das "Freies Komponentenverhalten" ausgeführt wird, wird die Methode "handleBehaviour( )" des Controllers gerufen und das Verhalten entsprechend übergeben.
handleBehaviour( b ) {
switch
( a.name ) {
case
'MY_BEHAVIOUR1'
:
// handle action
console.log(
"MY_BEHAVIOUR1"
, a.value );
return
Promise.resolve( { result:
true
, value:
"My Behaviour 1"
} );
break
;
case
'MY_BEHAVIOUR2'
:
// handle action
return
Promise.resolve( { result:
false
, value:
"My Behaviour 2"
} );
break
;
}
}
Die übergebenen Daten sind vom Typ "FreeComponentBehaviour", deren Struktur wie folgt definiert ist:
interface FreeComponentBehaviour {
/**
* The name of the behaviour
*/
name: string;
/**
* The behaviour input value
*/
value: any;
}
Actions stellen hingegen das Pendant zu den Events und Verhlaten, also den Eingangsweg der Komponente, dar. Analog zu den Events legt die Controller-Methode "getActionNames()" fest, welche Actions von der Komponente gehandelt werden.
Mit der Verhaltensaktion "Freie Komponentenaktion" (nur kompatibel mit "Freien Komponenten") kann dann die gewünschte Action an die Komponente gefeuert werden.
Die Daten des Verhaltens stehen dabei im Actionhandler des Controllers zur Verfügung.
Beispiel:
Controller |
Aktionskonfiguration |
getActionNames() { return [ 'MY_ACTION1' , 'MY_ACTION2' ]; } |
|
Tipp: Die aufgelisteten Aktionen können in der Sprachverwaltung über das Bundle freeComponentActions lokalisiert werden.
Sobald die "Freie Komponentenaktion" ausgeführt wird, wird die Methode "handleAction( )" des Controllers gerufen und die Aktion entsprechend übergeben.
handleAction( a ) {
switch
( a.name ) {
case
'MY_ACTION1'
:
// handle action
console.log(
"MY_ACTION1"
, a.value );
break
;
case
'MY_ACTION2'
:
// handle action
break
;
}
}
Die übergebene Action ist vom Typ "FreeComponentAction", deren Struktur wie folgt definiert ist:
interface FreeComponentAction {
/**
* The name of the action
*/
name: string;
/**
* The action value (usually the value passed by the behaviour)
*/
value: any;
/**
* The kind of interaction which triggered the action (e.g. 'MANUAL_CHANGED' if the user triggered the behaviour)
*/
changeKind: string;
}
Aufräumen/Zerstören der Komponente
Wird die Komponente nicht länger dargestellt (z.B. beim Einklappen eines ausklappbaren Containers), so wird die Controller-Methode "onViewDestroy( )" gerufen. Hier sollten sämtliche Element Eventhandler und Observer getrennt werden.
Der View-Kontext wurde dann auch zerstört und sollte nicht mehr verwendet werden. Tipp: Sobald die Komponente wieder dargestellt wird, wird "onViewInit()" wieder gerufen.
Wird die gesamte Komponente nicht mehr benötigt (z.B. beim Löschen eines wiederholenden Elementes), so wird zuerst "onViewDestroy( )" und dann "onDestroy( )" gerufen. Hier sollten dann sämtliche "aufräumende" Schritte durchgeführt werden.
View (HTML)
Der View Teil der Komponente wird mit HTML Code definiert. Dieser Code wird vom Hostelement als inner HTML geladen. Es gelten hierbei sämtliche vom HTML Standard vorgegebenen Regeln und Browser spezifische Eigenarten.
Einziger Sonderfall hierbei ist der Zugriff auf den Controller direkt vom Markup aus. Hierfür wurde das Schlüsselwort "$controller" eingeführt, welches in Javascriptteilen des Markups auf die Controller-Instanz verweist.
Beispiel:
Beispielcode |
Ergebnis |
Beispiel View
< button onClick = "$controller.onButtonClick(event)" >Click me!</ button > Beispiel Controller
class MyComponent { constructor( logger ) { this .logger = logger; } onButtonClick( e ) { this .logger.warn( "Click event" , e ); // logs into the browser console e.target.setAttribute( "color" , "warn" ); // sets the button color to "warn" } } return MyComponent; |
|
Auf das Hostelement kann nach der Initialisierung der View über den View-Kontext zugegriffen werden ( siehe onViewInit( context ) ).
Hinweis: Durch das Einfügen des View-Codes über innerHTML führen Browser i.d.R. keine Script Tags aus. Diese müssten dann manuell über DOM Funktionen (Element.appendChild()) nachgereicht werden.
Wichtig: Das Markup der Komponente wird bezüglich CSS Klassen, IDs und Attribute unverändert in den DOM der Applikation eingehängt. Hier findet keine Encapsulation/Scoping statt. Wird also ein Attribut im View-Code verwendet, so wird dieses redundant für jede Instanz der Komponente im DOM vorkommen.
Negativbeispiel:
<
div
id
=
"my-component-element"
></
div
>
onViewInit( context ) {
// DANGER! This will fetch the first element with id "my-component-element" even if this is the second or nth instance of the component!
var
containerEle = document.querySelector(
"#my-component-element"
);
}
In diesem Negativbeispiel kommen bei drei Instanzen der Freien Komponente auch drei Elemente im DOM vor, welche jeweils die selbe ID "my-component-element" haben.
Auf id Attribute sollte daher ohnehin verzichtet werden.
Positivbeispiel:
<
div
class
=
"my-component-element"
></
div
>
onViewInit( context ) {
// SAFE: Fetches only the element with class "my-component-element" within the Free Component instance
var
containerEle = context.element(
".my-component-element"
);
}
Über die Kontextfunktionen (FreeComponentContext) element(query) und elements(query) kann gewährleistet werden, dass nur Elemente innerhalb der Freien Komponente selektiert werden.
Style (CSS)
Mithilfe von Cascading Style Sheet (CSS) Code kann die View beliebig stilistisch angepasst werden. Dabei gelten sämtliche Regeln und Einschränkungen des CSS Standards.
Einziger Sonderfall hierbei ist ein direkter Selektor auf das Hostelement. Hierfür wurde das Schlüsselwort ":host" eingeführt, welches durch einen Selektor auf das Hostelement ersetzt wird.
:host als Root-Selektor zu verwenden wird dringendst empfohlen, um Kollisionen mit bereits bestehenden Stylesheets zu vermeiden und um die Performance der CSS Engine zu steigern.
Beispiel:
Beispielcode |
Ergebnis |
Beispiel View
< span class = "heading" >Hello World!</ span > Beispiel Stylesheet
:host .heading { display : block ; background-color : «_v( primaryColor )»; color : «_v( primaryContrastColor )»; padding : 0.5em 1em ; } Tipp: Über den Platzhaltersyntax «_v( Style-Eigenschaft-Name )» kann direkt auf die Style Palette zugegriffen werden. Im Controllercode kann via FCUtils.getStyleVar( Style-Eigenschaft-Name ) auf die Style Palette zugegriffen werden (ab 4.7.5). |
|
Farbindikator
Farbindikatoren werden beispielsweise von Validierern oder Aktionen gesetzt.
Der Farbindikator der Komponente wird über das "indicator" Attribut des Hostelements (context.hostElement.getAttribute('indicator')) automatisch definiert. Das Attribut ist nicht vorhanden, wenn kein Farbindikator gesetzt wurde.
Indikatorwerte sind:
primary - Primärfarbe
secondary - Sekundärfarbe
warn - Warnungsindikator
error or invalid - Fehler/Ungültig Indikator
success - Erfolgsindikator
Lokalisierung
Mit Hilfe des Reiters "Ressourcen" können lokalisierte Werte aus der Sprachverwaltung deklariert werden, welche dann über ihre ID (1) verwendet werden können.
Die Deklaration einer Ressource besteht dabei aus ihrer bereits erwähnten internen ID (1) und der Angabe des Sprachverwaltungseintrags via Bundle- (2) und Resourcenamen (3).
Warum müssen Ressourcen separat deklariert werden?
Beim Exportieren/Importieren einer freien Komponente in ein anderes Lobster Data Platform / Orchestration-System muss bekannt sein, welche Sprachverwaltungseinträge in der Komponente verwendet werden, damit diese mit exportiert werden können.
Da Ressourcennamen hier auch programmatisch hergeleitet werden können, ist es unmöglich die verwendeten Ressourcen automatisch zu erkennen. Darum die explizite Deklaration.
Im Controller der Komponente können deklarierte Ressourcen dann über die Methode "rs( ID, default, arguments )" des View-Kontexts ( übergeben an onViewInit( context ) ) verwendet werden:
onViewInit( context ) {
this
.context = context;
var
hintValue =
this
.context.rs(
"hint"
);
// loads the declared resource with ID "hint"
// ...
}
Die Methode "rs" unterstützt folgende Parameter:
Parametername |
optional |
Erklärung |
resourceIdentifier: string |
nein |
Die ID der Ressource Deklaration |
defaultValue: string |
ja |
Ein Standardwert, falls die deklarierte Ressource nicht gefunden wurde |
args: any[ ] |
ja |
Eine Liste von Argumenten, welche in einen String umgewandelt werden und für Platzhalter im Ressourcetext in der entsprechenden Reihenfolge eingesetzt werden. |
localeCode: string |
ja |
Gibt den Locale-Code der gewünschten Sprache an (z.B.: "de_DE"). |
Lokalisierungen können auch direkt statisch im Viewteil (HTML) verwendet werden. Dafür wurde ein Platzhaltersyntax vereinbart, welcher beim Initialisieren der View entsprechend ausgewertet und ersetzt wird.
Syntax für die Verwendung von Ressourcen direkt im Markup: <[resourceIdentifier,defaultValue,arg0,arg1,arg2,arg3,...]> Dabei sind alle Parameter bis auf "resourceIdentifier" optional.
Beispiel:
<
span
class
=
"heading"
title="<[hint,no hint resource]>"><[mainHeading]></
span
>
Importieren von (externen) Bibliotheken und Modulen
Innerhalb des Controller-Codes ist die globale Funktion "syncImport( importPath )" verfügbar, welche das Importieren von bereits definierten Modulen erlaubt. Hierfür ist jedoch Kenntnis über die interne Lobster API vonnöten, welche nicht Gegenstand dieser Dokumentation ist.
Ebenfalls global Verfügbar ist das SystemJS Framework, welches über das Schlüsselwort "System" ansprechbar ist.
Fremdbibliotheken, welche direkt über das Einbetten eines Javascripts geladen werden können, sind im Reiter "Abhängigkeiten" anzugeben. Die Komponente wird erst dann initialisiert, wenn alle Abhängigkeiten geladen wurden.
Da die Reihenfolge der zu importierenden Bibliotheken in den meisten Fällen extrem wichtig ist, kann die Ladereihenfolge über die "Auf-" und "Ab-" Funktionen (1) beeinflusst werden.
Analog zu den Skripten können natürlich auch Stylesheet-Dateien geladen werden. Die "onViewInit( context )" Methode des Controllers wird erst dann gerufen, wenn alle angegebenen Stylesheets ohne Fehler geladen wurden.
Achtung: Werden strikte Content Security Policies konfiguriert (Systemkonfiguration WebAppManager.xml Response Headers), wird es zu Problemen beim Einbinden externer Bibliotheken kommen. Diese können ebenfalls über den Content-Security-Policy HTTP Header als Ausnahme eingetragen werden, jedoch kann es vorkommen, dass die verwendete Bibliothek selbst keinen strikten Content Security Policies folgt, z.B. wenn sie inline Code Execution (eval()/Function()) verwendet. Hier muss dann entweder eine passende Bibliothek gewählt oder die Sicherheitseinstellungen gelockert werden.
Logging
Dem Constructor des Controllers wird die ihm zugewiesene Logger-Instanz übergeben. Diese bietet die folgenden Loglevel Methoden an:
trace( ... messageParts: Object[] )
debug( ... messageParts: Object[] )
info( ... messageParts: Object[] )
warn( ... messageParts: Object[] )
error( ... messageParts: Object[] )
fatal( ... messageParts: Object[] )
Hinweis: Standardmäßig loggt der Client mindestens das Level "warn". Für "info", "trace" und "debug" muss der Browser URL der Parameter "debug" angehängt werden (z.B.: http://pro.server.address/?debug)
Es ist empfohlen nicht über console.log( ) zu loggen, sondern den Logger zu verwenden, um zusätzliche nützliche Informationen wie z.B. die Log-Quelle oder den Zeitstempel zu erhalten und ein einheitliches Konsolenbild zu wahren.
Besonderheiten und Tipps
Debugging von Freien Komponenten
Der Controller-Code einer freien Komponente kann direkt in den Entwickler-Tools des Web-Browsers (i.d.R. F12) gedebugged werden.
Es gibt allerdings keinen direkten oder offensichtlichen Einsprungspunkt in den Code. Daher kann ein kleiner Trick genutzt werden.
Sobald im Controller-Code eine Konsolenausgabe getätigt wird, löst der Browser die Zeile der Ausgabe innerhalb der virtuellen Komponente auf.
In den meisten Browsern kann direkt, durch einen Klick auf diese Zeilenangabe, zur entsprechenden Stelle navigiert werden. Hier lassen sich dann auch sämtliche Debugger-Funktionen des Browsers (inklusive Breakpoints) nutzen.
Beispiel basierend auf dem integrierten Beispielcode (Einstellungen → Beispiel laden)
In der ersten Zeile des Controller-Codes wird simpel die Anweisung "console.log( 'DEBUG' )" ausgeführt, welche ohne Umwege einen Eintrag in die Browserkonsole schreibt.
Durch Klicken auf die Zeilenreferenz springt der Debugger des Browsers direkt in den Controller-Code, an die Stelle an der "console.log()" gerufen wurde.
Solange der Client nicht neu gestartet und der Code der Komponente nicht verändert wird, können hier persistente Breakpoints gesetzt werden.
Der Screenshot oben zeigt einen ausgelösten Breakpoint in Zeile 16 des Controller-Codes.
Das Aussehen des Debuggers variiert von Browser zu Browser, bietet aber in der Regel die selben Funktionen.
Arbeiten mit Listentypen
Formularbausteine und Elemente können verschiedene Listentypen verarbeiten und zurückliefern. Darauf sollte beim Entwickeln einer Freien Komponente geachtet werden. Die Daten eines Wiederholendes Element Containers liefern beispielsweise initial eine ArrayCollection, welche das IList interface implementiert. Bei der rein lesenden Verarbeitung einer solchen Collection wird simpel das Verwenden der toArray(false) Methode empfohlen. Der Parameter false sagt dabei aus, dass keine Kopie des internen Puffers zurückgeliefert werden soll, sondern das Original.
/**
* The number of elements in the list
*/
length: number;
/**
* Adds an item to the list and returns the
* new length of the list or 0 if the item was not added
*
* @param item The item which will be added
* @returns The new length of the list or 0 if the item was not added
*/
addItem(item: T): number;
/**
* Inserts an item at the given index
*
* @param item The item which will be inserted
* @param index The insert position
* @returns The new length of the list or 0 if the item was not added
*/
addItemAt(item: T, index: number): void;
/**
* Returns the item at the given position
*
* @param index The index of the item
* @returns The item at the given position
*/
getItemAt(index: number): T;
/**
* Returns the index of the given item or -1
* if the item is not contained in this collection.
* How the items are compared is defined by the 'useEquals' parameter (see useEquals parameter)
*
* @param item The item
* @param start The start index at which to begin the search
* @param useEquals If true, the element will be searched by using Env.areEqual()
* @returns The index of the given item or -1 if it is not contained
*/
getItemIndex(item: T, start?: number, useEquals?: boolean): number;
/**
* Determines whether the given item is contained in this ArrayCollection or not.
* How the items are compared is defined by the 'useEquals' parameter.
* If it is false (default), the simple indexOf(item) >= 0 condition will be checked.
* If it is true, an item match is considered through Env.areEqual().
*
* @param item The item which will be checked
* @param useEquals If true, the element will be searched by using Env.areEqual()
* @returns True if the given item already exists in this ArrayCollection, else false
*/
contains(item: T, useEquals?: boolean): boolean;
/**
* Removes all items from this list
*/
clear(): void;
/**
* Removes the item at the given index and
* returns it
*
* @param The index of the item
* @returns The removed item
*/
removeItemAt(index: number): T;
/**
* Removes the given item from the collection
* and returns the index on which it was removed.
* If the returned index is -1 the item was not in the collection.
* How the items are compared is defined by the 'useEquals' parameter (see useEquals parameter)
*
* @param item The item which will be removed
* @param start The start index at which to begin the search
* @param useEquals If true, the element will be searched by using Env.areEqual()
* @returns The index on which the item was removed or -1 if it was not in the collection
*/
removeItem(item: T, start?: number, useEquals?: boolean): number;
/**
* Replaces the item at the given index with the
* given one and returns the replaced item
*
* @param item The item which will be set
* @param index The index of the replaced item
* @returns The replaced item
*/
setItemAt(item: T, index: number): T;
/**
* Replaces the item at the given index with the
* given one and returns the replaced item
*
* @param index The old ( current ) index of the item
* @param newIndex The new position
* @returns The length of the list or 0 if the indexes was out of range
*/
moveItem(index: number, newIndex: number): number;
/**
* Writes this list to a native array and returns it
*
* @param copy Indicates whether the returned array is a copy of the source or the source array itself (defaults to true)
* @returns An array representation of this list (as copy)
*/
toArray( copy?: boolean ): T[];
/**
* Iterates through the list and calls the given callback function with three parameters:
* - item The current item
* - index The current index
* - list The list itself
*
* If one of the callback calls returns a value that is not undefined, the forEach loop will break and return this value.
*
* @param callback A callback function which will be called for each element in the list
* @param thisArg An optional parameter to define the 'this' scope which will be set to call the callback
* @returns The return value that has been returned by any of the callback calls or undefined if either undefined was returned or no callback returned anything
*/
forEach(callback: (item: T, index: number, list: IList<T>) => void, thisArg?: Object): any;
Hinweis: Zusätzlich stehen im Controller-Code Helfer-Utilities zur Verfügung, welche auch den Umgang mit Listen erleichtern und normalisieren. Diese Utilities sind über das Symbol FCUtils zugreifbar und werden im Unterkapitel "Freie Komponenten Utils" näher beschrieben.
Hier kann das Arbeiten mit Arrays und IList Objekten vereinheitlicht werden ohne dass auf den exakten Typen der Liste geachtet werden muss.
FCUtils.isCollection( anyData );
// returns true if data is an IList or an array
FCUtils.toArray( collection );
// returns collection as array if collection is an IList or an array
FCUtils.getItemAt( collection , i );
// returns the item in collection at index i
FCUtils.setItemAt( collection , item, i );
// sets the item in collection at index i
FCUtils.removeItem( collection , item );
// removes a specific item from collection
FCUtils.removeItemAt( collection , i );
// removes the item at index i from collection
FCUtils.addItem( collection , item );
// adds item to collection
FCUtils.addItemAt( collection , item, i );
// adds item to collection at index i
FCUtils.forEach( collection, cb );
// iterates through the collection calling the given callback function for each item (cb(item, index))
Datenprovider
Es gibt im Umfeld des Formularrenderers Bausteine, welche einen sogenannten Datenprovider liefern (z.B. LocalListDataProvider). So ein Provider unterstützt Datenpaging, was über die Methoden set/getOffset(), set/getMaxResults() und getOverallCount() realisiert werden kann.
Die Methode getItems() gibt dabei die Einträge der aktuell geladenen Seite zurück. Ist getOverallCount() größer als der Offset plus die Anzahl dieser Items, können noch weitere Einträge über setOffset( getOffset() + getMaxResults() ) gefolgt von update() nachgeladen werden. Sämtliche Datenprovider implementieren das IListDataProvider Interface:
/**
* Sets the item offset for the required page.
* e.g. Max results is 20 and page 2 is required, the offset would be set to 20
*
* @param value The item offset within the overall item set
* @returns This IListDataProvider instance
*/
setOffset(value: number): IListDataProvider<T>;
/**
* Returns the item offset for the required page.
* e.g. Max results is 20 and page 2 is required, the offset would be set to 20
*/
getOffset(): number;
/**
* Sets the maximum number of items per item page (set to 0 if paging is not required)
*
* @param value The maximum number of items per item page or 0 to turn off paging
* @returns This IListDataProvider instance
*/
setMaxResults(value: number): IListDataProvider<T>;
/**
* Returns the maximum number of items per item page (0 if paging is turned off)
*
* @returns The maximum number of items per item page (0 if paging is turned off)
*/
getMaxResults(): number;
/**
* Returns the overall number of items which would have been fetched with the current sort and filter configuration
* if paging was turned off
*
* @returns The overall number of items for the current sort and fitler configuration regardless of paging parameters
* @see getItemCount()
*/
getOverallCount(): number;
/**
* Returns the items of the current page as array
*/
getItems(): T[];
/**
* Updates the data by applying the filters, sorts and paging parameters.
* The result will be stored in the internal items array ( see getItems() )
*
* @returns A Promise resolving with this IListDataProvider instance once the data is available
*/
update(): Promise<IListDataProvider<T>>;