Using and Creating Widgets

Widgets are re-usable control elements for graphical user interfaces of web applications, e.g. calendar, toolbars, menus, RSS feeds or tables.

The INUBIT Widget framework enables you to create your own widgets. For your widgets, you can, for example, use the functions of the Ext JS framework that is delivered with the INUBIT software. The Ext JS framework is a client-side JavaScript or Ajax framework and offers an extensive collection of control elements.

The INUBIT software scope of delivery includes the ready-to-use Grid widget. This widget displays the data as a table and offers, among other functions, paging and sorting on the basis of the Ext JS grid, for example:

module guide 847 1

Functional principle

A widget consists of HTML, JavaScript and CSS code. In the INUBIT software, this code is generated dynamically in the execution of the task generator, just like the normal form in which the widget is embedded:

  • The XML structure of the form that is displayed in the Form mapping tab of the task generator also contains the XML structure of the widget. For the included Grid widget, for example, an XML table structure is generated which contains additional data for the configuration of the table, such as for paging, the maximum number of data records displayed on a page.

    This XML structure is usually filled dynamically with data from the input message.

  • A widget-specific XSLT style sheet generates the HTML code from the XML structure. This HTML code is then displayed in the form in the browser. In addition to the XSLT commands, the widget-specific XSLT style sheet also contains JavaScript and CSS code used to implement the functionality and the layout of the widget.

The XML structure and the XSLT style sheet of the widget are stored in the repository. Hence, the XML structure of the form remains compact and readable in the task generator and the widget can be re-used in other task generators.

Just like in other GUI components, widgets are integrated into a form using Drag-and-Drop.

You can use the same widget several times on the same page even on different panels.

If usage of multiple widgets causing rendering issue, make sure to use unique names in each component or meta (if present).

Using and Configuring the Grid Widget in the Task Generator

The Grid widget enables you to display your web application data in table form. The Grid widget is based on the functions of the Ext JS framework and offers the following features:

Proceed as follows

  1. In the task generator, display the Form mapping tab.

  2. Drag the Grid icon from the widget bar of the designer into its work area:

module guide 849 0

The XML target area shows the expected XML structure of the grid:

module guide 849 1

You can statically or dynamically fill the XML structure of the grid with data. The following sections provide some examples.

Refer to:

Task Generator Example: Dynamic Table with Client-Side Sorting without Paging

The following graphic shows the form mapping of a table that is filled through XML input messages. The number of rows and columns is defined by the XML input messages:

module guide 850 0

Sorting by table columns is available automatically as long as the sortable attribute of the Column elements has the value true.

Task Generator Example: Dynamic Table with Paging and Server-Side Sorting

In order to use the paging mechanism and server-side sorting, it must be possible to load the data in the Grid widget from a data source.

To do so, the workflow that implements your Grid widget as a portlet must be structured as in the following graphic:

module guide 850 1

The task generator that generates the grid widget is connected to the data source via a path: In the depicted workflow, this path starts with the WFC_JumpTarget Workflow Connector and leads to a Database Connector in a sub-workflow, and from there back to the task generator. The branch-in point in this path, that is, the ID of the first module, is entered in the Grid widget.

User actions such as the display of the Grid portlet or the sorting of data trigger a request in the task generator. The content of the request depends on the user action: For a sort action, for example, the request contains the following information:

  • First data record that is supposed to be displayed on the page

  • Number of all data records that are supposed to be displayed on the page

  • Selected sorting criterion (ascending/descending)

The request is transferred to the first module in the path and forwarded from there. The Database Connector evaluates the request and makes the data available. Following that, the requested data is sent to the task generator and displayed in the table.

Defining the branch-in point in the path to the data source

The following graphic shows the form mapping of the table:

module guide 851 1

You define the first module in the path to the data source in the Properties tab using the jump attribute of the widget element: A click a blank cell next to the attribute opens a dialog in which you choose the module.

The module must be located in the same workflow as the task generator!

Whether and which modules are located between the branch-in point and the data source depends on whether the request has to be formatted further. This can be necessary, for example, if you want to have the data sorted on the server side.

Defining the number of displayed data records

You define the number of data records per page in the Properties tab using the start and limit attributes of the Header element:

  • start: Number of the first displayed data record

  • limit: Number of displayed data records

Configuring server-side sorting

You have to implement the sorting manually. If you are using a database as the data source, you can implement the sort criteria, for example, by using the corresponding SQL commands.

Sorting criteria are included in the request that is sent to the data source. You can display the request by opening the watch point preceding the data source in watch mode.

Creating a Widget and Integrating it into the INUBIT Software in the Task Generator

Files required

To develop your own widget, you have to create the following files:

Name Function

Widget.xml

XML structure of the widgets. This has two functions:

  • It forms the structure in which the data from the input message is displayed. In the supplied Grid widget, for example, a table structure is generated.

  • Used for configuring the widget:

    In the XML structure, you can define all parameters that are, for example, required by the JavaScript code of the widget. You can enter the parameter values in the Properties tab in the designer, just like for all other GUI components in the designer.

    For the supplied Grid widget, for example, the maximum number of visible data records per page is defined using the start and limit attributes of the header element.

Widget.xsl

XSLT style sheet for displaying the widget in the web application. It contains, e.g., HTML, JavaScript code (possibly Ext JS framework code) for the functions of the widget and layout information (as CSS references).

Widget_bpc.xsl

XSLT style sheet for displaying the widget in the web application in Business Process Center (BPC). It contains, e.g., HTML, JavaScript code (possibly Ext JS framework code) for the functions of the widget and layout information (as CSS references).

Widget.gif

Graphic for the widget icon in the toolbar of the task generator. You can use the icon to drag & drop the XML structure to the desired position on the form. The graphic must be available in gif-format and have a max. height of 14 pixels.

Widget.jar

Optional. Java class for displaying the widget in the Form mapping tab of the task generator. The class must implement com.inubit.ibis.configuration.form.XulWidget.

If you are not providing a Java class, you must remove the uiClass attribute in the wd:Description element in the XML structure of your widget (Widget.xml).

If no Java class is available, the designer shows a placeholder instead of the widget. This does not affect the functionality of the widget.

The files should be made available in the global area of the repository at Global > System > Widgets in a separate folder and must be readable anonymously. Alternatively, the files can also be made available in any other repository folder outside the Global folder.

Refer to

  • Ext JS examples:

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

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

  • Ext JS homepage, refer to https://www.sencha.com/

Proceed as follows

  1. Use the Widget.xml and Widget.xsl (Widget_bpc.xsl for BPC portal users) files of the supplied Grid widget as templates: Download these files from the Repository directory at Global > System > Widgets > Grid.

  2. Open the Widget.xml file and adjust the XML structure. Note the comments in the file; they explain the use of the different XML elements.

  3. Adapt the Widget.xsl file (Widget_bpc.xsl for BPC portal users). Also, note the comments in this file.

  4. Generate an icon called Widget.gif.

  5. In the Repository, create a new directory underneath Global > System> Widgets. Add all files to this directory. Make sure that the files can be read anonymously.

  6. If you want to use the widget immediately, you have to make the icon visible first: Open a task generator, in the Module Editor on the local side, display the Form mapping tab and choose Burger menu> View > Reload external elements.

    Otherwise, the widget is only available once you restart the INUBIT Workbench.

Example: Date picker widget

A date picker widget makes it easier to enter a date:

module guide 853 0

The date picker uses the Ext JS framework, and it is based on the DateField class.

Example: 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>

Example: Widget.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 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>

Example: 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 a "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>