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:
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
Hi Diana, in my opinion i considerer your suggestion as a partial solution of a Business needs; what we’re solved here it’s just what the customer see! in the window-screen, this solution not cover the overall rows fetched!
Hi,
I have a similar requirement expect that i use treeTable instead of table.
Can you help me in getting the selected row (based on the selectBooleanCheckBox selection) in Javascript and select/deselect the descendants along with it?
I couldn’t get the row selected using the Javascript and traverese in order to make the tree selection.
Hi,
I am using Jdev Studio Edition Version 12.1.2.0.0. I have table select all functionality as per your suggestion in this blog
In my jsf page i have a table with scroll policy as page and range size is 25.
nbRows returns 25 and using above code i am able to select all 25 rows in current page of the table.
But when user navigates to other pages in af table and then comes back to the page where he made selection, those selections are lost. Is there way hat i can handle the page number of af table in javascript.
Please help me in solving this pagination issue.
Awesome post.Very well explained. I had similar requirement. Your blog really helped.
Ah smart, thanks for the addition
When new rows are fetched (when the user scrolls down for example), the changed value of the header checkbox is included in the post. So the select all event can be intercepted on the server to set the selected flag on non-fetched rows as well.
Ah smart! Thanks for the addition!