Oracle APEX | How to change select list value and Submit it dynamically - oracle

I have two select lists
1. The first one P4_country_id_B contains countries names
select country_name as d, country_id as r
from countries
2. The second one P4_CITY_ID_B contains cities of a country based on selected value in P4_CITY_ID_B.
select city_name as d, city_id as r
from cities
where country_id = :P4_country_id_B
Everything goes OK without any problem.
BUT
I use Execute PL/SQL Code Dynamic Action to change selected values of those lists like this (for example):
:P4_country_id_B:=country_returned_value;
:P4_CITY_ID_B:=city_returned_value;
Where
country_returned_value : is a one value of countries list values for example (USA)
city_returned_value : is a one value of cities list values for example (NewYourk).
The first selected list value changes but the second list never changes.
Notes:
I used P4_country_id_B,P4_CITY_ID_B as Page Items to Submit for the dynamic action.
I don't whan to submit the page instead of dynamic action
How can I change list values in this case please?.
Thanks in advance.

Cascading select lists are refreshed through ajax.
Change select list 1, select list 2 will be refreshed.
You execute plsql, which in turn will set the value of the items involved. Both are select lists, and one is dependent on the other.
So while both will be set, the change of the first one will cause the second to be refreshed.
In other words, you're doing it too fast. And while there is a solution, the proper way is a bit complex and I wouldn't build it in DA's personally.
You haven't specified how or when you call the code which sets the values for the items. So here I'll just assume a DA with an action of type "Execute JavaScript" (for example)
// perform a call to the ajax callback process to get the country and city id
// there are several ways to provide values to the process if necessary.
// for example through x01, which can be used in the proces like
// apex_application.g_x01
apex.server.process('GET_COUNTRY_DEFAULTS', {x01:""}).done(function(data){
// process returns JSON which contains 2 properties: country_id and city_id
// data.country_id
// data.city_id
// bind a ONE-TIME handler to the after refresh event of the city
// cascading LOVs fire the before and after refresh events like any other
// refreshable element in apex
// a one time handler since the values are different each time this code will
// execute
apex.jQuery("#Px_CITY_ID").one("apexafterrefresh",function(){
// after refresh of the list, attempt to set the value of the list to the
// value retrieved earlier
apex.item(this).setValue(data.city_id);
});
// finally, set the value of the country. Doing this will also trigger the
// refresh of dependent elements
apex.item('Px_CITY_ID').setValue(data.country_id);
// since a handler has been bound, the refresh will occur, the after refresh
// triggers, and the value will be set properly
});
Finally, create a new process on the page under "AJAX Callback", and name it GET_COUNTRY_DEFAULTS
DECLARE
l_country_id NUMBER;
l_city_id NUMBER;
BEGIN
l_country_id := 8;
l_city_id := 789;
htp.p('{"country_id":'||l_country_id||',"city_id":'||l_city_id||'}');
EXCEPTION WHEN OTHERS THEN
-- returning an error while using apex.server.process will prompt the user
-- with the error and halt further processing
htp.p('{"error":"'||sqlerrm||'"}');
END;
That should tie everything together.

I think there is some confusion here. My answer below, assumes, according to your question, the first list name is P4_country_id_B, and the second list name is Cities_LOV. If that is not the case, please clarify.
Your first list called P4_country_id_B, and you assign it to itself through the following statement:
:P4_country_id_B:=country_returned_value;
So basically, nothing has changed, the value of P4_country_id_B is the returned value of your list P4_country_id_B without any need for this assignment. Note, it is not clear to me, what is country_returned_value, because P4_country_id_B holds the returned value.
Secondly, you have a list called Cities_LOV, and you assign the returned value to P4_CITY_ID_B page item, through the following statement:
:P4_CITY_ID_B:=returned_city_value;
Again, I am not sure what is returned_city_value, because Cities_LOV holds the returned value of that list.
I am not sure what you are trying to achieve here. But I assume, you want to allow the user to select the Country first, and then based on that, you want to refresh the Cities list to display the cities in that particular country. If that is the case, then use a dynamic action on P4_country_id_B value change, to refresh the value of Cities_LOV. You only need to pass P4_country_id_B to that dynamic action.
UPDATE
After you corrected the wording of the question, the answer would be like this:
In your child list P4_CITY_ID_B make sure you set the option Cascading LOV parent item(s) to the parent list P4_country_id_B. You do not need the dynamic action. The child list, should refresh upon the change of the parent list. The answer here, goes in details about how to implement cascading list

Related

How to update a field when another is modified

I have three fields
1. Price (:P7_PRIJS)
2. Quantity (:P7_HOEVEELHEID)
3. Total (:P7_TOTAAL).
I want the total to be updated (price * quantity) the moment the quantity is changed. All items are on the same region, from the same table.
I have already created a trigger to update the total, this works, but is not visible in the screen.
I have tried with a Dynamic action, but get errors when doing so.
I just want to see in the form, before saving the updated total. How can this be done?
Dynamic action is a way to do that. Actually, you need two of them (which will look exactly the same), each of them created on P7_PRIJS and P7_HOEVEELHEID items (so that total gets calculated regardless of which item value you've changed).
Dynamic action's action is Set value:
set type: PL/SQL expression
PL/SQL expression: :P7_PRIJS * :P7_HOEVEELHEID
items to submit: P7_PRIJS,P7_HOEVEELHEID
affected elements: item, P7_TOTAAL

Can I use a list to repeat two tablixes on a report page?

See image below. Warehouse and Customer are dropdown lists populated via query. Year is a text field. The two tables display data from datasets driven by the report parameters. Is there a way to repeat the two tables based on each member of the Customer dropdown? Preferbly with a pagebreak after the 2nd table.
I normally do this using subreports. You could either create a single subreport that contains both tables or individual subreports. The individual approach might help with page breaks etc so that's the way I'd go.
Step 1: Create a report for your first table.
As you don't state what each table does, I'll make some up for the sake of illustration.
The key is to create a subreport that displays just the info you need in a single table. So in your case this might mean we only need to pass in a single parameter, CustomerID. You might need to pass in more such as Warehouse but I don't know...
In my made up scenario, let's assume the report shows customer contacts so we create a subreport (let's call it subCustomerConacts). It has a single parameter pCustomerID and a single dataset dsCustomerContacts. The query might looks something like SELECT * FROM CustomerContacts WHERE CustomerID = #pCustomerID. Add whatever tables/textbox etc you need to display your data.
Test this subreport works by manually typing in a CustomerID
Step 2: Create a report for your second table.
Do exactly the same again, creating a new subreport. Let call this subCustomerOrders. Repeat as above until you end up with another report that can display order details (or whatever you need).
Finally, create you main report.
This is basically what you have described in you question in terms of parameters etc.
Now to add this bit that will call you subreports.
Create a dataset (let's call it dsCustomerLoop) that contains each customer from your parameter something like SELECT DISTINCT CustomerID FROM myCustomers WHERE CustomerID IN (#myCustomerParameter)
Add a table to your report, 1 column wide and stretch it so it's wide enough to accommodate you subreports.
Set the dataset to point to dsCustomerLoop
Right-Click the cell in the detail row and do "Insert Row -> Inside Group - Below". You should not have two detail rows.
Next, right click the top detail row and do "Insert -> Subreport"
Right-Click the newly inserted subreport control and choose "properties".
Choose your first SubReport form the drop-down list
Click parameters on the left,
Click "Add" and select the CusomterID parmeter, set it's value to the CustomerID field.
Repeat this process on the seconds row, choosing your seconds subreport.
You may want to also add a 3rd row to the table, you could insert a rectangle into this with page breaks set to force a new page after each seconds subreport.
That's It. When the report runs it will produce two rows per customer, each row containing a subreport.
I hope this is clear enough, I've rushed through it a bit but if anything is unclear, let me know and I'll provide a clearer solution.

Error : FRM-41337:Cannot populate the list from record group

I have a record group that is using a block item, i.e. where cust_id = :order.cust_id
Sometimes it works, sometimes not.
When I query an existing record, I am able to add a new line and enter the condition code i.e. from the populated record group. But when I enter a new order, the list is empty. I tried to put the code in when-new-record-instance, but I get an error
select profile profile1, profile profile2
from dss.v_unit_conditions
where cust_id = :order.dsp_cust_id
and profile_type = 'UC'
and active = 'Y'
41337 - cannot populate list from record group
If I use that in when-tab-change, then I get the same error.
When you perform query, you acquire :ORDER.DSP_CUST_ID value so Record Group Query fetches something.
On the other hand, when you're entering a new order, I presume that :ORDER.DSP_CUST_ID is empty, query doesn't return anything and raises an error.
It means that :ORDER.DSP_CUST_ID must be known. In order to make the Record Group Query work, consider creating it dynamically, i.e. when :ORDER.DSP_CUST_ID gets its value. As it seems that you're entering it manually, the WHEN-VALIDATE-ITEM might be your choice. Have a look at CREATE_GROUP_FROM_QUERY (and, possibly, POPULATE_GROUP_FROM_QUERY) built-ins. They are described (with examples) in Forms Online Help System.

Passing more than 3 items in a reports column link

I have a report that is listing students and I want a column to edit a student. I've done so by following this answer:
How do you add an edit button to each row in a report in Oracle APEX?
However, I can only seem to pass 3 items and there's no option to add more. I took a screenshot to explain more:
I need to pass 8 values, how can I do that?
Thanks!
Normally, for this you would only pass the Primary Key columns (here looks like #RECORD_NUMBER# only). The page that you send the person to would then load the form based on the primary key lookup only. If multiple users were using this application, you would want the edit form to always retrieve the current values of the database, not what happened to be on the screen when a particular person ran a certain report.
Change the Target type to URL.
Apex will format what to already have into a URL text field which magically appears between Tem3 and Page Checksum.
All you need to do is to add your new items and values in the appropriate places in the URL.
I found a workaround, at least it was useful to my scenario.
I have an IR page, query returns 4 columns, lets say: ID, DESCRIPTION, SOME_NUMBER,SOME_NUMBER2.
ID NUMBER(9), DESCRIPTION VARCHAR2(30), SOME_NUMBER NUMBER(1), SOME_NUMBER2 NUMBER(3).
What I did was, to setup items this way:
P11_ITEM1-->#ID#
P11_ITEM2-->#DESCRIPTION#
P11_ITEM3-->#SOME_NUMBER##SOME_NUMBER2#
Previous data have been sent to page 11.
In page 11, all items are display only items.
And P11_ITEM3 actually received two concatenated values.
For example, the calling page has columns SOME_NUMER=4 and SOME_NUMBER2=150
so, in pag1 11, P11_ITEM3 shows 4150
In page 11 I created a Before Footer process (pl/sql expression)
to set up new items, for example P11_N1 as source SUBSTR(P11_ITEM3,1,1)
and item P11_N2 as source SUBSTR(P11_ITEM3,2,3)
So, I had those items with corresponding values from the calling IR page.
The reason I did not pass the primary key only for new lookup access, is because i do not want to stress database performing new queries since all data are already loaded into page items. I've been an oracle DBA for twenty years and I know there is no need to re execute queries if you already have the information somewhere else.
These workarounds are not very useful for a product that bills itself as a RAD tool.
Just include a single quoted word in the select statement (Select col1, 'Randomword', col2 from table 1;)
Then define that column as a link and bingo! More items than 3 to select.

Aggregate child table values in Birt

I have this Birt report that I inherited from another developer, consisting of a child table inside a master table. For each row in the master table, the child table lists items belonging to the current master row item.
The two tables are fed from different data sets, the child table dataset taking a parameter indicating the master item whose child items to fetch.
Now, what I need to do is add a SUM aggregate to the bottom of the master table, showing the total (for all master items) of a certain field in the child table.
Consider, for example, the following data:
MasterItem1
ChildItem1 SomeValue
ChildItem2 SomeValue
ChildItem3 SomeValue
MasterItem2
ChildItem1 SomeValue
ChildItem2 SomeValue
ChildItem3 SomeValue
--------------------------------
Total
(Why wasn't this done with grouping instead? Short answer: There are in fact two child tables to each master row, containing different numers and types of fields, so the previous developer probably didn't figure out a way to accomplish this with grouping.)
At first I thought I could simply add another child table inside the Total field, with an aggregate summing up the values from the child dataset. That didn't work, however, since the child dataset requires a parameter indicating the master item whose children to fetch, so there is no way to get ALL values from the child dataset at once.
I'm thinking there might be a way to create an expression that references the SomeValue fields in the child table directly, instead of going through the child data set.
Any suggestions are greatly appreciated.
It should be possible to declare a global variable at the start of the report, then add each of the child values to it in one of the child table row events and output it at the end of the reports - if you're comfortable writing Javascript, this is probably the quickest solution.
If you're not comfortable writing Javascript (I'm not) or if the above technique doesn't work out, you could try either:
creating a third dataset, combining the master data items from the main report with the child data items from the subreport and outputting the total in a new data table, or
combining the two existing child data value tables via a union (so that if the master table is A, the main child table is B and the subreport table is C, you have AB union AC), replacing the subreport table and the existing detail rows with new detail rows conditional on child row type, and a total at the end of the report based on the AC values.
Obviously, the latter of these approaches is more complicated - but I think it should be easier to understand and maintain.
The Global Variable is the way to go. For each row in the child table, add the required value to the global variable and then access it for display at the bottom of the table. There is not any hard JavaScript:
var Sum = reportContext.getPersistentGlobalVariable( "RunningAggregate" );
Sum = Sum + row["column Name"];
reportContext.setPersistentGlobalVariable( "RunningAggregate", Sum );
You can then access the Global Variable in the footer of your table via a Dynamic Text item.
Good Luck!
Thanks Mark and Mystik, both your answers led me on the right path!
My final solution is as follows:
1) Declare the sum-variable in the initialize method for the report:
var total = 0;
2) Add each row's value to the sum-variable in the onReder method of the data field containing the values:
total += parseInt(this.getValue());
3) Use the sum-variable as expression in the total-field.
Works like a charm.
Update:
Found a bug in my solution: the last line was left out of the sum. I think the value of the total-cell in the table footer is being defined before the last line has been rendered.
Fix:
Moved summing code from onRender method to onCreate
Added the following code to the total-cell's onRender method:
this.setDisplayValue(total);

Resources