Control-Break Style ADF Table - Comparing Values with Previous Row
- by Steven Davelaar
Sometimes you need to display data in an ADF Faces table in a control-break layout style, where rows should be "indented" when the break column has the same value as in the previous row. In the screen shot below, you see how the table breaks on both the RegionId column as well as the CountryId column.
To implement this I didn't use fancy SQL statements. The table is based on a straightforward Locations ViewObject that is based on the Locations entity object and the Countries reference entity object, and the join query was automatically created by adding the reference EO. To get the indentation in the ADF Faces table, we simple use two rendered properties on the RegionId and CountryId outputText items:
<af:column sortProperty="RegionId" sortable="false"
headerText="#{bindings.LocationsView1.hints.RegionId.label}"
id="c5">
<af:outputText value="#{row.RegionId}" id="ot2"
rendered="#{!CompareWithPreviousRowBean['RegionId']}">
<af:convertNumber groupingUsed="false"
pattern="#{bindings.LocationsView1.hints.RegionId.format}"/>
</af:outputText>
</af:column>
<af:column sortProperty="CountryId" sortable="false"
headerText="#{bindings.LocationsView1.hints.CountryId.label}"
id="c1">
<af:outputText value="#{row.CountryId}" id="ot5"
rendered="#{!CompareWithPreviousRowBean['CountryId']}"/>
</af:column>
The CompareWithPreviousRowBean managed bean is defined in request scope and is a generic bean that can be used for all the tables in your application that needs this layout style. As you can see the bean is a Map-style bean where we pass in the name of the attribute that should be compared with the previous row. The get method in the bean that is called returns boolean false when the attribute has the same value in the same row. Here is the code of the get method:
public Object get(Object key)
{
String attrName = (String) key;
boolean isSame = false;
// get the currently processed row, using row expression #{row}
JUCtrlHierNodeBinding row = (JUCtrlHierNodeBinding) resolveExpression(getRowExpression());
JUCtrlHierBinding tableBinding = row.getHierBinding();
int rowRangeIndex = row.getViewObject().getRangeIndexOf(row.getRow());
Object currentAttrValue = row.getRow().getAttribute(attrName);
if (rowRangeIndex > 0)
{
Object previousAttrValue = tableBinding.getAttributeFromRow(rowRangeIndex - 1, attrName);
isSame = currentAttrValue != null && currentAttrValue.equals(previousAttrValue);
}
else if (tableBinding.getRangeStart() > 0)
{
// previous row is in previous range, we create separate rowset iterator,
// so we can change the range start without messing up the table rendering which uses
// the default rowset iterator
int absoluteIndexPreviousRow = tableBinding.getRangeStart() - 1;
RowSetIterator rsi = null;
try
{
rsi = tableBinding.getViewObject().getRowSet().createRowSetIterator(null);
rsi.setRangeStart(absoluteIndexPreviousRow);
Row previousRow = rsi.getRowAtRangeIndex(0);
Object previousAttrValue = previousRow.getAttribute(attrName);
isSame = currentAttrValue != null && currentAttrValue.equals(previousAttrValue);
}
finally
{
rsi.closeRowSetIterator();
}
}
return isSame;
}
The row expression defaults to #{row} but this can be changed through the rowExpression managed property of the bean.
You can download the sample application here.