woensdag 21 maart 2012

ADF 11g: Remove the LOV button from the keyboard tab-sequence

The default behavior in ADF Faces when using the tab-key in a List of Values item is that the LOV icon behind the item will get the focus. For people who do a lot of data entry  this is not the preferred behavior, because they usually know the codes they have to enter. In combination with a function key defined to call the LOV dialog, there is no need to include the LOV icon in the tab-sequence. 

Looking for a way to solve this problem, I found several blogposts with a solution. Most of them are based on the usage of a clientListener of type 'keydown' on the LOV item. The clientListener calls a JavaScript function that checks the key pressed, and places the focus on the next input field when the tab-key is pressed. The challenge in these solutions is to determine what the next focus field should be. Finding the next item based on hardcoded id's isn't an elegant solution.

If we could influence the way the HTML was written by ADF Faces, the solution would be easy. The only thing we really need, is to set the HTML tabIndex attribute of the LOV icon to -1. This tells the browser to skip the icon from the tab sequence. However the af:inputListOfValues item does not support this feature declaratively. 

In the following I will explain how the tabIndex of the LOV icon can be set to -1. What we need is a clientListener and some JavaScript.

 function setLovTabIndex(event) {  
   var component = event.getSource();  
   var lovButtonId = component.getClientId() + '::lovIconId';  
   // We have to find the button using the document because finding it through Adf.Page.findComponent does not  
   // work. The icon is not a separate ADF component.  
   var lovButton = document.getElementById(lovButtonId);  
   // This is the trick: set tabindex to -1  
   lovButton.tabIndex = - 1;  
   // Remove the eventlistener to prevent action from being performed again. Not really a must, but cleaner.  
   // However this does not work in conjunction with the autosuggest feature on the lov-item.  
   // In that case the listener is not removed.  
   component.removeEventListener("focus", setLovTabIndex, false);  
   //event is handled on the client. Server does not need  
   //to be notified  

This JavaScript should be included in a JavaScript library that is part of your application.

The next step is to add an  af:clientListener to the af:inputListOfValues item:
     <af:inputListOfValues id="employeesManagerId" ...>  
      <f:validator binding="#{bindings.EmployeesManagerId.validator}"/>  
      <af:convertNumber groupingUsed="false" pattern="#{bindings.EmployeesManagerId.format}"/>  
      <af:clientListener method="setLovTabIndex" type="focus"/>  

As you can see, the listener is fired when the LOV item gets the focus. The JavaScript will lookup the related LOV icon, and will set the tabIndex of the icon to -1. After that, the clientListener is removed. The advantage of this approach is that there is no need to have any knowledge about the 'next focus field'. Furthermore the code is only fired once when the LOV item gets focus.

4 opmerkingen:

  1. Hi, very useful post.
    One of the challenges I find with customising LOV behaviour is that users then expect the same behaviour in the search panel (af:query) as well...
    So how would one add the af:clientListener to the LOV component rendered at runtime in the af:query component?

    1. Hi, sorry for the late reply.

      Given the fact that the af:query component creates its items on the fly, it is not possible to add a clientlistener to the items. However I've found another solution for the LOV icon problem that also solves this issue. This solution makes use of the javaScript library jQuery. I will describe this solution in a new Blog.

  2. Hi
    It is very useful to me. Thank you very much

  3. I was trying to resolve it for couple of days,
    This is a good solution. Thank you.