Showing posts with label ORACLE APEX. Show all posts
Showing posts with label ORACLE APEX. Show all posts

Wednesday, September 28, 2011

Set Multiple Items From Query

From time to time, it happens I have to set multiple items without submitting the page. So one day I decided to write a solution - not just for a particular table or form but a more generic one that I could reuse again.

The goal - create a procedure which would accept two parameters: a query and page items to set. The query would of course deliver values for these items.
The approach - On Demad Process (retrieving values) and JavaScript function (populating values).

In this post I assume that the reader is already familiar with incorporating JavaScript into APEX and what On Demad Process is as well as how to make an AJAX call from APEX. If not, please check here.

On Demand Process

The On Demad Process should retrieve the values from the query and return them JSON formatted. Fortunately for me, there was already a procedure that takes care of it: APEX_UTIL.JSON_FROM_ITEMS as I found it in APEX forum and here. One needs to set the session state of the items and then call this procedure. So more than half the work was already done:
declare
 l_dname  varchar2(200) := wwv_flow.g_x01;
begin
 for c_dept in (select * from dept WHERE DNAME = l_dname) loop
  APEX_UTIL.SET_SESSION_STATE('P4_DEPTNO',c_dept.DEPTNO);
  APEX_UTIL.SET_SESSION_STATE('P4_LOC',c_dept.LOC);
 end loop;
 apex_util.json_from_items('P4_DEPTNO:P4_LOC');
end; *

* The code snipe is from the Rutger's blog mentioned above.

My effort now was to turn the code above into something more dynamic where the query and the items can be passed as a parameter, something like this:
declare
  p_my_query       -- here my query as a parameter
  p_items_to_set   -- items to set also as a parameter
begin
 -- loop through the columns
 for cursor_for_my_query in ( p_my_query ) loop
   -- set the session state for the given items: p_items_to_set
   SET_SESSION_STATE ( array_of_items_to_set ( loop_variable ), array_of_column_values_from_cursor ( loop_variable ) );
 end loop;
 -- the rest...
 json_from_items(p_items_to_set);
end;

There is only a problem with the cursor: to reference columns one needs to know the column names - which are, at this point, unknown because the query is passed during the runtime. There is a way to get the column name from the dynamic query with the help of DBMS_SQL but it's a long way. Another trick would be passing the query to a collection and then walking through it to set the session state of items. I haven't tried that because, lucky me, at the moment I'm working on the APEX 4.0 and there is the package APEX_PLUGIN_UTIL which does the job I need. The code below (on demand process, called GET_ITEMS) shows how to achieve it. Attention! The code is simplified for better readability.
declare
  -- the sql statement passed through the global variable x01
  l_stmt           varchar2(4000) := apex_application.g_x01;
  -- the items that we want to set (x02)
  l_items_to_set   varchar2(1000) := apex_application.g_x02;  

  -- help variables
  a_items     apex_application_global.vc_arr2;
  l_RowSet APEX_PLUGIN_UTIL.t_column_value_list;
  l_sqlHandler APEX_PLUGIN_UTIL.t_sql_handler;
begin
  -- Get the sql handler
  l_sqlHandler := APEX_PLUGIN_UTIL.GET_SQL_HANDLER ( p_sql_statement => l_stmt
                                                    ,p_min_columns => 1
                                                    ,p_max_columns => 100
                                                    ,p_component_name => null );
  -- Prepare query - for the first row only
  APEX_PLUGIN_UTIL.PREPARE_QUERY ( p_sql_handler => l_sqlHandler, p_max_rows => 1 );

  -- Get data
  l_RowSet := APEX_PLUGIN_UTIL.GET_DATA ( l_sqlHandler ) ;

  -- Get the items and put them into a table, so they can be easily looped
  a_items := APEX_UTIL.STRING_TO_TABLE (l_items_to_set, ',' );

  -- Loop through the columns and items and set the session state
  for col in 1..l_RowSet.count loop
     APEX_UTIL.SET_SESSION_STATE( a_items(col), l_RowSet(col)(1) );
  end loop; 

  -- Call the procedure to create JSON string of items and values
  APEX_UTIL.JSON_FROM_ITEMS(l_items_to_set, ',');
end;

 Here the process from above explained in plain English:
  1. Prepare the query: e.g., columns and rows to be retrieved (here only one row)
  2. Fetch the data
  3. Walk through the columns and set the session state for the given items
  4. Call the procedure JSON_FROM_ITEMS which gives the JSON formatted string which can then be used in the JavaScript function (below).

JavaScript function

Here the corresponding JavaScript function to call the on demand process:
function set_multiple_items() {
  var get = new htmldb_Get(null, $v('pFlowId'), "APPLICATION_PROCESS=GET_ITEMS", $v('pFlowStepId'));
  var gReturn  = null;
  // pass the parameters that we need:
  // the query
  get.addParam('x01', 'select ename, job, hiredate, sal, comm from emp where empno=7839'); 
  // and the corresponding items to set
  get.addParam('x02', 'P1_ENAME,P1_JOB,P1_HIREDATE,P1_SAL,P1_COMM');
  gReturn = get.get();
  // assign the values
  json_SetItems(gReturn);
  get = null;
};

After working with it for a while I decided to turn the code into a plugin. I added one more parameter - Page Item To Submit, so I can change the item with JavaScript and include it in the where clause.
The plugin can be seen here in action and will be is published soon on the apex-plugin page.

I suppose that in the next APEX version this plugin will not be necessary.

Last but not least: I couldn't find any documentation neither for the APEX_UTIL.JSON_FROM_ITEMS nor json_SetItems, so I owe a huge credit to Rutger and APEX forum for writing about this.

Thursday, December 9, 2010

Region Display Selector - How I (Would) Like It

In APEX4 I welcomed very much the new region type - the Region Display Selector. The only thing I don't like is how it displays the regions. When I see all, I see nothing. So I decided to customize the RDS a bit - on page load, not all the regions should be shown, only the first one.

Approach: Find the second tab and simulate the click on it when the page loads.
With firefox and firebug this is done fast: rds tabs is the list with the class 'apex-rds'.
Here is the jQuery for my purpose:
$('ul.apex-rds > li:eq(1) > a').trigger('click');

Explanation:
Find the <a>, which is in the second <li>, which is in the <ul> with the class 'apex-rds' and simulate a click.
And I'm done.
The code can be put into a dynamic action or in a js-file - depending on one's needs.

Done. Almost, because somehow I am confused that the second and not first tab is selected.
Just changing the position of the 'Show all' would make me happy:


$('ul.apex-rds')                        // find the <ul> with the tabs
  .each(function(){                     // in case we have more than one RDS region
    $(this)
      .children('li.apex-rds-first')    // find the tab 'Show all'
      .remove()                         // remove it-the function returns the removed element itself
      .removeClass('apex-rds-first')    // switch the classes
      .addClass('apex-rds-last')
      .appendTo($(this))                // append to the ul again
      .prev('li.apex-rds-last')         // go to the one that was last before
      .removeClass('apex-rds-last')     // remove class
      .siblings('li:first')             // find the new first
      .addClass('apex-rds-first')       // add the class
      .children('a:first')              // find the href in it
      .trigger('click');                // and symulate a click
});

The last thing. I really like the new RDS region... but I am not able to choose which region (with the RDS flag) goes into this particular container. When you create a second RDS on the page, the result is quite confusing.

For an update please see the part two.

Wednesday, December 8, 2010

Dynamic Action on Button-Item

Reading the post How to Trigger Dynamic Action From Button by Martin Giffy D'Souza, I decided to give the dynamic action on buttons another try.
The action on the 'normal' button (in a region position) can be added as described and works perfectly, but if I want to do the same with the button-item, I get an unwanted effect - despite of disabling/rewriting the click event, the site will redirect: the apex.submit does the job.
The only trick that works is to remove the attribute 'onclick' explicitly attr('onclick','') and then to add the new event.

Create Dynamic Action -> Advanced -> Event: Page Load -> Action: Excecute JS Code

$(this.affectedElements) //when you choose Selection Type: Item(s) (by HTML button)
or
$('#custom') //with known id
  .attr('onclick','')
  .click(function(){alert('my event')})



This can, of course, be splitted into OnLoad action (disable the click first) and then another one to add a custom event.
All roads lead to Rome.