donderdag 19 april 2012

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

In a previous post I described a way to remove the LOV button from the keyboard tab-sequence. This solution was based on a combination of a focus-clientListener and some Javascript. As soon as an LOV item gets focus, the Javascript searches the related LOV button and turns the tabIndex of the button to -1. This way, the button will not be in the tab-sequence anymore.
One comment on this solution was that it will not work for LOV items that are displayed as part of an <af:query> component. This is caused by the fact that an <af:query> component renders its items on the fly, so it is not possible to add a clientListener to an item. However, there is another solution for this problem.

Since the previous post, I've been working on functionality  to 100% keyboard enable our ADF application. This functionality is based on a Javascript library called jQuery, This library turns out to be a very powerful tool in combination with the ADF client side.In this post I will describe a way to remove the LOV button from the keyboard tab-sequence, using jQuery. The solution will cover all LOV items, including the ones in e.g. an <af:query>.

In general, what the solution does, is find all LOV buttons in the page (HTML-document), and turn their tabIndex into -1.For this we first need a Javascript to find and change all LOV buttons. And this is where jQuery comes into place:

Javascript
var lovIcons = $('a[id$="lovIconId"]');

This statements finds all HTML-elements of type <a> with an id that ends with 'lovIconId'. This results in a list of all the LOV buttons.The $ sign in the statement is a reference to the functions in the jQuery library.

The next step is to turn the tabIndex for all the items into -1:

Javascript
$(lovIcons).attr('tabIndex', -1);

In this post I will not go into the details of jQuery, but as you can see a few simple statements can do the job.

To make use of jQuery, download the library from this location: http://code.jquery.com/jquery-1.7.js. Make sure the library is available in your project, and include a reference to the library in your page:

    <af:document id="d1">
      <f:facet name="metaContainer">
        <af:group>
          <af:resource type="javascript" source="/jsLibs/jquery-1.7.js"/>
        </af:group>
      </f:facet>
   .....


Now we need a way to trigger this Javascript. ADF pages are dynamic documents that can show or hide LOV items during different requests. This means that it is not enough to run the script during document-load. The easiest way is to run the script during every request. Perhaps there are more fine-grained ways, but for this post I will keep it simple. To trigger the script we make use of a PhaseListener. This PhaseListener adds the script to every response:

PhaseListener
package view.beans;

import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

import oracle.adf.share.logging.ADFLogger;

import org.apache.myfaces.trinidad.render.ExtendedRenderKitService;
import org.apache.myfaces.trinidad.util.Service;


public class DemoPhaseListener implements PhaseListener {
    private static final ADFLogger sLog = ADFLogger.createADFLogger(DemoPhaseListener.class);
    private static final long serialVersionUID = 1L;

    /**
     * placeholder method
     */
    @Override
    public void afterPhase(PhaseEvent phaseEvent) {
    }

    /**
     */
    @Override
    public void beforePhase(PhaseEvent phaseEvent) {
        if (phaseEvent.getPhaseId() == PhaseId.RENDER_RESPONSE) {

            FacesContext fctx = FacesContext.getCurrentInstance();
            ExtendedRenderKitService erks = Service.getRenderKitService(fctx, ExtendedRenderKitService.class);
            StringBuffer script = new StringBuffer();

            // Append the jQuery script to find all lov-icons and set their tabIndex to -1

            script.append("var lovIcons = $('a[id$=\"lovIconId\"]'); $(lovIcons).attr('tabIndex', -1);");
            erks.addScript(fctx, script.toString());
        }

    }

    @Override
    public PhaseId getPhaseId() {
        return PhaseId.RENDER_RESPONSE;
    }

}


The last step is to define the PhaseListener in the faces-config.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee">
  <lifecycle>
    <phase-listener>view.beans.DemoPhaseListener</phase-listener>
  </lifecycle>
  <application>
    <default-render-kit-id>oracle.adf.rich</default-render-kit-id>
  </application>
</faces-config>


So now, if you run your application, all LOV-buttons are skipped! Soon I will write some more about he possibilities of jQuery in combination with ADF clientside scripts.

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.


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  
   event.cancel()  
 }  
   

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"/>  
     </af:inputListOfValues>  
   

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.