After completing this How-To, you should be able to understand how to:
Table of Contents
In Oracle HTML DB, you can create a tabular form in two ways:
The easiest way to build a tabular form is to use Tabular Form wizard. This wizard creates a tabular form with a built in multiple row update process. This built in process does optimistic locking behind the scene to maintain the data integrity. In certain cases you may want to build a tabular form manually to control the display and functionality. You can use HTMLDB_ITEM package to build a tabular form manually. When you build a tabular form using HTMLDB_ITEM, you will need to build your own optimistic locking since it is not done automatically during update. This How To describes how you can create a tabular form using EMP table that is included when a new workspace is provisioned.
The simplest way to build a tabular form is to use Tabular Form wizard. This wizard creates a tabular form with a built in multiple row update process. This process checks for MD5 checksum values before doing the update in order to perform optimistic locking behind the scene. When you build a tabular form using the wizard, there are some editing restrictions. We will go over these restrictions in the later steps.
We will start by creating a tabular form on EMP table using the wizard with following steps:
Changing Updateable Column Display Type
When the wizard creates the tabular form, all updateable columns display as text field by default. The following section describes how to change this default display to a select list.
To change the default display of JOB and DEPTNO to a select list:
Run the page. The tabular form displays with JOB and DEPTNO column in select list (see Figure 3).
Figure 3 - Wizard created tabular form
Edit Restrictions on Wizard Generated Tabular Form
If you create a tabular form using the wizard, the wizard creates a SQL Query (updateable report) region type. When you wish to change the display type of updateable columns for this type of region, you need to use updateable column attributes. If you change the display type using HTMLDB_ITEM package in the select list of a SQL statement, the update will not work properly. For example, the wizard generated SQL statement cannot be mixed with HTMLDB_ITEM package:
select htmldb_item.hidden(1,empno) empno, "ENAME", "JOB", htmldb_item.select_list_from_query(2,mgr,'select ename, empno from emp') mgr, "HIREDATE", "SAL", "COMM", "DEPTNO" from "#OWNER#"."EMP" order by 1
The select list of a SQL statement of a tabular form should not be modified after it has been generated. Doing so can result in a checksum error when altering data of the form and applying updates. Consider the following example:
select ename from emp; Note that this should not be altered to: select lower(ename) from emp.
You can also create a tabular form manually by defining SQL report query using HTMLDB_ITEM package. When you build a tabular form manually, you have more control on editing the tabular form query and processing the update. However, be aware this manual approach does involve more steps. If you wish to build a complex tabular form, it is best to take the manual approach. Otherwise, building a simple tabular form can be easily done using the wizard.
To build a tabular form manually:
select htmldb_item.hidden(1,empno) empno, ename, htmldb_item.select_list_from_query(3,job,'select distinct job, job from emp') job, htmldb_item.popupkey_from_query(4,mgr,'select ename, empno from emp',10) mgr, wwv_flow_item.date_popup(6,null,hiredate) hiredate, htmldb_item.text(7,sal,10) sal, htmldb_item.text(8,comm,10) comm, htmldb_item.select_list_from_query(9,deptno,'select dname, deptno from dept') deptno from emp
To enhance the display of the report, you can change the report column headings as follows:
Figure 4 - Edit column headings
To submit the tabular form array values, you need to add a button.
To add a button, follow these steps:
To update employee records, you need to add a process:
-- -- update EMP -- for i in 1..htmldb_application.g_f01.count loop update emp set job = replace(htmldb_application.g_f03(i),'%'||'null%',NULL), mgr = replace(htmldb_application.g_f04(i),'%'||'null%',null), hiredate = htmldb_application.g_f06(i), sal = htmldb_application.g_f07(i), comm = htmldb_application.g_f08(i), deptno = replace(htmldb_application.g_f09(i),'%'||'null%',NULL) where empno = htmldb_application.g_f01(i); end loop;
When you run the page, you should see a tabular form looks like Figure 5.
Figure 5 - Manually created tabular form
Building a Tabular Form with Extra Rows for Insert
Building a tabular form with an extra row for insert is useful when you wish to have a tabular form that does both insert and update. This section describes how to add an extra insert row to the tabular form.
To add an extra insert row to a tablular form:
select x.empno, x.ename, x.job, x.mgr, x.hiredate, x.sal, x.comm, x.deptno from ( select htmldb_item.hidden(1,empno) empno, wwv_flow_item.display_and_save(2,ename) ename, htmldb_item.select_list_from_query(3,job,'select distinct job, job from emp') job, htmldb_item.popupkey_from_query(4,mgr,'select ename, empno from emp',10) mgr, wwv_flow_item.date_popup(6,null,hiredate) hiredate, htmldb_item.text(7,sal,10) sal, htmldb_item.text(8,comm,10) comm, htmldb_item.select_list_from_query(9,deptno,'select dname, deptno from dept') deptno from emp union all select htmldb_item.hidden(1,null) empno, htmldb_item.text(2,null) ename, htmldb_item.select_list_from_query(3,null,'select distinct job, job from emp') job, htmldb_item.popupkey_from_query(4,null,'select ename, empno from emp',10) mgr, wwv_flow_item.date_popup(6,null,null) hiredate, htmldb_item.text(7,null,10) sal, htmldb_item.text(8,null,10) comm, htmldb_item.select_list_from_query(9,null,'select dname, deptno from dept') deptno from dual) x
The primary key column, EMPNO, is not an updateable column on the tabular form. Since the primary key is required to insert the data into the EMP table, you need to have a trigger populate the primary key using a sequence. You can create a sequence and trigger in SQL Workshop.
To create a sequence and trigger using SQL Workshop:
select emp_seq.nextval into:new.empno from dual;
-- -- update EMP -- for i in 1..htmldb_application.g_f01.count loop if htmldb_application.g_f01(i) is not null then update emp set job = replace(htmldb_application.g_f03(i),'%'||'null%',NULL), mgr = replace(htmldb_application.g_f04(i),'%'||'null%',null), hiredate = htmldb_application.g_f06(i), sal = htmldb_application.g_f07(i), comm = htmldb_application.g_f08(i), deptno = replace(htmldb_application.g_f09(i),'%'||'null%',NULL) where empno = htmldb_application.g_f01(i); else if htmldb_application.g_f02(i) is not null then insert into emp (ename, job, mgr, hiredate, sal, comm, deptno) values (htmldb_application.g_f02(i), replace(htmldb_application.g_f03(i),'%'||'null%',NULL), replace(htmldb_application.g_f04(i),'%'||'null%',NULL), htmldb_application.g_f06(i), htmldb_application.g_f07(i), htmldb_application.g_f08(i), replace(htmldb_application.g_f09(i),'%'||'null%',NULL)); end if; end if; end loop;
Figure 7 - Tabular form with extra row for insert
Adding Optimistic Lockingoptimistic locking
Since you created this tabular form manually, optimistic locking is not done automatically behind the scenes. You need to add an optimistic locking to the tabular form in order to keep the data integrity. You can use HTMLDB_ITEM.MD5_CHECKSUM to include MD5 checksum values on the form. When the page is submitted, HTMLDB_ITEM.MD5_CHECKSUM populates the global HTMLDB_APPLICATION.G_FCS with a checksum of the values provided.
To add the checksum values, you need to edit the tabular form query as follows:
select x.empno, x.ename, x.job, x.mgr, x.hiredate, x.sal, x.comm, x.deptno, x.cks from ( select htmldb_item.hidden(1,empno) empno, wwv_flow_item.display_and_save(2,ename) ename, htmldb_item.select_list_from_query(3,job,'select distinct job, job from emp') job, htmldb_item.popupkey_from_query(4,mgr,'select ename, empno from emp',10) mgr, wwv_flow_item.date_popup(6,null,hiredate) hiredate, htmldb_item.text(7,sal,10) sal, htmldb_item.text(8,comm,10) comm, htmldb_item.select_list_from_query(9,deptno,'select dname, deptno from dept') deptno, htmldb_item.md5_checksum(job,mgr,hiredate,sal,comm,deptno) cks from emp union all select htmldb_item.hidden(1,null) empno, htmldb_item.text(2,null) ename, htmldb_item.select_list_from_query(3,null,'select distinct job, job from emp') job, htmldb_item.popupkey_from_query(4,null,'select ename, empno from emp',10) mgr, wwv_flow_item.date_popup(6,null,null) hiredate, htmldb_item.text(7,null,10) sal, htmldb_item.text(8,null,10) comm, htmldb_item.select_list_from_query(9,null,'select dname, deptno from dept') deptno, htmldb_item.md5_checksum(null,null,null,null,null,null) cks from dual) x
To assure the data integrity, next you need to edit the update process to get the original checksum values and compare them with the submitted checksum values. If they are not same, the application will raise an error.
To implement this, change the update process as follows:
declare l_cks wwv_flow_global.vc_arr2; j pls_integer := 1; begin -- -- Get original MD5 checksum -- select wwv_flow_item.md5(job,mgr,hiredate,sal,comm,deptno) cks BULK COLLECT INTO l_cks from emp; -- -- Compare the original checksum, l_cks, -- with submitted checksum, htmldb_application.g_fcs. -- If they are different, raise an error. -- for i in 1..l_cks.count loop if htmldb_application.g_fcs(i) != l_cks(i) then rollback; raise_application_error( -20001, 'Current version of data in database has changed '|| 'since user initiated update process.'); return; end if; end loop; -- -- update EMP -- for i in 1..htmldb_application.g_f01.count loop if htmldb_application.g_f01(i) is not null then update emp set job = replace(htmldb_application.g_f03(i),'%'||'null%',NULL), mgr = replace(htmldb_application.g_f04(i),'%'||'null%',null), hiredate = htmldb_application.g_f06(i), sal = htmldb_application.g_f07(i), comm = htmldb_application.g_f08(i), deptno = replace(htmldb_application.g_f09(i),'%'||'null%',NULL) where empno = htmldb_application.g_f01(i); else if htmldb_application.g_f02(i) is not null then insert into emp (ename, job, mgr, hiredate, sal, comm, deptno) values (htmldb_application.g_f02(i), replace(htmldb_application.g_f03(i),'%'||'null%',NULL), replace(htmldb_application.g_f04(i),'%'||'null%',NULL), htmldb_application.g_f06(i), htmldb_application.g_f07(i), htmldb_application.g_f08(i), replace(htmldb_application.g_f09(i),'%'||'null%',NULL)); end if; end if; end loop; end;
Adding Validation Using Collections
Collections enable you to temporarily capture one or more non-scalar values. You can use collections to store rows and columns currently in session state so they can be accessed, manipulated, or processed during a user's specific session. Think of a collection as a bucket in which you can temporarily store and name rows of information. This section describes how to add validations to a tabular form. The tabular form does not keep session state of the user input when the validations fail. To save the user input, you can use collections.
To start, you first create a not null validation to validate the user input.
To create a not null validation to validate the user input:
for i in 1..htmldb_application.g_f01.count loop if htmldb_application.g_f01(i) is not null then if replace (htmldb_application.g_f03(i),'%'||'null%',null) is null then return 'Job must have some value.'; end if; if htmldb_application.g_f06(i) is null then return 'Hiredate must have some value.'; end if; if htmldb_application.g_f07(i) is null then return 'Sal must have some value.'; end if; if replace(htmldb_application.g_f09(i),'%'||'null%',null) is null then return 'Deptno must have some value.'; end if; else if htmldb_application.g_f02(i) is not null then if replace(htmldb_application.g_f03(i),'%'||'null%',null) is null then return 'Job must have some value.'; end if; if htmldb_application.g_f06(i) is null then return 'Hiredate must have some value.'; end if; if htmldb_application.g_f07(i) is null then return 'Sal must have some value.'; end if; if replace(htmldb_application.g_f09(i),'%'||'null%',null) is null then return 'Deptno must have some value.'; end if; end if; end if; end loop;
The tabular form does not keep the session state when the validation fails. You can use collections to save the user input by creating a process as follows:
declare la_cks wwv_flow_global.vc_arr2; begin if htmldb_application.g_fcs.count > 0 then la_cks := htmldb_application.g_fcs; else la_cks := htmldb_application.g_f10; end if; htmldb_collection.create_or_truncate_collection('UPDATE_EMP'); htmldb_collection.create_or_truncate_collection('INSERT_EMP'); for i in 1..htmldb_application.g_f01.count loop if htmldb_application.g_f01(i) is not null then htmldb_collection.add_member( p_collection_name => 'UPDATE_EMP', p_c001 => htmldb_application.g_f01(i), p_c002 => htmldb_application.g_f02(i), p_c003 => htmldb_application.g_f03(i), p_c004 => htmldb_application.g_f04(i), p_c005 => htmldb_application.g_f06(i), p_c006 => htmldb_application.g_f07(i), p_c007 => htmldb_application.g_f08(i), p_c008 => htmldb_application.g_f09(i), p_c009 => la_cks(i)); else htmldb_collection.add_member( p_collection_name => 'INSERT_EMP', p_c001 => htmldb_application.g_f01(i), p_c002 => htmldb_application.g_f02(i), p_c003 => htmldb_application.g_f03(i), p_c004 => htmldb_application.g_f04(i), p_c005 => htmldb_application.g_f06(i), p_c006 => htmldb_application.g_f07(i), p_c007 => htmldb_application.g_f08(i), p_c008 => htmldb_application.g_f09(i), p_c009 => la_cks(i)); end if; end loop; end;
In order to show session state of the tabular form, you need to display the tabular form the previously saved collection. To make this happen, we will create a new tabular form region as follows:
select x.empno, x.ename, x.job, x.mgr, x.hiredate, x.sal, x.comm, x.deptno, x.cks from ( select htmldb_item.hidden(1,c001) empno, wwv_flow_item.display_and_save(2,c002) ename, htmldb_item.select_list_from_query(3,c003,'select distinct job, job from emp') job, htmldb_item.popupkey_from_query(4,c004,'select ename, empno from emp',10) mgr, wwv_flow_item.date_popup(6,null,c005) hiredate, htmldb_item.text(7,c006,10) sal, htmldb_item.text(8,c007,10) comm, htmldb_item.select_list_from_query(9,c008,'select dname, deptno from dept') deptno, htmldb_item.hidden(10,c009) cks from htmldb_collections where collection_name = 'UPDATE_EMP' union all select htmldb_item.hidden(1,c001) empno, htmldb_item.text(2,c002) ename, htmldb_item.select_list_from_query(3,c003,'select distinct job, job from emp') job, htmldb_item.popupkey_from_query(4,c004,'select ename, empno from emp',10) mgr, wwv_flow_item.date_popup(6,null,c005) hiredate, htmldb_item.text(7,c006,10) sal, htmldb_item.text(8,c007,10) comm, htmldb_item.select_list_from_query(9,c008,'select dname, deptno from dept') deptno, htmldb_item.hidden(10,c009) cks from htmldb_collections where collection_name = 'INSERT_EMP') x
htmldb_collection.collection_exists(p_collection_name => 'UPDATE_EMP') and htmldb_collection.collection_exists(p_collection_name => 'INSERT_EMP')
To submit the values from tabular form you created previously, you need to add a button.
To add a button, follow these steps:
Since you added a new region that displays tabular form from a collection, you need to change the update process to evaulate the checksum values from the new tabular form as well.
To change the update process, follow these steps:
declare l_cks wwv_flow_global.vc_arr2; j pls_integer := 1; begin -- -- Get original MD5 checksum -- select wwv_flow_item.md5(job,mgr,hiredate,sal,comm,deptno) cks BULK COLLECT INTO l_cks from emp; if htmldb_application.g_fcs.count > 0 then -- -- Compare the original checksum, l_cks, -- with submitted checksum, htmldb_application.g_fcs. -- If they are different, raise an error. -- for i in 1..l_cks.count loop if htmldb_application.g_fcs(i) != l_cks(i) then rollback; raise_application_error( -20001, 'Current version of data in database has changed '|| 'since user initiated update process.'); return; end if; end loop; else -- -- Compare the original checksum, l_cks, -- with submitted checksum from a collection, htmldb_application.g_f10. -- If they are different, raise an error. -- for i in 1..l_cks.count loop if htmldb_application.g_f10(i) != l_cks(i) then rollback; raise_application_error( -20001, 'Current version of data in database has changed '|| 'since user initiated update process.'); return; end if; end loop; end if; -- -- update EMP -- for i in 1..htmldb_application.g_f01.count loop if htmldb_application.g_f01(i) is not null then update emp set job = replace(htmldb_application.g_f03(i),'%'||'null%',NULL), mgr = replace(htmldb_application.g_f04(i),'%'||'null%',null), hiredate = htmldb_application.g_f06(i), sal = htmldb_application.g_f07(i), comm = htmldb_application.g_f08(i), deptno = replace(htmldb_application.g_f09(i),'%'||'null%',NULL) where empno = htmldb_application.g_f01(i); else if htmldb_application.g_f02(i) is not null then insert into emp (ename, job, mgr, hiredate, sal, comm, deptno) values (htmldb_application.g_f02(i), replace(htmldb_application.g_f03(i),'%'||'null%',NULL), replace(htmldb_application.g_f04(i),'%'||'null%',NULL), htmldb_application.g_f06(i), htmldb_application.g_f07(i), htmldb_application.g_f08(i), replace(htmldb_application.g_f09(i),'%'||'null%',NULL)); end if; end if; end loop; end;
Now the existing tabular form region from EMP table needs to display only when there is no existing collection. To accomplish this, you need to add a conditional display as follows:
not htmldb_collection.collection_exists(p_collection_name => 'UPDATE_EMP') and not htmldb_collection.collection_exists(p_collection_name => 'INSERT_EMP')
Next, you will create a before header process to delete the collection. This process will ensure the application will not display the tabular form from a collection when the page is rendered initially.
To create a before header process:
htmldb_collection.delete_collection('UPDATE_EMP'); htmldb_collection.delete_collection('INSERT_EMP');
htmldb_collection.collection_exists('UPDATE_EMP') and htmldb_collection.collection_exists('INSERT_EMP')
Now try to update and insert employee records. It will validate user input and do optimistic locking before doing the update and insert (see Figure 9).
Figure 9 - Tabular form with validation
Discuss this how-to in the OTN HTML DB Forum.