JasperReports is pretty straightforward when working with sub-reports. But you may need to display multiple different tables within a single report but without knowing how many tables will be needed in the final report. In this case, you need to have a dynamic mechanism. In the following example, a report with a random amount of tables will be created.
The sub-report
In this example, we have a report consisting of multiple tables. As we will provide each sub-report its own data source and parameter map, we do not have to mind anything special. First, we need a container which holds the data for each row.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Row { private final String column1; private final String column2; public Row(String column1, String column2) { this.column1 = column1; this.column2 = column2; } public String getColumn1() { return column1; } public String getColumn2() { return column2; } } |
The following example will generate a table with a title, a column header and rows depending on the data source.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<?xml version="1.0" encoding="UTF-8"?> <jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="table" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="03fae134-c6d0-4ad0-a6f6-135b759566da"> <parameter name="tableTitle" class="java.lang.String"/> <parameter name="columnHeader1" class="java.lang.String"/> <parameter name="columnHeader2" class="java.lang.String"/> <queryString> <![CDATA[]]> </queryString> <field name="column1" class="java.lang.String"/> <field name="column2" class="java.lang.String"/> <title> <band height="39" splitType="Stretch"> <textField> <reportElement x="0" y="0" width="550" height="30" uuid="f27fd612-da4b-4793-b276-6b879ed5840e"/> <textElement> <font size="20"/> </textElement> <textFieldExpression><![CDATA[$P{tableTitle}]]></textFieldExpression> </textField> </band> </title> <columnHeader> <band height="19" splitType="Stretch"> <textField> <reportElement x="0" y="0" width="280" height="15" uuid="d34198c4-16a7-4959-b78a-998a4b4c0f35"/> <textElement> <font isBold="true"/> </textElement> <textFieldExpression><![CDATA[$P{columnHeader1}]]></textFieldExpression> </textField> <textField> <reportElement x="280" y="0" width="270" height="15" uuid="b55ee2d6-e8d0-4746-bc5e-d1f25f4ccd64"/> <textElement> <font isBold="true"/> </textElement> <textFieldExpression><![CDATA[$P{columnHeader2}]]></textFieldExpression> </textField> <line> <reportElement x="0" y="16" width="551" height="1" uuid="56a18017-2064-4521-a2ea-2d15ec78e460"/> </line> </band> </columnHeader> <detail> <band height="15" splitType="Stretch"> <textField> <reportElement x="0" y="0" width="280" height="15" uuid="f9b9a84d-d65a-4344-9cf6-efd8794c8218"/> <textFieldExpression><![CDATA[$F{column1}]]></textFieldExpression> </textField> <textField> <reportElement x="280" y="0" width="270" height="15" uuid="2a9d1bd3-72e2-4645-a315-beba37b26c43"/> <textFieldExpression><![CDATA[$F{column2}]]></textFieldExpression> </textField> </band> </detail> </jasperReport> |
We’ll use a simple function in order to build the data source for the table we generate above.
1 2 3 4 5 6 7 8 9 10 |
private static JRDataSource createTableDataSource(int table) { List<Row> rows = new ArrayList<>(); int count = new Random().nextInt(10); for(int i = 1; i <= count; ++i) { rows.add(new Row("data " + table + "." + i + ".1", "data " + table + "." + i + ".2")); } return new JRBeanCollectionDataSource(rows); } |
Referencing the sub-report
We will use the detail section of the jasper report in order to support multiple different sub-reports. Usually there are three parameters required for each sub-report:
- The parameter map
- The data source (remember to always use a different data source for each sub report, otherwise you’ll have a mess)
- The report itself (it’s also possible to reference the actual file)
For this set, we create first create a POJO which holds this information.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class Subreport { private final JRReport report; private final JRDataSource dataSource; private final Map<String, Object> parameter; public Subreport(JRReport report, JRDataSource dataSource, Map<String, Object> parameter) { this.report = report; this.dataSource = dataSource; this.parameter = parameter; } public JRReport getReport() { return report; } public JRDataSource getDataSource() { return dataSource; } public Map<String, Object> getParameter() { return parameter; } } |
With this simple container, we create a data source which can be used when generating the actual report.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private static JRDataSource createTables(JRReport report) { List<Subreport> subreports = new ArrayList<>(); int count = new Random().nextInt(10); for(int i = 1; i <= count; ++i) { Map<String, Object> parameter = new HashMap<>(); parameter.put("tableTitle", "Table " + i); parameter.put("columnHeader1", "Column " + i + ".1"); parameter.put("columnHeader2", "Column " + i + ".2"); subreports.add(new Subreport(report, createTableDataSource(i), parameter)); } return new JRBeanCollectionDataSource(subreports); } |
Now we have to reference these reports within the parent report. To do so, we simply add the sub report to the detail section and reference the fields of the previously created POJO.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<?xml version="1.0" encoding="UTF-8"?> <jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="master" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="1a252b30-95d1-454a-a083-13dc7fb1c96f"> <queryString> <![CDATA[]]> </queryString> <field name="report" class="net.sf.jasperreports.engine.JRReport"/> <field name="dataSource" class="net.sf.jasperreports.engine.JRDataSource"/> <field name="parameter" class="java.util.Map"/> <background> <band splitType="Stretch"/> </background> <pageHeader> <band height="44" splitType="Stretch"> <staticText> <reportElement x="450" y="7" width="100" height="30" uuid="d0f41ec4-377c-47e1-9d1d-28b9c7ef0e17"/> <textElement textAlignment="Right"/> <text><![CDATA[Some Random Page Header]]></text> </staticText> </band> </pageHeader> <detail> <band height="50" splitType="Stretch"> <subreport> <reportElement x="-20" y="0" width="595" height="50" uuid="59e20eef-0644-46d9-bd73-119e5a17df6e"/> <parametersMapExpression><![CDATA[$F{parameter}]]></parametersMapExpression> <dataSourceExpression><![CDATA[$F{dataSource}]]></dataSourceExpression> <subreportExpression><![CDATA[$F{report}]]></subreportExpression> </subreport> </band> </detail> <pageFooter> <band height="25" splitType="Stretch"> <textField> <reportElement x="380" y="0" width="170" height="20" uuid="99662b0b-82f0-44f1-8048-7d7ee70ce014"/> <textElement textAlignment="Right"/> <textFieldExpression><![CDATA["Page " + $V{PAGE_NUMBER}]]></textFieldExpression> </textField> </band> </pageFooter> </jasperReport> |
Creating the report
Now, as we have fully created our environment to create a report with a variable amount of sub-reports, we simply need to fire up the generation process.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public static void main(String[] args) throws JRException, IOException { // No need to mention that you should precompile the reports try(InputStream masterIn = EmbeddedFonts.class.getResourceAsStream("/reports/master.jrxml"); InputStream tableIn = EmbeddedFonts.class.getResourceAsStream("/reports/table.jrxml")) { JasperReport masterReport = JasperCompileManager.compileReport(masterIn); JasperReport tableReport = JasperCompileManager.compileReport(tableIn); // JasperFillManager does not support immutable maps :( JasperPrint print = JasperFillManager.fillReport(masterReport, new HashMap<String, Object>(), createTables(tableReport)); JasperViewer.viewReport(print, false); } } |