ADF client-side architecture - Select All screen00036

ADF client-side architecture – Select All

A little while ago I got a very nice challenge: play around with the ADF client-side framework.

The customer had a table where users could multi-select via an extra column with checkboxes. Header of that column also contained a checkbox to select or deselect everything at once. For example:
screen00036

It was implemented using autoSubmit and partialTriggers. Due to the roundtrip to the server the response time was low when having a lot of rows. To speed it up, I was asked to look if (de)selecting all rows could be done on the client.

Never having worked with that part of ADF yet, I started searching and quickly found the Oracle documentation, but actual examples to clarify some topics.. hmmm not so much.
The use case is quite specific but I thought it would still be nice to blog about it to be at least an example of some of the client-side functionality for other people in need.

The column

Let’s start with the column:

<af:table var="row" id="tab" ..>
  <af:column id="checkboxCol" width="25" align="center" displayIndex="0">
    <f:facet name="header">
	<af:selectBooleanCheckbox id="checkboxHeader" value="#{bindings.selectEmployeeValue.inputValue}">
	  <af:clientListener method="selectRows" type="click"/>
	  <af:clientAttribute name="tableLocator" value="::tab"/>
	  <af:clientAttribute name="checkboxLocator" value=":checkbox"/>
	</af:selectBooleanCheckbox>
    </f:facet>
    <af:selectBooleanCheckbox id="checkbox" value="#{row.selected}" clientComponent="true"/>
  </af:column>
  .
  .
</af:table>

When the user clicks on the checkbox in the header some javascript code will have to loop through the rows to change the checkboxes of each row.

For this I add a client listener to the header checkbox which will trigger on a click and calls some javascript method which in my case I called “selectRows”.
To make the javascript method generic I add the table and checkbox identifiers as attributes of the checkbox.
As identifiers I use the locators of the table and row checkboxes relative to the header checkbox; when we look at the javascript it will become clear why.
Last but not least, all row checkboxes get a clientComponent=”true” to make sure the client framework can access it.

The ADF client-side architecture wraps around the DOM model and can create javascript objects for faces components.
Javascript objects are created in multiple cases, among others when a component has a clientListener or the property clientComponent=”true”.
When looking for API information you can easily guess which API you need to search for. When you have an exposed RichSelectBooleanCheckbox component its javascript equivalent will be an AdfRichSelectBooleanCheckbox.

The JavaScript

Defining the client listener is comparable to other server listeners like an action listener.
When you add i.e. an action listener you will need an listener method with an ActionEvent parameter.
A client listener needs a javascript method with an event parameter as well:

  function selectRows(event) { 
  
    /* TODO 
       retrieve the header checkbox from the event    
       retrieve attributes
	   find the table
       for each row, find the checkbox to set value of
	   while(..){
		  ..
	   }
	*/
  }

The clientListener is added to the header checkbox making this checkbox the source of the event.
Via the event we can retrieve this AdfRichSelectBooleanCheckbox:

  function selectRows(event) {
  
    var checkbox=event.getSource(); 
	
    /* TODO   
       retrieve attributes
	   find the table
       for each row, find the checkbox to set value of
	   while(..){
		  ..
	   }
	*/
  }

Once we have our checkbox object we can retrieve its attributes:

  function selectRows(event) {
  
    var checkbox=event.getSource(); 
	
    var checkboxLocator=checkbox.getProperty("checkboxLocator");
    var tableLocator=checkbox.getProperty("tableLocator");
	
    /* TODO   
	   find the table
       for each row, find the checkbox to set value of
	   while(..){
		  ..
	   }
	*/
  }

The similarities in the client-side architecture are again convenient. The javascript objects have a findComponent method to search from a relative path. The root has a findComponentByAbsoluteId method to search for an object starting from the root.

The tableListener attribute contains the relative path to the table, reasoning from position of the header checkbox. This way we can do a findComponent on the header checkbox to easily get the AdfRichTable object:

  function selectRows(event) {
  
    var checkbox=event.getSource(); 
	
    var checkboxLocator=checkbox.getProperty("checkboxLocator");
    var tableLocator=checkbox.getProperty("tableLocator");
	
    var table=checkbox.findComponent(tableLocator);
	
    /* TODO   
       for each row, find the checkbox to set value of
	   while(..){
		  ..
	   }
	*/
  }

So far so good, and nothing that can’t already be found when googling. But trying to reach the checkboxes of the rows was tricky.
To figure it out I started by looking at the HTML source of the ADF table with the checkbox of the first row:

<td style="width:25px" align="center" nowrap="" class="xxv">
  <span id="tab:0:checkbox" class="x1v">
    <span class="x2e">
      <span class="xgx">
         <input id="tab:0:checkbox::content" ...>
      </span>
    </span>
  </span>
</td>

The ID in the span made me do a couple of first attempts with the findComponent of the header checkbox like

checkbox.findComponent("::tab["+rowNumber+"]:checkbox");

Though this works the first time you open the page, it can break in certain situations.
Problem is that the first row not always gets 0 as a stamp etc. When you for instance have a master-form detail-table layout and navigate to the second master row, your detail row stamp continues to count from the last stamp of the first master row.
But also actions like fetching and refreshing changes the stamps.

After some more searching I found out that finding components by locator should solve the stamp issue.
This allows me to use [0] to address the first row and ADF will look up to which actual stamp this translates at that moment.
It resulted in some next attempts like

checkbox.findComponentByLocator("::tab["+rowNumber+"]:checkbox");

It turned out the table part of the expression was not correct yet.
Some more digging in the APIs to see how I could figure out the locator of the table made me stumble upon AdfUIComponent.getAbsoluteLocator() and a note in AdfUIComponent.findComponentByLocator that there was a method called AdfPage.findComponentByAbsoluteLocator().

Tying those last knots together finally gave a successful result:

  function selectRows(event) {
  
    var checkbox=event.getSource(); 
	
    var checkboxLocator=checkbox.getProperty("checkboxLocator");
    var tableLocator=checkbox.getProperty("tableLocator");
	
    var table=checkbox.findComponent(tableLocator);
    
    var nbRows=parseInt(table.getRows());
    var tableAbsLocator=table.getAbsoluteLocator();
    
    while(nbRows--){
      var rowCheckbox=AdfPage.PAGE.findComponentByAbsoluteLocator(tableAbsLocator+"["+nbRows+"]"+checkboxLocator);
      if(checkbox.getValue()!=rowCheckbox.getValue()){
        rowCheckbox.setValue(checkbox.getValue());
      }
    }
  }

I have created a quick little project for this.
I immediately admit it is quite ugly as I put the boolean for the checkboxes in my model, but it is just to show the client side selection.

Note

This solution only checks or unchecks all fetched rows. As non fetched rows are not in the HTML tree yet it is not possible to check them.
In my use case the query results were maxed to a certain amount which was also used as fetch size to solve this issue.

Resources

Oracle white paper – ADF Design Fundamentals Using JavaScript in ADF Faces Rich Client Application

Oracle Web User Interface Developer’s Guide (11.1.1.5) – H3 Using ADF Faces Architecture

Oracle Web User Interface Developer’s Guide (11.1.1.5) – H5 Handling Events

Oracle JavaScript API Reference (11.1.1) – http://docs.oracle.com/cd/E21043_01/apirefs.1111/e12046/overview-summary.html

Little example project – ClientSideMultiSelect.zip

7 Comments

  1. henry September 16, 2014
  2. Tamilselvi August 22, 2014
  3. Mozakkir February 11, 2014
  4. Mozakkir February 5, 2014
  5. Diana Veerman July 10, 2013