Tabular forms are very effective when it comes to mutating (table) data. Unfortunately APEX allows you to use only one tabular form per page. Lucky for us, there are ways to get more than one tab form on a page. In my demo application I have one example of two iFrames in one page; each iFrame contains a tabular form page. This is about the simplest way to get two tab forms on a page. Sadly this way you will have a set of buttons for each page. A more advanced way is to create the tabular forms yourself, instead of using the APEX generated tab form. There are a number of examples on how to do that, see amongst others Denes Kubicicek and Martin D'Souza.
In this post I would like to add to the stack, an example of how you can easily create tabular forms yourself. In the example I will use the apex_item package to create input fields for each report column. On top I'll use jQuery to add rows and in preparation of the delete process. The use jQuery for adding rows to the form shows a bit more of what apex is doing in the background when you generate an apex_item field. And, best of all, one tabular form is going to be an interactive report, with sorting and filtering options.
To start we create an interactive report on the EMP table, but instead of doing a normal "select * from EMP", we need to call each column with apex_item functions. The query will look as follows:
SELECT apex_item.checkbox2(1, empno) check$01 | |
, apex_item.hidden(2, empno) empno | |
, apex_item.text(3, ename) ename | |
, apex_item.text(4, job) job | |
, apex_item.text(5, mgr) mgr | |
, apex_item.text(6, hiredate) hiredate | |
, apex_item.text(7, sal) sal | |
, apex_item.text(8, comm) comm | |
, apex_item.text(9, deptno) deptno | |
, apex_item.md5_checksum(ename,job,mgr,hiredate,sal,comm,deptno) checksum | |
FROM emp |
Next, we create the DEPT report. This is done the same way we created the EMP report, only now we choose a standard report (APEX allows you to create no more than one interactive report per page). Again, make sure each column is set as 'Standard Report Column' and provide a static ID to distinguish your report. The query will look like this:
SELECT apex_item.checkbox2(11, deptno) check$01 | |
, apex_item.hidden(12, deptno) deptno | |
, apex_item.text(13, dname) dname | |
, apex_item.text(14, loc) loc | |
, apex_item.md5_checksum(dname,loc) checksum | |
FROM dept |
Now that we have two reports with input fields, we need to create a procedure to handle the inserts and updates on the EMP and DEPT tables. You could do this in one process, but here I made one insert/update process for EMP, and one for DEPT. That way it's easier to see what's going on.
First we create a submit button. Then we create a page process that fires on submit, after computations and validations. In the process we need to loop over the f_array and map each f_item with the correct table column, so f_02 is EMPNO, f_03 is ENAME, etc.
begin | |
for i in 1 .. apex_application.g_f02.count | |
loop | |
if apex_application.g_f02(i) is null or apex_application.g_f02(i) = '' | |
then | |
insert into emp | |
( ename | |
, job | |
, mgr | |
, hiredate | |
, sal | |
, comm | |
, deptno | |
) | |
values | |
( apex_application.g_f03(i) | |
, apex_application.g_f04(i) | |
, apex_application.g_f05(i) | |
, apex_application.g_f06(i) | |
, apex_application.g_f07(i) | |
, apex_application.g_f08(i) | |
, apex_application.g_f09(i) | |
); | |
else | |
update emp | |
set ename = apex_application.g_f03(i) | |
, job = apex_application.g_f04(i) | |
, mgr = apex_application.g_f05(i) | |
, hiredate = apex_application.g_f06(i) | |
, sal = apex_application.g_f07(i) | |
, comm = apex_application.g_f08(i) | |
, deptno = apex_application.g_f09(i) | |
where empno = apex_application.g_f02(i) | |
; | |
end if; | |
end loop; | |
end; |
begin | |
for i in 1 .. apex_application.g_f12.count | |
loop | |
if apex_application.g_f12(i) is null or apex_application.g_f12(i) = '' | |
then | |
insert into dept | |
( dname | |
, loc | |
) | |
values | |
( apex_application.g_f13(i) | |
, apex_application.g_f14(i) | |
); | |
else | |
update dept | |
set dname = apex_application.g_f13(i) | |
, loc = apex_application.g_f14(i) | |
where deptno = apex_application.g_f12(i) | |
; | |
end if; | |
end loop; | |
end; |
At this point we can do updates on both tab forms. The process for inserting records is there as well, so now is a good time to create our 'add row' function. The function will basically copy the last row of a table, empty all data and paste the row at the end of the table. Since this is a generic function that can be used on more than one page in your application, you should put the function in a JavaScript file that you include in your application. Here I simply put the function in the page header.
<script type="text/javascript"> | |
function addRow(tableId) { | |
/* declare some variables. */ | |
var thisTable = $('#'+tableId) // the table that will be affected | |
, newRow = $(thisTable).find('tbody tr:last').html() // new row that we're going to add | |
$(thisTable).find('tr:last').after('<tr>'+newRow+'</tr>') // place the new row after the last one in the table | |
$('#'+tableId+' tbody tr:last input').each(function(){$(this).val('')}) // clear all values from the new row | |
} | |
</script> |

$('#empForm table table').attr('id','empTable') | |
$('#deptForm table table').attr('id','deptTable') |


The last thing we need to do is make create a delete process. We want to delete all rows we've checked with the report checkboxes. The checkboxes of the EMP table and the DEPT table all hold the primary key values of each table row, so it makes sense to use that. We'll create a dynamic action that looks for checked checkboxes and store their corresponding values in a hidden page item. The dynamic action is fired when a checkbox of either table is clicked.
var empNos = $('#empTable input[name="f01"]:checked').map(function(){ | |
return $(this).val() | |
}).get().join(':') | |
var deptNos = $('#deptTable input[name="f11"]:checked').map(function(){ | |
return $(this).val() | |
}).get().join(':') | |
$('#P17_EMPNOS').val(empNos) | |
$('#P17_DEPTNOS').val(deptNos) |

declare | |
l_emp apex_application_global.vc_arr2 := apex_util.string_to_table(:p17_empnos); | |
l_dept apex_application_global.vc_arr2 := apex_util.string_to_table(:p17_deptnos); | |
begin | |
for i in 1 .. l_emp.count | |
loop | |
delete from emp where empno = l_emp(i); | |
end loop; | |
for i in 1 .. l_dept.count | |
loop | |
delete from dept where deptno = l_dept(i); | |
end loop; | |
end; |
That was the last step in the way. We now have one page with tow tabular forms. One tabular form is an interactive report. Each report has its own 'add row' button, page the page submit buttons control both tables. As I wrote at the beginning a used jQuery to add a row to a table because it helps to show what APEX is doing when you create apex_items: it simply generates the html for an input field. The f_xx number is used as name attribute for each input field. Note that this ‘add row’ function works well, but since it copies the html of the previous row, it only works if your report has at least one row to begin with. If you create a report on an empty table, there would be no table row to copy the html from. For the interactive report it serves to tell that all input fields are rendered as strings, even the numeric fields. Therefore aggregations on columns -e.g. the sum of salaries per department- won't work. Despite these issues, being able to put more than one tabular form on a page can be a great enrichment for your application. You can check a working example on my demo application
Good luck!
No comments:
Post a Comment