ADF Faces – Client Side Column Hide and Seek – Expanding/Collapsing Columns in Table

Tables are probably the most important ADF Faces component to present data to the end user. A table can present a huge amount of data at once, both in terms of rows and columns. This of course can be overwhelming. Frequently, only some of the columns – some of the fields in the records – are really essential at all times. Other elements – columns – are only required in special situations or in groups at a time. 

When designing the page, there are several approaches to such requirements. One is the Detail Disclosure, where the table columns represent only those key data elements and every row in the table can be disclosed (expanded) to show in line in the table the other data elements. Others include the table overflow right or below – where for the currently selected table row the data elements not shown in the columns are presented in a single record block to the side or bottom of the table. Yet another approach is a popup element that appears for the current table row and presents the additional fields not included in the table as columns. All these approaches share a drive to reduce the complexity of the UI without sacrificing the user’s ability to view all data relevant for a specific record in the table. They also (except for the detail disclosure) have in common that the additional fields are shown for only one record at a time. None of them make it easy or even possible to sort on the fields that are not part of the key set of columns.

In this article, we will discuss yet another way for ADF Tables of making specific information available when the user so desires – this time for all records at once. It is the client side Column Disclosure, a JavaScript based way of expanding and collapsing columns or groups of columns.

For example the following page contains a table that shows Employees and their Allocations on a project. For each employee, we only see a few details (id, first name and last name). There is much more to know about employees – email address, phone number, job title, salary, hiredate – however that information is not frequently required. When it is required, we typically want to compare the values (at least of Job, Hiredate and Salary) of the employees – so we should see them all at once.
 

ADF Faces - Client Side Column Hide and Seek - Expanding/Collapsing Columns in Table

Press the Expand icon in the Employees header and the Employee Details are expanded – client side, no server roundtrip, really fast (say instantaneously):....

ADF Faces - Client Side Column Hide and Seek - Expanding/Collapsing Columns in Table 

In the rest of the article, we will discuss how we can implement this functionality. Another article will discuss a server side solution (using Partial Page Rendering for a semi-instantaneous effect, without the somewhat ugly JavaScript code).

First of all, the purely visual side of things: by using nested af:column elements (as described in the article ADF Faces – Nested Columns), it was simple to create the Employee and Employee Details column clusters. I have added the collapse and expand icons to the Employee Column header:

  <af:column>
<f:facet name="header">
<af:panelGroup>
<af:outputText value="Employee"/>
<af:objectImage id="expandEmployeeDetails"
source="/jheadstart/images/expandCurrent.gif"
onclick="collapseOrExpandEmpDetails(this,true);"/>
<af:objectImage id="collapseEmployeeDetails"
source="/jheadstart/images/collapseCurrent.gif"
onclick="collapseOrExpandEmpDetails(this,false);"/>

</af:panelGroup>
</f:facet>
<!-- DEBUG:BEGIN:TABLE_LOV_ITEM : default/item/table/tableLovItem.vm, nesting level: 6 -->
<af:column id="AllocationsEmpIdColumn" ...>
<f:facet name="header">
<af:outputLabel value="EmpId"/>
</f:facet>
...
</af:column>
<af:column id="AllocationsFirstNameColumn" ..>
<f:facet name="header">
<af:outputLabel value="FirstName"/>
</f:facet>
...
</af:column>
<af:column id="AllocationsLastNameColumn" ... >
<f:facet name="header">
<af:outputLabel value="LastName" .../>
</f:facet>
...
</af:column>
<af:column id="EmpDetails" ...>
<f:facet name="header">
<af:outputLabel value="Employee Details" .. />
</f:facet>
<af:column id="AllocationsEmailColumn"
...

 

The Collapse Icon is initially not visible. When the Expand icon is clicked on by the user, the collapseOrExpandEmpDetails() function is invoked, passing the icon itself, along with the value true for the expand parameter. The function is implemented as follows:

  function collapseOrExpandEmpDetails(icon, expand) {
tbl = findParentWithTag(icon, "TABLE");
trs = tbl.getElementsByTagName("TBODY")[0].getElementsByTagName("TR");
// in the 1st tr, change COLSPAN of 3rd TH (Employee) from 3 to 9(if expand, we are about to show 6 columns)
// in the 1st tr, change COLSPAN of 3rd TH (Employee) from 9 to 3(if !expand, we are about to hide 6 columns)
trs[0].getElementsByTagName("TH")[2].setAttribute("colspan",expand?9:3);
// in the 2nd TR, show/hide TH number 3 (Employee Details)
showHideEl(trs[1].getElementsByTagName("TH")[3],expand);
// in trs[2], show/hide all th children (the headers of those six columns to be hidden)
tds = trs[2].childNodes;
for( var j = 0; j &lt; tds.length; j++ ) {
showHideEl(tds[j], expand);
} // for tds in tr

// from the 3rd TR onwards, loop over the TDs and THs; numbers 6 through 11 should be shown/hidden
for( var i = 3; i &lt; trs.length; i++ ) {
tds = trs[i].childNodes;
for( var j = 5; j &lt; 11; j++ ) {
showHideEl(tds[j], expand);
} // for tds in tr
}//for trs in table

// hide icon that was clicked
showHideEl(icon, false);
// show the other icon
if (expand)
showHideEl(document.getElementById(icon.id.replace('expand','collapse')), true);
else
showHideEl(document.getElementById(icon.id.replace('collapse','expand')), true);

}//collapseOrExpandEmpDetails()
 

The function has way too much knowledge about the Employee Details column groups to my taste. A more generic implementation would probably only need to know the icon and the TD identifying the column cluster (Employee Details); from its COLSPAN (6 in this case) and its ‘rownumber’ – the third row in this case – it should be able to derive the rest. I will leave that up to the reader…

Another interesting exercise would be to include a second column cluster in the table, for example for allocation details, and make that one collapsible/expandable as well.

It leverages two simple utility functions:

  function findParentWithTag(el, tagName) {
var parent = el.parentNode;< br /> if (parent)
if (parent.tagName ==tagName)
return parent;
else
return findParentWithTag(parent, tagName);
else
return null;
}//findParentWithTag

function showHideEl(el, show) {
el.setAttribute('style', show?'':'visibility:collapse;display:none;'); // to cater for both IE and Firefox
}

The function showHideEl is quite crude – it assumes sole possession of the  style attribute, which is quite selfish and not realistic. However, for the purpose of this article, it does the trick.

In order to start the page with the Employee Details collapsed, I added an onLoad trigger to the BODY element:

<afh:body id="body" onload="collapseOrExpandEmpDetails(document.getElementById('AllocationsTable:collapseEmployeeDetails'),false);">

The reference by client id here is a bit of a lazy quick implementation. I am sure you can do better… 

Note: at first I tried manipulating COLGROUP and/or COL elements. However, ADF Faces does not render nested columns (or any table column) using these elements. Dynamically adding COLGROUP elements to the DOM model succeeded; however, manipulation of the style properties of such dynamically added elements was not picked up by the browser (Firefox 2.0), so I gave up on this route.

// some of the experimental COL code
var col = document.createElement('COL');
col.setAttribute('width', '210');
col.setAttribute('style', 'visibility:collapse;display:none;');
tbl.insertBefore(col,tbl.firstChild);
col = document.createElement('COL');
col.setAttribute('width', '110');
col.setAttribute('style', 'font-weight:bolder;');
tbl.insertBefore(col,tbl.firstChild);

The code  I ended up with does the job. However, it can still be made more generic, for example to make it support multiple collapsible/expandable column clusters in a single table.

Note: this implementation has a disagreement with partial page rendering: as soon as the table is resorted or otherwise refreshed using PPR, it is always shown expanded.

2 Comments

  1. swapna February 18, 2009
  2. Adilson September 26, 2008