JasperReports Ultimate Guide - Sample Reference - Schema Reference - Configuration Reference - API (Javadoc)

JasperReports - Table of Contents Sample (version 4.6.0)


Shows how table-of-contents structures could be created for the generated reports.

Download All Sample Source Files
Browse Sample Source Files on SVN


Main Features in This Sample

Creating Table-Of-Contents Structures

File Resolver


top

Creating Table-Of-Contents StructuresDocumented by Sanda Zaharia


Description / Goal
How to create a table of contents with hyperlinks at the beginning of a document.

Since
0.4.2


Table Of Contents - Overview

A table of content becomes very necessary when complex documents with huge number of pages are generated. But, being almost impossible to determine the total number of pages, or the current content of generated pages at report design time, there is no built-in report element in JasperReports to handle the table of contents.
The document structure unveils itself step by step at report filling time. Therefore this is the proper moment for building a table of content. Once a print element is generated, its place within document is completely determined, so from now on we are able to find and reference that element. If the element is starting a new section, we can add a new entry in the table of contents, pointing here.
The table of content cannot be finalized before the report is completely filled, so it can be very easily placed on the last page of the document in the summary section. But how about documents that require to start with a table of contents? With JasperReports this document layout can be also realized, as shown in this sample.

Table Of Contents Sample

There are various ways to build tables of contents, depending on everyone's free imagination. The way imagined here is the following:
  • The table of content structure was configured in a subreport. See the reports/HeadingsReport.jrxml file for layout details. It is based on local anchor hyperlinks with different levels of indentation, depending on the heading type. Notice the presence of the invisible element labeled "HIDDEN TEXT TO MARK THE BEGINNING OF THE TABLE OF CONTENTS" in the title section. It will be used to identify the first page of the table of contents in the generated document.

  • A HeadingBean class (see the src/HeadingBean.java file) was created in order to store the meaningful information of individual entries in the table of contents. A HeadingBean object exposes the following bean properties:
    • headingType - an integer that indicates the heading type of the entry.
    • headingText - the text to be written in the table of contents for this entry.
    • reference - the hyperlink reference associated with the entry.
    • pageIndex - the page index to be associated with this entry in the table of contents.
    During the report filling a JRBeanCollectionDataSource will be populated with HeadingBean object. The mechanism used to populate it is an inventive combination between report scriptlets and printWhenExpressions, as we'll see in a while. The bean collection will be passed as data source parameter to the HeadingsReport subreport placed in the summary section. At that moment the collection data source will be entirely complete and ready to be used.

  • Every time the addHeading(String ) method in the HeadingsScriptlet class is called, a new member is added to the bean collection data source described above. So, we have to ensure that the method is called any time a heading element is met in the document. This is done using an interesting condition in printWhenExpressions in the master report (see the reports/TableOfContentsReport.jrxml file):
    <group name="FirstLetterGroup" minHeightToStartNewPage="60">
      ...
      <groupHeader>
      <band height="25">
        <line>
          <reportElement x="0" y="0" width="1" height="1">
            <printWhenExpression>
              <![CDATA[((HeadingsScriptlet)$P{REPORT_SCRIPTLET}).addHeading("FirstLetterGroup")]] ></printWhenExpression>
          </reportElement>
          <graphicElement/>
        </line>
        ...
        <textField>
          <reportElement mode="Opaque" x="190" y="10" width="325" height="15" backcolor="#c0c0c0" style="Sans_Bold"/>
          <textFieldExpression class="java.lang.String"><![CDATA[$V{FirstLetter}]] ></textFieldExpression>
          <anchorNameExpression><![CDATA["FirstLetterGroup_" + $V{FirstLetter}]] ></anchorNameExpression>
        </textField>
      </band>
      </groupHeader>
    </group>
    <group name="ShipCountryGroup" minHeightToStartNewPage="60">
      ...
      <groupHeader>
      <band height="20">
        <line>
          <reportElement x="0" y="0" width="1" height="1">
            <printWhenExpression>
              <![CDATA[((HeadingsScriptlet)$P{REPORT_SCRIPTLET}).addHeading("ShipCountryGroup")]] >
            </printWhenExpression>
          </reportElement>
          <graphicElement/>
        </line>
        ...
        <textField>
          <reportElement x="0" y="4" width="515" height="15" style="Sans_Bold"/>
          <textFieldExpression class="java.lang.String">
            <![CDATA["  " + String.valueOf($V{ShipCountryNumber}) + ". " + String.valueOf($F{ShipCountry})]] >
          </textFieldExpression>
          <anchorNameExpression><![CDATA["ShipCountryGroup_" + $V{ShipCountryNumber}]] ></anchorNameExpression>
        </textField>
      </band>
      </groupHeader>
      ...
    </group>
    One can see a similar construction present in both group headers of the report: a line with a printWhenExpression and a textfield with an anchorNameExpression. The addHeading(...) method called in the printWhenExpression populates the data source and always returns false. That means the line element never gets printed, being used only for calling the scriptlet method.
    But the related textfield will be printed and will be marked with the same anchor name as that one built in the reference field of the corresponding HeadingBean object. In the final document, clicking on any entry in the table of contents will send to the bookmarked element in the document.

  • If the document would require a table of contents placed at the end, all the work would stop here. There would be nothing else to do. But in this sample is assumed that the table of contents is needed at the beginning of the document, hence an additional step is required. After filling the JasperPrint object, one can iterate through its pages and elements. We have to identify the pages containing the table of contents, to cut and move them at the beginning of the document. That's what the moveTableOfContents(JasperPrint) method in the src/TableOfContentsApp.java is doing. The method is called at fill time after the JasperPrint object was generated.
The final result of the mechanism described above can be observed when running the sample.

Running the Sample

Running the sample requires the Apache Ant library. Make sure that ant is already installed on your system (version 1.5 or later).
In a command prompt/terminal window set the current folder to demo/hsqldb within the JasperReports source project and run the > ant runServer command. It will start the HSQLDB server shipped with the JasperReports distribution package. Let this terminal running the HSQLDB server.
Open a new command prompt/terminal window and set the current folder to demo/samples/tableofcontents within the JasperReports source project and run the > ant test view command.
It will generate all supported document types containing the sample report in the demo/samples/tableofcontents/build/reports directory.
Then the report will open in the JasperReports internal viewer.

top

File ResolverDocumented by Sanda Zaharia


Description / Goal
Shows how to use file resolver implementations in order to locate file resources referenced from within reports using relative paths.

Since
2.0.5


File Resolver - Overview

By default, the JR engine interprets resource locations as file system paths that can be either absolute or relative to the current user directory (given by the Java user.dir system property). If the path constructed this way resolves to an existing file, the report resource will be loaded from this file.
The default behavior can be changed by using the REPORT_FILE_RESOLVER parameter at fill time to pass a file resolver object. This object needs to be an instance of a class that implements the FileResolver interface. The interface contains a single method which is responsible for resolving a resource location to an abstract file path (represented by a java.io.File object).
When a file resolver is set via the report parameter, the engine will use it to interpret resource locations as file paths. If a file resolver returns a non-null file path for a resource location, the file located at the returned path will be used to load the resource.
JasperReports ships with the built-in SimpleFileResolver implementation that interprets resource locations as paths relative to one or several file system folders. To create such a file resolver, the user has to specify one or more resources folders. When the resolver is asked to resolve a resource location, it interprets the locations as a path relative to each of the resource folders, and returns the first path that corresponds to an existing file.
The SimpleFileResolver is called in this sample at fill time to resolve paths to compiled reports saved with the .jasper extension. See the example in the src/TableOfContentsApp.java:
public void fill() throws JRException
{
  long start = System.currentTimeMillis();
  //Preparing parameters
  Map parameters = new HashMap();
  parameters.put("ReportTitle", "Orders Report");

  SimpleFileResolver fileResolver =
    new SimpleFileResolver(
      Arrays.asList(new File[]{new File("build/reports")})
      );
  fileResolver.setResolveAbsolutePath(true);
  
  parameters.put(JRParameter.REPORT_FILE_RESOLVER, fileResolver);
  
  JasperReport jasperReport = (JasperReport)JRLoader.loadObject("build/reports/TableOfContentsReport.jasper");

  JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, getDemoHsqldbConnection());
  
  jasperPrint = moveTableOfContents(jasperPrint);

  JRSaver.saveObject(jasperPrint, "build/reports/TableOfContentsReport.jrprint");

  System.err.println("Filling time : " + (System.currentTimeMillis() - start));
}
Note: As shown in the API javadoc, the REPORT_FILE_RESOLVER parameter is now deprecated. It is highly recommended to switch to the JasperReportsContext approach wherever is possible.



© 2001- Jaspersoft Corporation www.jaspersoft.com