How to do pagination in JSF 2 - jsf

I am very new to JSF and was trying to implement a pagination using <ui:repeat>. I am using the #ViewScoped but currently my state is not saved. Every time the backing bean resets the page information and the next page is not displayed. Attaching the source code for reference.
WorkItemList.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:c="http://java.sun.com/jsp/jstl/core"
template="/WEB-INF/template/default.xhtml">
<ui:define name="content">
<h:form id="form">
<ui:repeat value="#{repeatPaginator.model}" var="listItem">
<div>
<h:outputText value="#{listItem}"/>
</div>
</ui:repeat>
<h:commandButton value="< prev" action="#{repeatPaginator.prev}"/>
<h:outputText value="#{repeatPaginator.pageIndex} / #{repeatPaginator.pages}"/>
<h:commandButton value="next >" action="#{repeatPaginator.next}"/>
<h:inputHidden value="#{repeatPaginator.pageIndex}"/>
</h:form>
</ui:define>
</ui:composition>
Backing Bean - RepeatPaginator.java
package test;
#ManagedBean
#ViewScoped
public class RepeatPaginator implements Serializable{
private static final long serialVersionUID = 1L;
private static final int DEFAULT_RECORDS_NUMBER = 2;
private static final int DEFAULT_PAGE_INDEX = 1;
private TestDelegate delegate;
private int records;
private int recordsTotal;
private int pageIndex;
private int pages;
private List<String> model;
public RepeatPaginator() {
delegate = new TestDelegate();
this.records = DEFAULT_RECORDS_NUMBER;
this.pageIndex = DEFAULT_PAGE_INDEX;
// Get Model
this.model = delegate.fetchCurrentList(getFirst(), getFirst()+records);
this.recordsTotal = delegate.getListSize();
if (records > 0) {
pages = records <= 0 ? 1 : recordsTotal / records;
if (recordsTotal % records > 0) {
pages++;
}
if (pages == 0) {
pages = 1;
}
} else {
records = 1;
pages = 1;
}
updateModel();
}
public void updateModel() {
int fromIndex = getFirst();
int toIndex = getFirst() + records;
if(toIndex > this.recordsTotal) {
toIndex = this.recordsTotal;
}
setModel(delegate.fetchCurrentList(fromIndex, toIndex));
}
public void next() {
if(this.pageIndex < pages) {
this.pageIndex++;
}
updateModel();
}
public void prev() {
if(this.pageIndex > 1) {
this.pageIndex--;
}
updateModel();
}
public int getRecords() {
return records;
}
public int getRecordsTotal() {
return recordsTotal;
}
public int getPageIndex() {
return pageIndex;
}
public int getPages() {
return pages;
}
public int getFirst() {
return (pageIndex * records) - records;
}
public List<String> getModel() {
if(model==null)
updateModel();
return model;
}
public void setModel(List<String> model) {
this.model = model;
}
public void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
}
}
Delegate - TestDelegate.java
#ViewScoped
public class TestDelegate {
private List<String> list = new ArrayList<String>();
public TestDelegate()
{
this.list.add("Item 1");
this.list.add("Item 2");
this.list.add("Item 3");
this.list.add("Item 4");
this.list.add("Item 5");
this.list.add("Item 6");
this.list.add("Item 7");
this.list.add("Item 8");
this.list.add("Item 9");
this.list.add("Item 10");
this.list.add("Item 11");
}
public List<String> fetchCurrentList(int from, int to)
{
return list.subList(from, to);
}
public int getListSize()
{
return this.list.size();
}
}

The #ViewScoped bean is not "magically present all the time". At the end of the request, it's values will be serialized and the bean is destructed.
This causes a Re-Construct of the bean at the beginning of the next request (click on next). Your Container will take care to deserialize the stored values again, in order to return the actual State of the bean. BUT: This happens after Bean-Construction, meaning when you try to use the values within the Constructor of the bean, nothing is restored.
Move your logic into a method that you annotate with #PostConstruct - then all values will be loaded and you can fetch the correct result. The container will call the method annotated with #PostConstruct once it restored the prior state of the bean.
This becomes important when you start to use ManagedProperties or CDI-Injections.
public RepeatPaginator() {
//no serialized data available here, Good location to initialize some stuff
//that is independent.
delegate = new TestDelegate();
this.records = DEFAULT_RECORDS_NUMBER;
this.pageIndex = DEFAULT_PAGE_INDEX;
}
#PostConstruct
public void init() {
//Here the ViewScoped values are restored. We can start to use them.
// Get Model
this.model = delegate.fetchCurrentList(getFirst(), getFirst()+records);
this.recordsTotal = delegate.getListSize();
if (records > 0) {
pages = records <= 0 ? 1 : recordsTotal / records;
if (recordsTotal % records > 0) {
pages++;
}
if (pages == 0) {
pages = 1;
}
} else {
records = 1;
pages = 1;
}
updateModel();
}

Related

PrimeFaces LazyDataModel resolves rows incorrectly after live scroll?

I'm trying to use PrimeFaces 5.3 (with JSF 2.2) to implement a data table that contains items, and each item has a list of command buttons to perform server side actions. I use primefaces LazyDataModel because I have huge datasets and cannot afford to load all the items when the view loads. But after livescrolling happens, rows do not behave as expected.
I have setup a small project to illustrate my issue. (full sources at the end of the post)
The issue
When I press A0, "Executed A0" must be printed on the console (and so on).
Everything works fine for the first data set. Here's what happens when I press the first three buttons:
Fetching starting at 0 //expected result
Executed A0 //A0
Executed A1 //A1
Executed A2 //A2
But when I scroll, and 3 other items are loaded, the first three rows do not work correctly.
Here's what happens when I press all the buttons in order:
Fetching starting at 3 //expected result
Executed A3 //A0
Executed A4 //A1
Executed A5 //A2
Executed A3 //A3
Executed A4 //A4
Executed A5 //A5
The first three buttons execute the actions of the last three ones :(
A server-side issue
It is not a client-side issue, I have monitored the requests sent.
When I press A0, the request contains the following POST parameters:
j_idt8:resultsTable:0:j_idt12:0:j_idt13=j_idt8:resultsTable:0:j_idt12:0:j_idt13
javax.faces.source=j_idt8:resultsTable:0:j_idt12:0:j_idt13
And when I press A3:
j_idt8:resultsTable:3:j_idt12:0:j_idt13=j_idt8:resultsTable:3:j_idt12:0:j_idt13
javax.faces.source=j_idt8:resultsTable:3:j_idt12:0:j_idt13
The correct row is referenced after resultsTable:
Debug mode
I also messed with the debug mode. When I press A1, the UIData#brodacast(FacesEvent) is called with an event that contains a field rowIndex=1. So it works fine at this point.
Then, I don't really understand what happens. But at the end of the process, the target method is executed on the wrong instance...
Source code
Item.java
public class Item {
private int id;
private String name;
private List<ItemAction> actions;
public Item(int id, String name, List<ItemAction> actions) {
this.id = id;
this.name = name;
this.actions = actions;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public List<ItemAction> getActions() {
return actions;
}
}
ItemAction.java
public class ItemAction {
private String label;
public ItemAction(String label) {
this.label = label;
}
public void execute(){
System.out.println("Executed " + label);
}
public String getLabel() {
return label;
}
}
TheService.java
public class TheService {
private static final int ITEMS_COUNT = 8;
public int getItemsCount() {
return ITEMS_COUNT;
}
public List<Item> retrieveItems(int startIndex, int pageSize){
List<Item> result = new ArrayList<>(pageSize);
int endIndex = startIndex + pageSize;
if(endIndex > ITEMS_COUNT){
endIndex = ITEMS_COUNT;
}
for(int index=startIndex; index<endIndex; index++){
result.add(generateItem(index));
}
return result;
}
private Item generateItem(int id){
List<ItemAction> actions = new ArrayList<>(2);
actions.add(new ItemAction("A" + id));
actions.add(new ItemAction("B" + id));
return new Item(id, "Item " + id, actions);
}
}
DatatableLazyModel.java
#ManagedBean
#ViewScoped
public class DatatableLazyModel extends LazyDataModel<Item>{
private TheService service;
private List<Item> allResults;
public DatatableLazyModel() {
this.service = new TheService();
this.allResults = new ArrayList<>();
}
#Override
public int getRowCount() {
return service.getItemsCount();
}
#Override
public List<Item> load(int first, int pageSize, String sortField, SortOrder sortOrder,
Map<String, Object> filters) {
System.out.println("Fetching starting at " + first);
List<Item> pageResults = service.retrieveItems(first, pageSize);
allResults.addAll(pageResults);
return pageResults;
}
}
index.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui" template="template/ui.xhtml">
<ui:define name="body">
<h3>Datatablelazy load</h3>
<h:form>
<p:dataTable id="resultsTable" var="item"
value="#{datatableLazyModel}" liveScroll="true" scrollRows="3"
scrollHeight="100" scrollable="true" lazy="true">
<p:column headerText="Name">
<h:outputText value="#{item.name}" />
</p:column>
<p:column headerText="Actions">
<ui:repeat var="action" value="#{item.actions}">
<p:commandButton action="#{action.execute()}" value="#{action.label}">
</p:commandButton>
</ui:repeat>
</p:column>
</p:dataTable>
</h:form>
</ui:define>
</ui:composition>
pom.xml dependencies
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>5.3</version>
</dependency>
Questions
I have tried this with more items loaded. Each time, only methods on the last loaded set are executed. Even from old components.
So basically, the question is : "How can I make all the buttons work as expected?"
Why do methods do not get executed on the correct instance? How is the target instance resolved?
Thank you for your help :)
The issue is with the LazyDataModel implementation that is meant to be used with pages and not live scroll, especially this snippet :
if(rowIndex == -1 || pageSize == 0)
this.rowIndex = -1;
else
this.rowIndex = (rowIndex % pageSize);
I subclassed LazyDataModel with my own implementation to solve the problem.
LiveScrollLazyDataModel.java
public abstract class LiveScrollLazyModel<T> extends LazyDataModel<T> {
private int rowIndex;
#Override
public void setRowIndex(int rowIndex) {
int oldIndex = getRowIndex();
if (rowIndex == -1 || getPageSize() == 0){
super.setRowIndex(-1);
this.rowIndex = -1;
}else{
super.setRowIndex(rowIndex);
this.rowIndex = rowIndex;
}
if (getResults() == null) {
return;
}
DataModelListener[] listeners = getDataModelListeners();
if (listeners != null && oldIndex != this.rowIndex) {
Object rowData = null;
if (isRowAvailable()) {
rowData = getRowData();
}
DataModelEvent dataModelEvent = new DataModelEvent(this, rowIndex, rowData);
for (int i = 0; i < listeners.length; i++) {
listeners[i].rowSelected(dataModelEvent);
}
}
}
#Override
public T getRowData() {
return getResults().get(this.rowIndex);
}
#Override
public boolean isRowAvailable() {
if(getResults() == null) {
return false;
}
return rowIndex >= 0 && rowIndex < getResults().size();
}
/**
* The complete list of results
* #return
*/
public abstract List<T> getResults();
}
The implementor needs to implement the getResults() method that provides the complete list of results.

Dynamic number of primefaces datatables with dynamic columns - Wrong number of columns

I have to show multiple dynamic number of primefaces datatables with dynamic columns on single page. Everything works fine if number of columns in each dynamic datatable is same.
Wrong behaviour occurs when there are different number of columns in different tables. All the tables show as many columns as in the first table in the list.
Tested on:
Primefaces: 3.4 and 4.0
JSF 2.0
Here is the code demonstrating the problem:
EDIT: The following code is assigning different number of columns to different tables. The first table should have 8 columns, second should have 7 columns, and so on. But all the tables take up 8 columns; i.e. they take the number of columns from first table.
Expected Output: 5 datatables. First table having 8 columns, Second table 7 columns, Third table 6 columns, and so on. Expected Output Screenshot -- This is actually the output on pf 3.3
Actual Output: 5 tables, all having 8 columns. Data shown is correct. But empty extra columns are also being shown, which should not be shown. Actual Output Screenshot
test2.xhtml:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<f:view contentType="text/html">
<h:head>
</h:head>
<h:body>
<h:form>
<ui:repeat value="#{tableBean.tables}" var="table">
<p:dataTable id="cars" var="car" value="#{table.carsSmall}"
style="font-size: 12px;'width: 70%;">
<p:columns value="#{table.columns}" var="column"
columnIndexVar="colIndex">
<f:facet name="header">
#{column.header}
</f:facet>
<h:outputText value="#{car[column.property]}"></h:outputText>
</p:columns>
</p:dataTable>
<br />
</ui:repeat>
</h:form>
</h:body>
</f:view>
</html>
TableBean.java:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
#ManagedBean(name = "tableBean")
#ViewScoped
public class TableBean implements Serializable
{
private static final long serialVersionUID = 1L;
private final static List<String> VALID_COLUMN_KEYS = Arrays.asList("model", "manufacturer", "year", "color");
private final static String[] colors;
private final static String[] manufacturers;
private String columnTemplate = "model manufacturer year";
static
{
colors = new String[10];
colors[0] = "Black";
colors[1] = "White";
colors[2] = "Green";
colors[3] = "Red";
colors[4] = "Blue";
colors[5] = "Orange";
colors[6] = "Silver";
colors[7] = "Yellow";
colors[8] = "Brown";
colors[9] = "Maroon";
manufacturers = new String[10];
manufacturers[0] = "Mercedes";
manufacturers[1] = "BMW";
manufacturers[2] = "Volvo";
manufacturers[3] = "Audi";
manufacturers[4] = "Renault";
manufacturers[5] = "Opel";
manufacturers[6] = "Volkswagen";
manufacturers[7] = "Chrysler";
manufacturers[8] = "Ferrari";
manufacturers[9] = "Ford";
}
private List<Car> carsSmall;
private List<ColumnModel> columns = new ArrayList<ColumnModel>();;
private List<TableBean> tables;
public List<TableBean> getTables()
{
if (tables == null)
{
tables = new ArrayList<TableBean>();
for (int i = 0; i < 5; i++)
{
TableBean t = new TableBean();
for (int j = 5; j > i; j--)
{
t.columnTemplate += " year";
t.createDynamicColumns();
}
tables.add(t);
}
}
return tables;
}
public TableBean()
{
carsSmall = new ArrayList<Car>();
populateRandomCars(carsSmall, 9);
createDynamicColumns();
}
private void populateRandomCars(List<Car> list, int size)
{
for (int i = 0; i < size; i++)
list.add(new Car(getRandomModel(), getRandomYear(), getRandomManufacturer(), getRandomColor()));
}
public List<Car> getCarsSmall()
{
return carsSmall;
}
private int getRandomYear()
{
return (int) (Math.random() * 50 + 1960);
}
private String getRandomColor()
{
return colors[(int) (Math.random() * 10)];
}
private String getRandomManufacturer()
{
return manufacturers[(int) (Math.random() * 10)];
}
private String getRandomModel()
{
return UUID.randomUUID().toString().substring(0, 8);
}
public List<ColumnModel> getColumns()
{
return columns;
}
public String[] getManufacturers()
{
return manufacturers;
}
public String[] getColors()
{
return colors;
}
static public class ColumnModel implements Serializable
{
private static final long serialVersionUID = 1L;
private String header;
private String property;
public ColumnModel(String header, String property)
{
this.header = header;
this.property = property;
}
public String getHeader()
{
return header;
}
public String getProperty()
{
return property;
}
#Override
public String toString()
{
return "[header=" + header + ", property=" + property + "]";
}
}
public void createDynamicColumns()
{
String[] columnKeys = columnTemplate.split(" ");
columns.clear();
for (String columnKey : columnKeys)
{
String key = columnKey.trim();
if (VALID_COLUMN_KEYS.contains(key))
{
columns.add(new ColumnModel(columnKey.toUpperCase(), columnKey));
}
}
}
}
Car.java:
import java.io.Serializable;
public class Car implements Serializable
{
private static final long serialVersionUID = 1L;
private String model;
private int year;
private String manufacturer;
private String color;
public Car(String model, int year, String manufacturer, String color)
{
super();
this.model = model;
this.year = year;
this.manufacturer = manufacturer;
this.color = color;
}
public String getModel()
{
return model;
}
public void setModel(String model)
{
this.model = model;
}
public int getYear()
{
return year;
}
public void setYear(int year)
{
this.year = year;
}
public String getManufacturer()
{
return manufacturer;
}
public void setManufacturer(String manufacturer)
{
this.manufacturer = manufacturer;
}
public String getColor()
{
return color;
}
public void setColor(String color)
{
this.color = color;
}
}
I think you should use Primefaces 3.3 because it is showing as expected as in my case.
For now this will work but i will try to get this fixed on Primefaces 4.0 too.
Please find attached image for the same. Image

Dynamic Textfield In JSF 2.0

hey I am using the following code to create the number of text fields as the user wants
<h:form>
<p>Number Of News <h:inputText value="#{news.noOfFields}" /></p>
<ui:repeat value="#{news.values}" var="item">
<hr/>
News #{item.no}
<h:inputText value="#{item.news}" /><br/>
</ui:repeat>
<hr/>
<h:commandButton styleClass="btn btn-blue" action="#{news.submit}" value="Save" />
</h:form>
The managed bean news has a class News as
#ManagedBean
#SessionScoped
public class News
{
private String noOfFields;
private List<NewsVO> values;
public News()
{
this.values = new ArrayList<NewsVO>();
}
public String submit() {
for(NewsVO newsVO : this.values)
{
System.out.println(newsVO.getNews());
System.out.println(newsVO.getNo());
}
return null;
// save values in database
}
public String getNoOfFields() {
return noOfFields;
}
public List<NewsVO> getValues() {
return values;
}
public void setValues(List<NewsVO> values) {
this.values = values;
}
public void setNoOfFields(String noOfFields) {
this.values = new ArrayList<NewsVO>();
try {
for(int i=0;i<Integer.valueOf(noOfFields);i++)
{
NewsVO newsVO = new NewsVO();
newsVO.setNo(i+1);
this.values.add(newsVO);
}
this.noOfFields = noOfFields;
}
catch(NumberFormatException ex) {
/*values = new String[1];*/
noOfFields = "1";
}
}
}
The NewsVO is just a javaBean class as follows
public class NewsVO
{
public int no;
public String news;
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getNews() {
return news;
}
public void setNews(String news) {
this.news = news;
}
}
The problem is the values inside the input Text doesn't get reflected on pressing the save button. It gives me null, even though, I have written something inside all the textfields.
<h:inputText value="#{item.news}" />
Everytime you push the submit button, all setters in the bean are called (including setNoOfFields()). In this setter you are resetting your list, that's why you loose your values. Since you only need to modify your list if there is a size change, here is a simple way doing it :
#ManagedBean
#SessionScoped
public class News
{
private int noOfFields;
private List<NewsVO> values;
public News()
{
this.values = new ArrayList<NewsVO>();
}
public String submit()
{
for(NewsVO newsVO : this.values)
{
System.out.println(newsVO.getNews());
System.out.println(newsVO.getNo());
}
return null;
// save values in database
}
public int getNoOfFields()
{
return noOfFields;
}
public List<NewsVO> getValues()
{
return values;
}
public void setValues(List<NewsVO> values)
{
this.values = values;
}
public void setNoOfFields(int noOfFields)
{
if(noOfFields < this.noOfFields)
{
for(int i = this.noOfFields - 1;i >= noOfFields;i--)
{
getValues().remove(i);
}
}
else if(noOfFields > this.noOfFields)
{
for(int i = this.noOfFields;i < noOfFields;i++)
{
NewsVO newsVO = new NewsVO();
newsVO.setNo(i+1);
getValues().add(newsVO);
}
}
}
}
Note : I've also changed your noOfFields getter/setter for simple int, JSF will do the conversion for you.

No View in JSP Example

I have a problem with the view in JSP (Java EE)
Only the heading is shown.
My Code:
Entitiy Class (Konto);
#Entity
public class Konto implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable=false)
#NotNull(message="Kontonummer muss angegenben werden")
#Pattern(regexp="[0-9][0-9][0-9][0-9]")
private String kontonummer;
#Column(nullable=false)
#NotNull(message="Kontostand muss angegeben werden")
#DefaultValue(value="0.0")
private Double ktostd;
#Column(nullable=false)
#DecimalMin(value="0", message="Der Zins muss zw. 0 und 10 % liegen")
#DecimalMax(value="0.1", message="Der Zins muss zw. 0 und 10 % liegen")
private Double habenZins;
#ManyToOne
#JoinColumn(nullable=false)
#NotNull(message="Besitzer muss angegeben werden")
private Besitzer besitzer;
public Besitzer getBesitzer() {
return besitzer;
}
public void setBesitzer(Besitzer besitzer) {
this.besitzer = besitzer;
}
public Double getHabenZins() {
return habenZins;
}
public void setHabenZins(Double habenZins) {
this.habenZins = habenZins;
}
public String getKontonummer() {
return kontonummer;
}
public void setKontonummer(String kontonummer) {
this.kontonummer = kontonummer;
}
public Double getKtostd() {
return ktostd;
}
public void setKtostd(Double ktostd) {
this.ktostd = ktostd;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Konto)) {
return false;
}
Konto other = (Konto) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "at.korn.entity.NewEntity[ id=" + id + " ]";
}
}
Kontolist.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
<h1>Kontoliste</h1>
<h:form>
<h:dataTable value="#{kontolist.kontos}" var="konto">
<h:column>
<f:facet name="header">
<h:outputText value="Kontonummer"></h:outputText>
</f:facet>
<h:outputText value="#{konto.kontonummer}"></h:outputText>
</h:column>
</h:dataTable>
</h:form>
</h:body>
</html>
KontoList Controller:
#ManagedBean
#SessionScoped
public class Kontolist {
#EJB
KontoFacadeLocal kontofacade;
private List<Konto> kontos;
/** Creates a new instance of kontolist */
public Kontolist() {
kontos = kontofacade.findAll();
}
public KontoFacadeLocal getKontofacade() {
return kontofacade;
}
public void setKontofacade(KontoFacadeLocal kontofacade) {
this.kontofacade = kontofacade;
}
public List<Konto> getKontos() {
setKontos(kontofacade.findAll());
return kontos;
}
public void setKontos(List<Konto> kontos) {
this.kontos = kontos;
}
}
Problem:
Only the header is shown. In the source from the browser is the same code without html injection (like value="#{konto.kontonummer}")
First of all, that is not a JSP file. That's a Facelets (XHTML) file. JSP is an ancient view technology. Facelets is the successor of JSP.
So, your concrete problem is that the JSF tags are not been parsed? That can happen when the request URL did not match the URL pattern of the FacesServlet as definied in web.xml. If it is for example *.jsf, then you'd need to change the request URL from
http://localhost:8080/contextname/kontolist.xhtml
to
http://localhost:8080/contextname/kontolist.jsf
However, much better is to just change the URL pattern of the FacesServlet to *.xhtml so that you do not need to fiddle with virtual URLs and introduce security constraints to prevent the enduser from accidently or awaringly viewing the raw *.xhtml pages.
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
See also:
What is the difference between creating JSF pages with .jsp or .xhtml or .jsf extension
Unrelated to the concrete problem, you've by the way a NullPointerException bug in your code. Replace
public Kontolist() {
kontos = kontofacade.findAll();
}
by
#PostConstruct
public void init() {
kontos = kontofacade.findAll();
}
Injected dependencies are namely not available during construction. The getter and setter for the kontofacate are also entirely superfluous, I'd remove them to prevent future confusion and abuse.

JSF2 Paging / Pager for Repeater

Do you know this feeling when every code you write works immedietly and you underrun your schedule :-P It's like 'oh yeah now I have time to make it perfect'. That's where I am at the moment^^
So I implemented a repeater with JSF (ui:repeat) and I thought about a paging for all the entities. Is there maybe an easy way to do that? What are the points I have to think about?
Would be nice if someone gives me some help. My googleskills haven't helped me so far :-P
Cheers...
Here is a simple example that should give you the idea on how to implement this.
RepeatPaginator:
public class RepeatPaginator {
private static final int DEFAULT_RECORDS_NUMBER = 2;
private static final int DEFAULT_PAGE_INDEX = 1;
private int records;
private int recordsTotal;
private int pageIndex;
private int pages;
private List<?> origModel;
private List<?> model;
public RepeatPaginator(List<?> model) {
this.origModel = model;
this.records = DEFAULT_RECORDS_NUMBER;
this.pageIndex = DEFAULT_PAGE_INDEX;
this.recordsTotal = model.size();
if (records > 0) {
pages = records <= 0 ? 1 : recordsTotal / records;
if (recordsTotal % records > 0) {
pages++;
}
if (pages == 0) {
pages = 1;
}
} else {
records = 1;
pages = 1;
}
updateModel();
}
public void updateModel() {
int fromIndex = getFirst();
int toIndex = getFirst() + records;
if(toIndex > this.recordsTotal) {
toIndex = this.recordsTotal;
}
this.model = origModel.subList(fromIndex, toIndex);
}
public void next() {
if(this.pageIndex < pages) {
this.pageIndex++;
}
updateModel();
}
public void prev() {
if(this.pageIndex > 1) {
this.pageIndex--;
}
updateModel();
}
public int getRecords() {
return records;
}
public int getRecordsTotal() {
return recordsTotal;
}
public int getPageIndex() {
return pageIndex;
}
public int getPages() {
return pages;
}
public int getFirst() {
return (pageIndex * records) - records;
}
public List<?> getModel() {
return model;
}
public void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
}
}
Bean:
public class TestBean {
private List<String> list;
private RepeatPaginator paginator;
#PostConstruct
public void init() {
this.list = new ArrayList<String>();
this.list.add("Item 1");
this.list.add("Item 2");
this.list.add("Item 3");
this.list.add("Item 4");
this.list.add("Item 5");
this.list.add("Item 6");
this.list.add("Item 7");
this.list.add("Item 8");
this.list.add("Item 9");
this.list.add("Item 10");
this.list.add("Item 11");
paginator = new RepeatPaginator(this.list);
}
public RepeatPaginator getPaginator() {
return paginator;
}
}
XHTML:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
template="/WEB-INF/template/default.xhtml">
<ui:define name="content">
<h:form>
<ui:repeat value="#{testBean.paginator.model}" var="listItem">
<div>
<h:outputText value="#{listItem}"/>
</div>
</ui:repeat>
<h:commandButton value="< prev" action="#{testBean.paginator.prev}"/>
<h:outputText value="#{testBean.paginator.pageIndex} / #{testBean.paginator.pages}"/>
<h:commandButton value="next >" action="#{testBean.paginator.next}"/>
<h:inputHidden value="#{testBean.paginator.pageIndex}"/>
</h:form>
</ui:define>
</ui:composition>
Pagination is in fact easy. You just have to keep passing one or two parameters around: firstrow and optionally rowcount (which can also be kept in the server side). When the enduser clicks Next, you just increment the value of firstrow with the value of rowcount. When the enduser clicks Back, you just decrement the value of firstrow with the value of rowcount. You only need to check if it doesn't exceed the borders of 0 and totalrows and alter accordingly.
Then, based on the desired firstrow and rowcount, you know exactly which data to display. If all the data is already in some List in Java's memory, then you just use List#subList() to obtain a sublist from it for display. It is however not efficient to duplicate the entire database table into Java's memory. It may not harm when it's only 100 rows, but when it's much more than that and/or you're duplicating it for every single user, then the application will run out of memory very soon.
In that case you'd rather like to paginate at DB level. In for example MySQL you can use LIMIT clause to obtain a subset of results from the DB. JPA/Hibernate even provides ways using setFirstResult() and setMaxResults() methods of Query and Criteria respectively. You can find examples in this and this answer.
You can find a basic JSF 1.2 targeted kickoff example of Google-like pagination (and sorting) in this article. It uses Tomahawk components, but in JSF 2.0 you can just leave them away by making the bean #ViewScoped (replaces t:saveState) and using ui:repeat (replaces t:dataList).
Last but not least, there are lot of component libraries which does all the works in a single component. For example RichFaces <rich:datascroller> and PrimeFaces <p:dataTable paginator="true"> (can also be done ajaxical).

Resources