Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Complex HTML tables made easy with Apache Velocity

Try a Java package that takes the pain out of HTML table creation and handling when using Velocity templates

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone

The Apache Velocity template engine simplifies creation of textual content by merging data contained in a Java object model with Velocity templates. But creating complex HTML tables with cells that span multiple columns and rows can lead to elaborate nested hierarchies of Velocity directives, which quickly render the Velocity template difficult to read and maintain. In this article Matthias Laux introduces a Java package that simplifies handling of even highly complex HTML tables, using a small number of directives while retaining all the benefits of Velocity for HTML generation. The package is independent of Apache Velocity and can potentially be used with other rendering schemes, such as JavaServer Pages.

I've worked on several projects that required generated HTML pages to display large amounts of data, typically available in a database or as XML files. To apply the well-established Model-View-Controller paradigm, I used Apache Velocity as the template engine to generate all the pages. The controller application's task was to read the data, fill in some Java data structures based on the data model for a given use case, and merge the data objects with Velocity templates.

Most of the data I worked with required extensive use of HTML tables on the Velocity side. The data contained hierarchical information, so the tables became increasingly complex because cells had to be merged using rowspan or colspan attributes to reflect the hierarchies. Correct output of required HTML markup elements such as <tr> and <td>, and the use of rowspan and colspan attributes, had to be controlled by Velocity directives (such as #if ... #end), which eventually overwhelmed the template's HTML code. This recurring pattern led me to implement a general solution for handling complex tables in this context. Beyond basic functionality, I wanted the solution to include some convenience features, such as:

  • The ability to grow a table dynamically after instantiation
  • Flexible behavior at the boundaries: clipping and autogrowth during cell insertion
  • Compacting of tables
  • Cloning of tables

This article introduces my solution: a Java package that takes the complexity out of HTML table generation with Velocity. First I'll demonstrate how complex tables lead to overly complex Velocity templates. Then I'll show you how you can use my solution to generate a complex table with minimal Java code and a few Velocity directives. Finally, with the help of a real-world example, you'll learn how to take advantage of the package's convenience features.

Complex tables, cluttered templates

Table 1 is an (abridged) example of a fairly complex table. Several of its cells are combined via rowspan attributes.

 

MainTopic  # Topic  # SubTopic  #
DOC 31 Developer Guide 17 Fact Sheet 4
General 8
HowTo 1
Module Whitepaper 3
Tutorial 1
General 7 Concept Paper 2
IDN 1
Release Documentation 3
User Guide 1
Training Material 7 eHF Conference 7
INF 28 Deployment Infrastructure 1 Performance 1
Development Environment 12 Generator 12
Test Infrastructure 14 Audit 1
Authentication 1
Document 1
Test Client 10
User Management 1
Tools for eHF Users 1 Security Workbench 1
 Total Count 59  Total Count 59  Total Count 59

Table 1. A fairly complex table

The values of the rowspan attributes in Table 1 must be assigned dynamically at page-creation time, because the number of hierarchy elements (MainTopic, Topic, and SubTopic), and the string values, aren't known until then. For this reason, the Velocity template must handle the required HTML output, causing the template to be cluttered with directives to handle all possible cases. As you can see in Listing 1, the template contains several references to the data model objects (such as $categoryData).

Listing 1. Portion of the Velocity template for Table 1

<table cellpadding="2" cellspacing="1" border="1" style="empty-cells:show">

  <tr bgcolor="$headerBackgroundColor">
    <td align="center"> $headerFontOn MainTopic $headerFontOff
    <td align="center"> $headerFontOn # $headerFontOff
    <td align="center"> $headerFontOn Topic $headerFontOff
    <td align="center"> $headerFontOn # $headerFontOff
    <td align="center"> $headerFontOn SubTopic $headerFontOff
    <td align="center"> $headerFontOn # $headerFontOff
  </tr>

  #foreach ( $mainTopic_Name in $categoryData.backlogElements.keySet() )
  
    #set ( $mainTopic_Category                     = $categoryData.categories.get($mainTopic_Name) )
    #set ( $mainTopic_Category_BacklogElementCount = $categoryData.backlogElements.get($mainTopic_Name).size() )
    #set ( $topic_Category_Count                   = $mainTopic_Category.backlogElements.size() )
  
    #set ( $rowCount1 = 0 )
    #foreach ( $topic_Name in $mainTopic_Category.backlogElements.keySet() )
      #set ( $topic_Category = $mainTopic_Category.categories.get($topic_Name) )
      #foreach ( $subTopic_Name in $topic_Category.backlogElements.keySet() )
        #set ( $rowCount1 = $rowCount1 + 1 )
      #end
    #end

    #set ( $first1 = 1 )

    #foreach ( $topic_Name in $mainTopic_Category.backlogElements.keySet() )
  
      #set ( $topic_Category                     = $mainTopic_Category.categories.get($topic_Name) )
      #set ( $topic_Category_BacklogElementCount = $mainTopic_Category.backlogElements.get($topic_Name).size() )
      #set ( $subTopic_Category_Count            = $topic_Category.backlogElements.size() )
  
      #set ( $rowCount2 = 0 )
      #foreach ( $subTopic_Name in $topic_Category.backlogElements.keySet() )
        #set ( $rowCount2 = $rowCount2 + 1 )
      #end

      #set ( $first2 = 1 )

      #foreach ( $subTopic_Name in $topic_Category.backlogElements.keySet() )
    
        #set ( $subTopic_Category_BacklogElementCount = $topic_Category.backlogElements.get($subTopic_Name).size() )
    
        <tr>

          #if ( $first1 == 1 )
            <td rowspan="$rowCount1" valign="top"> 
              $plainFontOn 
              <a href="#$formatter.getAnchorName($mainTopic_Name)">
              $mainTopic_Name 
              </a>

              $plainFontOff
            </td>
            <td rowspan="$rowCount1" valign="top"
                bgcolor="$colorScheme.getNextColor($mainTopic_Category_BacklogElementCount, $maxCount)"> 
              $plainFontOn 
              $mainTopic_Category_BacklogElementCount
              $plainFontOff
            </td>
            #set ( $first1 = 0 )
          #end
    
          #if ( $first2 == 1 )
            <td rowspan="$rowCount2" valign="top">
              <a href="#$formatter.getAnchorName($mainTopic_Name, $topic_Name)">

              $plainFontOn $topic_Name $plainFontOff
              </a>
            </td>
            <td rowspan="$rowCount2" valign="top" 
                bgcolor="$colorScheme.getNextColor($topic_Category_BacklogElementCount, $maxCount)"> 
              $plainFontOn $topic_Category_BacklogElementCount $plainFontOff
            </td>
            #set ( $first2 = 0 )
          #end
    
          <td valign="top"> 
            <a href="#$formatter.getAnchorName($mainTopic_Name, $topic_Name, $subTopic_Name)">

            $plainFontOn $subTopic_Name $plainFontOff
            </a>
          </td>
          <td valign="top" bgcolor="$colorScheme.getNextColor($subTopic_Category_BacklogElementCount, $maxCount)"> 
            $plainFontOn $subTopic_Category_BacklogElementCount $plainFontOff
          </td>
        </tr>

      #end
    
    #end
    
  #end

  <tr bgcolor="$subHeaderBackgroundColor">
    <td> $plainFontOn <b> Total Count </b> $plainFontOff
    <td> $plainFontOn <b> $cnt1       </b> $plainFontOff
    <td> $plainFontOn <b> Total Count </b> $plainFontOff
    <td> $plainFontOn <b> $cnt2       </b> $plainFontOff
    <td> $plainFontOn <b> Total Count </b> $plainFontOff
    <td> $plainFontOn <b> $cnt3       </b> $plainFontOff
  </tr>

</table><p>

Clearly, the Velocity directives in Listing 1 predominate over the actual formatting of the output, making the HTML code that controls the page's appearance difficult to read and maintain.

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comment
Login
Forgot your account info?
Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources