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
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.