Dynamic Action on a Tabular Form

I want to visually enable/disable certain items in each row of a tabular form depending on the value of another item in that row. I’m using APEX 4.1.

My tabular form has a number of editable fields representing budgets. Each line might be an Annual budget (AMOUNT_TYPE = ‘YEAR’) with a single amount for the year, or a Monthly budget (AMOUNT_TYPE = ‘MONTH’) with separate amounts for each of the twelve months.

The first editable item (internal id f02) is AMOUNT_TYPE which is a Select List with an LOV. The second editable item (internal id f03) is the Annual Amount and should only be enabled if AMOUNT_TYPE = ‘YEAR’. The 3rd, 4th … 14th items (internal ids f04..f15) are the Monthly Amounts and should only be enabled if AMOUNT_TYPE = ‘MONTH’.

To do this:

1. Define a visual style to be applied to items that are disabled.

Add this to the Page’s “HTML Header” attribute:

<style>
.textinputdisabled {
  color:grey;
  background-color:lightgrey;
  text-decoration:line-through;
}
</style>

In this instance, I’ve set the background color to a light grey, the text color to darker grey, and I’ve added a strikethrough effect.

2. Set the class on the AMOUNT_TYPE item

Edit the Column Attributes on the AMOUNT_TYPE column, set Element Attributes to:

class="typeselect"

3. Define the Dynamic Action
Event = Change
Selection Type = jQuery Selector
jQuery Selector = .typeselect
Condition = – No Condition –

True Action = Execute JavaScript Code
Fire On Page Load = yes

Code =

row_id = $(this.triggeringElement ).attr('id').substr(4);
if( $(this.triggeringElement ).val() == 'MONTH')
{
  $( "#f03_" + row_id ).prop( 'readOnly', 'readonly');
  $( "#f03_" + row_id ).prop( 'class', 'textinputdisabled');
  for (var i=4;i<16;i++)
  {
    column_id = ("0" + i).slice(-2);
    $( "#f" + column_id + "_" + row_id ).prop( 'readOnly', false);
    $( "#f" + column_id + "_" + row_id ).prop( 'class', false);
  }
}
else
{
  $( "#f03_" + row_id ).prop( 'readOnly', false);
  $( "#f03_" + row_id ).prop( 'class', false);
  for (var i=4;i<16;i++)
  { 
    column_id = ("0" + i).slice(-2);
    $( "#f" + column_id + "_" + row_id ).prop( 'readOnly', 'readonly');
    $( "#f" + column_id + "_" + row_id ).prop( 'class', 'textinputdisabled');
  }
}

The above code first determines the id for the row; $(this.triggeringElement).attr(‘id’) returns ‘f02_nnnn’ where nnnn is the row number left-padded with zeroes. For Oracle peeps, substr(4) is equivalent to SUBSTR(x,5).

If the value of the triggering item is MONTH, we want to disable the Annual amount item and re-enable (in case they were previously disabled) the Month amount items. And vice-versa.

To disable an item, we set the readOnly property (note the capital O: this is case sensitive!) to the value “readonly” (all lowercase); this makes it so that the user cannot modify the value in the field. Note that we don’t set the “disabled” property because that would stop the item being posted to the database, which will break the tabular form processing.

Side Note: at first, I was using the .attr and .removeAttr jquery functions to set/unset readOnly as per some notes I’d found on the web; this worked for Chrome, but it made all the items permanently read-only in IE7; after some googling I found this is a feature, not a bug; and that .prop is the correct function to use in this instance.

We also set the class to the CSS style we defined earlier.

Because I have 12 items in a row to modify, I use a Javascript loop. The expression to generate the column id (“0” + i).slice(-2) does the same job as the Oracle expression TO_CHAR(i, ‘fm00’).

Next, I want to enhance this page further, so that when the user is entering monthly amounts, the Total field automatically calculates the sum of all the months (while still saving the original annual amount, if any, on the database). I had to get outside help [stackoverflow] to get this working.

UPDATE (31/7/2015): to make this work if the tabular form has an “Add Row” button, you need to use a jquery “on” event handler instead of using the Dynamic Action, and refer to the item using just “this” instead of “this.triggeringElement”, e.g. put this into the forms Execute when Page Loads:

$(document).on("change", ".typeselect", function(){
 row_id = $(this).attr('id').substr(4); 
 ... etc. ...
});
Workaround for ORA-04063: view “x” has errors
Select All / Unselect All Checkbox in Interactive Report Header

Comments

  1. Hi,
    Thanks for the method, it works just fine but… I don’t know why, it does not work on newly added record.
    If the record existed before loading the page, then the code would work, if you would add it after loading the page then that is not the case, I have even tried adding an alert in the java script code, but it did not work.

    I am using Google chrome 31, oracle apex 4.2 on 11g express

    • Hi Ali,

      Thanks for the note. In my case, the application does not allow users to add rows, so I never tested this method in a scenario that allows users to add rows.

      I’m not sure if it’s possible to add a dynamic action to a page item if it’s added after the page has already been rendered.

      Jeff

  2. I am trying to execute the mentioned code on page load but it’s not working for me .. please could you help me.

    it’s working fine when change select list “Type” but on age load its just showing first console message.

    —————–page load——————
    console.log(“page load call”);
    $(document).on(“change”, “.typeselect”, function(){
    row_id = $(this).attr(‘id’).substr(4);
    console.log($(this).attr(‘id’).substr(4));
    if( $(this).val() == ‘N’)
    {
    $( “#f05_” + row_id ).prop( ‘readOnly’, ‘readonly’);
    $( “#f05_” + row_id ).prop( ‘class’, ‘textinputdisabled’);
    for (var i=6;i<16;i++)
    {
    column_id = ("0" + i).slice(-2);
    $( "#f" + column_id + "_" + row_id ).prop( 'readOnly', false);
    $( "#f" + column_id + "_" + row_id ).prop( 'class', false);
    }
    }
    else
    {
    $( "#f05_" + row_id ).prop( 'readOnly', false);
    $( "#f05_" + row_id ).prop( 'class', false);
    for (var i=6;i<16;i++)
    {
    column_id = ("0" + i).slice(-2);
    $( "#f" + column_id + "_" + row_id ).prop( 'readOnly', 'readonly');
    $( "#f" + column_id + "_" + row_id ).prop( 'class', 'textinputdisabled');
    }
    }
    });

    • Hi Ankit, on page load your code is executed, but your code only adds a trigger to the “typeselect” items, it doesn’t actually run the function straight away.
      If you want your code to run straight away, I would move it into a function on its own and then call it directly from your page load.

      e.g.

      function onChangeTypeSelect() {
      row_id = $(this).attr('id').substr(4);
      ...
      }

      $(document).on("change",".typeselect", onChangeTypeSelect);

      onChangeTypeSelect();

  3. Hello Jeffrey, Thanks for the reply.

    I have created separate function onChangeTypeSelect on page Function and Global Variable Declaration area like
    ————————————————
    function onChangeTypeSelect() {
    row_id = $(this).attr(‘id’).substr(4);
    console.log(“inside func”);
    console.log(row_id);
    if( $(this).val() == ‘N’)
    {
    $( “#f05_” + row_id ).prop( ‘readOnly’, ‘readonly’);
    $( “#f05_” + row_id ).prop( ‘class’, ‘textinputdisabled’);
    for (var i=6;i<16;i++)
    {
    column_id = ("0" + i).slice(-2);
    $( "#f" + column_id + "_" + row_id ).prop( 'readOnly', false);
    $( "#f" + column_id + "_" + row_id ).prop( 'class', false);
    }
    }
    else
    {
    $( "#f05_" + row_id ).prop( 'readOnly', false);
    $( "#f05_" + row_id ).prop( 'class', false);
    for (var i=6;i<16;i++)
    {
    column_id = ("0" + i).slice(-2);
    $( "#f" + column_id + "_" + row_id ).prop( 'readOnly', 'readonly');
    $( "#f" + column_id + "_" + row_id ).prop( 'class', 'textinputdisabled');
    }
    }
    }
    $(document).on("change",".typeselect", onChangeTypeSelect);
    —————————

    on page load calling function like:
    ———————————–
    onChangeTypeSelect();
    ————————-

    but getting error :
    Uncaught TypeError: Cannot read property 'substr' of undefined

    Please let me know if i am doing any wrong action.

    Thanks

    • Hi Ankit,

      Sorry, my mistake – that function won’t work on its own, it needs “this” to refer to something. One way would be to use the jQuery “each” function to call it once for each row.

      However, I think the far easier method is the one that I described in the article – create it as a Dynamic Action based on the jQuery selector, and set Fire on Page Load to “Yes”.

      Jeff

  4. Hi Jeffrey,

    ok, which event I should select to fire the dynamic action. (when user come to this page then rows should be enabled/disable as default based on type list)

    like:
    Event: ????
    Selection Type: JQuery Selector
    JQuery Selector: .typeselect

    Action: Execute JS
    Code:
    $(document).on(“change”, “.typeselect”, function(){
    row_id = $(this).attr(‘id’).substr(4);
    ….

    Is it correct, please could you check?

    Thanks

  5. yes, it’s working fine on change even but if I set Fire on Page Load to “Yes” then it should execute when page load right?

    but currently, it’s not executing on page load so I would like to execute this event when page load so by default rows will be enabled/ disable based on type selection saved in DB.

    please could you give me some hint about how to do it on page load?

    Thanks

    • You need to set Fire on Page Load to Yes. This is what causes the event to fire after the page has loaded. APEX will call your code for each instance of the target object that has been rendered on the page.

  6. It was my mistake 🙂 .. thanks a lot for your help.

    Ankit

    • No problem, I’m glad you were able to get it working.

      By the way, keep in mind that tabular forms are now deprecated and plans need to be made to replace them with Interactive Grid.

      Cheers, Jeff

  7. Hi Jeffrey,
    Yes, we will upgrade our tabular form to IG.

    One question:
    I have created one item using apex_item like:

    APEX_ITEM.SELECT_LIST(
    p_idx => 14,
    p_value => NULL,
    p_list_values => ‘Yes;Y,No;N’,
    p_attributes => ‘style=”color:red;”‘,
    p_show_null => ‘YES’,
    p_null_value => NULL,
    p_null_text => ‘-Select-‘,
    p_item_id => ‘f14_#ROWNUM#’) “tlist”

    and when I trying to get the trigging element value using code mentioned below:

    alert(apex.item(this.triggeringElement.id).getValue());

    this code is not working for me alert showing as “undefined”. if I want to get the triggering value for this item then how to get the value?
    please help me out.

    Thanks

    • Hi Ankit,

      I’m not quite sure. I suspect that apex.item() will not work in your case, since tabular forms do not actually generate real APEX items.

      Perhaps try something like this instead:

      alert($(this.triggeringElement).val());

      Caveat: I might be wrong 🙂

      Jeff

  8. Hi Jeffrey,

    Thanks a lot its works fine now.

    If I want to create the same form in IG then how to do it? ..did you posted already?

    sorry for asking many questions 🙂

    • Hi Ankit,

      The implementation of Interactive Grid is totally different to the old tabular form, so these techniques do not apply. They are more complex but also more powerful so there is a bit of a learning curve; but it is very much worthwhile as IGs are here to stay and tabular forms are now deprecated.

      An excellent resource for learning IGs and how to customise their behaviour is John Snyder’s blog. I would start here: https://hardlikesoftware.com/weblog/2019/11/04/apex-ig-cookbook-update-for-19-2/

      Jeff

Leave a Reply

Your email address will not be published / Required fields are marked *