Tuesday, September 27, 2011

Dynamic Actions - Changed behavior in APEX 4.1

APEX 4.1 Dynamic Actions have a changed behavior over 4.0 version when executing a Static Assignment or JavaScript Expression against multiple elements.

Reading that back makes it sound sufficiently obscure that it may not be an issue for you. Hopefully not, but let's run through the changed behavior, the reasons behind it, how to detect it and how to resolve it.

Firstly, some background.
As part of a demo on dynamic actions, I set the value of 3 items using a dynamic action, as shown below:
apex_4_0.png

This worked perfectly fine until I upgraded to 4.1, where the first items' value was set, and the remaining item values are set to null.

Comparing the Dynamic Actions JavaScript for setValue (JavaScript Expression) code for 4.0 to 4.1 reveals why the change occurs.

apex_dynamic_actions_4_0:
setValue : function(){
        ...snipped...
    
        } else if (lSetType === 'JAVASCRIPT_EXPRESSION') {
            lResult = eval(lJavaScriptExpression);
            if (lResult){
                this.affectedElements.each(function() {
                    $s(this, lResult);
                });
            }
        }
    },
apex_dynamic_actions_4_1:
setValue : function(){
        ...snipped...
    
        } else if (lSetType === 'JAVASCRIPT_EXPRESSION') {
            lJSExpression = eval(lJavaScriptExpression);
            this.affectedElements.each(function(i){
                // Set the value to the first affected element, null the rest.
                lValueToSet = (i===0?lJSExpression:'');
                $s(this, lValueToSet, null, lSuppressChangeEvent);
            });
        }
    },
As the comment indicates, the changed behavior is by design.

Reasons behind the change
Anthony Rayner from the APEX development team was kind enough to explain the reasoning behind the change:
This is indeed changed behaviour as you describe, introduced in 4.1. I don't know if you've seen this already, but we added a section to the release notes on just this. I have copied this below for your convenience:

4.9 Dynamic Action Set Value with Multiple Affected Elements

In dynamic actions, prior to Application Express 4.1, it was possible to use the Set Value action to set multiple Affected Elements, but only to the same value, not different values. In Application Express 4.1, we introduced the following new capabilities to set multiple values from dynamic actions:
  • Action 'Set Value' > Set Type 'SQL Statement' - You can now define a SQL Query to return between 1 and 100 columns, which is used to set the 'Affected Elements' in the order defined (1st column is used to set 1st affected element, and so on). Previously, this only supported querying for 1 column, but could be used to set multiple affected elements to that same 1 column value.
  • Action 'Execute PL/SQL Code' - A new 'Page Items to Return' attribute that sets any page item values on the page to their updated values in session state.
With the introduction of some built-in options to retrieve multiple values, Application Express no longer supports the ability to retrieve 1 value that could then be used to set multiple page items. This was done for simplicity and usability. If you had defined a 'Set Value' dynamic action that set multiple Affected Elements to the same value, here is how you can fix this, according to the 'Set Value' dynamic action's 'Set Type':
  • Static Assignment - Define additional 'Actions' for each Affected Element, setting them all to the same value.
  • JavaScript Expression - Define additional 'Actions' for each Affected Element, setting them all to the same value.
  • SQL Statement - Alter your SQL Statement such that it selects the same column value, with different column aliases for each Affected Element.
  • PL/SQL Expression - Consider changing this over to use the 'Execute PL/SQL Code' action type, in conjunction with the 'Page Items to Return' attribute. The 'PL/SQL Code' would need to ensure the page item's value is updated in session state, such that 'Page Items to Return' returned the updated value.
  • PL/SQL Function Body - Consider changing this over to use the 'Execute PL/SQL Code' action type, in conjunction with the 'Page Items to Return' attribute. The 'PL/SQL Code' would need to ensure the page item's value is updated in session state, such that 'Page Items to Return' returned the updated value.

So really this change was prompted by the introduction of support for setting multiple different values from 1 'Set Value > Set Type (SQL Statement)'. This introduced a 'positional' notation of sorts for this set type, whereby affected elements map, by position to values returned from the SQL Statement. We felt with this introduction, that it would be simpler not to continue to support the setting of multiple affected elements to the same value, but rather that if this is desired, that one of the above techniques should be used instead, depending on set type. We felt this in the long run was simpler and more intuitive and that hopefully this would have minimal impact when upgrading from 4.0.
Hmm, that makes the reasoning behind the change a lot clearer. I really like the idea of being able to use a single dynamic action to update a bunch of fields at once.

For example, you could change the shipping method for goods delivery. Using a Dynamic Action with a "PL/SQL Code" action type you could update the shipping charges, subtotal amount, expected delivery date and terms & conditions all at once. Nice!

How to detect if this impacts you
Running the following query in 4.0 or 4.1 will identify any dynamic actions that are affected:

select a.workspace
      ,a.application_id
      ,a.application_name
      ,a.page_id
      ,a.page_name
      ,a.attribute_01
      ,a.dynamic_action_name
      ,a.action_name
      ,a.action_sequence
      ,a.dynamic_action_event_result
      ,a.affected_elements
  from APEX_APPLICATION_PAGE_DA_ACTS a
 where a.action_code = 'NATIVE_SET_VALUE'
   and a.attribute_01 in ('JAVASCRIPT_EXPRESSION','STATIC_ASSIGNMENT')
   and a.affected_elements_type = 'Item'
   and instr(a.affected_elements, ',') > 0;


Adjusting for the changed behavior
In my example I already have the value I want to assign, the most appropriate alternative here is to use a JavaScript Expression. Instead of defining a single Dynamic Action to affect multiple items, just create multiple actions each updating a single item as shown below.


2 comments:

Steve Dagarin said...

Hi Mark,

Steve Dagarin here. Used to work with you at Corrections if this is the same Mark ? I was interested to see your demo on dynamic actions but this link: apex_4_0.png does not seem to go anywhere.

Mark Lancaster said...

Hi Steve

Must be another Mark L.
There isn't a demo link in the article, but you can see it in action at http://apex.oracle.com/pls/apex/f?p=200801:21