Tuesday, July 21, 2009

How to include rowspan information with h:dataTable

There is a well known limitation in the h:dataTable component of JSF's RI 1.2 (Mojarra) that you cannot set the rowspan attribute in any way. Recently I needed such a feature and here is what I did...

Step one: First you need to make the TableRenderer (com.sun.faces.renderkit.html_basic) work with the rowspan attribute. To do this, you need to create a new renderer that extends TableRenderer and overrides the renderRow() method. Next you copy the default implementation and after the line:

writer.startElement("td", column);

You insert :

writer.writeAttribute("rowspan", rowspan, null);

Voila, you have inserted your rowspan value for the current cell. Of course it is not yet evaluated so you need to think of the best way to calculate the rowspan in your case. Before that have a look at the final code (some other fixes included) I came up with for renderRow():

TableMetaInfo info = getMetaInfo(context, table);
info.newRow();
int columnIndex = 0;
for (UIColumn column : info.columns) {
String columnClass = info.getCurrentColumnClass();
int rowspan = ((UIMyData) table).getRowspan(columnIndex++, info.columns);
if (rowspan > 0) {
// Render the beginning of this cell
writer.startElement("td", column);
writer.writeAttribute("rowspan", rowspan, null);
if (columnClass != null) {

Step two: To evaluate the rowspan in my case, I extended UIData (javax.faces.component) and provided a method called getRowspan(int column, List columns). Since you're in the UIData you have access to the DataModel and RowIndex/RowCount. My decision was to prepare a matrix of rowspans that will reside in the DataModel so the implementation of my UIData looked as follows:

public int getRowspan(int column, List columns) {
return getDataModel().getRowspan(column, columns);
}

@Override
protected MyListDataModel getDataModel() {
MyListDataModel result;

DataModel dataModel = super.getDataModel();
if (dataModel instanceof MyListDataModel) {
result = (MyListDataModel) dataModel;
} else {
result = new MyListDataModel((List) dataModel.getWrappedData());
setDataModel(result);
}

return result;
}

As you can see MyListDataModel extends ListDataModel and what it does is initialize and provide access to my rowspans matrix:

private MutableInt[][] rowspans;

The getRowspan() methods is declared as follows:

public int getRowspan(int column, List columns) {
if (getRowIndex() < 0) {
throw new IllegalArgumentException("Row index should not be negative!");
}

if (rowspans == null) {
initRowspans(columns);
}

return rowspans[getRowIndex()][column].getValue();
}

Step three: Find your way of implementing the rowspans matrix. In my case I used a f:attribute attached to each h:column to be able to group the columns and then compare the value of the each item for that group.

Step four: Declare the renderer and the component and use them.

Final thoughts: I think this post could be a good example of how to start your rowspanning dataTable. I hope that it helps you and don't hesitate to discuss the issue in the comments box.

No comments:

Post a Comment