Create Wonderful WebUI Widgets

In case you have not yet tried the Wonderful WebUI Widgets app yourself, you may want to scan Working with Wonderful WebUI Widgets first.

Welcome and workflow

It is good practice to provide a welcome text to the end-user, such that this user can, when needed, read about the operation of the app, and about expected actions. In addition, it is good practice to make the welcome page part of the workflow, such that the user feels guided right from the start.

Map page

The latitude and longitude for the map widget of arbitrarily selected German cities are extracted from data provided by simplemaps.

Icons for nodes

The support for icons on nodes makes it easy to specify the icons to be used on the nodes via a definition similar to the following:

 1StringParameter sp_locationIcon {
 2    IndexDomain: i_loc;
 3    Definition: {
 4        if i_loc in s_sources then
 5            "aimms-factory"
 6        elseif i_loc in s_destinations then
 7            "aimms-store"
 8        else
 9            "aimms-width"
10        endif ;
11    }
12}

Detail tooltip

HTML can be used for tooltips on nodes so a definition like the following is used:

 1StringParameter sp_locationTooltip {
 2    IndexDomain: i_loc;
 3    Definition: {
 4        "<div align=\"left\">"  +
 5        "<Table>" +
 6                "<TR>"  +
 7                        "<TD>"  +
 8                                        "<B> Name : </B>" +
 9                        "</TD>" +
10                        "<TD>"  +
11                                        formatString("%e", i_loc) +
12                        "</TD>" +
13                "</TR>" +
14                "<TR>"  +
15                        "<TD>"  +
16                                        "<B> Type : </B>" +
17                        "</TD>" +
18                        "<TD>"  +
19                                        sp_locationType( i_loc ) +
20                        "</TD>" +
21                "</TR>" +
22                "<TR>"  +
23                        "<TD>"  +
24                                        "<B> " +
25                                        if i_loc in s_sources then
26                                            "Capacity"
27                                        elseif i_loc in s_intermediate then
28                                            "Capacity"
29                                        else
30                                            "Demand"
31                                        endif
32                                        + " : </B>" +
33                        "</TD>" +
34                        "<TD>"  +
35                                        p_locationSize(i_loc) +
36                        "</TD>" +
37                "</TR>"  +
38                if sum( i_locFrom, v_prod(i_locFrom ) ) then
39                "<TR>"  +
40                        "<TD>"  +
41                                        "<B> " +
42                                        if i_loc in s_sources then
43                                            "Production"
44                                        elseif i_loc in s_intermediate then
45                                            "Flow"
46                                        else
47                                            "Unmet demand"
48                                        endif
49                                        + " : </B>" +
50                        "</TD>" +
51                        "<TD>"  +
52                                        if i_loc in s_sources then
53                                            v_prod(i_loc)
54                                        elseif i_loc in s_intermediate then
55                                            sum( i_locFrom, v_flow(i_locFrom, i_loc) )
56                                        else
57                                            v_unmetDemand(i_loc)
58                                        endif +
59                        "</TD>" +
60                "</TR>"
61                else "" endif
62                +
63        "</Table>"
64    }
65}

There are some remarks regarding the above definition:

  1. It uses the table syntax of HTML.

  2. It is admittedly lengthy, but having this length and indentation, makes the structure apparent.

  3. Lines 38-61 are part of an if-then-else expression; based on a condition, a row is added to the tooltip table.

Detail context menu

 1StringParameter sp_mapLocationItemActions {
 2    IndexDomain: (webui::indexWidgetItemActionSpec,webui::indexPageExtension,webui::indexWidgetActionSpec);
 3    Definition: {
 4        {
 5            ('p_locationSize', '1', 'displaytext') : "Debug",
 6            ('p_locationSize', '1', 'icon'       ) : "aimms-bug",
 7            ('p_locationSize', '1', 'procedure'  ) : "pr_locDebug",
 8            ('p_locationSize', '1', 'state'      ) : if bp_developmentSupport then "Active" else "Hide" endif,
 9
10            ('p_locationSize', '2', 'displaytext') : formatString("Increase %s %e by 1", if ep_selectedLocation in s_destinations then "demand" else "capacity" endif, ep_selectedLocation),
11            ('p_locationSize', '2', 'icon'       ) : "aimms-volume-increase2",
12            ('p_locationSize', '2', 'procedure'  ) : "pr_locSizeIncrease1",
13            ('p_locationSize', '2', 'state'      ) : "Active",
14
15            ('p_locationSize', '3', 'displaytext') : formatString("Increase %s %e by 5", if ep_selectedLocation in s_destinations then "demand" else "capacity" endif, ep_selectedLocation),
16            ('p_locationSize', '3', 'icon'       ) : "aimms-volume-increase",
17            ('p_locationSize', '3', 'procedure'  ) : "pr_locSizeIncrease5",
18            ('p_locationSize', '3', 'state'      ) : "Active",
19
20            ('p_locationSize', '4', 'displaytext') : formatString("Decrease %s %e by 1", if ep_selectedLocation in s_destinations then "demand" else "capacity" endif, ep_selectedLocation),
21            ('p_locationSize', '4', 'icon'       ) : "aimms-volume-decrease2",
22            ('p_locationSize', '4', 'procedure'  ) : "pr_locSizeDecrease1",
23            ('p_locationSize', '4', 'state'      ) : "Active",
24
25            ('p_locationSize', '5', 'displaytext') : formatString("Decrease %s %e by 5", if ep_selectedLocation in s_destinations then "demand" else "capacity" endif, ep_selectedLocation),
26            ('p_locationSize', '5', 'icon'       ) : "aimms-volume-decrease",
27            ('p_locationSize', '5', 'procedure'  ) : "pr_locSizeDecrease5",
28            ('p_locationSize', '5', 'state'      ) : "Active",
29
30            ('p_locationSize', '6', 'displaytext') : formatString("Edit %e", ep_selectedLocation),
31            ('p_locationSize', '6', 'icon'       ) : "aimms-quill",
32            ('p_locationSize', '6', 'procedure'  ) : "pr_locSizeDetails",
33            ('p_locationSize', '6', 'state'      ) : "Active"
34        }
35    }
36}

Some remarks on the above:

  1. A defined list is created using { ... }. Note the absence of the word data here. It does require to put the element literals between single quotes (''). But then you can nicely use expressions behind the :.

    The advantage of this style of defining context menu behavior is that the small procedures that modify the data of the string parameter to fine control the behavior of such menus are no longer needed.

    This application uses a similar style for controlling the behavior workflow, status bar, widget menus, and page actions.

  2. Line 5: Debug - to help test the application. The visibility of the item

  3. Line 10: FormatString is used to make the descriptions of the menu items more to the point.

Gantt page

Data representation

There are three different data representations that need to be kept consistent:

  1. User data

  2. Gantt Chart data: start and length of each task.

  3. Coefficients used by optimization algorithm.

Border: use of CSS

We built on the intuition that blue indicates “can be changed manually in this session” and that black indicates “derived or computed data” and treated as readonly in this session.

Specifying tooltips is similar to what is used for the map widget, and not repeated here.

Context menu

The context menu is to be used for:

  1. The scheduled jobs in the Gantt Chart

  2. Each column/row in the Order Details tab.

 1StringParameter sp_ganttSpecItemActions {
 2    IndexDomain: (webui::indexWidgetItemActionSpec,webui::indexPageExtension,webui::indexWidgetActionSpec);
 3    Definition: {
 4        {
 5            ('p_WebUIGNTDuration', '1', 'displaytext') : "Debug",
 6            ('p_WebUIGNTDuration', '1', 'icon'       ) : "aimms-bug",
 7            ('p_WebUIGNTDuration', '1', 'procedure'  ) : "pr_jobDebug",
 8            ('p_WebUIGNTDuration', '1', 'state'      ) : if bp_developmentSupport then "Active" else "Hide" endif,
 9
10            ('p_WebUIGNTDuration', '2', 'displaytext') : formatString("Move order %e to front", ep_selectedOrder),
11            ('p_WebUIGNTDuration', '2', 'icon'       ) : "aimms-first",
12            ('p_WebUIGNTDuration', '2', 'procedure'  ) : "pr_moveToFront",
13            ('p_WebUIGNTDuration', '2', 'state'      ) : if bp_orderScheduledByLivingCreature( ep_selectedOrder ) then "Active" else "Inactive" endif,
14
15            ('p_WebUIGNTDuration', '3', 'displaytext') : formatString("Delay order %e until deadline", ep_selectedOrder),
16            ('p_WebUIGNTDuration', '3', 'icon'       ) : "aimms-last",
17            ('p_WebUIGNTDuration', '3', 'procedure'  ) : "pr_delayUntilDeadline",
18            ('p_WebUIGNTDuration', '3', 'state'      ) : if bp_orderScheduledByLivingCreature( ep_selectedOrder ) then "Active" else "Inactive" endif,
19
20            ('p_WebUIGNTDuration', '4', 'displaytext') : formatString("Allow scheduling of %e by living creature", ep_selectedOrder),
21            ('p_WebUIGNTDuration', '4', 'icon'       ) : "aimms-grab",
22            ('p_WebUIGNTDuration', '4', 'procedure'  ) : "pr_scheduleByLivingCreature",
23            ('p_WebUIGNTDuration', '4', 'state'      ) : if not bp_orderScheduledByLivingCreature( ep_selectedOrder ) then "Active" else "Inactive" endif,
24
25            ('p_WebUIGNTDuration', '5', 'displaytext') : formatString( "Allow scheduling of %e by optimization algorithm", ep_selectedOrder),
26            ('p_WebUIGNTDuration', '5', 'icon'       ) : "aimms-music",
27            ('p_WebUIGNTDuration', '5', 'procedure'  ) : "pr_scheduleByOptimizationAlgorithm",
28            ('p_WebUIGNTDuration', '5', 'state'      ) : if bp_orderScheduledByLivingCreature( ep_selectedOrder ) then "Active" else "Inactive" endif,
29
30            ('p_WebUIGNTDuration', '6', 'displaytext') : formatString("Delete order %e", ep_selectedOrder),
31            ('p_WebUIGNTDuration', '6', 'icon'       ) : "aimms-bin",
32            ('p_WebUIGNTDuration', '6', 'procedure'  ) : "pr_deleteJob",
33            ('p_WebUIGNTDuration', '6', 'state'      ) : "Active",
34
35            ('p_WebUIGNTDuration', '7', 'displaytext') : formatString("Edit order %e", ep_selectedOrder),
36            ('p_WebUIGNTDuration', '7', 'icon'       ) : "aimms-quill",
37            ('p_WebUIGNTDuration', '7', 'procedure'  ) : "pr_editJob",
38            ('p_WebUIGNTDuration', '7', 'state'      ) : "Active"
39        }
40    }
41}

Remarks:

  1. Similar to the context menu in the map widget above.

  2. Several actions are only available to job that can be scheduled manually.

  3. Every job can be edited.

  4. The use of 'p_WebUIGNTDuration' in the above definition implies: Only available to the Gantt Chart, so how to reuse this menu in the order table?

Let’s first identify the identifiers to be used, namely the ones in the columns:

 1Set s_ganttItemActionIdentifiers {
 2    SubsetOf: AllIdentifiers;
 3    Definition: {
 4        data  {
 5            bp_orderScheduledByLivingCreature,
 6            ep_orderProduct,
 7            p_orderLength,
 8            ep_orderProductType,
 9            p_orderQuantity,
10            ep_orderDeliveryDueDate,
11            ep_orderProductionLine,
12            ep_orderStartDate,
13            p_WebUIGNTDuration,
14            p_WebUIGNTStartTime
15        }
16    }
17}

And then we can easily replicate the data of sp_ganttSpecItemActions for each of these column names using the below definition:

 1StringParameter sp_ganttItemActions {
 2    IndexDomain: (webui::indexWidgetItemActionSpec,webui::indexPageExtension,webui::indexWidgetActionSpec);
 3    Definition: {
 4        if webui::indexWidgetItemActionSpec in s_ganttItemActionIdentifiers then
 5            sp_ganttSpecItemActions('p_WebUIGNTDuration', webui::indexPageExtension, webui::indexWidgetActionSpec)
 6        else
 7            ""
 8        endif
 9    }
10}

Author’s note: I really enjoyed creating this small AIMMS application.