Ask Reuben

Emulating Stack

Where is STACK in Genero Enterprise 4.00

What is replacement for STACK in Genero Enterprise 4.00

The STACK container was first introduced with Genero Mobile between the 2.50 and 3.00 releases of Genero Enterprise.  It was always listed as “This feature is experimental, the syntax/name and semantics/behavior may change in a future version.”.  Its lineage can also be traced back to the “Form” layout what was in GDC 2.20 Experimental Features.  With Genero Enterprise 4.00, STACK was removed.  As with the last article on ListView, the new responsive syntax is seen as being the successor to STACK.

There are a few options as to how you should code your screens that may have used STACK.  One is to replace with a GRID.  This will require adding in LABEL fields for the equivalent of TITLE.  So where you may have had …

STACK
GROUP
EDIT  formonly.field1, TITLE="Field 1";
EDIT  formonly.field2, TITLE="Field 2";
END -- GROUP
END -- STACK

… you might now give an iOS style with labels to the left of the widget.

GRID 
{
[l01        : :f01       ]
[l02        : :f02       ]
}
...
LABEL l01 : lbl_field1, STYLE="title", TEXT="Field 1";
LABEL l02 : lbl_field2, STYLE="title", TEXT="Field 2";
...
EDIT f01 = formonly.field1, TITLE="Field 1", STRETCH=X, JUSTIFY=RIGHT, SCROLL;
EDIT f02 = formonly.field1, TITLE="Field 2", STRETCH=X, JUSTIFY=RIGHT, SCROLL;

Or an Android style with labels above the widget

GRID 
{
[l01       ]
[f01       ]
[l02       ]
[f02       ]
}
....
LABEL l01 : lbl_field1, STYLE="title", TEXT="Field 1";
LABEL l02 : lbl_field2, STYLE="title", TEXT="Field 2";
...
EDIT f01 = formonly.field1, TITLE="Field 1", STRETCH=X, SCROLL;
EDIT f02 = formonly.field1, TITLE="Field 2", STRETCH=X, SCROLL;

By using GRID, you have the flexibility to control aspects of the rendering.  As shown above, that can include the position of the labels.  It can also include how you position large widgets such as IMAGE and TEXTEDIT.  It also gives you the opportunity to position multiple fields on the line.  This could include the following cases …

Entry of two fields to make a range …

[l01        : :f01a       |f01b      ]
....
LABEL l01 : lbl_field1, STYLE="title", TEXT="Date Range";
DATEEDIT f01a = formonly.date_from ;
DATEEDIT f01b = formonly.date_to;

BUTTONEDIT with a LABEL to display the code description …

[l01        : :f01a       |f01b      ]
....
LABEL l01 : lbl_field1, STYLE="title", TEXT="Date Range";
BUTTONEDIT f01a = formonly.code;
LABEL f01b = formonly.description, STYLE="description";

Multiple Checkbox …

[l01        : :mo|tu|we|th|fr|sa|su]
....
LABEL l01 : lbl_field1, STYLE="title", TEXT="Days Selected";
CHECKBOX mo = formonly.mo, TEXT="Mo";
CHECKBOX tu = formonly.tu, TEXT="Tu";
CHECKBOX we = formonly.we, TEXT="We";
CHECKBOX th = formonly.th, TEXT="Th";
CHECKBOX fr = formonly.fr, TEXT="Fr";
CHECKBOX sa = formonly.sa, TEXT="Sa";
CHECKBOX su = formonly.su, TEXT="Su";

Although using GRID requires a little more coding than STACK in that you can’t utilise the TITLE attribute and have to code the seperate LABEL widget,  it does give you a lot more flexibility with regard to how you position the fields on each line, wether you adapt an iOS or Android look and feel.

Some things to note …

  • I left out GROUP in the above for simplicity, but the use of Collapsible Group allows a long list of fields to be broken up into groups.  The user can then individually collapse a GROUP depending on what detail they are interested in.  Collapsible Groups are maintained by a Presentation Style you can apply to Groups.
  • In the first iOS like example, note the use of STRETCH=X on the widget fields and not the title/label fields.  This means the Grid Columns the widgets are in will stretch, but the Grid Columns the labels are in will not.  This means you don’t end up with white space around labels, and the available space is more likely to be available for entering and viewing data.
  • You can use a Presentation Style to group your LABEL widgets that are the equivalent of TITLE to ensure they look the same.  In the above I used STYLE=”title”.  In my .4st I would then associate some font characteristics with that style to control the appearance of each fields title.
  • In the first iOS like example, the use of the spacer between the label and fields to push the label and widget to the left and right edge of the screen
  • Where I have shown multiple lines in the first iOS and second Android like examples, note how the [ and ] signifying the end of the widget are in the same Grid Columns.  Where you have widgets of differing sizes, say a CHAR(10) and a CHAR(20), it is tempting to do something like …
[f01       ]
[f02                ]
...
f01 = formonly.field1, STRETCH=X;
f02 = formonly.field2, STRETCH=X;
  • … but the better solution is to make every field the same width, typically the smallest you would ever want the widget to appear.  12 characters is a good start as that is 10 for date dd/mm/yyyy and 2 characters for the button, and then use SCROLL to indicate that more than 12 characters can be entered into that field.
[f01         ]
[f02         ]
...
f01 = formonly.field1, SCROLL, STRETCH=X;
f02 = formonly.field2, SCROLL, STRETCH=X;

Alternatives

Using GRID  isn’t the only way you can emulate STACK.  The next two approaches need a little refinement before being considered for production but I will mention them and plant some seeds.

TABLE (FLIPPED)

The first is to use TABLE(FLIPPED) and do an INPUT to the TABLE fields …

TABLE (FLIPPED)
{
[f01       |f02       ]
}
END
EDIT  f01 = formonly.field1, TITLE="Field 1"; 
EDIT  f02 = formonly.field2, TITLE="Field 2";

… I think it requires some fine tuning of the UI to remove some of the TABLE aspects such as the current row highlighting.

DOM Tree Manipulation

The second is to do some DOM tree manipulation.  The code below will rearrange the fields in a GRID by altering their posX and posY attribute

FUNCTION rearrange()
DEFINE last_node, grid_node, root_node, child_node om.DomNode
DEFINE grid_list om.NodeList
DEFINE new_x, new_y INTEGER
DEFINE g, i INTEGER
DEFINE after_title_flag BOOLEAN = FALSE
DEFINE max_width INTEGER
DEFINE adj_width INTEGER

    LET root_node = ui.Window.getCurrent().getForm().getNode()

    -- Reduce Width of form by setting posX to 1 and arrange items vertically
    -- Three rules :-
    -- 1. If first field is a Static Label i.e what would be a Title then put next field on same row
    -- 2. If field has TAG="associate" then assume this is supposed to be on same line as previous
    -- 3. Calculate max width of Title Labels and adjust width of Static Labels to match, and move fields to right accordingly

    LET grid_list = root_node.selectByTagName("Grid")
    FOR g = 1 TO grid_list.getLength()
        -- Repeat for each grid
        INITIALIZE last_node TO NULL
        LET after_title_flag = FALSE
        LET max_width = 0
        LET new_y = 0

        LET grid_node = grid_list.item(g)
        FOR i = 1 TO grid_node.getChildCount()
            LET child_node = grid_node.getChildByIndex(i)
            IF child_node.getTagName() = "FormField" THEN
                LET child_node = child_node.getFirstChild()
            END IF
            IF (child_node.getAttribute("tag") = "associate" AND last_node IS NOT NULL) OR after_title_flag THEN
                -- Keep on this line if associated to previous line or if it is the first field after a title
                LET new_x = last_node.getAttribute("posX") + last_node.getAttribute("gridWidth") + 1
                CALL child_node.setAttribute("posX", new_x)
                CALL child_node.setAttribute("posY", new_y)
            ELSE
                -- Place on a new line
                LET new_y = new_y + 1
                CALL child_node.setAttribute("posX", 1)
                CALL child_node.setAttribute("posY", new_y)

                -- Keep track of max label width
                IF child_node.getTagName() = "Label" THEN
                    IF child_node.getAttribute("gridWidth") > max_width THEN
                        LET max_width = child_node.getAttribute("gridWidth")
                    END IF
                END IF
            END IF

            -- If static label, then set flag so next field is not put on a new line
            IF child_node.getTagName() = "Label" AND child_node.getParent().getTagName() = "Grid" THEN
                LET after_title_flag = TRUE
            ELSE
                LET after_title_flag = FALSE
            END IF
            LET last_node = child_node
        END FOR

        -- Second Pass, adjusting width, posX for width of title 
        FOR i = 1 TO grid_node.getChildCount()
            LET child_node = grid_node.getChildByIndex(i)
            IF child_node.getTagName() = "FormField" THEN
               LET child_node = child_node.getFirstChild()
            END IF

            IF child_node.getAttribute("posX") = 1 THEN
                -- If first field ie label then set width equal to the max width
                IF child_node.getTagName() = "Label" THEN
                    LET adj_width = max_width - child_node.getAttribute("gridWidth")
                    CALL child_node.setAttribute("gridWidth", max_width)
                ELSE
                    LET adj_width = max_width
                    CALL child_node.setAttribute("posX", child_node.getAttribute("posX") + adj_width +1)
                END IF
            ELSE
                -- move other nodes on this line out by the difference to max
                CALL child_node.setAttribute("posX", child_node.getAttribute("posX") + adj_width )
            END IF
        END FOR
    END FOR
END FUNCTION

If we were to ever have a responsive solution that was the equivalent of GRID(FLIPPED) to turn a GRID into something resembling a STACK, we would have to consider similar logic in order to link a Field to its Title / Label, and to allow in some case multiple fields on the same line.  The screenshots below show an example of a complex GRID being transformed to look like a STACK, by applying the rearrange function above to manipulate the posX and posY attributes.  if I was to take this further I would also consider setting the width attribute and if necessary adding scroll so the right edges aligned.