Skip to main content

Enhance DisplayTag

Posted by survivant on May 4, 2009 at 8:17 AM PDT

I did some enhancements to DisplayTag. You can start by reading my previous post on DisplayTag.
There were some features that I needed and Displaytag didn't have them. I took the source code and I did it.

The enchancements are not in Displaytag trunk, you will have to take my build or add it your self to the trunk.

I didn't see recent activities on DisplayTag, but I would like to see them sometime to the repository.

Now.. what are theses enhancements :)

- i18n for the export links
- remember to sort order on a refresh (via session's variable lookup)
- add TableHeaderTop to customize table header

The source code and the build are provided at the end :)

Let's take a look at the picture.

DisplayTag.jpg

The first section is a table that keep the sort order on a refresh and had a nice custom header. The export link is also i18n.
The second section is a table that doesn't have the enhancements except the i18n export.

Here the modifications to the code.

Add i18n support for export

First, you need to modify the file : displaytag.properties and activate theses lines

resource.provider=org.displaytag.localization.I18nStrutsAdapter
locale.provider=org.displaytag.localization.I18nStrutsAdapter

You will have to modify your Struts Bundle Resources to include this line.


export.links.label=<div class="exportlinks">Export displayed rows : {0}</div>

After that it's in the code, edit this file : org.displaytag.render.HtmlTableWriter

At the end of private void writeExportLinks()


String[] exportOptions = {buffer.toString()};
        
// handle title i18n
String evalTitle = this.properties.geResourceProvider().getResource(
    "export.links.label",
    TableProperties.PROPERTY_STRING_EXPORTBANNER,
    null,
    this.tableModel.getPageContext());

//write(MessageFormat.format(this.properties.getExportBanner(), exportOptions));
write(MessageFormat.format(evalTitle, exportOptions));

Variable lookup for sort order

In DisplayTag 1.3 there is a way to keep the sort order. You will have to add keepStatus=true to the tables.
What I didn't like about that is when you use the same tables for multiple displays (tiles) sometime you don't want to keep the order
when the page refresh, like after a new search.

Instead of a harcoded value in the value, I prefer to add a lookup session's variable.
I added the property : keepStatusProperty="variable name"

Like that you could change to value in the session and not in the JSP.

You will need to modify the java class: org.displaytag.tags.TableTag



/**
 * Preserve the current page and sort.
 */
private String keepStatusProperty;
    
/**
 * Preserve the current page and sort across session?
 * @param keepStatus <code>true</code> to preserve paging and sorting
 */
public void setKeepStatus(boolean keepStatus)
{
    this.keepStatus = keepStatus;
}

public void setKeepStatusProperty(String keepStatusProperty)
{
    this.keepStatusProperty = keepStatusProperty;
}

In the method private void initParameters() throws JspTagException, FactoryInstantiationException

add this


initHref(requestHelper);
        
if(keepStatusProperty!=null && keepStatusProperty.trim().length()>0){
   Boolean keepStatusEnabled = (Boolean)request.getSession().getAttribute(keepStatusProperty);     

   if(keepStatusEnabled==null){
      keepStatus = false;
   } else {
      keepStatus = keepStatusEnabled.booleanValue();
   }
}

Integer pageNumberParameter = getFromRequestOrSession(request, requestHelper, TableTagParameters.PARAMETER_PAGE);
this.pageNumber = (pageNumberParameter == null) ? 1 : pageNumberParameter.intValue();

In the class : org.displaytag.tags.TableTagBeanInfo

add this :

    
proplist.add(new PropertyDescriptor("keepStatusProperty", //$NON-NLS-1$
             TableTag.class, null, "setKeepStatusProperty")); //$NON-NLS-1$

and don't forgot the TLD : displaytag.tld

after the block keepStatus add this (the same as keepStatus)


<attribute>
   <name>keepStatusProperty</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
   <type>java.lang.String</type>
   <description>
      Preserve the current paging/sort status across session. The default is false (do not use sessions). Note that
      for this to work properly you need to assign to each table in your application a different id.
   </description>
</attribute>

The custom header with TableHeaderTop

We need to add a block in the TLD : displaytag.tld


<tag>
    <name>tableHeaderTop</name>
    <tag-class>org.displaytag.tags.TableHeaderTopTag</tag-class>
    <body-content>JSP</body-content>
    <display-name>column</display-name>
    <description>
      Displays a property of a row object inside a table. MUST be nested inside of a Table tag. The value displayed will
      be the results of a decorator (if any); else the property named by the 'property' attribute; or if the 'property'
      attribute is null, then the results of evaluating the JSP body of the tag.
    </description>
    <attribute>
      <name>property</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        name of the property in the bean specified in the parent table tag (via the "name" attribute) mapped to this
        column
      </description>
    </attribute>
    <attribute>
      <name>sortProperty</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        name of the property in the bean specified in the parent table tag (via the "name" attribute) which will be used
        to sort values in the column. This can be used when the column body is filled or a decorator is used and column
        should sort on undecorated values.
      </description>
    </attribute>
    <attribute>
      <name>title</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>title of the column (text for the th cell)</description>
    </attribute>
    <attribute>
      <name>comparator</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <description>
        The classname of comparator to use when sorting this column, or the comparator itself. Defaults to the
        DefaultComparator.
      </description>
    </attribute>
    <attribute>
      <name>titleKey</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <description>
        Resource key used to lookup the title value. Only works if "title" is not defined. Works together with a
        configured I18nResourceProvider, specified via the displaytag.properties file. By default, if JSTL is available,
        the JSTL provider is used, which makes this attribute work the same as fmt:message's key property.
      </description>
    </attribute>
    <attribute>
      <name>nulls</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>boolean</type>
      <description>
        By default, null values don't appear in the list. By setting 'nulls' to 'true', then null values will appear as
        "null" in the list (mostly useful for debugging). Defaults to 'false'.
      </description>
    </attribute>
    <attribute>
      <name>total</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>boolean</type>
      <description>
        If true, will total the contents of this column. This value is available via the Map named in varTotals for the
        table. Column values need to Numbers.
      </description>
    </attribute>
    <attribute>
      <name>sortable</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>boolean</type>
      <description>Set to 'true' to make the column sortable. Defaults to 'false'.</description>
    </attribute>
    <attribute>
      <name>defaultorder</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        The default sort order for this column. Valid values are "ascending" (default) or "descending"
      </description>
    </attribute>
    <attribute>
      <name>autolink</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>boolean</type>
      <description>
        Automatically hyperlink URLs and email addresses that appear in the column. Defaults to 'false'.
      </description>
    </attribute>
    <attribute>
      <name>format</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        A MessageFormat patter that will be used to decorate objects in the column. Can be used as a "shortcut" for
        simple column decorations. @since 1.1
      </description>
    </attribute>
    <attribute>
      <name>escapeXml</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>boolean</type>
      <description>
        Set it to true to escape special characters in html and xml output. Defaults to 'false'. @since 1.1
      </description>
    </attribute>
    <attribute>
      <name>media</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        Use this attribute to keep a column from being output during an export. The column will only render for the
        named media type(s) - it won't be added to the table if the current request media is not supported. Can be any
        space separated combination of 'html', 'csv', 'xml', 'all', or 'excel'. Defaults to 'all'. See the export page
        in the example webapp for more details.
      </description>
    </attribute>
    <attribute>
      <name>href</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        <![CDATA[
        The base URL used to construct the dynamic link. If this attribute is provided, then the data that is shown for
        this column is wrapped inside a <a href=""> tag with the url provided through this attribute. Typically you
        would use this attribute along with one of the struts-like param attributes (param*) to create a dynamic link so
        that each row creates a different URL based on the data that is being viewed. An empty href value will generate
        a link to the current page, preserving parameters just like for paging links.]]>
      </description>
    </attribute>
    <attribute>
      <name>url</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        The base URL used to construct the dynamic link. This attribute has the same functionality as the href
        attribute, but it pre-pends the contextPath.
      </description>
    </attribute>
    <attribute>
      <name>paramId</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        The name of the request parameter that will be dynamically added to the generated href URL. The corresponding
        value is defined by the paramProperty and (optional) paramName attributes, optionally scoped by the paramScope
        attribute.
      </description>
    </attribute>
    <attribute>
      <name>paramName</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        The name of a JSP bean that is a String containing the value for the request parameter named by paramId (if
        paramProperty is not specified), or a JSP bean whose property getter is called to return a String (if
        paramProperty is specified). The JSP bean is constrained to the bean scope specified by the paramScope property,
        if it is specified. If paramName is omitted, then it is assumed that the current object being iterated on is the
        target bean.
      </description>
    </attribute>
    <attribute>
      <name>paramProperty</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        The name of a property of the current object being iterated on, whose return value will be used as the value of
        the parameter (named by the paramId attribute) that will be dynamically added to this href URL. If paramName is
        also specified the property will not be fetched from the object being iterated on, but from the bean specified
        by paramName. The support of paramProperty in conjunction with paramName will be probably removed in future: use
        paramProperty only if you need a property in the iterated object, elsewhere use only paramName (you can select a
        property using an expression name.property).
      </description>
    </attribute>
    <attribute>
      <name>paramScope</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        @deprecated - use Expressions in paramName. The scope within which to search for the bean specified by the
        paramName attribute. If not specified, all scopes are searched. If paramName is not provided, then the current
        object being iterated on is assumed to be the target bean.
      </description>
    </attribute>
    <attribute>
      <name>maxLength</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>int</type>
      <description>
        If this attribute is provided, then the column's displayed is limited to this number of characters. An elipse
        (...) is appended to the end if this column is linked, and the user can mouseover the elipse to get the full
        text. Be careful on using this attribute for String which can contain html tags or entities, or together with
        the autolink attribute turned on: displaytag will do its best trying to avoid leaving unclosed tags or broken
        entities in the output, but a complex or bad input could lead to unattended results.
      </description>
    </attribute>
    <attribute>
      <name>maxWords</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>int</type>
      <description>
        If this attribute is provided, then the column's displayed is limited to this number of words. An elipse (...)
        is appended to the end if this column is linked, and the user can mouseover the elipse to get the full text. Be
        careful on using this attribute for String which can contain html tags or entities, or together with the
        autolink attribute turned on: displaytag will do its best trying to avoid leaving unclosed tags or broken
        entities in the output, but a complex or bad input could lead to unattended results.
      </description>
    </attribute>
    <attribute>
      <name>class</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        html pass through attribute; use this instead of directly coding presentational atttributes.
      </description>
    </attribute>
    <attribute>
      <name>headerClass</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>"class" html attribute added only for header cells.</description>
    </attribute>
    <attribute>
      <name>style</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>html pass through attribute.</description>
    </attribute>
    <attribute>
      <name>group</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>int</type>
      <description>
        The grouping level (starting at 1 and incrementing) of this column (indicates if successive contain the same
        values, then they should not be displayed). The level indicates that if a lower level no longer matches, then
        the matching for this higher level should start over as well. If this attribute is not included, then no
        grouping is performed.
      </description>
    </attribute>
    <attribute>
      <name>decorator</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        Whitespace separated list of column decorators to apply to the current column. A table decorator name can be the
        name of an object in page, request, session or application scope or a fully qualified class name of a class
        implementing the org.displaytag.decorator.DisplaytagColumnDecorator interface. If a decorator is specified for
        the entire table, then this decorator will decorate that decorator.
      </description>
    </attribute>
    <attribute>
      <name>sortName</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>
        Used with sort="external", the name that should be given to the server to sort this column. IE if
        sortName="buzz", then the href for this column to sort will have a parameter d-(encodedId)-s=buzz. If sortName
        is ommitted the value for the sort param will default to the column number.
      </description>
    </attribute>
    <attribute>
      <name>headerScope</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>"scope" html attribute added only for header cells.</description>
    </attribute>
    <attribute>
      <name>scope</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.String</type>
      <description>"scope" html attribute.</description>
    </attribute>
    <attribute>
      <name>value</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
      <type>java.lang.Object</type>
      <description>
        Static value to be used for the column. It has the same meaning of setting a value in the tag body, but values
        set using this attribute will not be coerced to Strings. You may need to use the value attribute instead of a
        scriptlet in the tag body if you need to calculate totals on numeric values.
      </description>
    </attribute>
  </tag>

In the java class : org.displaytag.model.TableModel

  
/**    * Table header.    */   private String tableHeaderTop; /** * Obtain this table's row header. * @return This table's caption. */ public String getTableHeaderTop() {     return this.tableHeaderTop; } /** * Set this table's caption. * @param caption This table's caption. */ public void setTableHeaderTop(String tableHeaderTop) {     this.tableHeaderTop = tableHeaderTop; }

In the java class : org.displaytag.render.HtmlTableWriter

We need to change the constructor and add few methods

  
    private TableHeaderTopTag tableHeaderTopTag; /** * This table writer uses a <code>TableTag</code> and a <code>JspWriter</code> to do its work. * @param tableTag <code>TableTag</code> instance called back by this writer. * @param out The output destination. */ public HtmlTableWriter(    TableModel tableModel,    TableProperties tableProperties,    Href baseHref,    boolean export,    JspWriter out,    TableHeaderTopTag tableHeaderTopTag,    CaptionTag captionTag,    PaginatedList paginatedList,    SmartListHelper listHelper,    int pagesize,    HtmlAttributeMap attributeMap,    String uid) {    this.tableModel = tableModel;    this.properties = tableProperties;    this.baseHref = baseHref;    this.export = export;    this.out = out;    this.tableHeaderTopTag = tableHeaderTopTag;    this.captionTag = captionTag;    this.paginatedList = paginatedList;    this.listHelper = listHelper;    this.pagesize = pagesize;    this.attributeMap = attributeMap;    this.uid = uid; } protected void writeTableHeaderTop(TableModel model) {    this.write(tableHeaderTopTag.getOpenTag() + model.getTableHeaderTop() + tableHeaderTopTag.getCloseTag()); }

In the method protected void writeTableHeader(TableModel model)

 
// open thead write(TagConstants.TAG_THEAD_OPEN); // render header rows if (model.getTableHeaderTop() != null) {    writeTableHeaderTop(model); } // open tr write(TagConstants.TAG_TR_OPEN);

In the java class : org.displaytag.render.ItextTableWriter

  
/** * Write the table's caption to a iText document. * @see org.displaytag.render.TableWriterTemplate#writeCaption(org.displaytag.model.TableModel) */ protected void writeTableHeaderTop(TableModel model) throws Exception {     //nothing }               

In the java class : org.displaytag.render.TableWriterTemplate

  
/**      * Called by writeTable to write the table's caption.      * @param model The table model for which the content is written.      * @throws Exception if it encounters an error while writing.      */     protected abstract void writeTableHeaderTop(TableModel model) throws Exception;

In the java class : org.displaytag.tags.TableTag

  
/** * Static caption added using the footer tag. */ private String tableHeaderTop; /** * Child header tag. */ private TableHeaderTopTag tableHeaderTopTag; /** * Set the child caption tag. * @param captionTag Child caption tag */ public void setTableHeaderTopTag(TableHeaderTopTag tableHeaderTopTag) {    this.tableHeaderTopTag = tableHeaderTopTag; } public void setTableHeaderTop(String string) {    this.tableHeaderTop = string;    this.tableModel.setTableHeaderTop(this.tableHeaderTop); } /** * Obtain the child caption tag. * @return The child caption tag */ public TableHeaderTopTag getTableHeaderTopTag() {    return this.tableHeaderTopTag; }

In the method : protected void writeHTMLData() throws JspException

  
// use HtmlTableWriter to write table new HtmlTableWriter(    this.tableModel,    this.properties,    this.baseHref,    this.export,    out,    getTableHeaderTopTag(),    getCaptionTag(),    this.paginatedList,    this.listHelper,    this.pagesize,    getAttributeMap(),    this.uid).writeTable(this.tableModel, this.getUid());

And finally two new classes : org.displaytag.tags.TableHeaderTopTag and org.displaytag.tags.TableHeaderTopTagBeanInfo

You can download the modify source code and a demo at my kenai repository


Follow me on Twitter

Related Topics >>

Comments

<p>&nbsp;Hello,</p> <p>I try to install and configure this ...

Hello,

I try to install and configure this feature with Eclipse .... but I failed !!!

Do you have a tutorial to configure this source with Eclipse.

Thanks !

Sebastien, This is great work

Sebastien,
This is great work and its exactly what I am looking for. I need to create DisplayTag tables with multiple row headers. I tried downloading and compiling the source code. It looks like youonly checked in the files you changed rather than the whole displayTag project which is fine. I got the release 1.2 of display tag and put your code on top but it looks like it is still missing some files. For example org.displaytag.tags.TableHeaderTopTag and org.displaytag.tags.TableHeaderTopTagBeanInfo. Anyway you can zip up and send me the source code. It would be great if you send the entire source code and the sample app you wrote. My email is bshamsian at yahoo dot com.
Any help is greatly appreciated
Ben