
This time I had the demand to create dynamic JSF tables with icefaces. The disadvantage of the usual static way like the following is that you have barley access on the columns in the backing bean and much rendundant xhtml code for each table.
<ice:dataTable value="#{viewUsersBean.users}"
var="item"
rows="10">
<ice:column>
<f:facet name="header">
<ice:outputText value="#{msgs['user.firstName']}"/>
</f:facet>
<ice:outputText value="#{item.firstName}"/>
</ice:column>
<ice:column>
<f:facet name="header">
<ice:outputText value="#{msgs['user.lastName']}"/>
</f:facet>
<ice:outputText value="#{item.lastName}"/>
</ice:column>
</ice:dataTable>
A cool approach is to make a xhtml dataTable template and configure the columns in the spring context. We also need a backing table data model to access the attributes in a generic way (see TableModel#getCellValue).
viewUsers.xhtml:
<ice:form partialSubmit="true">
<ui:decorate template="/templates/dataTableTemplate.xhtml">
<ui:param name="tableModel" value="#{viewUsersBean.tableModel}"/>
<ui:param name="items" value="#{viewUsersBean.users}"/>
</ui:decorate>
</ice:form>
dataTableTemplate.xhtml:
<ice:dataTable value="#{items}"
var="item"
rows="10">
<ice:columns value="#{tableModel.columnModel}"
var="columnConfig">
<f:facet name="header">
<ice:panelGroup>
<ice:commandSortHeader columnName="#{columnConfig.field}" >
<ice:outputText value="#{columnConfig.label}"/>
</ice:commandSortHeader>
</ice:panelGroup>
</f:facet>
<ice:outputText value="#{tableModel.cellValue}"/>
</ice:columns>
</ice:dataTable>
web-context.xml:
<bean id="viewUsersBean" class="com.bamboit.webapp.controller.ViewUsersBean" scope="request">
<constructor-arg index="0" value="com.bamboit.model.User"/><constructor-arg index="1">
<list>
<bean class="com.bamboit.model.ColumnConfig">
<property name="field" value="firstName"/>
<property name="label" value="user.firstName"/>
</bean>
<bean class="com.bamboit.model.ColumnConfig">
<property name="field" value="lastName"/>
<property name="label" value="user.lastName"/>
</bean>
</list>
</constructor-arg>
</bean>
public class ViewUsersBean {
private Class entityClass;
private TableModel tableModel;
private UserManager userManager;
public ViewUsersBean(Class entityClass, List<ColumnConfig> columnConfig) {
this.entityClass = entityClass;
this.tableModel = new TableModel( columnConfig );
}
public void init() {
tableModel.setItems( userManager.getAll() );
}
public TableModel getTableModel() {
return tableModel;
}
public void setUserManager( UserManager userManager ) {
this.userManager = userManager;
}
}
public class TableModel<t>() {
private List< T > items;
private int rowIndex;
private List<ColumnConfig> columnConfig;
private DataModel columnModel;
public TableModel(List<ColumnConfig> columnConfig) {
this.columnConfig = columnConfig;
this.columnModel = new ListDataModel( columnConfig );
}
public DataModel getColumnModel() {
return dataModel;
}
public void setItems( List<t> items ) {
this.items = items;
}
public Object getCellValue ()
{
try
{
if ( isRowAvailable() && columnModel.isRowAvailable() )
{
final int col = columnModel.getRowIndex();
final Object rowData = getRowData();
try
{
return BeanUtils.getProperty( rowData, columnConfigs.get(
col ).getField() );
}
catch ( final NestedNullException e )
{
// in this case we have a nested property and a nested reference returns null
// for us this mean the cellvalue is null too
return null;
}
}
}
catch ( final IllegalAccessException e )
{
log.error("Failed to lookup cell value, possibly invalid column field configured",
e );
}
catch ( final InvocationTargetException e )
{
log.error("Failed to lookup cell value, possibly invalid column field configured",
e );
}
catch ( final NoSuchMethodException e )
{
log.error("Failed to lookup cell value, possibly invalid column field configured",
e );
}
return null;
}
public boolean isRowAvailable ()
{
return items != null &&
(!(rowIndex > items.size()) || !(rowIndex < 0));
}
public int getRowCount ()
{
return items.size();
}
public Object getRowData ()
{
return items.get( rowIndex );
}
public int getRowIndex ()
{
return rowIndex;
}
public void setRowIndex ( int rowIndex )
{
this.rowIndex = rowIndex;
}
public Object getWrappedData ()
{
return null;
}
public void setWrappedData ( Object o )
{
}
}
public class ColumnConfig
{
private String label;
private String field;
...
}
This is a basis to add more features like:
- attribute type specific output rendering
- row selection
- table inline data editing
- data pagination
- role based column rendering
- user customized column order/visibility
I'll describe this features in my next blogs.

5 Kommentare: