JSF Table Component using Guava Table

It’s a wonderful learning experience to create a custom component using JSF. I spent nearly 2 weeks to understand and create a nice little table component in JSF using Guava.

For those of you new to Google Guava, click here to read in detail on that.

To give a quick intro, Google Guava is a utility library which provides much sophisticated API’s for your java application. One such feature I was impressed with is “Table”. As the name suggests it is a Collection which supports holding data in a tabular format.

Why I used Guava table instead of using existing components like “dataTable” from PrimeFaces & RichFaces ????

1. Guava Table is pretty simple and efficient to use.

2. I can have more granular control in creating my table.

3. Implementation is quite easy and also it’s easy to understand the component code.

Ok, here we go to the code,

Step 1: Create a custom JSF Composite Component with following attributes.


<composite:attribute name="columnHeaders" type="java.lang.String[]" required="true"/>

<composite:attribute name="propertyNames" type="java.lang.String[]"/>

<composite:attribute name="rowIdentifier"/>

<composite:attribute name="dataList" type="java.util.List"/>

columnHeaders : Table Column headers, gets displayed AS-IS in the table.

propertyNames: Properties or Variable names in the object that you are about to pass inside a collection.

dataList: A collection of Objects which holds the data. Remember, the object inside this should have all properties listed in the above attribute “propertyNames”

rowIdentifier: A unique identifier for the row, If not provided I would provide a default running number as the rowId.

Step 2: JSF composite component bean

@FacesComponent("simpleTable")
public class SimpleTable extends UINamingContainer {

private static final String EMPTY = "empty";
 private static final String COLUMN_HEADERS = "columnHeaders";
 private static final String PROPERTY_NAMES = "propertyNames";
 private static final String ROW_IDENTIFIER="rowIdentifier";
 private static final String DATA_LIST = "dataList";

private Table<Object, String, Object> table;

 @Override
 public void encodeBegin(final FacesContext context) throws IOException {
 initializeTable();
 super.encodeBegin(context);
 }

public Object[] getRows() throws Exception {
 return getTable().rowKeySet().toArray();
 }

public Object getCellValue(final Object rowKey, final String columnKey) {
 return getTable().column(columnKey).get(rowKey);
 }

public String[] getColumns() {
 return getTable().columnKeySet().toArray(new String[0]);
 }

public boolean isTableEmpty() {
 return table == null || table.row(0).containsValue(EMPTY);
 }

public int getColumnSize() {
 final Map<string, map<object,="" object="">> columnSize = getTable().columnMap();
 return isShowRemoveLink() ? columnSize.size() : columnSize.size() + 1;
 }

private void initializeTable() {
createTable(getColumnHeaders(), getPropertyNames(), getDataList());
 }

private Table<object, string,="" object=""> getTable() {
 if (isTableEmpty()) {
 initializeTable();
 }
 return table;
 }

private void createTable(final String[] columnHeaders, final String[] propertyNames, final Collection<!--?--> dataCollection) {
 table = CollectionUtils.isEmpty(dataCollection) ? populateOnlyHeaders(columnHeaders)
 : populateTableContents(columnHeaders, propertyNames, dataCollection);
 }

private Table<object, string,="" object=""> populateTableContents(final String[] columnHeaders,
 final String[] propertyNames, final Collection<!--?--> dataCollection) {

 final ImmutableTable.Builder<Object, String, Object> table = new ImmutableTable.Builder<Object, String, Object>();
 int rowIndex = 0;
 final boolean hasCustomRowIdentifier = hasCustomRowIdentifier();

 for (final Object dataObject : dataCollection) {
 int columnIndex = 0;
 for (final String propertyName : propertyNames) {
 try {
 final Object rowId = hasCustomRowIdentifier?getRowId(dataObject):rowIndex;
 table.put(rowId, columnHeaders[columnIndex],getProperty(dataObject, propertyName));
 } catch (final Exception e) {
 throw new RuntimeException("Invalid data header " + propertyName + " passed as parameter ");
 }
 columnIndex++;
 }
 rowIndex++;
 }
 return table.build();
 }

private Object getRowId(final Object dataObject) throws IllegalAccessException,InvocationTargetException, NoSuchMethodException {
 return getProperty(dataObject, getRowIdentifier());
 }

private Table<object, string,="" object=""> populateOnlyHeaders(final String[] displayHeaders) {
 final ImmutableTable.Builder<Object, String, Object> table = new ImmutableTable.Builder<Object, String, Object>();
 for (final String displayHeader : displayHeaders) {
 table.put(0, displayHeader, EMPTY);
 }
 return table.build();
 }

private boolean hasCustomRowIdentifier(){

return getRowIdentifier() != null;
 }

 private String getRowIdentifier(){
 return (String) getAttributes().get(ROW_IDENTIFIER);
 }

 private String[] getColumnHeaders() {
 return (String[]) getAttributes().get(COLUMN_HEADERS);
 }

private String[] getPropertyNames() {
 return (String[]) getAttributes().get(PROPERTY_NAMES);
 }

private Collection<!--?--> getDataList() {
 return (Collection<!--?-->) getAttributes().get(DATA_LIST);
 }
}

 createTable() is where the actual table is being constructed. If No Content is given to the table , it just populates the headers only. If content is available it populates the complete table.

I preferred using “ImmutableTable” just because I don’t need any natural ordering by the collection. So I can add whatever is required and build a table out of it.

Step 3: Composite implementation of the Component

So we have defined the attributes, created a component backing bean and now it’s time for the Component Implementation.

<composite:implementation>

 <a4j:outputPanel id="simpletablebody" layout="block" styleClass="simple-table">
 <table>
 <thead>
 <tr>
 <ui:repeat var="column" value="#{cc.columns}">
 <th>
 <span>#{column}</span>
 </th>
 </ui:repeat>
</tr>
 </thead>
 <ui:fragment rendered="#{!cc.tableEmpty}">
 <tbody>
 rowKey" value="#{cc.rows}">
 <tr>
 columnKey" value="#{cc.columns}">
 <td>
 #{cc.getCellValue(rowKey,columnKey)}
 </td>
 </ui:repeat>

 </tr>
 </ui:repeat>
 </tbody>
 </ui:fragment>
 </table>
 <!--<span class="hiddenSpellError" pre=""-->a4j:outputPanel>

 </composite:implementation></em>

This implementation is just to render the table. I haven’t shown here the additional functionality like “Remove A Row” link, title and table empty message etc. It’s easy to implement if you get the table rendered.

Remember rowIdentifier value would be the key to remove the row. So implement a identifier and when you want to remove a row return back the rowId.

Table of parameters that I actually used,

 

Parameter Name Type Required? Default Value Description
columnHeaders java.lang.String[] Yes Actual table column headers is passed and it is displayed as-is in the given order. e.g: columnHeaders=”Host,Company”
propertyNames java.lang.String[] No Properties/Variable names in the input object. There should be a getter method in the object passed using “dataList” collection. e.g: propertyNames=”displayName,company”.
The object should have getDisplayName() and getCompany() methods in it.
NOTE: property names should follow the same order as “columnHeaders” parameter.
rowIdentifier String No It’s a property name too. Used to identify a particular row in the table. If NOT provided rowIndex would start from 0.
dataList java.util.List No A collection of objects which has getter methods for all propertyNames listed in the “propertyNames” parameter.
title String No Simple Table Default title is provided.
emptyMessage String No No records found Default Message is provided.
showTitle String No true Default to true and it always displays the title.
showRemoveLink String No false Default no “Remove” link is displayed
rowRemoveEventListener method-signature Yes (if showRemoveLink is true) This parameter is required, when you set “showRemoveLink” to TRUE. The listener should accept a parameter (rowId) and process the remove event.

 

 

Happy Coding !!!

,

Leave a Comment

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: