DataTable column headers and footers can be combined for grouped display.
<div class="card">
<h:form>
<div class="flex justify-content-between">
<div>
</div>
<div>
<p:commandButton value="XLSX" styleClass="mr-2 mb-2" >
<p:dataExporter type="xlsxstream" target="tblSales tblPlayers" fileName="grouped"/>
</p:commandButton>
<p:commandButton value="PDF" styleClass="mr-2 mb-2" >
<p:dataExporter type="pdf" target="tblSales tblPlayers" fileName="grouped"/>
</p:commandButton>
</div>
</div>
<p:dataTable id="tblSales" var="sale" value="#{dtGroupView.sales}">
<f:facet name="header">
<h:outputText value="Product Sales"/>
</f:facet>
<p:columnGroup type="header">
<p:row>
<p:column rowspan="3" headerText="Product"/>
<p:column colspan="4" headerText="Sale Rate"/>
</p:row>
<p:row>
<p:column colspan="2" headerText="Sales"/>
<p:column colspan="2" headerText="Profit"/>
</p:row>
<p:row>
<p:column headerText="Last Year"/>
<p:column headerText="This Year"/>
<p:column headerText="Last Year"/>
<p:column headerText="This Year"/>
</p:row>
</p:columnGroup>
<p:column>
<h:outputText value="#{sale.manufacturer}"/>
</p:column>
<p:column>
<h:outputText value="#{sale.lastYearProfit}%"/>
</p:column>
<p:column>
<h:outputText value="#{sale.thisYearProfit}%"/>
</p:column>
<p:column>
<h:outputText value="#{sale.lastYearSale}">
<f:convertNumber type="currency" />
</h:outputText>
</p:column>
<p:column>
<h:outputText value="#{sale.thisYearSale}">
<f:convertNumber type="currency" />
</h:outputText>
</p:column>
<p:columnGroup type="footer">
<p:row>
<p:column colspan="3" style="text-align:right" footerText="Totals:"/>
<p:column>
<f:facet name="footer">
<h:outputText value="#{dtGroupView.lastYearTotal}">
<f:convertNumber type="currency" currencySymbol="$"/>
</h:outputText>
</f:facet>
</p:column>
<p:column>
<f:facet name="footer">
<h:outputText value="#{dtGroupView.thisYearTotal}">
<f:convertNumber type="currency" currencySymbol="$"/>
</h:outputText>
</f:facet>
</p:column>
</p:row>
</p:columnGroup>
<f:facet name="footer">
<h:outputText value="Data between 2013-2014"/>
</f:facet>
</p:dataTable>
<p:dataTable id="tblPlayers" var="player" value="#{dtGroupView.players}" style="margin-top:40px">
<f:facet name="header">
<h:outputText value="Dynamic Columns"/>
</f:facet>
<p:columnGroup type="header">
<p:row>
<p:column rowspan="2" headerText="Player"/>
<p:column colspan="#{dtGroupView.yearCount}" headerText="Goals"/>
</p:row>
<p:row>
<p:columns var="year" value="#{dtGroupView.years}" sortBy="#{player.getGoals(year)}" filterBy="#{player.getGoals(year)}" headerText="#{year}"/>
</p:row>
</p:columnGroup>
<p:column>
<h:outputText value="#{player.name}"/>
</p:column>
<p:columns value="#{dtGroupView.years}" var="year">
<h:outputText value="#{player.getGoals(year)}"/>
</p:columns>
<f:facet name="footer">
<h:outputText value="Data between 2010-2014" styleClass="font-bold" />
</f:facet>
</p:dataTable>
</h:form>
</div>
package org.primefaces.showcase.view.data.datatable;
import jakarta.annotation.PostConstruct;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import io.quarkus.runtime.annotations.RegisterForReflection;
import org.primefaces.showcase.domain.Player;
import org.primefaces.showcase.domain.Sale;
@Named("dtGroupView")
@ViewScoped
@RegisterForReflection(serialization = true)
public class GroupView implements Serializable {
private static final String[] MANUFACTORS;
private static final String[] PLAYER_NAMES;
static {
MANUFACTORS = new String[10];
MANUFACTORS[0] = "Bamboo Watch";
MANUFACTORS[1] = "Black Watch";
MANUFACTORS[2] = "Blue Band";
MANUFACTORS[3] = "Blue T-Shirt";
MANUFACTORS[4] = "Brown Purse";
MANUFACTORS[5] = "Chakra Bracelet";
MANUFACTORS[6] = "Galaxy Earrings";
MANUFACTORS[7] = "Game Controller";
MANUFACTORS[8] = "Gaming Set";
MANUFACTORS[9] = "Gold Phone Case";
PLAYER_NAMES = new String[10];
PLAYER_NAMES[0] = "Lionel Messi";
PLAYER_NAMES[1] = "Cristiano Ronaldo";
PLAYER_NAMES[2] = "Arjen Robben";
PLAYER_NAMES[3] = "Franck Ribery";
PLAYER_NAMES[4] = "Ronaldinho";
PLAYER_NAMES[5] = "Luis Suarez";
PLAYER_NAMES[6] = "Sergio Aguero";
PLAYER_NAMES[7] = "Zlatan Ibrahimovic";
PLAYER_NAMES[8] = "Neymar Jr";
PLAYER_NAMES[9] = "Andres Iniesta";
}
private List<Sale> sales;
private Integer lastYearTotal;
private Integer thisYearTotal;
private List<Integer> years;
private List<Player> players;
@PostConstruct
public void init() {
sales = new ArrayList<>();
for (int i = 0; i < 10; i++) {
sales.add(new Sale(MANUFACTORS[i], getRandomAmount(), getRandomAmount(), getRandomPercentage(), getRandomPercentage()));
}
years = new ArrayList<>();
years.add(2010);
years.add(2011);
years.add(2012);
years.add(2013);
years.add(2014);
players = new ArrayList<>();
for (int i = 0; i < 10; i++) {
players.add(new Player(PLAYER_NAMES[i], generateRandomGoalStatsData()));
}
}
public List<Sale> getSales() {
return sales;
}
private int getRandomAmount() {
return (int) (Math.random() * 100000);
}
private int getRandomPercentage() {
return (int) (Math.random() * 100);
}
public Integer getLastYearTotal() {
if (lastYearTotal == null) {
lastYearTotal = sales.stream().mapToInt(Sale::getLastYearSale).sum();
}
return lastYearTotal;
}
public Integer getThisYearTotal() {
if (thisYearTotal == null) {
thisYearTotal = sales.stream().mapToInt(Sale::getThisYearSale).sum();
}
return thisYearTotal;
}
public List<Integer> getYears() {
return years;
}
public int getYearCount() {
return years.size();
}
public List<Player> getPlayers() {
return players;
}
private Map<Integer, Integer> generateRandomGoalStatsData() {
Map<Integer, Integer> stats = new LinkedHashMap<>();
for (int i = 0; i < 5; i++) {
stats.put(years.get(i), getRandomGoals());
}
return stats;
}
private int getRandomGoals() {
return (int) (Math.random() * 50);
}
}
package org.primefaces.showcase.domain;
import java.io.Serializable;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public class Sale implements Serializable {
private String manufacturer;
private int lastYearSale;
private int thisYearSale;
private int lastYearProfit;
private int thisYearProfit;
public Sale() {
}
public Sale(String manufacturer, int lastYearSale, int thisYearSale, int lastYearProfit, int thisYearProfit) {
this.manufacturer = manufacturer;
this.lastYearSale = lastYearSale;
this.thisYearSale = thisYearSale;
this.lastYearProfit = lastYearProfit;
this.thisYearProfit = thisYearProfit;
}
public int getLastYearProfit() {
return lastYearProfit;
}
public void setLastYearProfit(int lastYearProfit) {
this.lastYearProfit = lastYearProfit;
}
public int getLastYearSale() {
return lastYearSale;
}
public void setLastYearSale(int lastYearSale) {
this.lastYearSale = lastYearSale;
}
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public int getThisYearProfit() {
return thisYearProfit;
}
public void setThisYearProfit(int thisYearProfit) {
this.thisYearProfit = thisYearProfit;
}
public int getThisYearSale() {
return thisYearSale;
}
public void setThisYearSale(int thisYearSale) {
this.thisYearSale = thisYearSale;
}
}
package org.primefaces.showcase.domain;
import java.util.LinkedHashMap;
import java.util.Map;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public class Player {
private String name;
private final Map<Integer, Integer> goals;
public Player() {
goals = new LinkedHashMap<>();
}
public Player(String name, Map<Integer, Integer> goals) {
this.name = name;
this.goals = goals;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGoals(int year) {
return goals.get(year);
}
}