Create Forms in WebUI

Use the AIMMS WebUI Forms framework to create modern-looking forms for data entry.

Three basic actions are needed:

  • Select the identifiers for which you want to provide data

  • Write the code to perform checks

  • Draw the form on the screen by selecting a few widgets

This makes the AIMMS WebUI Forms framework an efficient method to create forms from the model builder’s perspective.

The AIMMS WebUI Forms framework follows CRUD and thus provides your users the ability to:

  • C - create new data

  • R - read existing data

  • U - update existing data

  • D - delete invalid data

This image below illustrates a form in the AIMMS WebUI.

../../_images/FormData-ModelIdentifiersExchange.png

New or modified data is entered (red rectangle), and once it passes the checks, it is copied to the actual model identifiers as if it were a single transaction.

To properly implement such a form, several identifiers are needed. Each of them inherits some properties from the corresponding model identifier. In addition, AIMMS code is needed for checking and for copying/deleting.

The AIMMS WebUI Forms framework minimizes effort for developers by creating the needed additional identifiers, and generating code for copying, deleting, and the repetitive part of checking.

Let’s use an example to show how to deploy this framework in order to create an advanced form. This example is part of an inventory management application, where data of a Stock Keeping Unit is entered and maintained.

  1. Identify the identifiers in the model storing the master data

  2. Create the declarations and procedures support the form

  3. Add procedure to create a new element

  4. Add procedure to check data for an element

  5. Link the callbacks to AIMMS WebUI Forms framework

  6. Draw the form on the WebUI canvas

  7. Add user-friendly labels to facilitate proper user entry

Identify the identifiers in the model storing the master data

The targets of the form to be created are the model identifiers in which we want to store validated data. We continue by declaring such identifiers. In our running example, stock keeping unit (SKU) data is maintained. In order to limit the size of the example, per SKU we only maintain the following properties:

  • manufacturer (name),

  • volume (liter),

  • color (in color set), and

  • stock (non-negative integer).

Thus we declare the corresponding model identifiers as follows:

 1DeclarationSection Stock_Keeping_Units {
 2    Set s_stockKeepingUnit {
 3        Index: i_sku;
 4    }
 5    StringParameter sp_manufacturer {
 6        IndexDomain: i_sku;
 7    }
 8    Parameter p_volume {
 9        IndexDomain: i_sku;
10        Unit: m3;
11    }
12    ElementParameter ep_color {
13        IndexDomain: i_sku;
14        Range: AllUsableColors;
15    }
16    Parameter p_stock {
17        IndexDomain: i_sku;
18        Range: {
19            {0..inf}
20        }
21    }
22}

Create the declarations and procedures

We need the following extra declarations in order to work with the forms:

  • When we are editing existing data, we want to know for which element in the set s_stockKeepingUnit that is. The WebUI forms uses a binary parameter for this. In our example we will call this binary parameter bp_SKUFORM_Selection(i_sku).

  • Each SKU needs a unique identification before it can be added to the set. This identification can simply be a unique number or a name. In our example we use a name. That is why we add an extra string parameter: sp_SKUFORM_InternalName(i_sku). Actually, this is just another property, modeled here via a string parameter.

  • A procedure to create a new element in the set s_stockKeepingUnit. In our running example, this procedure will be pr_SKUFORM_Create. This is a callback procedure; it will be called by the AIMMS WebUI Forms framework. This procedure is detailed in section Create a new element.

  • A procedure that validates the newly entered or modified data. In our running example, this will be pr_SKUFORM_Check. This is also a callback procedure; it will be called by the AIMMS WebUI Forms framework. This procedure is detailed in section Check data for an element.

  • A procedure that links the above declarations and initializes a WebUI Form. In our running example, this will be pr_SKUFORM_Setup. It needs to be called once by the application before the form is shown in the browser. This procedure is detailed in section Linking callbacks to WebUI Forms framework.

The above declarations are shown below, in the AIMMS model explorer:

../../_images/3-Procedure-callback-declarations.png

Both callbacks have an argument named formData. This argument communicates the strings entered by the application user. This argument is declared as follows:

1StringParameter formData {
2    IndexDomain: webui::ffn;
3    Property: Input;
4}

Here the index webui::ffn is an index in the set webui::AllFormFieldNames. This index and set are available in the AimmsWebUI system library and will be linked to the model identifiers later on.

The set webui::AllFormFieldNames is a subset of AllIdentifiers, which allows us to link easily to the model identifiers at hand.

In the following three steps we will discuss the selected details of these three procedures.

Create a new element

The procedure pr_SKUFORM_Create is expected to create a new element in the set for which the form is setup. In our running example that is s_stockKeepingUnit. You can use element names different from the literal text entered by the user, but our example does not.

This procedure has two arguments:

  • string parameter: formData(webui::ffn). This input argument passes the user entered input for each form field.

  • string parameter: sp_newSKUName. This output argument is the name of the element created in the set s_stockKeepingUnit.

Before this procedure is called, the name was already verified by a check procedure which we will discuss in the next section.

1SetElementAdd(s_stockKeepingUnit,ep_anSKU,
2             formData('sp_SKUFORM_InternalName'));
3sp_newSKUName := formData('sp_SKUFORM_InternalName');

Here, ep_anSKU is a local element parameter with range s_stockKeepingUnit.

Check data for an element

The check procedure pr_SKUFORM_Check is called as soon as we save the data. It has two arguments, an input argument that contains the strings entered by the user, and an output argument that contains any corresponding error messages about these strings. The data is only accepted if there are no errors.

This procedure has two arguments:

  • string parameter: formData(webui::ffn). This input argument passes the user entered input for each form field.

  • string parameter: validationErrors. This output argument contains, per field, the error messages (if any).

Selected checks of this procedure are discussed below.

To check the name entered, minimum length of 2, and specified at all:

The first if in the code below checks new element names.

A new name does not exist; and this corresponds to an empty bp_SKUFORM_Selection. The second if in the code below checks whether the name already exists.

1if (StringLength(formData('sp_SKUFORM_InternalName')) < 2) then
2    validationErrors('sp_SKUFORM_InternalName') := webui::CreateValidationError("validation-error-min-length");
3endif;
4
5if (formData('sp_SKUFORM_InternalName') = "form-enter-InternalName" ) then
6    validationErrors('sp_SKUFORM_InternalName') := webui::CreateValidationError("validation-error-required-field");
7endif;

Any errors are logged by the function webui::CreateValidationError.

Check that the new name does not override an existing element:

1if ( not exists[ i_sku | bp_SKUFORM_Selection(i_sku) ] ) then
2    if ( StringToElement(s_stockKeepingUnit, formData('sp_SKUFORM_InternalName')) ) then
3        validationErrors('sp_SKUFORM_InternalName') :=
4                      webui::CreateValidationError("validation-error-name-already-exists");
5    endif;
6endif;

Selected remarks about the above code:

  • On line 1: Check that we are creating a new element; there is no i_sku such that this i_sku is existing data being edited.

  • On line 2: StringToElement() returns the element in the set that is its first argument of the element with name that is its second argument. It returns the empty element if no such element can be found.

If both conditions of line 1 and line 2 are true, then an existing element is re-created, which is not allowed.

Check data of sku string property:

Next we check that the manufacturer is specified and the length is at least 3.

1if (StringLength(formData('sp_manufacturer')) < 3) then
2     validationErrors('sp_manufacturer') :=
3           webui::CreateValidationError("validation-error-not-a-valid-manufacturer-name");
4endif;

Here we just enforce that the manufacturer name is at least three characters.

Check data of sku integer property:

Lastly we check that the stock available is a non-negative integer:

 1block
 2    p_loc_Stock := Val(formData('p_stock'));
 3    if ( ( p_loc_Stock < 0 ) or ( mod(p_loc_Stock,1) <> 0 ) ) then
 4        validationErrors('p_stock') :=
 5            webui::CreateValidationError("validation-error-not-a-valid-availability");
 6    endif;
 7onerror ep_err do
 8    validationErrors('p_stock') :=
 9        webui::CreateValidationError("validation-error-not-a-valid-availability");
10    errh::MarkAsHandled(err);
11endblock;

Note the use of error handling here, as the AIMMS intrinsic functions Val and Mod may throw an error upon invalid input.

More about error handling:

The next step details the last procedure required for the form.

Linking callbacks to WebUI Forms framework

In our running example, we use the procedure pr_SKUFORM_Setup as the procedure which links the model identifiers, SKUFORM procedures and the actual form in WebUI together. This procedure is called at the end of the StartupProcedure in order to make sure it is called before the form is opened for the first time.

First we name the model identifiers that identify the fields in the form (here FormFields is a subset of AllIdentifiers):

1FormFields := data {
2    'sp_SKUFORM_InternalName',
3    'sp_manufacturer',
4    'p_volume',
5    'ep_Color',
6    'p_stock'};

Next we will actually link the fields:

1webui::SetupForm(
2    formId              :  "SKUForm",
3    selInMaster         :  'bp_SKUFORM_Selection',
4    detailsIdentifiers  :  FormFields,
5    validationHandler   :  'pr_SKUFORM_Check',
6    updateEntryCallback :  'pr_SKUFORM_Create');

Draw the form on the WebUI canvas

The widgets

After starting the AIMMS WebUI we can create the two necessary widgets:

  • A legend widget, contents: bp_SKUFORM_Selection

  • A scalar widget, contents:

    • webui_runtime::SKUForm_p_stock,

    • webui_runtime::SKUForm_ep_color,

    • webui_runtime::SKUForm_p_volume,

    • webui_runtime::SKUForm_sp_manufacturer,

    • webui_runtime::SKUForm_sp_SKUFORM_InternalName.

A page menu for the actions

  • Secondary page actions are used to link the FORM procedures to the widget using the string parameter sp_SKUFORM_WidgetActions declared and defined as follows:

     1StringParameter sp_SKUFORM_WidgetActions {
     2    IndexDomain: (i_SKUFORM_WidgetActionNumber,webui::indexWidgetActionSpec);
     3    Definition: {
     4        {
     5            ( '1', 'displaytext' ) : "Save"                              ,
     6            ( '1', 'icon'        ) : "aimms-floppy-disk"                 ,
     7            ( '1', 'procedure'   ) : "webui_runtime::SKUForm_SaveForm"   ,
     8            ( '1', 'state'       ) : "Active"                            ,
     9
    10            ( '2', 'displaytext' ) : "Create"                            ,
    11            ( '2', 'icon'        ) : "aimms-file-plus"                   ,
    12            ( '2', 'procedure'   ) : "webui_runtime::SKUForm_NewEntry"   ,
    13            ( '2', 'state'       ) : "Active"                            ,
    14
    15            ( '3', 'displaytext' ) : "Delete"                            ,
    16            ( '3', 'icon'        ) : "aimms-bin"                         ,
    17            ( '3', 'procedure'   ) : "webui_runtime::SKUForm_DeleteEntry",
    18            ( '3', 'state'       ) : "Active"
    19        }
    20    }
    21}
    

This will result in the following form:

../../_images/4-Basic-widget-placing-temp.png

Fig. 72 4 Basic widget placing

We will try to make the names easier to understand in the next sub step.

Create user-friendly names

Phrase adapting in the WebUI is achieved via translation files. In our running example we adapt using InventoryManagement\MainProject\WebUI\resources\languages\skuform-messages.properties, with the following contents.

 1webui_runtime::SKUForm_sp_SKUFORM_InternalName = Name
 2webui_runtime::SKUForm_sp_manufacturer = Manufacturer
 3webui_runtime::SKUForm_p_volume = Volume
 4webui_runtime::SKUForm_ep_Color = Color
 5webui_runtime::SKUForm_p_stock = Stock
 6
 7webui_runtime::form-enter-sp_SKUFORM_InternalName =
 8webui_runtime::form-enter-sp_manufacturer =
 9webui_runtime::form-enter-p_volume =
10webui_runtime::form-enter-ep_color =
11webui_runtime::form-enter-p_stock =
12
13no-bp_SKUFORM_Selection-selected =
14
15validation-error-min-length = A name must be at least two characters long.
16validation-error-name-already-exists = A person with this name already exists.
17validation-error-required-field = Required field.
18validation-error-not-a-valid-Volume = Not a valid volume.
19validation-error-not-a-valid-Stock = Not a valid stock.

With this phrase adapting, the form now looks as follows:

../../_images/4-Basic-widget-placing-translated-names-temp.png

Fig. 73 4 Basic widget placing - translated names

Making the page resizeable using grid layout

Once we have created the widgets with the necessary information, we enhance the page by using the grid layout feature of WebUI. The page has two columns, a relatively small one for the master data, and a wide one for entering the details. This is why we create the following layout:

../../_images/4d-defining-grid-layout-to-be-used.png

Selected remarks about the grid layout here:

  • Line 4: we use two columns, the second one wider than the first.

  • Line 5: we do not split the information over multiple rows.

  • Line 6: the areas used are named internally as area-a, and area-b

  • Lines 13, and 22: the areas are named to the end-user Area Master, and Area Detail.

And with the widgets moved to the respective areas, we get the following resizeable page:

../../_images/4-Basic-widget-placing-using-grid-layout.png

Example project

You can download a sample project below.