1

It's been a shile since I am trying to programmatically build multiple table withing the same report with Jasper, so I decided to use again the DynamicJasper library. (which despite finding it sometimes rather unclear, it's usually very convenient).

Anyways, I thought what I need is concatenating multiple table reports in the same report (or page).

The main "data source" I am using is a list of lists, every list is ofc a different "table" to be filled. A similar mechanism happends for those headers. (I'll show everything below, in a moment)

As far as I saw from the DynamicJasper, it acts in a rather simple and direct way by declaring some kind of DynamicReportBuilder with some general informations about the report (Title, whidth, style, ecc..) and then the addConcatenatedReport should make what I am looking for.

It's written also that every subreport MUST be identified by a "string" who will be a hook for the data source (AKA the filling data) which has to be specified lately within a Map of parameters.

Everything fine till here, but printing that whole results always in a white page!

See: http://dynamicjasper.com/2010/10/08/how-to-add-concatenated-subreports/

I'll post from now how I approached the problem, taking the above example as a guideline.

Starting from the last thing:

this.dynamicReport = new Generator().withTitle(this.title).withHeaders(this.headerData).prepare();
    Map generatedParams = this.buildParams();
    this.jasperReport = DynamicJasperHelper.generateJasperReport(this.dynamicReport, new ClassicLayoutManager(), generatedParams);
    this.jasperPrint = JasperFillManager.fillReport(this.jasperReport, generatedParams);

I create the Generator class, which is in charge of just generating the report structure (and the concatenated report as well).

Within the Generator there are 2 main functions:

public DynamicReport prepare() throws Exception {
    Style rowStyle = new Style();
    rowStyle.setBorder(Border.NO_BORDER());
    rowStyle.setBackgroundColor(Color.LIGHT_GRAY);
    rowStyle.setTransparency(ar.com.fdvs.dj.domain.constants.Transparency.OPAQUE);

    DynamicReportBuilder reportBuilder = new DynamicReportBuilder()
            .setTitleStyle(new Style())
            .setTitle(this.title)
            .setDetailHeight(15)
            .setLeftMargin(20)
            .setRightMargin(20)
            .setTopMargin(20)
            .setBottomMargin(20)
            .setPrintBackgroundOnOddRows(true)
            .setOddRowBackgroundStyle(rowStyle);

    for (HeaderData headerData : headerData) {
        reportBuilder.addConcatenatedReport(
                this.createSubreportForEntry(headerData),
                new ClassicLayoutManager(),
                headerData.getDataSourceName(), // <-------- see?
                DJConstants.DATA_SOURCE_ORIGIN_PARAMETER,
                DJConstants.DATA_SOURCE_TYPE_COLLECTION,
                false
        );
    }

    reportBuilder.setUseFullPageWidth(true);

    return reportBuilder.build();
}


private DynamicReport createSubreportForEntry(HeaderData headerData) throws Exception {
    FastReportBuilder reportBuilder = new FastReportBuilder();

    for (HeaderColumn headerColumn : headerData.getHeaderColumns()) {
        reportBuilder.addColumn(headerColumn.getColumnName(), headerColumn.getColumnCode(), String.class.getName(), headerColumn.getWidth());
    }

    return reportBuilder
            .setTitle(headerData.getBoxName())
            .setMargins(5, 5, 20, 20)
            .setUseFullPageWidth(true)
            .build();
}

As I said, those are giving the shape to what should be printed, and I even highligted the data source, so that you know where it is. (that should be fine, I guess)

Now, what are those HeaderData?

public class HeaderData {
    private String boxName;
    private String dataSourceName;
    private List<HeaderColumn> headerColumns;
}

public class HeaderColumn {
    private String columnCode; // Hook for the datasource
    private String columnName; // what should be shown to the user
    private int width;
    private int height;
    private Style style;
    private Style headerStyle;
}

I had to create a dynamic structure for headers, because every table will have different columns!

And at last, the function that generated those params (which contains those filling collections):

private Map buildParams() {
    Map params = new HashMap<>();
    this.reportRows.forEach(tuple2 -> {
        if (tuple2._1 != null && tuple2._2 != null) {
            params.put(tuple2._1.toString(), tuple2._2);
        }
    });
    return params;
}

This is the outcome:

[
  key: DataSourceName1, value: List[Class1(prop1=1, prop2=2), Class1(...), Class1(...), ...],
  key: DataSourceName2, value: List[Class2(...), Class2(...), Class2(...), ...],
  key: DataSourceName3, value: List[Class3(...), Class3(...), Class3(...), ...],
]

As you see, every "value" is a list of objects of different class. This is due to the fact (again) that I don't have the same headers for every table!

I guess that each "property" (prop1, prop2, ecc..) are being linked together with the "columnCode" (as shown above, in the createSubreportForEntry)

Ofc, attributes within those classes reflect those HeaderColumn.columnCode.

AH! At the -very last- I'd like also to post how I print that:

    JRPdfExporter exporter = new JRPdfExporter();

    File outputFile = new File(path);
    File parentFile = new File(path).getParentFile();

    if (parentFile != null) {
        parentFile.mkdirs();
    }

    FileOutputStream fos = new FileOutputStream(outputFile);

    SimpleExporterInput simpleExporterInput = new SimpleExporterInput(jasperPrint);
    OutputStreamExporterOutput simpleOutputStreamExporterOutput = new SimpleOutputStreamExporterOutput(fos);

    exporter.setExporterInput(simpleExporterInput);
    exporter.setExporterOutput(simpleOutputStreamExporterOutput);

    exporter.exportReport();

I'm not sure if there's a even better way by using the pure JasperReport library, either if I can have an hybrid approach, eg: DynamicJasper for generating the report structure (the famous Generator class above), pure JasperReport for filling it.

NOTE: As I said at the beginning, the word "pogrammatically" means that using JRXML is out of scope (yeah, it would have been easier maybe)

EDIT:

By debugging net.sf.jasperreports.engine.fill.JRVerticalFiller#fillReport I see that it goes in an "if" branch for "no data", so I guess using a map for passing collections it's not the correct way (?)

jasper version I use: 6.11.0

Alex K
  • 22,315
  • 19
  • 108
  • 236
Andrea Grimandi
  • 631
  • 2
  • 8
  • 32

1 Answers1

0

It seems that almost nobody had ever tried to use this library before, so I recently came across the PDFBox from Apache, who did the job in a more easier way (for those of you that don't know this library -> https://pdfbox.apache.org/index.html).

Wat I found out further -> https://github.com/vandeseer/easytable for creating even prettier tables in a even more simple way (very well documented as well!).

I'd suggest those two, to any of you searching for a dynamic way to generate tables in a PDF via code.

Andrea Grimandi
  • 631
  • 2
  • 8
  • 32