User interface programming / Dialog programming basics |
Dialogs can be created at runtime with the ui.Dialog class.
The dynamic dialogs can be used in conjunction with base.SqlHandle objects, to get database table column information in order to build forms dynamically.
Unlike static dialog instructions, dynamic dialogs do not require a data model (i.e. program variables containing the values for fields): Dynamic dialogs hold the data model internally, and behave by default in unbuffered mode: When an action is fired and the corresponding trigger handler is executed, the field values are available.
Before you instanciate a new ui.Dialog object, you must load an existing compiled .42f form, or create a new form dynamically in your program.
Forms build at runtime must be created with the ui.Window.createForm() method, and must contain a valid definition with layout containers, form fields, and screen records.
The createForm() method will be invoked by using the current window. For the main form of the program, use directly the (empty) SCREEN window. For child windows, create the windows without a form by using following syntax:
OPEN WINDOW w1 WITH 1 ROWS, 1 COLUMNS
Assuming that there is a current empty window, you can then create the ui.Form object, to finally get the om.DomNode object to build your form:
DEFINE w ui.Window, f ui.Form, n om.DomNode LET w = ui.Window.getCurrent() LET f = w.createForm() LET n = f.getNode() ...
Use om classes, to build you form dynamically. A good practice to create dynamic forms is to write first a .per file, that implements a static version of one of the forms you want to build at runtime. Compile the .per to a .42f and inspect the generated XML file, to understand the structure of the form file.
DEFINE d ui.Dialog
The dynamic dialog creation methods take the list of field definitions as parameter, as a dynamic array with a record structure using two members to define the field name and data type.
DEFINE fields DYNAMIC ARRAY OF RECORD name STRING, type STRING END RECORD
LET fields[1].name = "formonly.cust_id" LET fields[1].type = "INTEGER" LET fields[2].name = "formonly.cust_name" LET fields[2].type = "VARCHAR(50)" LET fields[3].name = "formonly.cust_modts" LET fields[3].type = "DATETIME YEAR TO FRACTION(5)"
When the list of field definition is complete, create the dynamic dialog object.
LET d = ui.Dialog.createInputByName(fields)
LET d = ui.Dialog.createDisplayArrayTo(fields, "sr_custlist")
For more details, see ui.Dialog.createDisplayArrayTo.
LET d = ui.Dialog.createConstructByName(fields)
Dynamic dialogs can be configured with user-defined triggers, for example to execute code when a specific action is fired.
DEFINE d ui.Dialog ... CALL d.addTrigger("ON ACTION print") CALL d.addTrigger("ON DELETE") ...
Note that some triggers must be identified with the user-defined action name, as in "ON ACTION print".
User-defined triggers will then be handled in the dynamic dialog loop, when the event occurs.
For more details, see: ui.Dialog.addTrigger.
To implement the "body" of a dynamic dynamic, mix a WHILE loop with the ui.Dialog.nextEvent() method, to handle dialog events.
The WHILE loop will act as the main event handler of your dynamic dialog, and will loop, waiting for dialog events until you explicitely exist the loop with an EXIT WHILE instruction.
DEFINE d ui.Dialog ... WHILE TRUE CASE d.nextEvent() WHEN "BEFORE DISPLAY" ... WHEN "ON ACTION print" ... WHEN "ON DELETE" ... WHEN "AFTER DISPLAY" ... END WHILE
Several implicit trigger names are supported by dynamic dialogs, such as "BEFORE ROW", "AFTER FIELD field-name". These triggers are equivalent to the static dialog control blocks, to control the behavior of your dynamic dialog.
The event handlers for the user-defined triggers that have been added with the addTrigger() method must also be handled in the dynamic dialog loop.
WHILE TRUE CASE d.nextEvent() WHEN "ON ACTION jump" CALL d.nextField("customer.cust_name") ...
WHILE TRUE CASE d.nextEvent() WHEN "AFTER FIELD cust_name" IF LENGTH(d.getFieldValue("customer.cust_name")) < 3 THEN ERROR "Customer name is too short" CALL d.nextField("customer.cust_name") END IF ...
For more details, see the ui.Dialog.nextEvent() method reference.
A dynamic dialog stores field values in internal buffers created according to the field definitions provided in the creation method. Access to these values is required, to implement the dynamic dialog. For example, to set default values before entering the dialog loop, modifying and/or querying values during the dialog loop, and to get the entered values after dialog termination when accepted by the user.
When implementing a display array dynamic dialog handling a record list, the set/get field value methods apply to the current row: If you want to set or get field values of a particular row, first move to the row with the ui.Dialog.setCurrentRow() method.
CALL d_list.setCurrentRow("sr_custlist", index) FOR i=1 TO fields.getLength() CALL d_rec.setFieldValue( fields[i].name, d_list.getFieldValue(fields[i].name) ) END FOR
A dynamic dialog created with ui.Dialog.createConstructByName handles query by example input.
LET field_condition = DIALOG.getQueryFromField("customer.cust_name")
FOR i=1 TO fields.getLength() LET field_condition = d.getQueryFromField(fields[i].name) IF field_condition IS NOT NULL THEN IF where_clause IS NOT NULL THEN LET where_clause = where_clause, " AND " END IF LET where_clause = where_clause, field_condition END IF END FOR
Regular static dialog instructions implement the accept and cancel actions, to respectively validate or abort the dialog.
These actions are created automatically for static dialogs, but must be created by hand for dynamic dialogs.
In the case of cancel, you can mimic the behavior of static dialogs by setting the INT_FLAG register to TRUE and then leave the WHILE loop with an EXIT WHILE.
For the accept action, call the ui.Dialog.accept() method to validate field input and leave the dialog, and execute an EXIT WHILE in the "AFTER INPUT" event to leave the dialog loop.
DEFINE d ui.Dialog ... LET d = ui.Dialog.createInputByName(fields) CALL d.addTrigger("ON ACTION cancel") CALL d.addTrigger("ON ACTION accept") ... WHILE TRUE CASE d.nextEvent() WHEN "ON ACTION cancel" LET int_flag = TRUE EXIT WHILE WHEN "ON ACTION accept" CALL d.accept() WHEN "AFTER INPUT" EXIT WHILE END CASE END WHILE
Some synchronization code needs to be implemented to properly destroy the dynamic dialog. A dialog needs to be destroyed by closing its corresponding window/form.
LET d = NULL CLOSE WINDOW w1
To write generic code accessing a database, implement the dynamic dialog with field names and types coming from the base.SqlHandle cursor.
FUNCTION build_field_list(dbtable, fields) DEFINE dbtable STRING, fields DYNAMIC ARRAY OF RECORD name STRING, type STRING END RECORD DEFINE h base.SqlHandle, i INT LET h = base.SqlHandle.create() CALL h.prepare("SELECT * FROM " || dbtable) CALL h.open() CALL h.fetch() CALL fields.clear() FOR i=1 TO h.getResultCount() LET fields[i].name = h.getResultName(i) LET fields[i].type = h.getResultType(i) END FOR END FUNCTION
For more details, see The SqlHandle class.