Populating listitems dynamically
Listitems in forms are quite useful if one has to offer a smaller choice of values to the enduser. Often the values to be displayed have to be taken from another table, so the listitems values have to be read at runtime.
Also, listitems are often used for so called "domains". A domain in general is a list of discrete values, each of which has a meaning in form of a text. To avoid hardcoding the values, in many projects there is a domaintable which stores the allowed values along with the textual meaning.
So, lets have a look at how a listitem can be "populated" using Forms-builtin's.
As example, lets assume we want to have a listitem in a form which is based on the column DEPARTMENT_ID from the HR-table EMPLOYEES, and in the form the item should be displayed as a listitem where the user can choose the department-name from.
One way to populate a listitem is to use a record-group in conjunction with a query. The query to populate a list must have to columns of datatype VARCHAR2, where the first one is the Label to be displayed to the user, and the second one is the value stored as itemvalue. Here's the example to populate the department-item, in my example used in the PRE-FORM-trigger:
DECLARE rg RECORDGROUP; nDummy NUMBER; BEGIN rg:=CREATE_GROUP_FROM_QUERY('RG', 'SELECT DEPARTMENT_NAME LABEL, TO_CHAR(DEPARTMENT_ID) VALUE FROM DEPARTMENTS ORDER BY 2'); nDummy:=POPULATE_GROUP(rg); POPULATE_LIST('EMPLOYEES.DEPARTMENT_ID', rg); END;
The other way to populate a listitem is to use the built-in's to handle a listitem in detail. There are built-in's to add or delete a row or to clear the entire list-item.
To achieve the same result as in the former example using these built-in's, the following code can be used
DECLARE CURSOR crDept IS SELECT DEPARTMENT_NAME LABEL, TO_CHAR(DEPARTMENT_ID) VALUE FROM DEPARTMENTS ORDER BY 2; it ITEM:=FIND_ITEM('EMPLOYEES.DEPARTMENT_ID'); BEGIN CLEAR_LIST(it); FOR rec IN crDept LOOP ADD_LIST_ELEMENT(it, GET_LIST_ELEMENT_COUNT(it)+1, rec.LABEL, rec.VALUE); END LOOP; END;
Of course, you could also use dynamic sql instead of the "fixed" cursor.
When filling a listitem dynamically you won't be able to define a "hardcoded" deafult-value, you will receive a FRM-30362.
To set a default-value in this case, you can either use an "indirect" initial value with using a global (something like :GLOBAL.G_INITIAL_DEPTNO) or parameter (something like :PARAMETER.P_INITIAL_DEPTNO). In both cases you have to make sure that the initial value in in the list. The other way is the use of a WHEN-CREATE-RECORD-trigger ad there setting the value of the listitem to the desired value, e.g.
:EMPLOYEES.DEPARTMENT_ID:=20;
Using the first method its quite easy to encapsulate the logic in a generic procedure:
PROCEDURE PR_POPULATE_LIST(i_vcItem IN VARCHAR2, i_vcQuery IN VARCHAR2) IS itList ITEM:=FIND_ITEM(i_vcItem); rgQuery RECORDGROUP:=FIND_GROUP('RG_QUERY'); nDummy NUMBER; BEGIN -- Check item-existance IF ID_NULL(itList) THEN -- Error-message "Item does not exist" message('Item does not exist', ACKNOWLEDGE); RAISE FORM_TRIGGER_FAILURE; ELSE -- delete possibly existing record-group IF NOT ID_NULL(rgQuery) THEN DELETE_GROUP(rgQuery); END IF; -- create the group from the query rgQuery:=CREATE_GROUP_FROM_QUERY('RG_QUERY', 'SELECT LABEL, TO_CHAR(VALUE) VALUE FROM (' || i_vcQuery ||')'); IF NOT FORM_SUCCESS THEN RAISE FORM_TRIGGER_FAILURE; ELSE -- populate the group nDummy:=POPULATE_GROUP(rgQuery); IF nDummy!=0 THEN -- Error-message "Error during group-population" message('Error during group-population' || TO_CHAR(nDummy), ACKNOWLEDGE); RAISE FORM_TRIGGER_FAILURE; ELSE -- fianlly, populate the list-item POPULATE_LIST(itList, rgQuery); END IF; END IF; END IF; END;
The error-handling would have to be adjusted according to the message-handling used in the application.
So finally, the code to fill a list-item is simply a
PR_POPULATE_LIST('EMPLOYEES.DEPARTMENT_ID', 'SELECT DEPARTMENT_NAME LABEL, DEPARTMENT_ID VALUE FROM DEPARTMENTS ORDER BY 2');