Widgets verwenden und erstellen

Widgets sind wiederverwendbare Steuerelemente für grafische Benutzeroberflächen von Web-Applikationen, wie z.B. Kalender, Toolbars, Menüs, RSS-Feeds oder Tabellen.

Das INUBIT Widget Framework bietet Ihnen die Möglichkeit, eigene Widgets zu erstellen. Für Ihre Widgets können Sie z.B. die Funktionen des Ext JS-Frameworks nutzen, das zusammen mit der INUBIT-Software ausgeliefert wird. Das Ext JS-Framework ist ein client-seitiges JavaScript- bzw. Ajax-Framework und bietet eine umfangreiche Sammlung von Steuerelementen.

Im Lieferumfang der INUBIT-Software ist das einsatzbereite Widget Grid enthalten. Dieses Widget stellt Daten als Tabelle dar und bietet u.a. Paging und Sortierung auf Basis des Ext JS-Grids, z.B.:

module guide 875 1

Funktionsprinzip

Ein Widget besteht aus HTML-, JavaScript- und CSS-Code. In der INUBIT-Software wird dieser Code bei der Ausführung des Task Generators genauso dynamisch erzeugt wie das normale Formular, in dem das Widget eingebettet ist:

  • Die XML-Struktur des Formulars, die im Register Formular Mapping des Task Generators angezeigt wird, enthält auch die XML-Struktur des Widgets. Für das mitgelieferte Grid-Widget wird z.B. eine XML-Tabellenstruktur aufgebaut, die zusätzlich Daten zur Konfiguration der Tabelle enthält, wie z.B. für das Paging, die maximale Anzahl der Datensätze auf einer Seite.

    Meist wird diese XML-Struktur dynamisch mit Daten aus der Eingangsnachricht gefüllt.

  • Ein widget-spezifisches XSLT-Stylesheet erzeugt aus der XML-Struktur den HTML-Code, der im Browser im Formular angezeigt wird. Das widget-spezifische XSLT-Stylesheet enthält neben XSLT-Befehlen auch JavaScript- und CSS-Code, mit dem Funktionalität und Layout des Widgets realisiert werden.

Die XML-Struktur und das XSLT-Stylesheet des Widgets werden im Repository abgelegt. Damit bleibt die XML-Struktur des Formulars im Task Generator kompakt und lesbar und das Widget kann in anderen Task Generatoren wiederverwendet werden.

Widgets werden, genau wie die anderen GUI-Komponenten, per Drag’n’Drop in ein Formular integriert.

Sie können dasselbe Widget mehrfach auf einer Seite verwenden, auch in verschiedenen Panels.

Falls die Verwendung mehrerer Widgets Rendering-Probleme verursacht, stellen Sie sicher, dass Sie eindeutige Namen in jeder Komponente oder Meta (falls vorhanden) verwenden.

Grid-Widget verwenden und konfigurieren

Mit dem Grid-Widget können Sie in Ihrer Web-Applikationen Daten in Tabellenform darstellen. Das Grid-Widget basiert auf Funktionen des Ext JS-Frameworks und bietet folgende Features:

So gehen Sie vor

  1. Zeigen Sie im Task Generator das Register Formular Mapping an.

  2. Ziehen Sie das Grid-Icon aus der Widget-Leiste des Designers in dessen Arbeitsfläche:

    module guide 877 0

    Im Bereich XML-Ziel wird die erwartete XML-Struktur des Grids angezeigt:

    module guide 877 1

Sie können die XML-Struktur des Grids statisch oder dynamisch mit Daten füllen.

Beispiele siehe:

Beispiel: Dynamische Tabelle mit client-seitiger Sortierung ohne Paging

Die folgende Abbildung zeigt das Formular Mapping einer Tabelle, die durch XML-Eingangsnachrichten befüllt wird. Die Anzahl der Reihen und Spalten wird durch die XML-Eingangsnachrichten definiert:

module guide 878 0

Die Sortierung nach Tabellenspalten steht automatisch zur Verfügung, solange das sortable-Attribut des Column- Elements den Wert true hat.

Beispiel: Dynamische Tabelle mit Paging und server-seitiger Sortierung

Der Einsatz des Paging-Mechanismus und der server-seitigen Sortierung setzt voraus, dass die Daten im Grid-Widget aus einer Datenquelle geladen werden können.

Dazu muss der Workflow, der Ihr Grid-Widget als Portlet realisiert, wie in der folgenden Abbildung aufgebaut sein:

module guide 878 1

Der Task Generator, welcher das Grid-Widget erzeugt, ist über einen Pfad mit der Datenquelle verbunden: In dem abgebildeten Workflow beginnt dieser Pfad bei dem Workflow Connector WFC_JumpTarget und führt zu einem Database Connector in einem Subworkflow, von dort aus wieder zurück zum Task Generator. Die Einsprungstelle in diesen Pfad, also die ID des ersten Moduls, wird im Grid-Widget eingegeben.

Benutzeraktionen, wie z.B. die Anzeige des Grid-Portlets oder das Sortieren der Daten, lösen bei dem Task Generator einen Request aus. Der Inhalt des Requests ist abhängig von der Benutzeraktion: Bei einer Sortieraktion enthält der Request z.B. folgende Informationen:

  • Erster Datensatz, der auf der Seite angezeigt werden sollen

  • Anzahl aller Datensätze, die auf der Seite angezeigt werden sollen

  • Gewähltes Sortierkriterium (aufsteigend/absteigend)

Der Request wird an das erste Modul im Pfad übergeben und von diesem weitergeleitet. Der Database Connector wertet den Request aus und stellt die Daten bereit. Danach werden die angeforderten Daten an den Task Generator gesendet und in der Tabelle angezeigt.

Einsprungstelle in Pfad zur Datenquelle festlegen

Die folgende Abbildung zeigt das Formular Mapping der Tabelle:

module guide 879 1

Das erste Modul im Pfad zur Datenquelle definieren Sie im Register Eigenschaften über das jump-Attribut des Widget- Elements: Ein Klick auf die leere Zelle neben dem Attribut öffnet einen Dialog, in dem Sie das Modul auswählen.

Das Modul muss in demselben Workflow wie der Task Generator liegen!

Ob und welche Module zwischen der Einsprungstelle und der Datenquelle liegen, ist abhängig davon, ob der Request weiter aufbereitetet werden muss. Dies kann z.B. nötig sein, wenn Sie die Daten server-seitig sortieren lassen möchten.

Anzahl der angezeigten Datensätze festlegen

Die Anzahl der Datensätze pro Seite legen Sie im Register Eigenschaften über die start- und limit-Attribute des Header-Elements fest:

  • start: Nummer des ersten angezeigten Datensatzes

  • limit: Anzahl der angezeigten Datensätze

Server-seitige Sortierung konfigurieren

Die Sortierung müssen Sie manuell implementieren. Wenn Sie als Datenquelle eine Datenbank einsetzen, dann können Sie die Sortierkriterien z.B. durch entsprechende SQL-Befehle umsetzen.

Die Sortierkriterien sind in dem Request enthalten, der an die Datenquelle gesendet wird. Sie können den Request anzeigen, in dem Sie im Watch-Modus den Watchpoint vor der Datenquelle öffnen.

Widget erstellen und in die INUBIT-Software integrieren

Benötigte Dateien

Um ein eigenes Widget zu entwickeln, müssen Sie folgende Dateien erstellen:

Name Funktion

Widget.xml

XML-Struktur des Widgets. Diese hat zwei Funktionen:

  • Bildet die Struktur, in der Daten aus der Eingangsnachricht angezeigt werden. Bei dem mitgelieferten Grid-Widget wird z.B. eine Tabellenstruktur aufgebaut.

  • Dient der Konfiguration des Widgets:

    In der XML-Struktur können alle Parameter definiert werden, die z.B. der JavaScript-Code des Widgets benötigt. Die Parameterwerte können im Designer im Register Eigenschaften angegeben werden, genauso wie bei den anderen GUI-Komponenten im Designer.

    Beim mitgelieferten Grid-Widget wird z.B. die Anzahl der maximal sichtbaren Datensätze pro Seite über die start und limit-Attribute des Header-Elements definiert.

Widget.xsl

XSLT-Stylesheet für die Darstellung des Widgets in der Web-Applikation. Enthält z.B. HTML, JavaScript-Code (evtl. Ext JS-Framework-Code) für die Funktionalität des Widgets und Layout-Informationen (als CSS-Referenzen).

Widget_bpc.xsl

XSLT-Stylesheet zur Anzeige von Widgets in Web-Applikationen im Business Process Center (BPC). Es enthält z.B. HTML, JavaScript-Code (möglicherweise Code des Ext JS-Framework) für die Funktionen des Widgets und Layout-Informationen (als CSS-Referenzen).

Widget.gif

Grafik für das Widget-Icon in der Symbolleiste des Task Generators. Über das Icon kann die XML-Struktur per Drag-and-Drop an der gewünschten Position im Formular eingefügt werden. Die Grafik muss im gif-Format mit einer max. Höhe von 14 Pixel vorliegen.

Widget.jar

Optional. Java-Klasse für die Darstellung des Widgets im Reiter Formular Mapping des Task Generators. Die Klasse muss com.inubit.ibis.configuration.form.XulWidget implementieren.

Wenn Sie keine Java-Klasse bereitstellen, müssen Sie in der XML-Struktur Ihre Widgets (Widget.xml) bei dem Element wd:Description das Attribut uiClass entfernen.

Wenn keine Java-Klasse vorhanden ist, wird im Designer ein Platzhalter statt des Widgets angezeigt. Die Funktionalität des Widgets wird dadurch nicht beeinträchtigt.

Die Dateien sollten im globalen Bereich des Repositorys unter Global > System > Widgets in einem eigenen Ordner verfügbar gemacht werden und anonym lesbar sein. Alternativ dazu können die Dateien auch in irgendeinem anderen Repository-Ordner außerhalb des Repository-Ordners Global bereitgestellt werden.

Siehe

  • Ext JS-Beispiele:

    • Ext JS 6: https://<server>:<port>/html/js/extjs6/

    • Ext JS 7: https://<server>:<port>/html/js/extjs7/

  • Ext JS-Homepage siehe: https://www.sencha.com/

So gehen Sie vor

  1. Nutzen Sie die Dateien Widget.xml und Widget.xsl (Widget_bpc.xsl für BPC-Portalbenutzer) des mitgelieferten Grid-Widgets als Vorlagen: Laden Sie diese Dateien aus dem Repository-Verzeichnis Global > System > Widgets > Grid herunter.

  2. Öffnen Sie die Datei Widget.xml und passen Sie die XML-Struktur an. Beachten Sie dabei die Kommentare in der Datei, diese erläutern die Verwendung der verschiedenen XML-Elemente.

  3. Passen Sie die Datei Widget.xsl (Widget_bpc.xsl für BPC-Portalbenutzer) an. Beachten Sie auch in dieser Datei die Kommentare.

  4. Erzeugen Sie ein Icon mit dem Namen Widget.gif.

  5. Erzeugen Sie im Repository ein neues Verzeichnis unterhalb von Global > System> Widgets. Fügen Sie alle Dateien diesem Verzeichnis hinzu. Stellen Sie sicher, dass die Dateien anonym lesbar sind.

  6. Wenn Sie das neue Widget sofort einsetzen möchten, müssen Sie das Icon zuerst sichtbar machen: Öffnen Sie einen Task Generator, zim Modul-Editor auf Lokal-Seite zeigen Sie das Register Formular Mapping an und wählen Sie Burger-Menü > Ansicht > Externe Elemente neu laden.

    Ansonsten steht das Widget erst nach dem Neustart der INUBIT Workbench zur Verfügung.

Beispiel: Datepicker-Widget

Ein Datepicker vereinfacht die Eingabe eines Datums:

module guide 881 0

Der Datepicker nutzt das Ext JS-Framework und basiert auf der Klasse DateField.

Beispiel: Widget.xml

<?xml version="1.0" encoding="UTF-8"?>
<wd:Description xmlns:wd="http://inubit.com/widget/schema" type="ExtDateField"  uiClass="com.inubit.web.extjs.form.ExtDateFieldWidget" namespace="http://inubit.com/widget/ExtForm" prefix="">
    <Widget>
        <wd:Attributes>
            <!-- Keep in mind that, if you are using several date pickers in a form,
            every instance must have a unique name-->
            <name/>
        </wd:Attributes>
        <DateField>
            <wd:Attributes>
                <!-- The date field should be 300 px wide;
                    the text field can have all attributes of a "normal" text field -->
                <width>300</width>
            </wd:Attributes>
        </DateField>
        <Meta>
            <wd:Attributes>
                <!-- The format value can be defined in the designer and defines in which format the date
                    is displayed, permissible values are specified by the Ext JS framework -->
                <format/>
            </wd:Attributes>
        </Meta>
    </Widget>
</wd:Description>

Beispiel: Widget.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:isgrid="http://inubit.com/widget/ISGrid" xmlns:inubit="http://inubit.com/xsl" version="2.0">
    <xsl:output method="html" encoding="UTF-8"/>
    <xsl:template match="extform:Widget[@type='ExtDateField']">
        <xsl:variable name="wid" select="concat($ns,@name)"/>
        <xsl:variable name="tf">
            <TextField name="{@name}" xmlns="">
                <xsl:for-each select="extform:DateField/@*[name()!='name']"><xsl:attribute name="{local-name(.)}"><xsl:value-of select="."/></xsl:attribute></xsl:for-each>
                <xsl:value-of select="extform:DateField"/>
            </TextField>
        </xsl:variable>
        <script language="javascript" type="text/javascript">
            addCssIS('/html/js/extjs/resources/css/ext-all.css');
            addCssIS('/html/js/extjs/resources/css/xtheme-gray.css');
        </script>
        <xsl:apply-templates select="$tf/TextField"/>
        <script language="javascript" type="text/javascript">
            Ext.onReady(function(){
                try {
                    new Ext.form.DateField({
                        applyTo: '<xsl:value-of select="$wid"/>'
                        <xsl:if test="extform:DefaultTextField/@mandatory = 'true'">
                            ,allowBlank: false
                        </xsl:if>
                        <xsl:if test="extform:Meta/@format != ''">
                            ,format: '<xsl:value-of select="extform:Meta/@format"/>'
                        </xsl:if>
                    });
                }
                catch(e) {
                    showMessageIS('Error in ExtDateField widget!',e);
                }
            });
        </script>
    </xsl:template>
</xsl:stylesheet>

Beispiel: Widget_bpc.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:isgrid="http://inubit.com/widget/ISGrid" xmlns:inubit="http://inubit.com/xsl" version="2.0">
    <!-- The widget namespace must be identical with the one defined in the Widget.xml -->
    <!--$Id: Widget_bpc.xsl,v 1.0 2022/01/24 $-->
    <xsl:output method="html" encoding="UTF-8"/>
    <!-- need to set this params, will be filled from outside -->
    <xsl:param name="initDataISGrid"/>
    <xsl:param name="jumpDataISGrid"/>
    <!-- Dummy variables to test this script in an XSLT-Converter; they are set from outside on execution time -->
    <!--<xsl:variable name="proxy"></xsl:variable>-->
    <!--<xsl:variable name="lang"></xsl:variable>-->
    <!--<xsl:variable name="ns"></xsl:variable>-->
    <!--<xsl:function name="inubit:translate"><xsl:param name="element"/></xsl:function>-->
    <!-- The value of the type attribute must be the same as the widget type defined in the widget.xml -->
    <xsl:template match="isgrid:Widget[@type='ISGrid']">
        <!-- Portlet namespace und unique name of the widget are used as identifier -->
        <xsl:variable name="wid" select="concat($ns,@name)"/>
        <!-- div for the grid widget; EXT JS will fill it -->
        <div id="{$wid}">
            <input type="hidden" id="{concat($wid, '_content')}" name="{concat(@name, '_content')}" value=""/>
        </div>
        <!-- JavaScript and XSLT code of the widget; for this grid widget EXT JS code is used -->
        <script language="javascript" type="text/javascript">
            Ext.require([
            'Ext.data.*',
            'Ext.grid.*',
            'Ext.util.*',
            'Ext.toolbar.Paging',
            'Ext.tip.QuickTipManager'
            ]);
            Ext.onReady(function() {
            try {
            <!-- Count of columns is needed multiple times -->
            <xsl:variable name="columnCount" select="count(isgrid:Header/isgrid:Column)"/>
            <!-- Create the Data Model -->
            if(typeof(<xsl:value-of select="concat($ns,@id)"/>DataModel) == 'undefined'){
            Ext.define('<xsl:value-of select="concat($ns,@id)"/>DataModel', {
            extend: 'Ext.data.Model',
            proxy: {
            type: 'ajax',
            reader: 'xml'
            },
            fields: [
            <xsl:for-each select="isgrid:Header/isgrid:Column">
                {name: '<xsl:value-of select="inubit:translate(.)"/>', mapping: 'Column:nth(<xsl:value-of
                    select="position()"/>)'}
                <xsl:if test="position()&lt;$columnCount">,</xsl:if>
            </xsl:for-each>
            <xsl:choose>
                <xsl:when test="not(@itemId)">
                    ,{name: 'id', mapping: '@id' }
                </xsl:when>
                <xsl:otherwise>
                    ,{name: 'id', mapping: '@itemId' }
                </xsl:otherwise>
            </xsl:choose>
            ]
            })
            };
            <!-- Create the Data Store -->
            <xsl:value-of select="$wid"/>storeExtJsIS = Ext.create('Ext.data.Store', {
            <!-- need to sort over all data on the server side -->
            <xsl:if test="not(isgrid:Row)">remoteSort: true,</xsl:if>
            <xsl:if test="not(isgrid:Row)">pageSize: <xsl:value-of select="isgrid:Header/@limit"/>,
            </xsl:if>
            model: '<xsl:value-of select="concat($ns,@id)"/>DataModel',
            proxy: {
            type: 'ajax',
            <xsl:choose>
                <xsl:when test="isgrid:Row">
                    <!-- load the init data, that is mapped directly in the form -->
                    url: '<xsl:value-of select="$initDataISGrid"/><xsl:value-of select="@name"/>',
                </xsl:when>
                <xsl:otherwise>
                    <!-- load the first data from jump; this is for paging purpose -->
                    url: '<xsl:value-of select="$jumpDataISGrid"/><xsl:value-of select="@name"/>',
                </xsl:otherwise>
            </xsl:choose>
            reader: {
            type: 'xml',
            root: 'Widget',
            // records will have an "Row" tag
            record: 'Row',
            <xsl:choose>
                <xsl:when test="not(@itemId)">
                    idProperty: '@id',
                </xsl:when>
                <xsl:otherwise>
                    idProperty: '@itemId',
                </xsl:otherwise>
            </xsl:choose>
            totalProperty: '@total'
            },
            simpleSortMode : true
            }
            });
            <!-- If there are no rows, paging with a jump in the workflow is used to load data -->
            <xsl:if test="not(isgrid:Row)">
                <!-- paging bar -->
                var <xsl:value-of select="$wid"/>pagingBarExtJsIS = Ext.create('Ext.toolbar.Paging',{
                store: <xsl:value-of select="$wid"/>storeExtJsIS,
                displayInfo: true,
                emptyMsg: '-'
                });
            </xsl:if>
            <!-- Create the Grid -->
            if(Ext.Element.cache['<xsl:value-of select="$wid"/>']){
            Ext.Element.cache['<xsl:value-of select="$wid"/>'].destroy(); }
            <xsl:value-of select="$wid"/>gridExtJsIS = new Ext.grid.GridPanel({
            store: <xsl:value-of select="$wid"/>storeExtJsIS
            ,itemId: '<xsl:value-of select="$wid"/>gridExtJsIS'
            ,columns: [
            <xsl:for-each select="isgrid:Header/isgrid:Column">
                {header: '<xsl:value-of select="inubit:translate(.)"/>'
                <xsl:if test="@width">
                    ,width:
                    <xsl:value-of select="@width"/>
                </xsl:if>
                ,dataIndex: '<xsl:value-of select="inubit:translate(.)"/>'
                ,sortable: <xsl:value-of select="not(@sortable='false')"/>}
                <xsl:if test="position()&lt;$columnCount">,</xsl:if>
            </xsl:for-each>
            ]
            <xsl:if test="@enableRowDND='true'">,viewConfig: { plugins: { ptype: 'gridviewdragdrop' } }</xsl:if>
            <xsl:if test="@rowSelectable='true'">,selModel: { selType: 'checkboxmodel'<xsl:if
                    test="@singleRowSelection='true'">, mode : "SINGLE"</xsl:if>}
                ,listeners: {select: function(selModel, record, index, options) {
                var selectedrow = <xsl:value-of select="concat($wid, 'gridExtJsIS')"/>.getSelectionModel().getSelection();
                var result='';
                Ext.each(selectedrow, function (item) {
                result=result + "&lt;"+item.id+ "&gt;on&lt;/"+item.id+"&gt;";
                });
                document.getElementById('<xsl:value-of select="concat($wid, '_content')"/>').value =result; }}
            </xsl:if>
            ,renderTo: '<xsl:value-of select="$wid"/>',
            inubitBpcWebAppNs: '<xsl:value-of select="$ns"/>'
            ,
            <xsl:choose>
                ...
            </xsl:choose>
        </script>
    </xsl:template>
</xsl:stylesheet>