Ask Reuben

ui.Form.setDefaultInitiailizer

How can I execute some code every time a form is opened?

How can I apply something to forms once without having to code each form individually?

Good developers are fundamentally “lazy” in that they will find a smart way to code something once rather than repeating code or actions unnecessarily.  One tool that developers use to reduce coding is ui.Form.setDefaultInitiailizer.  This is a method that defines a function that will be called every time a form is opened.  This function can then be used to do things liking adding elements to the form, adding attributes to widgets and containers,  or make changes to the form based on certain conditions.

How it works is that near the start of your program, typically in a library function you will have a line …

CALL ui.Form.setDefaultInitializer(function-name)

You will then have a function with that name that has the signature that it takes as input one argument of type ui.Form and returns nothing e.g.

FUNCTION init_form(u ui.Form) RETURNS ()

When a form is opened, the runtime executes this function.  This function will typically make use of ui.Form.getNode() to return the DomNode corresponding to the form and from there you can traverse and make changes to the form.

I have an example in a GitHub repository that makes use of ui.Form.setDefaultInitializer.  That example can be found here, the image below shows two images, the before image is how a form with some tables is coded and looks by default, and the after image is after the function called by ui.Form.setDefaultInitializer appends some BUTTON’s and a LABEL beneath two of the TABLE’s.  This panel is added to any TABLE’s that meet certain conditions as a form is opened.  I will walk through some of the code beneath the screenshots …

BeforeAfter

In the test 4gl, we see the call to set the form initializer function …

CALL ui.Form.setDefaultInitializer("init_form")

This function has one input parameter of type ui.Form, and in turn calls a library function passing this ui.Form object, the variable f …

FUNCTION init_form(f)
DEFINE f ui.Form

    -- Call library method that will append an actionpanel to a table
    CALL table_actionpanel.append_actionpanel_to_table(f)
END FUNCTION

This library function when called as a form is opened, iterates through the form and finds all Table nodes…

LET root_node = f.getNode()
LET nl = root_node.selectByTagName("Table")
FOR i = 1 TO nl.getLength()
    LET table_node = nl.item(i)

… each table node is then checked to see if it meets the criteria for having an action panel added.  In this case it needs to have a VBOX parent and the value of the TAG attribute needs to contain a particular text string (“actionpanel”) that will have been added to the Table’s  TAG attribute value by the developer …

-- Table must have VBOX node as parent. If not move onto to next table
LET parent_node = table_node.getParent()
IF parent_node.getTagName() != "VBox" THEN
    CONTINUE FOR
END IF

-- Use a value of actionpanel inside tag attribute to determine if add actionpanel
IF table_node.getAttribute("tag") MATCHES "*actionpanel*" THEN
    #OK
ELSE
    CONTINUE FOR
END IF

Having found a valid Table node, the runtime then adds beneath the Table, some BUTTON’s and a LABEL centred horizontally

LET hbox_node = parent_node.createChild("HBox")

-- add node that will stretch, in this case empty image so that panel is centred
CALL add_empty_image(hbox_node)
LET grid_node = hbox_node.createChild("Grid")

-- add buttons on left hand side
CALL add_table_button(grid_node,tab_name,"find",1)
CALL add_table_button(grid_node,tab_name,"firstrow",3)
CALL add_table_button(grid_node,tab_name,"prevpage",5)
CALL add_table_button(grid_node,tab_name,"prevrow",7)

-- add a label in middle for text
LET label_node = grid_node.createChild("Label")
CALL label_node.setAttribute("name",SFMT("lbl_%1", tab_name))
CALL label_node.setAttribute("width",8)
CALL label_node.setAttribute("sizePolicy","fixed")
CALL label_node.setAttribute("justify","center")
CALL label_node.setAttribute("posX",9)

-- add buttons on right hand side
CALL add_table_button(grid_node,tab_name,"nextrow",17)
CALL add_table_button(grid_node,tab_name,"nextpage",19)
CALL add_table_button(grid_node,tab_name,"lastrow",21)
CALL add_table_button(grid_node,tab_name,"append", 23)

-- add node that will stretch, in this case empty image so that panel is centered
CALL add_empty_image(hbox_node)

That is 8 BUTTON’s and a LABEL have been added to every TABLE without having to explicitly code these onto every TABLE.  The names are consistent so I can display text to the LABEL and respond to the BUTTON actions.

If in the future I need to change this, I can amend this library function.  So for example say I needed to add or remove some buttons I would code this once in my library function, and running the program will incorporate this change.

One tip for effective use of ui.Form.setDefaultInitializer is that I found it was good to have a VBOX or HBOX container around what you might want to potentially change like I did in this article.  This allows you to add a sibling container either immediately before or after a particular container.  This might even be a VBOX container as first child of LAYOUT so that you can potentially add something to top or bottom of every form.  This type of code change can normally be scripted so although it involved every form, it did not require a developer manually editing each .per.

If looking for another example of ui.Form.setDefaultInitializer, I also use it in this repository and article as a way to add an indicator to a field to denote that it is mandatory.  That is find every field that is NOT NULL and do something, perhaps adding a style, adding a placeholder, or adding an adjacent label displaying “*”.

The FORM clause and the concept of SubForms can also be used to code something once and reuse it with the design of forms.  However an individual subform with named elements can only be used once within a form due to the requirement for names to be unique.   ui.Form.setDefaultInitializer allows for repetition with an individual form as unique names can be assigned.   This table action panel example could not be coded as a subform as we need a unique name for the buttons and label.  Note how the name is defined using SFMT as a composite of the table name and button name

CALL button_node.setAttribute("name",SFMT("%1.%2", tab_name, name))

It should also be noted that GBC Customisation can also be used to achieve the same effect and is probably the better long term approach.  However different skillsets are required, a 4gl developer will lean towards use of ui.Form.setDefaultInitializer whilst someone familiar with web technologies would lean towards the GBC Customisation approach.  An example along these lines is the GBC theme variable $gbc-Edit-desktop-comment-to-placeholder  which takes the value of the COMMENT attribute and uses it as PLACEHOLDER.  This could also have been implemented via use of ui.Form.setDefaultInitializer to set the PLACEHOLDER attribute value in the AUI tree to be equal to the value of the corresponding COMMENT attribute.

ui.Form.setDefaultInitializer is a tool in the Genero developers toolbox that enables the developer to code their User Interface once and have it apply to multiple forms.