What's the Problem?
Let's say you have an IG on employee table and you want to update selected employees commission based on user's input. Ideally, it should be very simple, where you can write UPDATE statement in page process and select IG region as "Editable Region" under "Execution Options" of the page process. But, when you select rows and submit page, you can see that, this process won't get executed! The reason isSelection of 'Row Selector' check-boxes is not considered as row-change. Thus selected rows are not submitted to server.
So, is there any work around?
Yes! Luckily there are lot of JavaScript (JS) APIs available to work with IG. If you are not already aware, you can refer "APEX IG Cookbook" or JavaScript API Reference documentation.If we continue with above Employee IG example, when user selects IG rows, enters "Commission %" and clicks on "Update Commission" button, then we can write Dynamic Action (D.A.) as below.
- Create D.A. and give it a proper name e.g. "Bulk Update Commission"
- In "When" Section, select
- Event: Click
- Selection Type: Button
- Button: UPDATE_COMISSION (In this demo, button name is UPDATE_COMISSION, you may need to change it depending on your button name)
- True Action
- Action: Execute JavaScript Code
- Code: Refer below code
JS Code:
// Clear previous selections, if any
$s("P5_SELECTED_ROWS", ""); // IG static id is defined as emp var gridView = apex.region("emp").widget().interactiveGrid("getViews").grid; var records = gridView.getSelectedRecords(); var empNo; // define empty JSON object to hold selected rows var selRecords = { "rows": [] }; var selRecordsJSON; if (records.length > 0) { if (confirm("Do you want to update commission for selected employee(s)? Please confirm.")) { $.each(records, function(i, r) { // employee number defined as PK for IG empNo = gridView.model.getRecordId(r); // you can also get other columns values using below code // ENAME is column name in IG query //empName = gridView.model.getValue(r, "ENAME"); // Push selected rows into an array selRecords.rows.push({ "empNo": empNo }); }); // convert JSON into string selRecordsJSON = JSON.stringify(selRecords); console.log(selRecordsJSON);
apex.page.submit({ request: "UPDATE_COMMISSION", set: { "P5_SELECTED_ROWS": selRecordsJSON }, showWait: true }); } } else { alert("Please select atleast one employee."); }
Note: When creating IG region, ensure correct column is selected as "Primary Key" column. If you have selected "ROWID" as Primary Key column, then above JavaScript code getRecordId will return ROWID value, not the primary key column value. In such cases, you need to modify server side PL/SQL (discussed below) accordingly, i.e. update variable to use ROWID type, change UPDATE statement to use ROWID column etc. If you use ROWID as Primary Key and use below PL/SQL code as is, then you will get "ORA-06502: PL/SQL: numeric or value error: character to number conversion error
"
As we want to execute above D.A. when "Update Commission" button is clicked, we should set button's action (Button > Properties > Behavior > Action) to "Defined by Dynamic Action".
And finally, we can create regular PL/SQL page process at "Page Processing > Processing" section which execute below code. Make this process conditional based on request value used in above JavaScript code.
PL/SQL Code:
Here is the link to working example.
Thank you!
DECLARE l_empno emp.empno%type; l_values apex_json.t_values; l_row_count PLS_INTEGER; BEGIN apex_debug.message('inside process Bulk Update Commission'); -- parse text to JSON apex_json.parse(p_values => l_values, p_source => :P5_SELECTED_ROWS); -- get number of rows selected l_row_count := apex_json.get_count(p_path => 'rows', p_values => l_values); -- loop through the selected rows FOR i IN 1..l_row_count LOOP -- get Primary Key value from JSON data l_empno := null; -- if primary key column type is not a number type, then remove TO_NUMBER part l_empno := TO_NUMBER(apex_json.get_varchar2(p_path => 'rows[%d].empNo', p0 => i, p_values => l_values)); apex_debug.message('l_empno' || l_empno); -- update employee commission UPDATE emp SET comm = sal*TO_NUMBER(:P5_COMMISSION)/100 WHERE empno = l_empno; END LOOP; END;
In this example, we have updated the salary for selected employees. Once we get list of records selected in the IG, then we can do what ever we want based on our requirement. Other typical use case would be to add selected records into APEX Collection, so that we can process these records later. In such cases, we can use APEX_COLLECTION APIs to add selected records into a collection.
Please note, this works well with pagination type Scroll. But, if you are using pagination type as Page, then getSelectedRecords gives rows selected from current page alone. In these cases, we need to set persistSelection to true. To do this, go to "IG > JavaScript Initialization Code" and put following code.
function(config) { config.defaultGridViewOptions = { persistSelection: true }; return config; }
Thank you!
Comments
I'm trying to reproduce your example, but i'm having: ORA-06502: PL/SQL: numeric or value error. I supposed that's from the conversion:
l_empno := TO_NUMBER(apex_json.get_varchar2(p_path => 'rows[%d].empNo', p0 => i, p_values => l_values));
The rows that i’m getting from javascript console log are something like this:
{"rows":[{"empNo":"[\"10\",\"7839\"]"},{"empNo":"[\"30\",\"7698\"]"}]}
If you can guide me..i’m not sure how to debug this..
Thanks,
Adelina.
If you just select rows and submit pages, then those rows won't be visible in "Save Interactive Grid Data / Interactive Grid Automatic Row Processing (DML)" process. Because, row selection is not considered as data change. So selected rows are not submitted to server.
You may try getting this (bulk operation on selected rows) done using automatic DML process created for IG and let me know if it works. Thank you.
Error Stack: ORA-06502: PL/SQL: numeric or value error: character to number conversion error
Backtrace: ORA-06512: at line 15
I am getting this error message. plz advise.
I am facing another issue. Data gets updated only second time when I click the submit button.
Alternatively, you can use APEX_DEBUG package to log few entries from your PL/SQL code, enable debug and submit the page. Then review the debug output. This can help you to figure what is going on.
That is, if I have several pages, it processes the selected ones on the page where I am located.
Is there a way to process all selected rows from all pages?
You are correct. It works well with pagination type "scroll". But, if you are using pagination type as "Page", then getSelectedRecords gives rows selected from current page alone. In these cases, we need to set persistSelection to true. I have updated blogpost with these details.
Thanks you.
I have added the PL/SQL Page process in "Page Processing > Processing" but for some reason I can't see the update happening. Hence, I added the pl/sql code to execute from DA . But I have noticed that we have to click the update button twice to get all the records updated . And the same I noticed because the p5_selected_rows item doesn't capture all the rows in first click but captures all the records from the IG in second click .
Any idea what I could be missing in page -> process or why the clicking of button twice works.
Thanks
Please ensure Button's "Action" is selected as "Defined by Dynamic Action". I have updated the blog with more details. Please check and ensure everything is done as explained in this blog. If you are still facing issues, please set-up an example at apex.oracle.com and share me your workspace details along with developer username/password. I can check it.
It works now fine.
There is no limit for JSON object size. However, we are converting JSON object to string and passing it to server via page item P5_SELECTED_ROWS. Here, AFAIK, we have limit of 32k for page items.
If you are using scroll pagination, by default, fetches 50 rows at a time. Looks like you have used "Select All" option (header checkbox) when rows fetched so far is 100. In JS code, comment apex.page.submit call and do
console.log(selRecords.rows.length);
With this you can see selected record count in the console.
l_empno emp.empno%type;
This is not ref cursor. Here I am declaring variable l_empno and specifying that it's data type will be same as that of empno column in emp table. Search in google for oracle pl/sql %type to get more details.
Regards,
Hari
Thank you for your post. I followed it and it worked as expected.
I have only 1 query, in addition to selected rows, i also need the user to select value of 1 column in each selected row, which will specify the type of relation it is creating for selected row. How can i pass that info .Having trouble capturing the info of user selected column.
Appreciate your help.
Thanks.
Example is already given in the JavaScript code, to read additional columns data. Read inline comments.
// you can also get other columns values using below code
// ENAME is column name in IG query
//empName = gridView.model.getValue(r, "ENAME");
Next, you can pass empName as-well to server just like empNo
selRecords.rows.push({"empNo": empNo, "empName": empName});
Next, you can access this column value on server side as below
l_empname := apex_json.get_varchar2(p_path => 'rows[%d].empName', p0 => i, p_values => l_values);
Thanks so much for an excellent tutorial. It helped me out a lot.
Skip
I am facing an error : Error: ORA-06502: PL/SQL: numeric or value error
I am trying to add the selected row to another table that I created, and delete the selected row from the current table. However the values are being printed on the page element P2_Store_DATA, so I guess JS code is working fine.
Also if you can please let me know how can I track the changes in the pl/sql code in oracle apex. I mean where are the printing statements like: DBMS_OUTPUT.PUT_LINE(row_cnt) is printed??
Here is pl/sql code.
BEGIN
apex_json.parse(p_values => l_values, p_source => :P2_STORE_DATA);
row_cnt := apex_json.get_count(p_path => 'rows', p_values => l_values);
DBMS_OUTPUT.PUT_LINE(row_cnt);
FOR i IN 1..row_cnt LOOP
nums := null;
nums := TO_NUMBER(apex_json.get_varchar2(p_path => 'rows[%d].num_id', p0 => i, p_values => l_values));
apex_debug.message('nums' || nums);
INSERT INTO EXCLUSION_SHEET(NUM,EMAIL,GID,STATUS)
SELECT NUM,EMAIL,GID,STATUS from DAT where Id = nums;
DELETE from dat where Id = nums;
END;
As discussed on the blog post, ensure primary key column is a number. If it's not a number (or ROWID selected as primary key), then you will get this error.
You should use apex_debug package for debugging in APEX. You can watch this video to know more about debugging.
Regards,
Hari
Really amazing content so thanks so much!
I just want to know how can we implement this solution with a custom button defined in the toolbar (either via JS or as a plugin)?
How can we address that button's name in the DA?
Thanks again!