Ask Reuben

ON CHANGE for GUI Widgets

Why does the ON CHANGE logic differ based on the type of widget?

Within the ON CHANGE documentation entry, there is an interesting two paragraphs

For editable fields defined as EDIT, TEXTEDIT or BUTTONEDIT, the ON CHANGE block is executed when leaving a field, if the value of the specified field has changed since the field got the focus and the modification flag is set for the field. The field is left when user validates the dialog, when moving to another field, or when moving to another row in an INPUT ARRAY. However, if the text edit field is defined with the COMPLETER attribute to enable autocompletion, the ON CHANGE trigger will be fired after a short period of time, when the user has typed characters in.

For editable fields defined as CHECKBOX, COMBOBOX, DATEEDIT, DATETIMEEDIT, TIMEEDIT, RADIOGROUP, SPINEDIT, SLIDER or URL-based WEBCOMPONENT (when the COMPONENTTYPE attribute is not used), the ON CHANGE block is invoked immediately when the user changes the value with the widget edition feature. For example, when toggling the state of a CHECKBOX, when selecting an item in a COMBOBOX list, or when choosing a date in the calendar of a DATEEDIT. Note that for such item types, when ON CHANGE is fired, the modification flag is always set.

The question is why do we make that distinction between these two types of widgets?

The distinction between the two groups of widgets is that the widgets in the second group all have the property that the user can change the value by using a mouse and that action will produce a definitive value.  In this article I will refer to these as GUI Widgets.  For the widgets in the first group,  the value is entered using the keyboard, and as the user is typing the value will only be partially complete until the user signals the value is complete by exiting the field or accepting the dialog.

As the GUI Widgets allow the user to select a final value by using a mouse, the users expectation is that something might happen as you select this new value using the mouse. The 4gl needs to be able to respond straight away when the user has selected the new value in the GUI widget.  We accomplish by triggering the ON CHANGE for these widgets when the user selects a value using the GUI widget.

We can best see this with a field that uses a RADIOGROUP, CHECKBOX, or COMBOBOX where based on the value chosen, other fields will be made active or hidden.  The user selects a value using the GUI Widget and we have the ability to respond straight away via the ON CHANGE block and show/hide, enable/disable fields as required.  The user does not have to exit this field to trigger the ON CHANGE.

With the following example, note how the user can change a value in the RADIOGROUP and the number of visible fields changes straight away.  When using the EDIT field, the user has to tab out of the EDIT field to trigger the ON CHANGE.

#! askreuben135.per
LAYOUT (TEXT="ON CHANGE for GUI Widgets Example")
GRID
{
[l00e                 |f00e              ]
[l00r                 |f00r              ]
------------------------------------------
[l01                  |f01               ]
[l02                  |f02               ]
[l03                  |f03               ]
}
END
END
ATTRIBUTES

LABEL l00e : lbl_number_of_fields_edit, TEXT="Number of Fields (EDIT)";
EDIT f00e = formonly.number_of_fields_edit, NOT NULL;

LABEL l00r : lbl_number_of_fields_radio, TEXT="Number of Fields (RADIOGROUP)";
RADIOGROUP f00r = formonly.number_of_fields_radio, ITEMS=((0,"0-Zero"),("1","1-One"),("2","2-Two"),("3","3-Three")), ORIENTATION=HORIZONTAL, NOT NULL;

LABEL l01 :  lbl_field1, TEXT ="Field 1";
EDIT f01 = formonly.field1;

LABEL l02 :  lbl_field2, TEXT ="Field 2";
EDIT f02 = formonly.field2;

LABEL l03 :  lbl_field3, TEXT ="Field 3";
EDIT f03 = formonly.field3;

#! askreuben135.4gl
MAIN
    DEFINE f ui.Form

    DEFINE number_of_fields_radio, number_of_fields_edit INTEGER
    DEFINE field1, field2, field3 STRING

    OPTIONS FIELD ORDER FORM
    OPTIONS INPUT WRAP

    OPEN FORM f FROM "askreuben135"
    DISPLAY FORM f
    LET f = ui.Window.getCurrent().getForm()

    LET number_of_fields_radio = "3"
    LET number_of_fields_edit = "3"

    INPUT BY NAME number_of_fields_radio, number_of_fields_edit, field1, field2, field3 ATTRIBUTES(WITHOUT DEFAULTS = TRUE, UNBUFFERED)
        BEFORE INPUT
            CALL hide_fields(3)

        ON CHANGE number_of_fields_edit
            LET number_of_fields_radio = number_of_fields_edit
            CALL hide_fields(number_of_fields_edit)

        ON CHANGE number_of_fields_radio
            LET number_of_fields_edit = number_of_fields_radio
            CALL hide_fields(number_of_fields_radio)
    END INPUT
END MAIN

FUNCTION hide_fields(number_of_fields INTEGER)
    DEFINE f ui.Form
    LET f = ui.Window.getCurrent().getForm()
    CALL f.setElementHidden("lbl_field1", number_of_fields <= 0)
    CALL f.setElementHidden("lbl_field2", number_of_fields <= 1)
    CALL f.setElementHidden("lbl_field3", number_of_fields <= 2)

    CALL f.setFieldHidden("field1", number_of_fields <= 0)
    CALL f.setFieldHidden("field2", number_of_fields <= 1)
    CALL f.setFieldHidden("field3", number_of_fields <= 2)
END FUNCTION