Ask Reuben – May 7, 2025

ui.RadioGroup

Why is there a ui.ComboBox class for COMBOBOX but not for other widgets? 

How can I dynamically add items to a RADIOGROUP widget?

All experienced developers have at some point in their life, started off coding a project in one direction then decided a little later on that perhaps they need to go in another direction, or perhaps they have started off on one project, got distracted onto another project and never got back to finish or tidy up that original project.

For Genero, ui.ComboBox is one such area.  It was implemented with plans to add other other similar classes for other widgets and containers, and then there was a thought that perhaps there should be a more generic ui.Field class, and for whatever reason, consider that this was over 20 years ago, 20 years later we still have ui.ComboBox as an orphan.  There is no ui.Field, there is no ui.RadioGroup class yet.  However what you should be aware of is that anything you are likely to want to code using ui.Field, or ui.RadioGroup etc is most likely implementable via AUI Tree manipulation and you can write your own code to do this.

For instance if you study ComboBox and RadioGroup fields in the AUI Tree you will see that they have the similar design where there is the FormField node, the Widget node (RadioGroup or ComboBox), and then one or more Items nodes as children containing what the user can select in the widget …

RadioGroupComboBox

… and that you can write your own code to provide a Radiogroup equivalent of ui.ComboBox.

You will typically find that most Genero developers have written their own libraries to code what might not have an explicit ui dot something method.  This code  might similar to this code I have in Github in the fgl_auitree repository.  For example, the RadioGroup equivalent of ui.ComboBox.addItem could be coded as …


FUNCTION ui_RadioGroup_addItem(rg ui_RadioGroup, name STRING, text STRING)
DEFINE child om.DomNode

    -- Check that we are starting with a RadioGroup node
    IF rg.getTagName() != "RadioGroup" THEN
        -- Should throw an exception here
        RETURN
    END IF

    -- Add a new node of type Item
    LET child = rg.createChild("Item")

    -- Set the name and text attribute values
    CALL child.setAttribute("name", name)
    CALL child.setAttribute("text", text)
END FUNCTION

This ability to code these functions to manipulate the AUI Tree is perhaps one of the reasons there has never been to date any push to tidy up the loose end and move beyond ui.ComboBox to ui.Radiogroup or ui.Field.  We could remove ui.ComboBox but that would be unnecessarily disruptive so it has stayed as an orphan.

I could leave the article there but the ability to code Methods means that you can potentially be a little smarter about how you implement this code to manipulate the AUI tree.  Below I have a ui_RadioGroup.4gl which can be considered the RadioGroup equivalent of ui.ComboBox.  (I have only shown the forName, addItem, clear methods, if you wanted the equivalents of the other ui.ComboBox methods hopefully you can figure them out yourself)


#! ui_RadioGroup.4gl

PUBLIC TYPE ui_RadioGroup_Type RECORD
    om om.DomNode
END RECORD

PUBLIC FUNCTION (rg ui_RadioGroup_Type) forName(field_name STRING)
DEFINE field_node om.DomNode

    INITIALIZE rg.om TO NULL
    
    LET field_node = ui.Window.getCurrent().getForm().findNode("FormField", field_name)
    IF field_node IS NULL THEN -- try again prefixing with formonly.
        LET field_node = ui.Window.getCurrent().getForm().findNode("FormField", "formonly."||field_name)
    END IF
    -- Repeat replacing FormField with TableColumn and other alternates from ScrollGrid, Matrix

    IF field_node IS NOT NULL THEN
        LET rg.om = field_node.getFirstChild()
        IF rg.om.getTagName() = "RadioGroup" THEN
            # Good, it is a RadioGroup
        ELSE
            -- TODO, add exception
            INITIALIZE rg.om TO NULL
        END IF
    END IF
END FUNCTION



PUBLIC FUNCTION (rg ui_RadioGroup_Type) clear()
DEFINE item_list om.NodeList
DEFINE i INTEGER

    LET item_list = rg.om.selectByTagName("Item")
    FOR i = item_list.getLength() TO 1 STEP -1
        CALL rg.om.removeChild(item_list.item(i))
    END FOR
END FUNCTION



PUBLIC FUNCTION (rg ui_RadioGroup_Type) addItem(n STRING,t STRING)
DEFINE item_node om.DomNode

    LET item_node = rg.om.createChild("Item")
    CALL item_node.setAttribute("name",n)
    CALL item_node.setAttribute("text",t)    
END FUNCTION

After using

IMPORT FGL ui_RadioGroup

to signify that I want to use this library, I am in position to have …

DEFINE rg ui_RadioGroup.ui_RadioGroup_Type

similar to

DEFINE rg ui.ComboBox

To set the items, I am in a position to have …

CALL rg.clear()
CALL rg.addItem("Y","Yes")
CALL rg.addItem("N","No")

similar to

CALL cb.clear()
CALL cb.addItem("Y","Yes")
CALL cb.addItem("N","No")

and its only the initialisation that is slightly different but not too different …

CALL rg.forName(n)

versus

LET cb = ui.ComboBox.forName(n)

Using such a library my Radiogroup is not limited to being what is hard-coded via the ITEMS attribute in the definition.  I can programmatically add/remove items as required.  I have in my toolbox, the ability to do with a RadioGroup  what I can do with a ComboBox.  The Function Methods functionality helps write tidy code here.

The code below is a little test program that imports the ui_RadioGroup.4gl library.


#! ui_RadioGroup_test.4gl

IMPORT util
IMPORT FGL ui_RadioGroup
MAIN
    DEFINE rec RECORD
        cb1 CHAR(1),
        cb2 CHAR(1),
        cb3 CHAR(1),
        rg1 CHAR(1),
        rg3 CHAR(1)
END RECORD

DEFINE program_name STRING

    WHENEVER ANY ERROR STOP
    DEFER INTERRUPT
    DEFER QUIT
    OPTIONS FIELD ORDER FORM
    OPTIONS INPUT WRAP

    LET program_name = base.Application.getProgramName()

    CALL ui.Dialog.setDefaultUnbuffered(TRUE)
    CLOSE WINDOW SCREEN

    LET rec.cb1 = "Y"
    LET rec.cb2 = "Y"
    LET rec.cb3 = "Y"
    LET rec.rg1 = "Y"
    LET rec.rg3 = "Y"
 
    OPEN WINDOW w WITH FORM  program_name ATTRIBUTES(TEXT=program_name)
    INPUT BY NAME rec.* ATTRIBUTES(WITHOUT DEFAULTS=TRUE, UNBUFFERED)
        BEFORE INPUT
            CALL populate_combo_by_fieldName("cb3")
            CALL populate_radio_by_fieldName("rg3")

        ON ACTION show
            MESSAGE util.JSON.stringify(rec)
    END INPUT
   
END MAIN

FUNCTION populate_combo_by_fieldName(n STRING)
DEFINE cb ui.ComboBox

    LET cb = ui.ComboBox.forName(n)
    CALL populate_combo_yesno(cb)
END FUNCTION

FUNCTION populate_combo_yesno(cb ui.ComboBox)

    CALL cb.clear()
    CALL cb.addItem("Y","Yes")
    CALL cb.addItem("N","No")
END FUNCTION


FUNCTION populate_radio_by_fieldName(n STRING)
DEFINE rg ui_RadioGroup.ui_RadioGroup_Type

    CALL rg.forName(n)
    CALL populate_radio_yesno(rg)
END FUNCTION


FUNCTION populate_radio_yesno(rg ui_RadioGroup.ui_RadioGroup_Type)

    CALL rg.clear()
    CALL rg.addItem("Y","Yes")
    CALL rg.addItem("N","No")
END FUNCTION

#! ui_RadioGroup_test.per

LAYOUT
GRID
{
ComboBox (Items)       [f01         ]
ComboBox (Initializer) [f02         ]
ComboBox (Function)    [f03         ]
RadioGroup (Items)     [f04         ]
RadioGroup (Function)  [f06         ]
}
END
END

ATTRIBUTES
COMBOBOX f01 = formonly.cb1, NOT NULL, ITEMS=(("Y","Yes"),("N","No"));
COMBOBOX f02 = formonly.cb2, NOT NULL, INITIALIZER=populate_combo_yesno;
COMBOBOX f03 = formonly.cb3, NOT NULL;
RADIOGROUP f04 = formonly.rg1, NOT NULL, ITEMS=(("Y","Yes"),("N","No")), ORIENTATION=HORIZONTAL;
-- No initializer for RADIOGROP, hence skipping f05, rg2
RADIOGROUP f06 = formonly.rg3, NOT NULL, ORIENTATION=HORIZONTAL;

INSTRUCTIONS
SCREEN RECORD scr(cb1, cb2, cb3, rg1, rg3);