April 4, 2008

Dynamic JSF tables

Here, I like to present a small open source library allowing to dynamically create JSF tables with Java code instead of using JSP tags.
JSP pages tend to get clumsy and not very easy to get at first sight, so we benefit from smaller files, more dynamic views as well as from the decouplement of pages and underlying bean properties.

Features

The required table structure (shown columns) can be put together by manual coding or automatically from processing properties of a given Java bean.
Column labels can be retrieved from assigned resource files.
The concrete component classes used are externally configured, for example with Spring, thus leaving the table factory independent of various JSF libraries and making it possible to switch the UI without complex engagement.
Numeric columns get right aligned by default.

At present there are the following column types:
  • Standard-Text
  • Link
  • Checkbox
  • Drop-Down (<select>-Tag)
  • Image

If these column types are not sufficient, additional individually built columns can be integrated as well.

Packages:
  • de.voodoosoft.jroots.ui.jsf.factory.table
  • de.voodoosoft.jroots.ui.jsf.factory.table.impl
Dependencies:
  • de.voodoosoft.jroots.ui.jsf.core
  • de.voodoosoft.jroots.core.annotations

Example
jroots-example1
(configured with Spring's dependency injection):

What is left inside the JSP is the raw table without columns but with bindings for both the table component and the presented data.
<h:dataTable id="customerList"
   binding="#{customerController.dataTable}"
   value="#{customerController.beanData}"
   var="rowBean">
</h:dataTable>

In order to resolve Spring beans placed in JSP pages, the following configuration is required:

web.xml:
<listener>
   <listener-class>
      com.sun.faces.config.ConfigureListener
   </listener-class>
</listener>

<listener>
   <listener-class>
      org.springframework.web.context.ContextLoaderListener
   </listener-class>
</listener>

faces-config.xml:
<variable-resolver>
   org.springframework.web.jsf.DelegatingVariableResolver
</variable-resolver>

The UI-controller is defined inside Spring'S application context:
<bean id="customerController"
   class="de.voodoosoft.jroots.examples.example2.ui.jsf.customer.CustomerController"
   scope="request">
   <property name="tableFactory" ref="tableFactory"/>
</bean>

Dependency injection is used to assign the used TableFactory to the controller:
public class CustomerController {
    public void setTableFactory(TableFactory factory) {
        this.factory = factory;
    }

    private TableFactory factory;

When JSF asks for the table component, it gets built by the TableFactory:
public class CustomerController {
   public UIData getDataTable() {
      if (dataTable == null) {
         TableDefinition tableDef = factory.buildDefinition(CustomerBean.class);
         dataTable = factory.buildTable(tableDef);
      }

      return dataTable;
   }

The table structure follows the properties of the given bean, in this example name and address:
public class CustomerBean {
   public CustomerBean(String name, String address) {
      this.name = name;
      this.address = address;
   }

   private String name, address;

The second example jroots-example2 demonstrates the usage of RichFaces components instead of the reference implementation. All that has to be done is to modify the application context: properties of tableFactory and columnBuilder get component classes of RichFaces:
<bean id="tableFactory"
   class="de.voodoosoft.jroots.ui.jsf.factory.table.impl.DefaultTableFactory">
   <property name="resourceTag" value="res"/>
   <property name="rowBeanTag" value="rowBean"/>
   <property name="uiDataClass" value="org.richfaces.component.html.HtmlDataTable"/>
   <property name="columnBuilder">
      <map>
         <entry key="de.voodoosoft.jroots.ui.jsf.factory.table.ColumnDefinition" value-ref="defaultColumnBuilder"/>
      </map>
   </property>
</bean>


Finally, the third example jroots-example3 shows generation of link and drop-down columns. There are annotations for both column types. However, there is no need to use those, you can define any column by manual coding as well:
public class Guitar {

   @Link
   private String name;

   @DropDown(targetEntity = Wood.class)
   private Long bodyWood;

   @DropDown(targetEntity = Wood.class)
   private Long neckWood;

Links can either be mapped to controller methods (formSubmit = true) or point to external targets. Link targets are set inside the controller:

Internal Link:
LinkColumnDefinition linkCol = tableDef.getColumn("name");
linkCol.setTarget("#{guitarController.onGuitarClicked}");

External Link:
linkCol = new LinkColumnDefinition("google");
linkCol.setOutput("google");
linkCol.setFormSubmit(false);
linkCol.setTarget("http://www.google.de/search?q=#{guitarController.dataTable.rowData.name}");
tableDef.addColumn(linkCol);
Here we have the characteristic that controller properties are evaluated and passed as URL parameters. To access the current row bean, getRowData() of UIData is invoked in this example.

Spring configuration:



Download:
All the involved jar files
  • jroots-core.jar
  • jroots-jsf-core.jar
  • jroots-jsf-factory.jar
and the presented examples are available for download: voodoosoft

No comments:

Post a Comment