In this section, I shall discuss some special tips and techniques offered by Forms with respect to lists and list items.
Populating List Items Dynamically
List items appear as either drop-down list boxes, T-lists, or combo boxes in Forms. Mostly, list items have static values as their elements, created during design time. However, there arises a need to populate a list item with elements based on runtime criteria or from a database table. For example, one might populate a drop-down list box with all the state codes from a look-up table named STATE_TAB. This can be done either using a query or adding elements one by one at runtime. This way of populating list items programmatically without hard-coding their values at design time is what is termed dynamically populating list items.
This technique involves populating the list with a query or without a query. I will discuss populating by means of a query because it is very powerful and efficient.
One elegant way to populate list items dynamically is to use programmatically created records groups. This record group should have a two-column structure, both being of character data type. The first column should correspond to the label of the list item, and the second column, to the corresponding value.
The record group has to be created programmatically. However, it can either query or nonquery as long as it follows the two-column structure mentioned here.
Never try to populate a list item using a query directly. Always create and populate a query record group, and use this record group to do the job.
The code performs these tasks :
The sample code is given here:
DECLARE rg_list_id RECORDGROUP; rg_name VARCHAR2(20) := 'RG_LIST'; ret_code NUMBER; --The following holds a SELECT query from which the list elements are derived. v_select VARCHAR2(300); BEGIN v_select := 'SELECT state_code, state_code FROM state_tab ORDER BY 2'; rg_list_id := FIND_GROUP(rg_name); IF NOT Id_Null(rg_list_id) THEN DELETE_GROUP(rg_list_id); END IF; rg_list_id := CREATE_GROUP_FROM_QUERY(rg_name, v_select); ret_code := POPULATE_GROUP(rg_list_id); POPULATE_LIST('LIST_ITEM','RG_LIST'); DELETE_GROUP(rg_list_id); END;
Use a form procedure, passing the input query and list item name as parameters.
A nonquery record group can also be used instead of a query record group, provided that it is created programmatically using CREATE_GROUP and populated using POPULATE_GROUP or POPULATE_GROUP_WITH_QUERY.
Populating a List Item with Date and Number Values
Sometimes, it might be necessary to populate NUMBER and DATE columns as list item element values. Because list items always retain character values only, for both the label and value, it is necessary to perform conversion to VARCHAR2 from NUMBER and DATE.
As an illustration, consider a drop-down list box showing all departments in an organization. You can assume that the DEPT table has the following structure:
CREATE TABLE DEPT (ID NUMBER(6) PRIMARY KEY, NAME VARCHAR2(30) NOT NULl);
The label column has its values derived from the NAME column. The value corresponding to each NAME is derived from the ID column and should be stored in the list item as a character value. This requires the use of TO_CHAR. Therefore, the query text in the preceding example changes to
v_select := 'SELECT name, TO_CHAR(id) FROM dept ORDER BY 1';
After populating the list, the value can be accessed by doing a reconversion to NUMBER using the TO_NUMBER function. The following shows how to access the ith element from the list discussed in the preceding example:
DECLARE v_id NUMBER; v_id_char VARCHAR2(6); item_id ITEM; BEGIN item_id := FIND_ITEM('LIST_ITEM'); IF ID_NULL(item_id) THEN MESSAGE('Invalid List'); RAISE FORM_TRIGGER_FAILURE; END IF; FOR I IN 1..GET_LIST_ELEMENT_COUNT(item_id) LOOP v_id_char = GET_LIST_ELEMENT_VALUE(item_id, i); v_id := TO_NUMBER(v_id); . . . . use this value for further processing END LOOP; END;
Use conversion functions TO_CHAR, TO_DATE, and TO_NUMBER.
On input to the list, use TO_CHAR for both date and numeric values that correspond to the Value column of the list.
On output from the list, use TO_DATE and TO_NUMBER, respectively, for date and numeric values.
Use these in the SELECT column list, which is used to populate the list.
Adding Items to the Beginning of a List
How many of you are aware of the fact that in Forms, you can add elements to the beginning of an already populated list item without repopulating it? This seems trivial at first thought but is a very powerful and flexible technique to be used in many demanding situations.
Use ADD_LIST_ELEMENT and specify the constant 1 for the element index. This displaces all the existing elements to one position below and makes room for the new element with index number 1. Do not specify the constant instead of 1.
The code you will use is as follows:
ADD_LIST_ELEMENT(list_id, 1, , );
Adding Items in the Middle and to the End of a List
So far, I have discussed how to add elements dynamically to an empty list, how to access noncharacter list elements, and how to add to the beginning of an existing list. Now I will present a simple method to insert elements in the middle of an existing list and to append elements to the end of an existing list.
Use ADD_LIST_ELEMENT and specify the index number of the new element as
current element index + 1
where current element is the element after which the new element should be added. This displaces all the remaining elements to one position below and makes room for the new element.
To add to the end of the list, specify the constant returned by GET_LIST_ELEMENT_COUNT, which gives the number of existing elements, and then specify the index of the new element as the value of this constant incremented by 1.
The following is the code for this:
DECLARE cnt NUMBER := 0; BEGIN cnt := GET_LIST_ELEMENT_COUNT(list_id); ADD_LIST_ELEMENT(list_id, (cnt+1), , ); END;
In this example, you take the count of the list elements and increment it by 1. This value serves as the index for the new list element to be added. This works even when the list is wholly empty, because you initialize the count to zero before incrementing it by 1. Therefore, it adds the first element in case of an empty list.
Simulating a Drill-Down and Drill-Up LOV Using T-Lists
LOV is the Forms acronym for List of Values. It functions in a manner similar to a pick list of choices. Drill-down LOV refers to descending through the LOV to its sublevels, starting from a highlighted element in the LOV.
Drill-down LOVs are very useful for tables involving recursive and/or parent-child relationships. Examples of such relationships are those involving a department and its employees, a manager and employees , or a company and its hierarchical representation.
In each of these cases, a foreign key is involved that is either self-referential or parent- referential. For example, the department-employee relationship involves a parent-referential foreign key from the Department table (the parent table). The manager-employees relationship is self-referential, with the primary and foreign keys being selected from the same table. Also, the information is hierarchical. The company information is another example of a hierarchical representation.
LOVs are a commonly required feature of any application involving this kind of look-ups. The features of LOVs supported by Forms are limited in the sense that
Out of these limitations, the most required functionality in case of parent-child relationships, especially tree-oriented, is the drill-down.
Drill-down functionality can be incorporated in an LOV directly using a Forms-provided LOV or using list items. This section discusses the implementation details of building a drill-down LOV using list items. The same method can be followed when using a Forms-supplied LOV.
You will use a T-list and dynamically populate it using a record group. The drill-down is achieved by dynamic replacement of the same record group query again and again till there is no further drill-down. Double-clicking a parent element will show this parent element and all its children one level below. The user "drills down" the LOV to reach a deeper level. The operation is repeatable until leaf elements are reached. The string '- Parent' is appended to the parent element label to identify it as the parent. This element is always the first element of the list, irrespective of whether it has children.
Double-clicking a parent element will show its parent and all its children, that is, one level above. The user "drills up" the LOV to reach a higher level. The operation is repeatable until the root element is reached. Once at the root, the original list is restored; that is, all EMPNO s that exist as MGR s. The string '- Parent' is still appended to the parent element label until the initial list is restored.
The same T-list and the same record group are reused for the drill-down and drill-up operations.
The selection of an element is done by pressing Shift and double-clicking on the element. The normal double-clicking being reserved for drill-down, the Shift+double-click is used as an alternative for selecting an element from the LOV.
The property class PC_TLIST has its properties set as shown in Figure 1.7.
Figure 1.7. A property class named PC_TLIST for the T-list item LOV.
Remember to create a NULL list element; that is, both the label and its value are NULL. NULL means a null value, not the string 'NULL'.
CREATE TABLE EMP (EMPNO NUMBER(10) PRIMARY KEY, ENAME VARCHAR2(30) NOT NULL, MGR NUMBER(10) REFERENCES EMP (EMPNO), HIREDATE DATE, SAL NUMBER(11,2), JOB VARCHAR2(20), DEPTNO NUMBER);
The LOV functions the following way. At first, all the employees at the parent level are displayed. The query for doing this follows:
SELECT ename, TO_CHAR(empno) empno FROM emp WHERE empno IN (SELECT mgr FROM emp a)
This SELECT statement also guarantees that the first-level EMPNO is selected.
PROCEDURE p_populate_list(item_name_in VARCHAR2, query_in varchar2, o_retcd OUT NUMBER) IS rg_id RECORDGROUP; retcd NUMBER; rg_name VARCHAR2(100); item_id ITEM; BEGIN item_id := FIND_ITEM(item_name_in); IF ID_NULL(item_id) THEN o_retcd := -1; RETRUN; END IF; rg_name := 'RG_'substr(item_name_in, INSTR(item_name_in, '.', 1)+1, LENGTH(item_name_in)); rg_id := FIND_GROUP(rg_name); IF NOT ID_NULL(rg_id) THEN DELETE_GROUP(rg_id); END IF; rg_id := CREATE_GROUP_FROM_QUERY(rg_name, query_in); retcd := POPULATE_GROUP(rg_id); IF (retcd <> 0) THEN o_retcd := retcd; RETURN; END IF; POPULATE_LIST(item_name_in, rg_id); IF NOT FORM_SUCCESS THEN o_retcd := -2; END IF; o_retcd := 0; END;
The appropriate trigger for calling the p_populate_list procedure is WHEN-NEW-FORM-INSTANCE :
WHEN-NEW-FORM-INSTANCE DECLARE query_in VARCHAR2(32767) := 'SELECT ename, TO_CHAR(empno) empno FROM emp WHERE empno IN (SELECT mgr FROM emp); retcd NUMBER; BEGIN p_populate_list('BLOCK2.LOV', query_in, retcd); IF (retcd <> 0) THEN MESSAGE('ERR: Could not populate list item.'); RAISE FORM_TRIGGER_FAILURE; END IF; END;
DECLARE query_in VARCHAR2(32767); item_name VARCHAR2(100) := NAME_IN('SYSTEM.TRIGGER_ITEM'); retcd NUMBER; current_rows_mgr NUMBER; current_rows_empno NUMBER; BEGIN query_in := 'select LPAD(ename, ((LEVEL-1)*4+LENGTH(ename)), '''') DECODE(TO_CHAR(empno), ' NAME_IN(item_name)', ''- Parent'', NULL) ename, TO_CHAR(empno) ' ' FROM emp ''WHERE empno = 'TO_NUMBER(NAME_IN(item_name)) 'or mgr = 'TO_NUMBER(NAME_IN(item_name)) ' START WITH empno = ' TO_NUMBER(NAME_IN(item_name))'CONNECT BY PRIOR empno = mgr'; p_populate_list(item_name, query_in, retcd); END;
WHEN-LIST-ACTIVATED DECLARE query_in VARCHAR2(32767); item_name VARCHAR2(100) := NAME_IN('SYSTEM.TRIGGER_ITEM'); retcd NUMBER; current_rows_mgr NUMBER; current_rows_empno NUMBER; BEGIN IF INSTR(GET_LIST_ELEMENT_LABEL(item_name, 1),'Parent', 1) = 0 THEN -- if current element is in the initial list query_in := 'SELECT LPAD(ename, ((LEVEL-1)*4+LENGTH(ename)), '''') DECODE(TO_CHAR(empno), ' NAME_IN(item_name)',''- Parent'',NULL) ename, TO_CHAR(empno)' 'FROM emp ' 'WHERE empno = 'TO_NUMBER(NAME_IN(item_name))'or mgr = ' TO_NUMBER(NAME_IN(item_name)) 'START WITH empno = 'TO_NUMBER(NAME_IN(item_name)) 'CONNECT BY PRIOR empno = mgr '; ELSIF INSTR(GET_LIST_ELEMENT_LABEL(item_name, 1),'Parent', 1) > 0 and (TO_NUMBER(GET_LIST_ELEMENT_VALUE(item_name,1)) != TO_NUMBER(NAME_IN(item_name))) THEN -- if current is a child of a parent query_in := 'SELECT LPAD(ename, ((LEVEL-1)*4+LENGTH(ename)), '''') DECODE(TO_CHAR(empno), ' NAME_IN(item_name)', ''- Parent'',NULL) ename, TO_CHAR(empno)' 'FROM emp ' 'WHERE empno = 'TO_NUMBER(NAME_IN(item_name))'or mgr = ' TO_NUMBER(NAME_IN(item_name)) 'START WITH empno = 'TO_NUMBER(NAME_IN(item_name)) 'CONNECT BY PRIOR empno = mgr '; ELSIF INSTR(GET_LIST_ELEMENT_LABEL(item_name, 1),'Parent', 1) > 0 AND (TO_NUMBER(GET_LIST_ELEMENT_VALUE(item_name,1)) = TO_NUMBER(NAME_IN(item_name))) THEN -- if current element is a parent BEGIN current_rows_empno := TO_NUMBER(NAME_IN(item_name)); MESSAGE(TO_CHAR(current_rows_empno), ACKNOWLEDGE); SELECT mgr INTO current_rows_mgr FROM emp WHERE empno = current_rows_empno; EXCEPTION WHEN NO_DATA_FOUND THEN NULL; END; IF current_rows_mgr IS NOT NULL THEN query_in := 'SELECT LPAD(ename, ((LEVEL-1)*4+LENGTH(ename)), '''') DECODE(TO_CHAR(empno), 'TO_CHAR(current_rows_mgr) ', ''- Parent'', NULL) ename, TO_CHAR(empno) ' 'FROM emp ' 'WHERE empno = 'current_rows_mgr'or mgr = 'current_rows_mgr 'START WITH empno = 'current_rows_mgr 'CONNECT BY PRIOR empno = mgr '; ELSE query_in := 'SELECT ename, TO_CHAR(empno) empno FROM emp WHERE empno IN (SELECT mgr FROM emp)'; END IF;; END IF; p_populate_list(item_name, query_in, retcd); END;
Figures 1.8 through 1.10 depict the drill-down operation on the sample list discussed here. The parent for the second level is suffixed with the phrase '- Parent' to mark it as the parent element for the children displayed below it.
Figure 1.8. Original List before drill-down
Figure 1.9. First Level LOV after drill-down
Figure 1.10. Second Level LOV after drill-down
Simulating the Find Feature of List of Values
The Find feature of LOVs enables you to enlarge or reduce the LOV list as the user types in characters of the LOV element value. This feature can be simulated using COMBO BOX and T-LIST type list items in Forms 4.5. Although it doesn't use the exact mechanism offered by LOVs, the method described here imitates the same functionality. You will assume that the list to be displayed is SITE_NAME, based on the SITE_TAB table. The SITE_TAB table has the following structure:
SITE_NO NUMBER(6) NOT NULL, SITE_NAME VARCHAR2(20) NOT NULL.
Follow these steps:
DECLARE rg_list_id RECORDGROUP; ret_code NUMBER; BEGIN rg_list_id := FIND_GROUP('RG_LIST'); IF NOT ID_NULL(rg_list_id) THEN DELETE_GROUP(rg_list_id); END IF; rg_list_id := CREATE_GROUP_FROM_QUERY('RG_LIST', 'SELECT site_name, site_name FROM site_tab WHERE site_name LIKE ''''':LIST_ITEM_COMBO'%'''''); ret_code := POPULATE_GROUP(rg_list_id); POPULATE_LIST('LIST_DEST_ITEM','RG_LIST'); DELETE_GROUP(rg_list_id); END;
This reduces or enlarges the list, based on user input in LIST_ITEM_COMBO.
Advanced GUI Development: Developing Beyond GUI
Advanced Forms Programming
Object-oriented Methods in Forms
Intelligence in Forms
Additional Interesting Techniques
Working with Trees
Oracle 8 and 8i Features in Forms Developer