Refresh a Classic Report based on a collection - oracle

I am currently learning apex but got stuck.
I am using Oracle apex 4.2. My app consists of 2 reports. In first I am choosing some checkboxes and clicking button which fires DA with some js code and uses apex.server.process with AJAX Callback like in this link.
Next the "On Demand" process launches PL/SQL procedure (I know there is no reason for that, but I did it for learning purpose) which processes checked values and concatenates them into one clob which is then being added to collection.
So each time I am clicking the button, exactly 1 new clob is being added.
My second report just reads ALL clobs from collection, and I want to refresh it each time after I click the button without submitting all of the page content. I have added DA with a True Action: Refresh but it works with delay. When I first click it - nothing happens, on the second click the clob which was generated previously shows up in the report region and so on. I guess I need to submit collections value to session state but I have nothing to add to the reports "Page Items to Submit" option.

Not seeing "most recent" data
The only reason you would see "outdated" data is because you use an ajax call to update the collection. It's asynchronous!
Similar, the refresh is async too. The actions don't wait for eachother to complete.
It can go kind of like this:
collect the checkboxes and send them off to some process, which somewhere down the line will return me something
sometimes, it depends, the ajax call is fast enough to have finished before the region refresh occurs
refresh the region
other times, the ajax call has taken a bit longer, and the refresh has already occured
To solve this, you'll have to add something to your apex.server.process call: the refresh of the collection-based report should only occur when the callback has completed.
Firstly though: add a static report id to your collection-based report. Do this by editing it. Providing a static id allows for easier selection of the region through a selector.
apex.server.process('Process Checked Rows', {f01: arr_f01}).done(function(){
//do something when the callback has finished
//like triggering the refresh event on the report required
apex.jQuery("#your_report_static_id_goes_here").trigger("apexrefresh");
});
Now the report will only refresh when you're sure the checkboxes have been processed into the collection.
Using a clob for storing IDs
As I've pointed out in the comments, it makes no sense at all to use a clob to keep the selected IDs together. There's just no argument to make for it beside perhaps pure experimentation. Honestly, time better spent elsewhere if you want to experiment with CLOBs.
Collections are meant to be a Global Temporary Table replacement within the context of Apex sessions. Use them like you would use a table. Simply insert the IDs of the selected rows into the collection.
FOR i IN 1..apex_application.g_f01.count
LOOP
apex_collection.add_member
( p_collection_name => 'CHECKBOX_COLLECTION'
, p_n001 => apex_application.g_f01(i) --NOTE: using N001 here to store a numerical ID
);
END LOOP;
If you only want to have uniques then you can do that here by adding a select on the collection, or add a distinct in the query used to loop or select over the collection.
FOR i IN 1..apex_application.g_f01.count
LOOP
DECLARE
l_exists BINARY_INTEGER;
BEGIN
SELECT 1
INTO l_exists
FROM apex_collections
WHERE collection_name = 'CHECKBOX_COLLECTION'
AND n001 = apex_application.g_f01(i);
EXCEPTION WHEN no_data_found THEN
apex_collection.add_member
( p_collection_name => 'CHECKBOX_COLLECTION'
, p_n001 => apex_application.g_f01(i) --NOTE: using N001 here to store a numerical ID
);
END;
END LOOP;
You can then eeasily loop over the records in this collection or use it to limit rows in other queries (eg another report).
SELECT n001 selected_id
FROM apex_collections
WHERE collection_name = 'CHECKBOX_COLLECTION';

Related

Oracle apex apex_application.g_f0x don't get table items

I have a report with apex_item(s). The report is complicated so I show only what is relevant for simplification.
The report is created with:
select ID || apex_item.hidden(11,ID) as "ID",
apex_item.text(p_idx => 12, p_value => VOLUME) as "Volume"
from TEMP;
That gives:
On submit page, I have a process that counts the report rows (later I loop on this rows to do some action):
apex_debug.warn('count f11: ' || apex_application.g_f11.count);
The result is 2 as expected.
The problem - when I try to save changes from the report without submitting the page:
I have a button "update server" which have a dynamic action on click with PL/SQL action:
The PL/SQL contains exactly the same code as the loop above but the result is now 0.
My goal is to make actions based on the rows. without submitting the page, But I can't understand how can I loop this rows without submit.
Thanks
When you're running a Dynamic Action of the type Execute PL/SQL Code (now called "Execute Server-side Code"), you can specify page items to be submitted with the action that will be set in session state before the PL/SQL block runs, but I don't think you can specify those fxx whatever they're called type of items. I think you have two options:
Create a hidden item and populate that with a Javascript action that runs before your PL/SQL action, then submit that hidden item along with your PL/SQL action.
Change your PL/SQL code to an Ajax Callback process, then invoke it with apex.server.process in a Javascript DA. You can specify fxx type items to submit with that call.

PL/SQL: ORA 01422 fetch returns more than requested number of rows

I am developing an order transaction where a user can order a product. Once they clicked the 'add to cart' button, it will be able to save on the database in how many times they want with the same order id. Order id is like a transaction id.
My problem is that whenever I want to display the items that customer ordered, it displays an error or ORA 01422. How can I resolve this error?
Here is my code
DECLARE
order_item_id NUMBER;
BEGIN
order_item_id := :MOTOR_PRODUCTS_ORDER.M_ORDERID;
SELECT MOTOR_ID,
MOTOR_QTY_PURCHASED,
UNITPRICE
INTO :MOTOR_ORDER_ITEMS.MOTOR_ID,
:MOTOR_ORDER_ITEMS.MOTOR_QTY_PURCHASED,
:MOTOR_ORDER_ITEMS.UNITPRICE
FROM MOTOR_ORDERS
WHERE motor_order_id = order_item_id;
END;
As krokodilo says, this error is caused because your query returns multiple rows. Depending on what you want to do, you have a couple of options.
If you want multiple values then either use a loop and process them one row at a time (if you are going to be performing a dml operation use bulk collect). If you only want a single row then narrow your result set down with an extra where clause, or use MAX to ensure you only get one value back.
If there is more than one row which will be returned from a query you'll need to use a cursor. One way to do this is with a cursor FOR loop:
DECLARE
order_item_id NUMBER;
BEGIN
order_item_id := :MOTOR_PRODUCTS_ORDER.M_ORDERID;
FOR aRow IN (SELECT MOTOR_ID, MOTOR_QTY_PURCHASED, UNITPRICE
FROM MOTOR_ORDERS
WHERE motor_order_id = order_item_id)
LOOP
-- Do something here with the values in 'aRow'. For example, you
-- might print them out:
DBMS_OUTPUT.PUT_LINE('MOTOR_ID=' || aRow.MOTOR_ID ||
' MOTOR_QTY_PURCHASED=' || aRow.MOTOR_QTY_PURCHASED ||
' UNITPRICE=' || aRow.UNITPRICE);
END LOOP;
END;
Best of luck.
This looks like a Forms question; is it? If so, my suggestion is to let Forms do that job for you.
According to what you posted, there are two blocks:
MOTOR_PRODUCTS_ORDER (a master block, form type)
MOTOR_ORDER_ITEMS (a detail block, tabular type)
I guess that there is a master-detail relationship between them. If there's none, I'd suggest you to create it. Although you can make it work without such a relationship, it'll be much more difficult. If you are unsure of how to do it, start from scratch:
delete MOTOR_ORDER_ITEMS block (detail)
create it once again, this time by following the Data Block Wizard
Set MOTOR_PRODUCTS_ORDER to be its master block
Relationship is on ORDER_ID column/item
Let's presume that by this point everything is set up. Retrieving items that belong to that ORDER_ID is now very simple:
navigate to master block
enter query mode
enter value into an item that represents ORDER_ID
execute query
End of story. Forms triggers & procedures (which were created by the Wizard) will do its job and retrieve both master and detail records.
No need for additional coding; if you're skilled developer, you can create such a form in a matter of minutes. Won't be beautiful, but will be effective & reliable.
There's really no use in doing it manually, although it is possible. Your code works if there's a single item for that ORDER_ID. For two or more items, as you already know, it'll fail with TOO-MANY-ROWS error.
Basically, if you insist, you should use a loop:
you'd enter ORDER_ID into the master block
as you need to move through the detail block, i.e. use NEXT_RECORD, which is a restricted procedure, you can't use number of triggers (open Forms Online Help System and read about them) so a "Show items" button (with its WHEN-BUTTON-PRESSED trigger) might be just fine
a cursor FOR loop would be your choice
for every row it fetches, you'd populate block items and
navigate to next record (otherwise, you'd keep overwriting existing values in the 1st tabular block row)
As I said: possible, but not recommended.

Oracle APEX Selection list

I have a form on a table with some values (text fields) and a select list. The select list is declared in shared components and shows me values from another table. Also I have some process (after submit) to modify and create new table entry. Everything works fine without propagating values from select list. There are no errors from the process. It looks like the process didn't get a value for :P22_WORKER_LIST from the selection list. It should work when I push the create or save buttons but nothing happened. Every instruction in BEGIN END block runs great without this one.
Process:
BEGIN
<some instructions>
UPDATE "WORKER" SET ACCOUNT_LOGIN = :P22_LOGIN
WHERE SURNAME = :P22_WORKER_LIST;
END;
Thank you for suggestion with the session state. After submiting page it turned out that the value for my :P22_WORKER_LIST is a WORKER_ID instead a SURNAME.
BEGIN
<some instructions>
UPDATE "WORKER" SET ACCOUNT_LOGIN = :P22_LOGIN
WHERE WORKER_ID = :P22_WORKER_LIST;
END;

Cascading select list default value not set Oracle Apex

Getting an issue with cascading select list. Default value of a cascading select list is not set, not being displayed.
I have a select list named P3_ENTITY that is cascading and having 3 parents: P3_APPLICATION, P3_SCHEMA_LIST and a checkbox P3_CATEGORY_CHECKBOX
I set the default value of P3_ENTITY in a Plsql process SET_VALUE that gets fired on page rendering.
Process code is like :
BEGIN
:P3_ENTITY := :F100_FILTER_ENTITY;
END;
Now P3_ENTITY select list takes time to be loaded at the time of page load, but as execution of process SET_VALUE becomes completed before load of P3_ENTITY select list, that default value gets overwritten by NULL after full load of that select list.
Please help.
Thanks in Advance.
To set cascading parents to a list implies that each time that the parents change, the list will be refreshed, this does not cause the re execution of the plsql block that you defined to set a default value.
What you could do, is create a dynamic action that execute your plsql block on an After Refresh basis, this means that once the refresh event is complete the dynamic action will assign the :F100_FILTER_ENTITY value again.

FRM-40401 no changes to save after WHEN-CREATE-RECORD trigger

I googled this but, unfortunately, could not find any solution.
I have a simple form (Oracle Forms Builder 10g) with a single block. The form is written in Oracle EBS style, that is, the block is based on a view that fetches the base table fields together with the rowid and DML events (on-insert, on-update etc. triggers) are handled by a table handler package.
The functionality I wanted to add was the following: when a user creates a new record, the form automatically suggests values for all fields in the form. So, I created a WHEN-CREATE-RECORD trigger that calculates the field values and assigns them. All, except the primary key wich is based on a Sequence and is handled by the package.
Everything runs OK when I create the new record but when I attempt to save it, all I get is a FRM-40401 "no changes to save" error and nothing happens.
I tried to trace the error and it seems like the form considers the record as NEW with no changes on it. This happens even if I try to explicitly alter the record status to INSERT.
I already tried to change the default behaviour to STANDARD.COMMIT (created an ON-COMMIT trigger for that) but this did not dfix anything.
For the record, I tried to make the form table-based, getting rid of table handlers and leaving all DML to Forms. I still get FRM-40401.
I can't understand what is going wrong, any ideas please?
The record status is reset to NEW after the when-create-record trigger completes. This normally makes sense, since you are effectively setting the default values for items, but the user hasn't actually entered any data yet.
You need something to mark the record for insert after the trigger has finished - normally the user would do this when they enter some data into the record. If you want the user to be able to save the record without changing anything in it, you could perhaps add something to the save button to do a no-change assignment, e.g.
:MYBLOCK.ANYITEM := :MYBLOCK.ANYITEM;
This would cause the record to be marked for insert.
OK, for the time being I used the classic TIMER workaround and everything works as it should:
PACKAGE body form_timers IS
PROCEDURE CREATE_NEW_RECORD;
procedure do_create(name varchar2) is
timer_id TIMER;
Begin
timer_id := CREATE_TIMER(name,1,NO_REPEAT);
End;
procedure expired is
expired_timer CHAR(20);
BEGIN
expired_timer:=GET_APPLICATION_PROPERTY(TIMER_NAME);
IF expired_timer='CREATE_NEW_RECORD' THEN
CREATE_NEW_RECORD;
-- ELSIF expired_timer='T2' THEN
-- /* handle timer T2 */ NULL;
ELSE
NULL;
END IF;
END;
PROCEDURE CREATE_NEW_RECORD IS
/* create record logic goes here */
END;
END;
... but still, I'd like to know why this behaviour occurs.

Resources