In diesem Tutorial möchte ich beschreiben, wie man eine Pagination in eine JavaServerFaces Tabelle implementiert.
Sicherlich kann man hierzu ein JSF Framework wie Primefaces zu Hilfe nehmen, jedoch bringt dieses für “nur” die Funktion der Pagination einen sehr großen Overhead mit.
Als Basis dieses Tutorial nutze ich ein einfaches JSF Projekt mit einer statischen Liste von Objekten (quasi ohne Datenbankanbindung). Das leere Projekt kannst du dir unter folgendem Link herunterladen oder folgst dem Tutorial Erstellen eines JavaServerFaces 2.2 Projektes mit Eclipse & Apache Maven, dort habe ich beschrieben wie du dir ein Projekt mit Apache Maven und Eclipse einrichten kannst.
JSF DataTable
Für die Darstellung einer Tabelle in JSF gibt es zwei mögliche Komponenten, einmal das PanelGrid und einmal die DataTable. In diesem Tutorial verwende ich die Komponente DataTable.
Zunächst einmal benötigen wir eine kleine Seite welche unsere DataTable anzeigen soll (und zusätzlich eine passende Überschrift).
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"> <h:head> <meta charset="ISO-8859-1"></meta> <title>JSFPagination</title> </h:head> <h:body> <h2>JSFPagination</h2> <h:form> ... </h:form> </h:body> </html>
Die DataTable soll später Daten mit dem Server austauschen / Aktionen ausführen, daher muss diese in eine Form gekapselt werden.
Definieren der DataTable
Die DataTable wird mit dem JSF Tag h:datatable eingeleitet und geschlossen.
<h:dataTable value="#{welcomeBean.tblContent}" var="tblEntry"> </h:dataTable>
In dem Attribut “value” wird die Liste mit den Objekten für die Tabelle hinterlegt und über das Attribut “var” können wir dann beim Rendern der Tabelle auf die Objekte zugreifen und durch ggf. weiteren Code die Tabelle manipulieren.
Als nächsten Schritt definieren wir wie viele Zeilen in der Tabelle auf einmal angezeigt werden sollen. Dieses erledigen wir, indem das Attribut “rows” verwendet wird. Nun könnte man als Wert einfach eine Zahl einfügen, jedoch benötigen wir diesen Wert später für unsere Pagination daher ist es besser diese Zahl in einer Bean zu speichern.
<h:dataTable value="#{welcomeBean.tblContent}" var="tblEntry" rows="#{welcomeBean.maxTblRows}" >
Eigentlich ist diese Zahl fix und wir könnten daraus eine Konstante via “private static final” machen, jedoch könnten wir dann nicht mehr auf diese in unsere XHTML zugreifen, denn nach der Java Beans Konversation benötigen wir Getter und Setter. Und Getter und Setter an einer Konstante klingt nicht nur komisch, sondern ist es auch. Daher wird dieses ein einfaches Feld in der Bean.
Nun fehlt noch das wichtige Attribut, ab welchem Index diese Zeilenzahl gelten soll. Dieses wird über das Attribut “first” gelöst. D.h. der Wert von “first” repräsentiert den Index aus der Liste der Objekte.
<h:dataTable value="#{welcomeBean.tblContent}" var="tblEntry" first="#{welcomeBean.currentRows}" rows="#{welcomeBean.maxTblRows}" >
Der Wert beginnt wie üblich bei der java.util.List bei 0.
Nun können wir die Tabelle aufbauen. Mein Testobjekt hat zwei Felder “ID” und “Text”.
<h:dataTable value="#{welcomeBean.tblContent}" var="tblEntry" first="#{welcomeBean.currentRows}" rows="#{welcomeBean.maxTblRows}"> <h:column> <f:facet name="header">ID</f:facet> #{tblEntry.id} </h:column> <h:column> <f:facet name="header">Text</f:facet> #{tblEntry.text} </h:column>
Mit dem JSF Tag “h:column” wird eine Spalte definiert. Zusätzlich können wir mit ‘f:facet name=”header”‘ definieren wie der Spaltenkopf beschrieben ist. In diesem Beispiel steht nur der Text “ID” im Spaltenkopf. In einer echten JSF Anwendung würde man nun diesen Text internationalisieren (mehrsprachig anbieten).
Der Wert der Zelle wird durch “#{tblEntry.<Feldbezeichnung>}” eingeleitet. (Es ist nicht nötig den Wert in ein “h:outputText” zu stecken.)
Tabellenfußzeile für die Pagination
Neben dem bereits genannten Tabellenkopf gibt es auch eine Fußzeile, welche man definieren kann. Und genau in diese Zeile möchten wir unsere Pagination darstellen. Dazu müssen wir zunächst einmal diese Zeile wie folgt definieren.
<f:facet name="footer"> ... </f:facet>
Damit wir später die Schaltflächen und die Auswahlliste sauber ausrichten können müssen wir diese in ein “h:panelGrid” stecken. Dieses bewirkt, dass wir in unsere Fußzeile eine zusätzliche Tabelle erhalten, welche über die gesamte Breite der äußeren Tabelle geht.
Schaltflächen Weiter & Zurück
Für die einfache Seitennavigation reichen schon die zwei Schaltflächen “Weiter” & “Zurück” aus.
<h:panelGroup> <h:commandButton value="Zurueck" action="#{welcomeBean.zurueck}" rendered="#{welcomeBean.displayZurueckBtn}" /> <h:commandButton value="Weiter" action="#{welcomeBean.weiter}" rendered="#{welcomeBean.displayWeiterBtn}" /> </h:panelGroup>
Für die Darstellung der Schaltflächen benötigen wir 2 Attribute, einmal “value” für den Text und einmal für die Sichtbarkeit “rendered”. Das Attribut “action” definiert eine Methode in unserer Java Bean welche ausgeführt werden soll, wenn die Schaltfläche betätigt wird.
Auswahlliste für die Seiten
Um jedoch auch auf bestimmte Seiten zuzugreifen, benötigen wir zusätzlich zbsp. eine Auswahlliste welche zwischen den beiden Schaltflächen angezeigt werden soll.
Für die Darstellung einer Auswahlliste benötigen wir das JSF Tag “h:selectOneMenu”.
Dieser enthält ähnlich wie die DataTable das Attribut “value” jedoch ist dieses nicht eine Liste sondern der ausgewählt Wert aus der Liste!
Die Einträge in dieser Liste werden entweder selber mit “f:selectItem” oder “f:selectItems” definiert
<f:selectItem itemValue="1" itemLabel="Text 123"/>
Da wir ggf. eine undefinierte Liste von Werten haben benötigen wir den zuletzt genannten Tag “f:selectItems”.
<h:selectOneMenu value="#{welcomeBean.selectedPage}"> <f:selectItems value="#{welcomeBean.pages}" var="page" itemLabel="Seite #{page}" itemValue="#{page}" /> <f:ajax listener="#{welcomeBean.pageChange}" render="@form"/> </h:selectOneMenu>
In dem Attribut “value” wird unsere java.util.List mit den Seitennummern stecken und das bereits bekannte Attribut “var” enthält einen Index auf den aktuellen Wert.
Die Attribute “itemLabel” für das sichtbare Textfeld in der Auswahlliste und “itemValue” für den Wert welcher später in der Java Bean verarbeitet werden soll, werden aus dem Wert der aktuellen Iteration befüllt.
Damit die Änderung beim auswählen eines Eintrages sofort sichtbar werden benötigen wir eine Aktion dieses wird durch “f:ajax” realisiert. Das Attribut “listener” enthält eine Methode in der Java Bean welche ausgeführt wird. Nach Abschluss dieser Aktion soll dann die Komponente im Attribut “render” aktualisiert werden, der Wert “@form” definiert das die äußere Form sich aktualisieren soll und somit alle darin befindlichen Komponenten (Tabelle, Schaltfläche, Auswahlliste etc.).
<f:facet name="footer"> <h:panelGroup> <h:commandButton value="Zurueck" action="#{welcomeBean.zurueck}" rendered="#{welcomeBean.displayZurueckBtn}" /> <h:selectOneMenu value="#{welcomeBean.selectedPage}"> <f:selectItems value="#{welcomeBean.pages}" var="page" itemLabel="Seite #{page}" itemValue="#{page}" /> <f:ajax listener="#{welcomeBean.pageChange}" render="@form"/> </h:selectOneMenu> <h:commandButton value="Weiter" action="#{welcomeBean.weiter}" rendered="#{welcomeBean.displayWeiterBtn}"/> </h:panelGroup> </f:facet>
CSS Style
Für das Styling von HTML Komponenten benötigen wir eine CSS Datei diese Datei wird in JSF im Pfad “webapp\resources\css\” abgelegt und im Kopfbereich der XHTML Datei wiefolgt eingebunden:
<h:head> <h:outputStylesheet name="css/styles.css"></h:outputStylesheet> </h:head>
DataTable
Damit wir nicht nur eine platte Tabelle erhalten, können wir der DataTable einige Attribute setzen, damit die Datenzeilen der Tabelle abwechselnd eingefärbt werden.
.table { border-collapse:collapse; border:1px solid #000; margin:10px; } .tableHeader { text-align:center; background-color:#B5B5B5; border-bottom:1px solid #000; padding:2px; } .tableRowOdd { text-align:center; background:#FFF; } .tableRowEven { text-align:center; background-color:#D3D3D3; }
Des Weiteren setzen wir der Tabelle ein generelles inneres Padding damit die Zahlen und vor allem die Schaltflächen nicht so dicht an den Linien anliegen.
<h:dataTable cellpadding="3" ....
Tabellenfuß ausrichten
Die Komponenten im Tabellenfuß sind per Standard linksbündig, dieses wollen wir nun mit einem CSS Style manipulieren. Der Tabellenfuß wird in dem HTML Tag “tfoot” gerendert. Nun kann man relativ simple in der CSS Datei definieren, dass alle Tabellenfüße in der Anwendung diesen Style haben.
tfoot{ text-align: center; border-top: 1px solid #000; }
Sollte es also nun mehr als eine Tabelle in der Anwendung geben muss hier mit einer zusätzlichen ID oder einem XPath gearbeitet werden.
Schaltflächen & Auswahlliste
Für etwas mehr Abstand zwischen den Komponenten definieren wir folgendes zusätzliche CSS Style
.footerBtn{ margin-right:5px; margin-left:5px; }
Dieser CSS Style sorgt dafür, dass links und rechts neben den Schaltflächen ein Abstand von 5 Pixel eingerichtet wird.
Nachdem wir nun das Frontend definiert und entwickelt haben wollen wir uns als Nächstes mit dem Backend beschäftigen.
Java Beans – Backend mit Businesslogik für die Pagination
Zunächst einmal benötigen wir eine Java Bean (im Weiteren nur als Bean bezeichnet) welche unsere Daten halten soll.
@ManagedBean @SessionScoped public class WelcomeBean extends Pagination<TableEntrie> { }
Da die Pagination für andere Seiten / Beans verwendet werden kann werde ich diese Logik in eine eigene abstrakte Klasse auslagern.
public abstract class Pagination<T> { ... }
Zunächst einmal benötigen wir eine Liste mit Elementen für unsere Tabelle.
protected List<T> tblContent = new ArrayList<>(); public List<T> getTblContent() { return tblContent; } public void setTblContent(List<T> tblContent) { this.tblContent = tblContent; }
Diese Liste befüllen wir in der Methode “doRefresh”.
public abstract List<T> loadContent(); public String doRefresh() { tblContent.clear(); tblContent.addAll(loadContent()); return SUCCESS; }
Da die Methode “loadContent” abstrakt ist, muss diese in unserer “WelcomeBean” ausprogrammiert werden.
@Override public List<TableEntrie> loadContent() { List<TableEntrie> entries = new ArrayList<>(); for (int i = 0; i < 100; i++) { entries.add(new TableEntrie(i, "Text " + i)); } return entries; }
Für dieses Tutorial verwende ich eine Liste von 100 Einträgen.
Die Aktionen für die Schaltflächen “Weiter” & “Zurück” manipulieren das Feld für den aktuellen Index der Liste.
private int maxTblRows = 5; private int currentRows; public String weiter() { currentRows += maxTblRows; if (currentRows > tblContent.size()) { currentRows = 0; } return SUCCESS; } public String zurueck() { currentRows -= maxTblRows; if (currentRows <= maxTblRows) { currentRows = 0; } return SUCCESS; }
Des Weiteren müssen wir noch beim Erreichen des Endes und dem Anfang der Liste die Schaltfläche “Weiter” bzw. “Zurück” ausblenden. Hierzu erzeugen wir uns die Methode “handlePagination”.
private boolean displayWeiterBtn; private boolean displayZurueckBtn; private void handlePagination() { displayWeiterBtn = (currentRows + maxTblRows) < getTblContent().size(); displayZurueckBtn = currentRows >= maxTblRows; } public boolean isDisplayWeiterBtn() { return displayWeiterBtn; } public void setDisplayWeiterBtn(boolean displayWeiterBtn) { this.displayWeiterBtn = displayWeiterBtn; } public boolean isDisplayZurueckBtn() { return displayZurueckBtn; } public void setDisplayZurueckBtn(boolean displayZurueckBtn) { this.displayZurueckBtn = displayZurueckBtn; }
Diese Methode müssen wir bei jedem aufrufen der Schaltflächen ausführen.
Somit haben wir die beiden Schaltflächen implementiert und können nun den ersten Test starten.
Auswahlliste implementieren
Als nächsten Schritt wollen, wir nun eine Auswahlliste implementieren, welche die Möglichkeit bietet eine bestimmte Seite anzusteuern. Zunächst einmal benötigen wir eine Liste für die Seiten. Und eine Logik welche diese Liste befüllt.
Zuerst haben wir eine Liste mit den Werten für die Tabelle erzeugt diese benötigen wir, um die Liste mit den Seitenzahlen zu erzeugen. Hier müssen wir die Anzahl der Einträge durch die maximale Anzahl der Tabelle dividieren und dieses auf die nächst höhere ganze Zahl aufrunden.
private List<Integer> pages = new ArrayList<>(); public String doRefresh() { ... pages.clear(); int maxPages = (int) Math.ceil((double) (tblContent.size() / maxTblRows)); for (int i = 1; i <= maxPages; i++) { pages.add(i); } handlePagination(); return SUCCESS; } public List<Integer> getPages() { return pages; } public void setPages(List<Integer> pages) { this.pages = pages; }
Zusätzlich müssen wir die bereits bestehende Methode “handlePagination” um das Handling der Seitenzahl erweitern, denn sonst wird die Auswahlliste beim Betätigen der Schaltflächen nicht aktualisiert.
selectedPage = (int) Math.ceil((double) (currentRows / maxTblRows)) + 1;
Nun noch die Methode implementieren, welche beim Auswählen einer Seite in der Auswahlliste aufgerufen werden soll.
public void pageChange(AjaxBehaviorEvent abe) { currentRows = (selectedPage - 1) * maxTblRows; handlePagination(); }
Da Einträge in einer java.util.List mit dem Index 0 beginnen müssen wir von der Seitenzahl -1 rechnen!
Download
Hier nun das fertige Projekt zum Download
Troubleshooting
Eventuell kann es beim Einrichten des Eclipseprojektes geschehen, dass die “Deployment Assembly” zu den Maven Dependencies “verloren” gehen. Hier muss man dann auf das Projekt klicken und im Kontextmenü “Properties” wählen.
In dem neuen Dialog wird dann der Eintrag “Deployment Assembly” ausgewählt (1). Ist hier nun kein Eintrag “Maven Dependencies” vorhanden müssen wir diesen über die Schaltfläche “Add..” hinzufügen (2).
Im nächsten Schritt wird nun der Eintrag “Java Build Path Entries” gewählt (1) und die Schaltfläche “Next >” betätigt (2).