Jump to content

Table of Contents with iReport


akuehn

Recommended Posts

Hello,

First I want to thank to the persons that make this technology available. I work with JasperReports for about one year now an I thought it was time to give back some of my findings. Be aware, it could be, that I have missed some information and that my findings may not be accurate in every detail.

As I could see, there are some questions and a few solutions about "Table of Contents". I have to point out that features like table-of-contents (with leader points, indentations, ...), indices, flow text and so on, are "document" functionalities. A reporting application has core functionalities in other fields. So it is quite logical, that iReport has absolutely no built-in support for such features, although it may me desirable to discuss such features in future.

Here are some points from the questions I saw in the posts:

  • TOC entries are already known at the beginning (e.g. passed as a parameter to the main report) or TOC entries are not known at the beginning and have to be collected at report fill time.
  • Page numbers are not known at the beginning of the report and have to be collected at report fill time.
  • TOC should appear as first page or after the title page or at the end of the report.

As a short summary I have found that there is only one possible way to create a TOC, it works the same way as the one given in the demo samples:

  • You need some piece of software that collects the TOC entries and page numbers at report fill time.
  • The TOC can only be placed at the end of the report, in the Summary band or (?) in a group footer band of a group that has only one run per report. In the first case there are no headers/footers and also no page numbers, as the Summary band does not have them by definition. This will be useful, when moving the toc later to another location or when using "page x of y" constructs.
  • If the toc should not be placed at the end, you will have to use the approach from the demo sample. Maybe - I will try out soon - it is possible to create a scriptlet that does the page moving from the end to some other place when the report has finished rendering. This would make the toc moving independent from the surrounding application code.
  • Placing the toc before the end of the report will not work, even when the toc entries (and anchor names, ...) were known at the beginning of the report.
  • This leads to the fact, that it does not help, if the toc entries are known before the report is run. In other words: Passing the toc entries e.g. as a parameter to the main report will not help you in creating a toc with page numbers. In again other words this implies that you dont need to change your data preparing java code to create a toc as you can collect all usually necessary information for the toc during fill time.
  • Having to collect all the toc information during fill time has, by the way, the advantage that you can be absolutely sure, not to miss toc entries or to create too many toc entries. In some cases not all data may be used in a report. Collecting the toc entries at fill time ensures that you have the exact number of toc entries.

This solution with pre-defined toc entries I first tried did not work:

  • I prepared a List<Map> with all the toc entries + anchor keys (but without page numbers) for hyperlinking and passed it as a parameter "tocListing" to the main report. The map contains tocEntry and tocKey. The tocEntry holds the "title" and the tocKey holds a unique String identifier.
  • I created a TocCollector class with methods String addEntry(<tocKey>, <pageNumber>) and Integer getPageNumber(<tocKey>). The constructor takes the $P{tocListing} as parameter and has the job to assign page numbers to the tocListing.
  • The class is initialized in a parameters' "tocCollector" default expression, so we have a running instance of a TocCollector that can be accessed as $P{tocCollector} at any time.
  • I created a group that runs once per report and put in the group header band a table with two columns, $F{tocEntry} and $P{tocCollector}.getPageNumber($F{tocKey}).
  • To collect the page numbers during the detail band run, I put $P{tocCollector}.addTocEntry("section-" + $V{REPORT_COUNT}, $V{PAGE_NUMBER}) to the anchor expression of the fields I wanted to collect.

So far, the solution was on a good way, with the exception that the placement of the toc at the beginning returned always null from the getPageNumber(..) method because the page numbers were not yet collected. What I wanted, was the following:

  • At "now" time the report should run and create first the toc entries from the tocListing, then the report, calling tocCollector.addTocEntry(..) with the appropriate page number wherever necessary.
  • At "Report" time all page number fields in the toc table at the beginning of the report should be evaluated, using getPageNumber(<tocKey>). Note: This makes the assumption that the engine would be able to evaluate the method parameter <tocKey> at "now" time and to evaluate the method call at "report" time, buffering meanwhile the environment of the "now" run. The engine may probably not support such a complex requirement.

After having configured this way, it turned out, that the getPageNumber(<tocKey>) method was always called before the calls to addTocEntry(<tocKey>, <pageNumber>) method, even when the pageNumber field was set to "Report" (the setting is ignored?). In a way this may be seen as expected behaviour, as the call occurs in a table and this may not support distinct evaluation time settings.

The solution that worked, looks like the demo sample solution:

It seems that the statement I read, that said more or less "with iReport you can create Tocs in various ways, just use your ideas" is not that true as I hoped it to be. I would rather say: "There is exactly one way to create a toc, it is described in the samples, you need quite an effort to create it and the result is halfway o.k.".

So, again: The only solution for a toc works in the way described in the demo samples. There is only one addition:

  • I saw that all propagated solutions use a scriptlet that is derived from JRDefaultScriptlet. This is not necessary as long as you do not need: a) directly access fields, variables etc. of the report, or b) you do not want to override one or more methods from JRDefaultScriptlet.

To make it short:

  • I changed the above class and added a new method: getDataSource() that returns a JRRewindableDataSource. The latter just returns a newly created JRMapCollectionDataSource.
  • The getPageNumber() method is not more necessary.
  • The class has an empty constructor and is created (like above) as default expression for a parameter named "tocCollector" like described above.
  • To each field that should be collected for the toc I set addTocEntry(...) as anchor expression (see code below to understand this placement).
  • The table in the summary band that will hold the toc gets its own "tocDatasource" with fields tocKey, tocText and pageNumber. The table datasource expression is $P{tocCollector}.getDataSource().
  • The page number column text field expression is changed to $F{pageNumber}.
  • It finally became a slight variant of the solution in the demo/samples - from which I initially thought: «I don't believe it's so much work».

I hope I could add some information, maybe some things are not clear, I would appreciate to give more precise information as soon as I find the time. The code of the class (having some more fields and still containing the now obsolete getPageNumber method) is below:

Code:


Post Edited by akuehn at 12/22/2011 19:27
Link to comment
Share on other sites

  • 2 weeks later...
  • Replies 3
  • Created
  • Last Reply

Top Posters In This Topic

  • 1 year later...
  • 2 weeks later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...