Thursday, February 25, 2010

DisplayTag: Producing Decorators for Lists of Maps

I have been using the Display tag library (hereafter referred to as displaytag) heavily on a big in-house web application project. Displaytag helped me make relatively short work of pagination and data export, the resulting pages look very professional to boot. Best of all using displaytag meant I was able to concentrate my efforts on other parts of the project.

Much of the time I use the displaytag in the typical way (e.g. to display collections of beans). However, the changeable nature of some of the reports required lead me to produce some of the screens using Lists of Maps. Lists of Maps are very easily acquired from a database (e.g. Spring's ColumnMapRowMapper). Once you configure displaytag to display lists of maps then you have the flexibility to make changes to the report contents purely by changing an SQL statement. Also using the SQL "AS" keywords means that user friendly table column names are easily supported. So by making use of lists of maps you can produce very flexible and easily extensible reporting screens.

So far, so good...

Formatting data and adding extra display columns

By default whether you are passing a collection of beans or a list of maps to displaytag it just works! e.g.


<display:table name="dummyData"></display:table>

Elsewhere where I have used the typical way (collections of beans) of supplying data to displaytag. Sometimes I need to reformat a date, currency column or change some columns into links. You can do most of this inside the JSP page (messy!) or alternatively you can make use of the displaytag TableDecorator. You can also use TableDecorator's to add new columns that don't exist in the data, however, once you start specifying custom generated columns then you need to explicitly specify all the columns you would like to see in the displaytag table, e.g.:


<display:table name="dummyData" decorator="edu.bristol.SampleDecorator">
<display:column property="id" />
<display:column property="date" />
<display:column property="url" />
<display:column property="columnPopulatedByTableDecorator" title="Decorator generated"/>
</display:table>

I wanted to do this with a list of maps. After a little research on the forums, I found that you could still make use of TableDecorator generated custom columns by explicitly specifying the columns in your map. As the data originates from the database, each list row will contain a map with the same set of map keys. First you retrieve the map keyset from the first result row, store this keyset in the request and then you can explicitly iterate through the map columns:


<display:table name="dummyData" decorator="edu.bristol.SampleDecorator">
<c:forEach items="${keyset}" var="key">
<display:column property="${key}" />
</c:forEach>
<display:column property="columnPopulatedByTableDecorator" title="Decorator generated"/>
</display:table>

At this point, the "columnPopulatedByTableDecorator" will be generated but the TableDecorator doesn't do any re-formatting of the existing data columns.

Producing Decorators for Lists of Maps

So now we come to the novel aspect of this blog entry! The next question is how I reformat the data in my list of maps for displaytag purposes. I searched through the forums and couldn't get an answer for this, so I came up with a solution. Internally, displaytag uses Commons BeanUtils for data manipulation. We have seen that it can handle lists of maps without any additional configuration - so it must be doing something clever! The TableDecorator test code for displaytag includes tests for accessing mapped data, so I knew that it should be possible to access the mapped data in my own TableDecorator. Part of the difficulty in accessing the data is that my key names were not easily transformed into Java method names, the keys contain spaces, capitalization and other characters that are not permitted in a method name. My solution was to alter my list of maps to become a list of "wrapped maps". Where a wrapped map looks something like:


public class WrappedMap {

Map m;

public WrappedMap(Map m) {
this.m = m;
}

public Object getMap(String propertyName) {
return m.get(propertyName);
}
}

My decorator for this list of wrapped maps could now access my oddly named keys, such as:


public class WrappedMapDecorator extends TableDecorator{

final private String startDate = "Start Date";
final private String websiteAddress = "Website Address";

public String getMap(String propertyName){
WrappedMap wm = (WrappedMap) getCurrentRowObject();
Object propertyValue = wm.getMap(propertyName);
if(startDate.equals(propertyName)){
// Implement appropriate formatting/decoration
return "<i>" + propertyValue.toString() + "</i>";
}
if(websiteAddress.equals(propertyName)){
// Implement appropriate formatting/decoration
return "<b>" + propertyValue.toString() + "</b>";
}
return propertyValue.toString();
}

}

Finally, my JSP would need to be tweaked to produce the appropriate table:


<display:table name="dummyData" decorator="edu.bristol.WrappedMapDecorator">
<c:forEach items="${keyset}" var="key">
<display:column property="map(${key})" title="${key}" />
</c:forEach>
<display:column property="columnPopulatedByTableDecorator" title="Decorator generated" />
</display:table>

I am happy with this solution. I maintain the flexibility that the List of Maps technique makes possible but it is also possible to decorate the supplied data.

Incidentally, you may have noticed that I haven't been blogging much in recent times. I am now the father of 2 pre-schoolers and so what free time I get is usually spent collapsed in a heap rather than blogging! Anyway I hope you find this useful...