Integrating Jmesa with Stripes

I received a requirement were the user would like to do some sorting, exporting and filtering on a table we display on several screens in our application.

We use Stripes Framework for our web layer because of its simplicity. I know people might echo STRIPES! What is stripes? At the time I had the privilege of moving from Struts 1.2.9 to a less stressful Java web framework(about late 2008), Stripes was the only Java web framework that gave me a Rails like feel with its convention over configuration principles.

DisplayTag is probably the most popular table rendering API in the Java world, but it did not look like it was going to fulfill my requirement.

Remember my users would like to dynamically filter the result we send to them. The result they would be viewing would be a list of the recent transactions they have performed in their facilities.

For example, they might want to see all transactions with a type of "C/IN" or "C/OUT" and the likes. They might also want to filter by the day the transaction occurred or by the name of the individual the transaction was performed for.

DisplayTag was good for two out of three things I needed to solve. Sorting and exporting was covered, but filtering was not. Or may be I am unaware on DisplayTag's filtering capabilities.

Another thing I noticed that DisplayTag lacked was that when the user decides to export the table into a CSV or Excel file, the user is only allowed to export the data on the screen and not the entire result passed to the name attribute on the display:table tag.

This was a big minus for me. Jmesa is another table rendering API in Java. Jmesa had all three functionality I needed so I chose to integrate it into our application. Jmesa would also export all the data passed to the TableModel.setItems(items) method and not just the data rendered on the jsp page.

The libraries I needed to add to our application were:

  •  commons-io-1.3.jar
  • jexcel-2.6.6.jar 
  • jmesa-3.0.jar
  • poi-3.0-FINAL.jar 
  • slf4j-api-1.4.3.jar
  • slf4j-log4j12-1.4.3.jar

Take a look at the jars required is the setup page because you might be missing some of the required jars. If you look at the Jmesa's basic tutorial, it will show you how your project should be structured. You should also take a look at the preference page if you want to change the default project structure.

I would be using the president example on the Jmesa example page to show the integration example since I cannot really show too much of the code I wrote for our application. Lets convert the BasicPresidentController to a Stripes ActionBean.

 

@UrlBinding("/presidents")
public class BasicPresidentActionBean extends ActionBean {

    @SpringBean protected PresidentService presidentService;
    private static final String VIEW = "/WEB-INF/jsp/president/list.jsp";
    private String table;

    @DefaultHandler
    public Resoluction president()
      {
        
        TableModel tableModel = new TableModel("presidents", request, response);
        tableModel.setItems(presidentService.getPresidents());
        tableModel.addFilterMatcher(new MatcherKey(Date.class, "born"), new DateFilterMatcher("MM/yyyy"));
        tableModel.setExportTypes(CSV, JEXCEL, PDF);
        tableModel.setStateAttr("restore");

        if (tableModel.isExporting()) {
            tableModel.setTable(getExportTable());
        } else {
            tableModel.setTable(getHtmlTable());
        }

        this.table = tableModel.render();
        if (tableModel.isExporting()) return null;
        return new ForwardResolution(VIEW);
    }

    private Table getExportTable() {
        Table table = new Table().caption("Presidents");

        Row row = new Row();
        table.setRow(row);

        Column firstName = new Column("name.firstName").title("First Name");
        row.addColumn(firstName);

        Column lastName = new Column("name.lastName").title("Last Name");
        row.addColumn(lastName);

        Column career = new Column("career");
        row.addColumn(career);

        Column born = new Column("born").cellEditor(new DateCellEditor("MM/yyyy"));
        row.addColumn(born);

        return table;
    }

    private Table getHtmlTable() {
        HtmlTable htmlTable = new HtmlTable().caption("Presidents").width("600px");

        HtmlRow htmlRow = new HtmlRow();
        htmlTable.setRow(htmlRow);

        HtmlColumn firstName = new HtmlColumn("name.firstName").title("First Name");
        firstName.setCellEditor(new CellEditor() {
            public Object getValue(Object item, String property, int rowcount) {
                Object value = new HtmlCellEditor().getValue(item, property, rowcount);
                HtmlBuilder html = new HtmlBuilder();
                html.a().href().quote().append("http://www.whitehouse.gov/history/presidents/").quote().close();
                html.append(value);
                html.aEnd();
                return html.toString();
            }
        });
        htmlRow.addColumn(firstName);

        HtmlColumn lastName = new HtmlColumn("name.lastName").title("Last Name");
        htmlRow.addColumn(lastName);

        HtmlColumn career = new HtmlColumn("career").filterEditor(new DroplistFilterEditor());
        htmlRow.addColumn(career);

        HtmlColumn born = new HtmlColumn("born").cellEditor(new DateCellEditor("MM/yyyy"));
        htmlRow.addColumn(born);

        return htmlTable;
    }

    public void setPresidentService(PresidentService presidentService) {
        this.presidentService = presidentService;
    }

    public String getTable(){ return this.table;}


}

 

And the basic jsp file.

<html>
    
    <head>
        <title>Basic JMesa Example</title>
    </head>
    
 <body>
<form name="presidentsForm" action="${pageContext.request.contextPath}/presidents">
        ${actionBean.table}
</form>
        
  <script type="text/javascript">
            function onInvokeAction(id) {
                $.jmesa.setExportToLimit(id, '');
                $.jmesa.createHiddenInputFieldsForLimitAndSubmit(id);
            }
            function onInvokeExportAction(id) {
                var parameterString = $.jmesa.createParameterStringForLimit(id);
                location.href = '${pageContext.request.contextPath}/presidents?' + parameterString;
            }
    </script>      
  </body>
</html>

 

This is basically how you would integrate Jmesa into your Stripes actionBeans. I do not like my actionBean to be this big. I try to keep it as slim as possible. So, I normally just extend the TableModel class Jmesa provides and encapsulate the getHtmlTable() and getExportTable() methods.

public class PresidentTableModel extends TableModel{
  
public PresidentTableModel(String id, HttpServletRequest request, HttpServletResponse response){
  super(id, request,response);
}
public Table createTable(){

   	if (isExporting()) 
       return getExportTable();
    else 
       return getHtmlTable();
  
}

private Table getExportTable() {
       Table table = new Table().caption("Presidents");

       Row row = new Row();
       table.setRow(row);

       Column firstName = new Column("name.firstName").title("First Name");
       row.addColumn(firstName);

       Column lastName = new Column("name.lastName").title("Last Name");
       row.addColumn(lastName);

       Column career = new Column("career");
       row.addColumn(career);

       Column born = new Column("born").cellEditor(new DateCellEditor("MM/yyyy"));
       row.addColumn(born);

       return table;
   }

   private Table getHtmlTable() {
       HtmlTable htmlTable = new HtmlTable().caption("Presidents").width("600px");

       HtmlRow htmlRow = new HtmlRow();
       htmlTable.setRow(htmlRow);

       HtmlColumn firstName = new HtmlColumn("name.firstName").title("First Name");
       firstName.setCellEditor(new CellEditor() {
           public Object getValue(Object item, String property, int rowcount) {
               Object value = new HtmlCellEditor().getValue(item, property, rowcount);
               HtmlBuilder html = new HtmlBuilder();
               html.a().href().quote().append("http://www.whitehouse.gov/history/presidents/").quote().close();
               html.append(value);
               html.aEnd();
               return html.toString();
           }
       });
       htmlRow.addColumn(firstName);

       HtmlColumn lastName = new HtmlColumn("name.lastName").title("Last Name");
       htmlRow.addColumn(lastName);

       HtmlColumn career = new HtmlColumn("career").filterEditor(new DroplistFilterEditor());
       htmlRow.addColumn(career);

       HtmlColumn born = new HtmlColumn("born").cellEditor(new DateCellEditor("MM/yyyy"));
       htmlRow.addColumn(born);

       return htmlTable;
   }
}

Jmesa also provides a tag library like DisplayTag, that you can use if you prefer to use tag libraries. I hope this can help someone get comfortable integrating stripes with jmesa.