Tuesday, 15 November 2016

Disabling Apex items the right way

Sometimes you want to show data without the user being able to change it. The easiest way in Apex to use reports and read only items. There are use cases however for which this is not good enough.
For example when you want to change from read only to editable item dynamically or vv. Or if you want to disable items in a tabular form selectively.
In these cases it is necessary to generate the editable items and to disable them afterwards. In this blogpost you can read how you can do this without compromising the functioning of your page. A special chapter describes the way to selectively disable items in a tabular form.

How to disable items correctly with JavaScript

If you want to disable items, the two most obvious options are the HTML attributes disabled and readonly. But these both have their shortcomings: disabled hides the item and its content from the form and readonly only applies to text inputs and textarea's.

The attribute disabled is the most dangerous one to use. If you disable one input item on line 1 in a tabular form all the cells below shift one place up. Look at this example of a tabular form on the EMP table. On the right is the starting situation. The first item of Commission is disabled. To the right you see the result after saving. All the values have shifted up one place! So the data has been transferred to other records! Ward is a lucky guy now and Martin is left with no commission. Pretty scary, isn't it?



The success message indicates exactly what happened, 4 rows have been changed. 

So disabled is dangerous to use and readonly is limited in item types.

Luckily we have the class apex_disabled that can be used to disable all types of items. This class uses the css property pointer-events that prevents any mouse action. It also takes care that the item looks not editable (grey background etc. ).
This class does however not prevent the user from navigating to the item with keyboard navigation, i.e. with the Tab key. This can be prevented by applying the attribute tabindex="-1"

So the correct way to disable an item is applying the class apex_disabled and the attribute tabindex="-1". It takes care that the item cannot be reached by the user either by mouse or by keyboard.

function disable_item ( itemName )  
{ 
   $('#'+itemName).addClass('apex_disabled').attr('tabindex','-1');    
}
function enable_item ( itemName )  
{ 
   $('#'+itemName).removeClass('apex_disabled').removeAttr('tabindex');    
}

NB To be sure the application always has to perform a server side check on autorisation because the item values can easily be manipulated using a browser inspector. 

How to selectively disable items in a tabular form

If you want to disable items selectively meaning specific rows or cells you have two options: 

1. generate the tabular form yourself using apex_item and create readonly items when applicable
2. use an Apex tabular form and disable the items using JavaScript

The first option takes a lot of coding and is hard to maintain. You will soon get a big query which combines data and logic. 

The second option delivers much clearer code and you can style the items the Apex way. The only thing you need to do is to supply Apex with the items that need to be read only for each row. It should take the form of a delimited list of column names (equal to the ones in Apex). You can code that into your query or use a function supply the list.
An attribute data-item="DISABLE_ITEMS" is  added to the column containing this list in order to find the data. 

In the after refresh Dynamic Action JavaScript is used to loop along all the rows and perform the disabling of the items. You can use the code below:

function disable_tabform_items ( tabSelector )  
{ 
    // loop along all the rows with a DISABLE_ITEMS cell
    $(tabSelector).find('[data-item="DISABLE_ITEMS"]').each( 
      function() 
    {
      // make array of item names to be disabled
      list = $(this).val().split(':');
      if ( list.length > 0 ) 
      { 
        // identify the row
        var tr = $(this).closest('tr');

        // disable all the item in the list  
        for ( i=0 ; i < list.length ; i++ ) 
        {
          $(tr).find('[headers="'+list[i]+'"]')
               .find('input,textarea,select,button.ui-datepicker-trigger,a.a-Button--popupLOV')
               .addClass('apex_disabled')
               .attr('tabindex','-1');
        }       
      }
    });
}
An example of this you can find in action here.
There you can also download the demo application in order to see how it works form the inside.

Happy apexing!

06-09-2017:  The code of disable_tabform_items is changed to also support Popup LOVs

14 comments:

Kevin Zhang said...

Thanks for sharing this great tip!!!

Kevin Zhang (#kzhangkzhang)

Kevan Gelling said...

Is there anyway to disable the item but still make it available for copy-and-paste?

Dick Dral said...

The items are available for copy-and-paste when using the attribute readonly.
The drawback of readonly is that it only applies to input[type="text"] and textareas. In IE the styling cannot be modified using CSS.

So if you want to have the content available for copy-and-paste and you do not mind the styling in IE, you could apply readonly to text inputs and textareas. Ue CSS to style these elements the same way you style the other disabled elements.

Good luck!

Unknown said...

Thanks, it works but not for Popup LOV items.
Is there any way for disabling Popup LOV item ?

Dick Dral said...

Hi Allaf,

I just changed the code so that Popup LOV are also supported.

Rotan said...



thank you for the post..

Tried to add a new row in the application but it gives Error

ORA-06502: PL/SQL: numeric or value error: character to number conversion error

I also tried to follow the same approach in my environment (5.1.3) there also we have same error

Any Suggestions.

Anonymous said...

Getting the following error in your example app and in my app. Possibly a issue with the latest Apex version.

1 error has occurred
Current version of data in database has changed since user initiated update process. (Row 1)

Anonymous said...

The error occurs on the Apply Changes

APEX_NEWBIE said...

Nice trick, one issue:
For items when you click on the label it moves the cursor into disabled field and allows the user to modify it.

I've added the below to fix it:
to disable
$('#'+itemName+'_LABEL').addClass('apex_disabled').attr('tabindex','-1');

to enable
$('#'+itemName+'_LABEL').removeClass('apex_disabled').removeAttr('tabindex');

I think tabindex is probably not necessary.

Anonymous said...

How to disable apex5 Item. item which is populated through a dynamic action.
I selected the disable item as yes. But that value is not storing in backend table.

Please give me a suggestion. how can I resolve it.

Maluju Artes said...

HI... I followed your post by the book, but I identified a side effect. When a double click occurs on the label of the fields disabled, the field becomes editable, that is, back to is enabled. Do you know what could be happening?

Dick Dral said...

Hi Maluju,

The effect you describe I can reproduce. When the text in an input is double clicked on, the input becomes active.
The disabled effect is based on the css claque pointer-events:none.
Apparently it does not prevent this action.
I do not have a solution for this behavior.

Anonymous said...

This tip has helped me. Thanks very much!

Ulrik Larsen said...

We can also use the Disable option under a dynamic action (Identification) setting...