Decorators

A "decorator" is a design pattern where one object provides a layer of functionality by wrapping or "decorating" another object.

Table decorators

<display:table name="test" decorator="org.displaytag.sample.Wrapper" >
  <display:column property="id" title="ID" />
  <display:column property="email" />
  <display:column property="status" />
  <display:column property="date" />
  <display:column property="money" />
</display:table>

Let's assume you have list of business objects that you want to display, and the objects contain properties that don't return native Strings, and you want control over how they get displayed in the list (for example, Dates, money, numbers, etc...). I would be bad form to put this type of formatting code inside your business objects, so instead create a Decorator that formats the data according to your needs.

Notice the following 4 key things (and refer to the TableDecorator javadoc for some of the other details).

  • The Wrapper class must be a subclass of TableDecorator. There is various bootstrapping and API methods that are called inside the TableDecorator class and your class must subclass it for things to work properly (you will get a JspException if your class does not subclass it).
  • Be smart and create your formatters just once in the constructor method - performance will be a lot better...
  • Notice how the getDate() and getMoney() methods overload the return value of your business object contained in the List. They use the TableDecorator.getCurrentRowObject() method to get a handle to the underlying business object, and then format it accordingly.
  • We do not have to overload each of the other business object properties (like getID, getEmail, etc...). The decorator class is called first, but if it doesn't implement the method for the property called, then the underlying business class is called.

The way this works is that a single decorator object is created right before the table tag starts iterating through your List, before it starts processing a particular row, it gives the object for that row to the decorator, then as the various properties getXXX() methods - the decorator will be called first and if the decorator doesn't implement the given property, the method will be called on the original object in your List.

Column Decorators

You can also specify decorators that work on individual columns, this would allow you to come up with data specific formatters, and just reuse them rather then coming up with a custom decorator for each table that you want to show a formatted date for.

<display:table name="test">
  <display:column property="id" title="ID" />
  <display:column property="email" />
  <display:column property="status" />
  <display:column property="date" decorator="org.displaytag.sample.LongDateWrapper" />
</display:table>

Table decorators, column decorators or code in the column body?

As a rule of thumb, a decorator is faster than using scriptlet or custom tags in the column body when using paging. When the column body is filled and full list is sorted, all the records need to be "prepared" by the table tag iterating on the whole list. If the column body is used the content will be evaluated for any row, also for the non displayed ones; using property, on the other hand, will cause the decorator only to be called for displayed rows.

A table decorator have the power to add extra properties to your objects: for example you can add a getFullAddress() method to your table decorator and then use property="fullAddress" in a column. A table decorator can also provide custom html code added at the beginning/end of rowss and table.

A column decorator is rather limited in its funcionality: it simply format an available value, and has actually no access to the page context or other properties. However, it is the simplest and most reusable block if you simply need to format dates, number or custom strings.

Leaving decorators out and filling the column body is the simplest solution if you don't have to worry too much about paging and performance and it is optimal in a small, non paged, table. During sorting, though, if the column body is used, the result will be always sorted as a String.

Table decorators and exports

When a table decorator should be used both to render an HTML table and to export to other media, configure the decorator using the table tag's decorator attribute. For example:

<display:table name="test" decorator="org.displaytag.sample.ModelTableDecorator" >
  <display:column property="id" title="ID" />
  <display:column property="email" />
  <display:column property="status" />
  <display:column property="date" />
  <display:column property="money" />
</display:table>

Since this decorator will also be called when exporting to non-HTML media, such as Excel, PDF, or RTF, it should not output HTML. Such a decorator would normally be useful for adding extra properties to your objects or for media-agnostic formatting.

You can also configure a different decorator for each medium in which you want to render the table. In the following example, a different table decorator is used to help render the table in HTML, PDF, RTF, and Excel. The HTML decorator decorates the table using HTML, the PDF and RTF decorators using the iText API, and the Excel decorator using the HSSF API.

<display:table name="test" export="true">
    <display:setProperty name="decorator.media.html"  value="org.displaytag.sample.decorators.HtmlTotalWrapper" />
    <display:setProperty name="decorator.media.pdf"   value="org.displaytag.sample.decorators.ItextTotalWrapper" />
    <display:setProperty name="decorator.media.rtf"   value="org.displaytag.sample.decorators.ItextTotalWrapper" />
    <display:setProperty name="decorator.media.excel" value="org.displaytag.sample.decorators.HssfTotalWrapper" />
</display:table>

If you use the same decorator.media.export name table decorators in every page, you can configure them in your displaytag properties file.

Note that if you configure table decorators with both the table tag's decorator attribute and the decorator.media.export name config property, the table tag attribute value takes precedence and the decorator.media.export name values are ignored. In the example below, only ModelTableDecorator will be used.

<display:table name="test" export="true" decorator="org.displaytag.sample.ModelTableDecorator" >
    <display:setProperty name="decorator.media.html"  value="org.displaytag.sample.decorators.HtmlTotalWrapper" />
    <display:setProperty name="decorator.media.pdf"   value="org.displaytag.sample.decorators.ItextTotalWrapper" />
    <display:setProperty name="decorator.media.rtf"   value="org.displaytag.sample.decorators.ItextTotalWrapper" />
    <display:setProperty name="decorator.media.excel" value="org.displaytag.sample.decorators.HssfTotalWrapper" />
</display:table>