At the office, I've got a tabular form where I had to build my own save process, rather than relying on APEX's automagic MRU/MRD processes*. This seems to be one of those areas where things ought to be simpler than they actually are, unfortunately.
In an ideal world, APEX would recognize that you're trying to build a process to handle a tabular form (they already have custom tabular form validations; why not custom tabular form processes?) and give you a way to say “Do this for every row in the form”. Sadly, they don't (yet–I'm going to continue to hope that they improve this soon!). So, if the canned MRU/MRD processes don't work for you for whatever reason, you need to build your own looping logic.
Enter the apex_application.g_fXX collections. These are a set of varchar2 arrays, numbering from g_f01 to g_f50, which hold the data from your “updatable reports”, which I believe only translates to “tabular forms” in practice, as I'm not aware of any other updatable reports.
The first challenge is figuring out which g_fXX arrays hold the data you need. It's a fairly safe bet that they're used in the order the columns appear in the tree view, without including the non-updatable fields, but the safest way to find out is to check the source code of your page. Look for code like this:
In this case, this field (new_min) uses the g_f04 array. The 0022 indicates that this is from the 22nd row of the report, but you shouldn't need to worry about that.
This gives you enough that you should be able to write your PL/SQL, along these lines:
begin for i in 1..apex_application.g_f01.count loop my_pkg.update_values(apex_application.g_f01(i), apex_application.g_f02(i), apex_application.g_f03(i), apex_application.g_f04(i), apex_application.g_f05(i)); end loop; end;
Similar logic applies to building a multi-row delete process. For most of us, that's all you need to do.
…but. (Surprise!) Say you have some jQuery or javascript enabling and disbabling fields dynamically. Guess what–a disabled field doesn't get added to the g_fXX records, so g_f02(i) may be from a different row than g_f05(i). If you're lucky, there'll be one array that's shorter than the others, and your application will throw a (rather unhelpful) “No data found” error. If you're unlucky, however, your arrays will happen to have the same length–and bad data will slip into the database. And that can be really hard to track down.
My solution? Enable all of the fields right before submitting the page. In my case, I already had the Submit button redirecting to a URL to pop up a confirmation dialog, so I hooked it in there; you could also set up a dynamic action to fire right before submit. Here's my code:
javascript:{if (confirm('Save all changes on this page?')) { $('input:disabled').attr('disabled',false); apex.submit('SUBMIT'); }}
* Three reasons: first, the tabular form is on a view, which you can't do inserts, updates, or deletes on. Second, I have a checkbox on the row to indicate whether or not the row should be processed, which the MRU doesn't understand. Third, our security model requires custom save processes to help prevent accidental data manipulation, which I highly recommend, despite the extra work involved..