Since their inception in Forms 4.0, record groups have played a significant role in enhancing the development of forms, whether it be LOV manipulation, multiple selection, or intermediate sorting, to name a few uses. In this section, I will discuss techniques for
Changing a Record Group Dynamically
Record groups can be created either at design time or programmatically at runtime (dynamically). Also, dynamically created record groups can be locally scoped or globally scoped. Locally scoped means that the data contained in such a record group is accessible to the particular form in which it is created. Therefore, such a record group is not sharable across forms in a multiform application. A globally scoped record group has its data sharable across all forms in a single multiform session.
There are varied uses for creating and manipulating record groups dynamically, the most common ones being
I begin by providing a brief introduction to creating and manipulating dynamic record groups, both query and nonquery. In the subsequent subsections, I then discuss the techniques not often encountered in the normal creation and manipulation procedures.
Dynamically creating record groups involves creating a nonquery record group at runtime. This involves the following steps:
The following code illustrates this method:
DECLARE rg_id RECORDGROUP; rg_name VARCHAR2(20) := 'RG_DEPT'; gc_id1 GROUPCOLUMN; gc_id2 GROUPCOLUMN; row_cnt NUMBER := 5; BEGIN /* First check whether the record group already exists. If Yes, delete it. */ rg_id := FIND_GROUP(rg_name); IF NOT ID_NULL(rg_id) THEN DELETE_GROUP(rg_id); END IF; /* Create the record group to derive a handle or internal id */ rg_id := CREATE_GROUP(rg_name); IF ID_NULL(rg_id) THEN MESSAGE('ERR: Creating Record Group 'rg_name); RAISE FORM_TRIGGER_FAILURE; END IF; /* Add two group columns, deptno and dname */ gc_id1 := ADD_GROUP_COLUMN(rg_id, 'deptno', NUMBER_COLUMN); gc_id2 := ADD_GROUP_COLUMN(rg_id, 'dname', CHAR_COLUMN, 20); /* Create 5 rows and populate them in a loop. The constant 5 is chosen at random to illustrate the concepts */ FOR i in 1..row_cnt LOOP ADD_GROUP_ROW(rg_id, END_OF_GROUP); SET_GROUP_NUMBER_CELL(gc_id1, i, i); SET_GROUP_CHAR_CELL(gc_id2, i, 'Department 'TO_CHAR(i)); END LOOP; END;
Creating a query record group at runtime involves the following steps:
The following code implements this method:
DECLARE rg_id RECORDGROUP; rg_name VARCHAR2(20) := 'RG_DEPT'; query_string := 'SELECT deptno,, dname FROM dept ORDER BY dname'; ret_code NUMBER; BEGIN /* First check whether the record group already exists. If Yes, delete it. */ rg_id := FIND_GROUP(rg_name); IF NOT ID_NULL(rg_id) THEN DELETE_GROUP(rg_id); END IF; /* Create the record group from an input query to derive a handle or internal id, as well as the column structure */ rg_id := CREATE_GROUP_FROM_QUERY(rg_name , query_string); IF ID_NULL(rg_id) THEN MESSAGE('ERR: Creating Record Group 'rg_name); RAISE FORM_TRIGGER_FAILURE; END IF; /* Populate the created record group with data from the query used to create the group. This need not be done in a loop. */ ret_code := POPULATE_GROUP(rg_id); IF (ret_code <> 0) THEN MESSAGE('ERR: Populating Group'); RAISE FORM_TRIGGER_FAILURE; END IF; /* This line containing the call to POPULATE_GROUP can be replaced by the following line if the query to populate the data is different. ret_code := POPULATE_GROUP_WITH_QUERY(rg_id, 'SELECT deptno, dname FROM dept WHERE loc = ''NEW YORK'' ORDER BY dname'); */ END;
Dynamically manipulating record groups amounts to adding, changing, and deleting row(s) in the following ways:
Tip
Here is an important tip that reminds us of an often-ignored point about creating record groups dynamically. You can change a record group dynamically only if it's created dynamically (query or nonquery) or if it's a query record group created at design time.
Changing GLOBAL_SCOPE record groups is discussed in Chapter 4, "Advanced Forms Programming" under the section "Sharing a Record Group across Forms."
Adding to the Beginning of a Record Group
How many of you are aware that in Forms 4.5 you can add rows to the beginning of an already created record group? This seems trivial at first thought, but is a powerful and flexible technique to be used in many demanding situations.
To do this, use ADD_GROUP_ROW and specify the constant 1 for the row index, as follows :
ADD_GROUP_ROW(rg_id, 1);
This displaces all the existing rows to one position below and makes room for the new row with row number 1. Do not specify the constant instead of 1.
Adding in the Middle or at the End of a Record Group
To add a record in the middle of a record group, use ADD_GROUP_ROW and specify the index number of the new row as
current row index + 1
where current row is the row after which the new row should be added. This displaces all the remaining rows to one position below and makes room for the new row.
To add a record to the end of the record group, specify the constant END_OF_GROUP for the index number.
Multiple Selection
Multiple selection is a common requirement in almost all applications in which the user expects a choice of elements to choose from by click-and-highlight. The multiple selection is made possible by giving the user the flexibility of choosing from a SELECT_ALL list on the left. After the entire selection is made , an ADD button enables the selected records to be transferred to the right side. The question of retaining the selected records on the left hand side (LHS) is application and user-requirement specific. Here, I will retain the selected records from the LHS as soon as the user presses the ADD button.
Now I will discuss the technique of multiple selection by using dynamically created record groups and using Select and De-select toggle by click operation only.
To illustrate this technique, consider a payroll application in which users are presented with an initial selection screen displaying a list of all departments. Selection of multiple departments is allowed. The payroll must be processed for the employees of the multiple-selected departments.
The design is as follows:
There will be no facility to perform INSERT, UPDATE, DELETE, or QUERY records in both the blocks.
These buttons belong to the control block.
Figure 2.7 shows a typical multiselection functionality. To create it, follow these steps:
Figure 2.7. Multiple selection of departments.
DATABASE BLOCK | YES |
Number of Records Displayed | 10 |
Query Allowed | Yes |
Insert Allowed | No |
Update Allowed | No |
Delete Allowed | No |
Query Data Source Type | TABLE |
Query Data Source Name | DEPT |
QUERY ALL RECORDS | YES |
Note the special property QUERY ALL RECORDS. This is set to YES to enable all the records to be fetched into the Forms buffer at the very beginning of the query.
DATABASE BLOCK | NO |
Number of Records Displayed | 10 |
Query Allowed | No |
Insert Allowed | Yes |
Update Allowed | No |
Delete Allowed | No |
The items DEPTNO and DNAME have the Insert Allowed and Update Allowed properties set to No. The block property Insert Allowed is set to Yes to enable programmatically creating a record while populating the selected records from the record group.
GO_BLOCK('ALL_DEPT'); Check_package_failure; EXECUTE_QUERY;
SET_ITEM_INSTANCE_PROPERTY('ALL_DEPT.DEPTNO', CURRENT_RECORD, VISUAL_ATTRIBUTE, 'VA_UNSELECTED'); SET_ITEM_INSTANCE_PROPERTY('ALL_DEPT.DNAME', CURRENT_RECORD, VISUAL_ATTRIBUTE, 'VA_UNSELECTED');
DECLARE ret_code NUMBER; BEGIN ret_code := selection_toggle('ALL_DEPT'); :ctrl_blk.add_count := NVL(:ctrl_blk.add_count,0) + ret_code; IF :ctrl_blk.add_count > 0 THEN SET_ITEM_PROPERTY('CTRL_BLK.PB_ADD', ENABLED, PROPERTY_TRUE); IF :ctrl_blk.add_count > 1 THEN SET_ITEM_PROPERTY('CTRL_BLK.PB_ADD_ALL', ENABLED, PROPERTY_TRUE); END IF; ELSE SET_ITEM_PROPERTY('CTRL_BLK.PB_ADD', ENABLED, PROPERTY_FALSE); SET_ITEM_PROPERTY('CTRL_BLK.PB_ADD_ALL', ENABLED, PROPERTY_FALSE); END IF; END;
The function code is as follows:
FUNCTION selection_toggle(block_name VARCHAR2) RETURN NUMBER IS row_num Number; row_no Number; v_num Number; v_added VARCHAR2(1); curr_rownum NUMBER; present_in_selection BOOLEAN := FALSE; present_in_group BOOLEAN := FALSE; already_added BOOLEAN := FALSE; BEGIN IF GET_ITEM_INSTANCE_PROPERTY(block_name'.' GET_BLOCK_PROPERTY(block_name, FIRST_ITEM), CURRENT_RECORD, VISUAL_ATTRIBUTE) = 'VA_UNSELECTED'THEN DISPLAY_ITEM(block_name'.' GET_BLOCK_PROPERTY(block_name, FIRST_ITEM), 'VA_SELECTED'); DISPLAY_ITEM(block_name'.' GET_BLOCK_PROPERTY(block_name, LAST_ITEM), 'VA_SELECTED'); -- Creating the Record Group rg_lhs.rg_id := FIND_GROUP(rg_lhs.rg_name); IF FORM_SUCCESS THEN IF ID_NULL(rg_lhs.rg_id) THEN rg_lhs.rg_id := CREATE_GROUP(rg_lhs.rg_name); IF FORM_SUCCESS THEN IF ID_NULL(rg_lhs.rg_id) THEN MESSAGE('ERR: Creating Group 'rg_lhs.rg_name); RAISE FORM_TRIGGER_FAILURE; END IF; END IF; END IF; END IF; -- Add Group Columns rg_lhs.gc_id1 := FIND_COLUMN(rg_lhs.rg_name'.'rg_lhs.column_name1); IF ID_NULL(rg_lhs.gc_id1) THEN rg_lhs.gc_id1 := ADD_GROUP_COLUMN(rg_lhs.rg_id, rg_lhs.column_name1, NUMBER_COLUMN); END IF; rg_lhs.gc_id2 := FIND_COLUMN(rg_lhs.rg_name'.'rg_lhs.column_name2); IF ID_NULL(rg_lhs.gc_id2) THEN rg_lhs.gc_id2 := ADD_GROUP_COLUMN(rg_lhs.rg_id, rg_lhs.column_name2, CHAR_COLUMN, 20); END IF; rg_lhs.gc_id3 := FIND_COLUMN(rg_lhs.rg_name'.'rg_lhs.column_name3); IF ID_NULL(rg_lhs.gc_id3) THEN rg_lhs.gc_id3 := ADD_GROUP_COLUMN(rg_lhs.rg_id, rg_lhs.column_name3, CHAR_COLUMN, 5); END IF; -- Getting group row count rg_lhs.row_cnt := get_group_row_count(rg_lhs.rg_id); -- current row num is row cnt incremented by 1 row_num := NVL(rg_lhs.row_cnt,0) + 1; -- If rg cnt is 0, i.e., the very first time, add rg row. IF rg_lhs.row_cnt = 0 THEN Add_group_row(rg_lhs.rg_id, row_num); set_group_number_cell(rg_lhs.gc_id1, row_num, TO_NUMBER(name_in('ALL_DEPT.DEPTNO'))); set_group_char_cell(rg_lhs.gc_id2, row_num, name_in('ALL_DEPT.DNAME')); set_group_char_cell(rg_lhs.gc_id3, row_num, 'N'); END IF; -- Search if row already present in the record group. for i in 1 .. rg_lhs.row_cnt loop v_num := get_group_number_cell(rg_lhs.gc_id1, i); if :all_dept.deptno = v_num then curr_rownum := i; present_in_group := TRUE; exit; end if; end loop; if present_in_group then -- Search if row already selected. rg_lhs.sel_cnt := GET_GROUP_SELECTION_COUNT(rg_lhs.rg_id); for j in 1 .. rg_lhs.sel_cnt loop row_no := get_group_selection(rg_lhs.rg_id, j); v_num := get_group_number_cell(rg_lhs.gc_id1, row_no); if :all_dept.deptno = v_num then present_in_selection := TRUE; exit; end if; end loop; if not present_in_selection then set_group_selection(rg_lhs.rg_id, curr_rownum); end if; else -- not present in group, so add new row Add_group_row(rg_lhs.rg_id, row_num); set_group_number_cell(rg_lhs.gc_id1, row_num, TO_NUMBER(name_in('ALL_DEPT.DEPTNO'))); set_group_char_cell(rg_lhs.gc_id2, row_num, name_in('ALL_DEPT.DNAME')); set_group_char_cell(rg_lhs.gc_id3, row_num, 'N'); SET_GROUP_SELECTION(rg_lhs.rg_id, row_num); end if; Return(1); ELSIF GET_ITEM_INSTANCE_PROPERTY(block_name'.' GET_BLOCK_PROPERTY(block_name, FIRST_ITEM), CURRENT_RECORD, VISUAL_ATTRIBUTE) = 'VA_SELECTED'THEN -- If already selected, de-select it rg_lhs.sel_cnt := GET_GROUP_SELECTION_COUNT(rg_lhs.rg_id); for idx in 1 .. rg_lhs.sel_cnt loop row_no := get_group_selection(rg_lhs.rg_id, idx); v_num := get_group_number_cell(rg_lhs.gc_id1, row_no); v_added := get_group_char_cell(rg_lhs.gc_id3, row_no); if :all_dept.deptno = v_num then if (v_added != 'Y') then unset_group_selection(rg_lhs.rg_id, row_no); end if; exit; else null; end if; end loop; DISPLAY_ITEM(block_name'.' GET_BLOCK_PROPERTY(block_name, FIRST_ITEM), 'VA_UNSELECTED'); DISPLAY_ITEM(block_name'.' GET_BLOCK_PROPERTY(block_name, LAST_ITEM), 'VA_UNSELECTED'); message(rg_lhs.sel_cnt); pause; Return (-1); END IF; END;
DECLARE row_no NUMBER; sl_cnt NUMBER; BEGIN IF (:ctrl_blk.add_count > 0) THEN GO_BLOCK('SELECTED_DEPT'); CLEAR_BLOCK(NO_VALIDATE); sl_cnt := GET_GROUP_SELECTION_COUNT(rg_lhs.rg_id); for idx in 1 .. sl_cnt loop row_no := get_group_selection(rg_lhs.rg_id, idx); COPY(GET_GROUP_NUMBER_CELL(rg_lhs.gc_id1, row_no), 'SELECTED_DEPT.DEPTNO'); COPY(GET_GROUP_CHAR_CELL(rg_lhs.gc_id2, row_no), 'SELECTED_DEPT.DNAME'); SET_GROUP_CHAR_CELL(rg_lhs.gc_id3, row_no, 'Y'); CREATE_RECORD; end loop; go_block('ALL_DEPT'); execute_query(no_validate); -- RESET_GROUP_SELECTION(rg_lhs.rg_id); :ctrl_blk.add_count := NULL; SET_ITEM_PROPERTY('CTRL_BLK.PB_ADD', ENABLED, PROPERTY_FALSE); SET_ITEM_PROPERTY('CTRL_BLK.PB_ADD_ALL', ENABLED, PROPERTY_FALSE); END IF; END;
This method works well from Forms 5.x onwards. In Forms 4.5, you have three choices:
The actual code for WHEN-BUTTON-PRESSED of the ADD ALL button is as follows:
SET_BLOCK_PROPERTY('SELECTED_DEPT',QUERY_DATA_SOURCE_NAME, 'DEPT'); GO_BLOCK('ALL_DEPT'); CLEAR_BLOCK('NO_VALIDATE'); SET_BLOCK_PROPERTY('ALL_DEPT', CURRENT_RECORD_ATTRIBUTE, 'VA_UNSELECTED'); GO_BLOCK('SELECTED_DEPT'); EXECUTE_QUERY(ALL_RECORDS);
The WHEN-BUTTON-PRESSED trigger for PB_REMOVE_ALL is
GO_BLOCK('SELECTED_DEPT'); CLEAR_BLOCK(NO_VALIDATE'); SET_BLOCK_PROPERTY('SELECTED_DEPT', CURRENT_RECORD_ATTRIBUTE, 'VA_UNSELECTED'); GO_BLOCK('ALL_DEPT'); EXECUTE_QUERY(ALL_RECORDS);
Intermediate Sorting Using Record Groups
Imagine, in the preceding illustration, if the selected records in the intermediate record group were required to be sorted by DNAME before populating them to the RHS block. This could be a requirement if the user selects records from the LHS block at random.
The description of the bubble sort algorithm is as follows:
To sort an array of n integers, a1, a2, ..., aN, For i in 1 to n loop For j in (i+1) to n-1 loop if a(i) > a(j) then temp = a(i) a(i) = a(j) a(j) = temp end if end loop end loop
The following piece of code illustrates the preceding bubble sort algorithm for sorting rows in a record group:
DECLARE I NUMBER; J NUMBER; Row_count NUMBER; Rg_id recordGroup; Gc_id1 GroupColumn; Gc_id2 GroupColumn; BEGIN /* Check for the existence of the Record Group */ Rg_id := FIND_GROUP('RG_LHS'); IF ID_NULL(rg_id) THEN Return (-1); END IF; /* Determine the sort columns */ Gc_id1 := FIND_COLUMN(rg_id, 'deptno'); Gc_id2 := FIND_COLUMN(rg_id, 'dname'); /* Get the Record Group Row Count */ Row_count := GET_GROUP_ROW_COUNT(rg_id); /* Implement the bubble sort algorithm */ FOR i IN 1..(rg_lhs.sel_count - 1) LOOP FOR j in (i+1)..rg_lhs.sel_count LOOP temp1_id := GET_GROUP_NUMBER_CELL(gc_id1,i); temp1_name := GET_GROUP_CHAR_CELL(gc_id2,i); temp2_id := GET_GROUP_NUMBER_CELL(gc_id1,j); temp2_name := GET_GROUP_CHAR_CELL(gc_id2,j); IF temp1_name > temp2_name THEN SET_GROUP_NUMBER_CELL(gc_id1,i, temp2_id); SET_GROUP_CHAR_CELL(gc_id2, i, temp2_name); SET_GROUP_NUMBER_CELL(gc_id1,j, temp1_id); SET_GROUP_CHAR_CELL(gc_id2, j, temp1_name); END IF; END LOOP; END LOOP; /* End of sorting */ code for populating RHS block from record group follows> < END;
GUI Development
Advanced GUI Development: Developing Beyond GUI
Multi-form Applications
Advanced Forms Programming
Error-Message Handling
Object-oriented Methods in Forms
Intelligence in Forms
Additional Interesting Techniques
Working with Trees
Oracle 8 and 8i Features in Forms Developer