Collections of web application techniques

Thursday, September 3, 2009

Custom Pagination With Seam - Simplified

As it turned out, it is extremely easy to enhance the pagination that comes with Seam to do all the things below:
- selectable page sizes
- current page and total number of pages are displayed
- total number results is displayed
- paging is not limited to just first, previous, next and last result sets but much more flexible as below.



The easiest way to start is to use seam-gen to generate the code. Once you have the generated code working, do the following to enhance the default pagination.

1. Open the file that ends with “List” such as PersonList and add the code below to your class. PageCalc source is being provided at the end of this blog.


private PageCalc pageCalc;

private int pageIndex;

@Override
public Long getResultCount() {
Long count = super.getResultCount();
if (pageCalc == null) {
pageCalc = new PageCalc();
pageCalc.setTotal(count);
pageCalc.setUpPaging(pageIndex, getMaxResults());
}
return count;
}

public int getPageIndex() {
return pageIndex;
}

public void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
setFirstResult((pageIndex - 1) * getMaxResults());
}

public PageCalc getPageCalc() {
return pageCalc;
}


2. Open the xhtml file such as PersonList.xhtml and add these lines just before the dataTable tag. This will render all the controls above the table. The code for paginationControl.xhtml is also included at the end of this blog.


<ui:include src="paginationControl.xhtml">
<ui:param name="bean" value="#{personList}"/>
<ui:param name="pageNumber" value="pageIndex"/>
<ui:param name="dataListener" value="data"/>
</ui:include>


Note that #{personList} should be replaced with the name of your backing bean; something like managerList, departmentList,… whatever your page is about.

This also uses ajax to render only the part that needs to be re-rendered. In this example I have wrapped the data table with the outputPanel tag. The id is being passed to the included file so it knows what to re-render.


<a:outputPanel id="data">
<rich:dataTable …
</a:outputPanel>


The pageIndex is also being passed to the paginationControl where in PersonList.page.xml you should have a line like this to tie the current page to the backing bean:


<param name="pageIndex" value="#{personList.pageIndex}"/>


3. Test it out and enjoy.

Code for paginationControls.xhtml:


<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
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:rich="http://richfaces.org/rich"
xmlns:a="http://richfaces.org/a4j"
xmlns:c="http://java.sun.com/jstl/core"
>
<style>
#pagination a:link, #pagination a:active, #pagination a:visited {
padding-left:2px;
padding-right:2px;
text-decoration:none;
}
</style>

<div style="clear:both">
<span style="float:left">
<h:outputText value="Total Number of Matches: #{bean.resultCount}"/>
</span>

<span style="float:right">
<h:outputLabel value="Page Size:">
<h:selectOneMenu id="pageSize" value="#{bean.maxResults}">
<f:selectItem itemLabel="10" itemValue="10" />
<f:selectItem itemLabel="15" itemValue="15" />
<f:selectItem itemLabel="20" itemValue="20" />
<f:selectItem itemLabel="25" itemValue="25" />
<f:selectItem itemLabel="50" itemValue="50" />
<f:selectItem itemLabel="100" itemValue="100" />
<a:support event="onchange" action="#{bean.setFirstResult(0)}" reRender="data" />
</h:selectOneMenu>
</h:outputLabel>
</span>
</div>

<div id="pagination" style="clear:both">
<h:outputText value="Page #{bean.pageCalc.pageIndex} of #{bean.pageCalc.lastPageIndex} - " />
<h:panelGroup rendered="#{bean.pageCalc.leftArrows}">
<s:link reRender="#{dataListener}">
<h:outputText value="&lt;&lt; "/>
<f:param name="#{pageNumber}" value="1" />
<f:param name="maxResults" value="#{bean.maxResults}" />
</s:link>

<s:link reRender="#{dataListener}">
<h:outputText value="&lt; "/>
<f:param name="#{pageNumber}" value="#{bean.pageCalc.pageIndex - 1}" />
<f:param name="maxResults" value="#{bean.maxResults}" />
</s:link>
</h:panelGroup>

<h:outputText value="... " rendered="#{bean.pageCalc.leftDots}" />

<h:panelGroup rendered="#{bean.pageCalc.pageListSize > 1}">
<ui:repeat value="#{bean.pageCalc.pageList}" var="iter">
<s:link reRender="#{dataListener}"
value="#{iter}"
rendered="#{bean.pageCalc.pageIndex != iter}">
<f:param name="#{pageNumber}" value="#{iter}"/>
<f:param name="maxResults" value="#{bean.maxResults}" />
</s:link>

<h:outputText value=" #{iter} "
rendered="#{bean.pageCalc.pageIndex == iter}"
style="font-weight:bold;"/>
</ui:repeat>
</h:panelGroup>

<h:outputText value="... " rendered="#{bean.pageCalc.rightDots}" />

<h:panelGroup rendered="#{bean.pageCalc.rightArrows}">
<s:link reRender="#{dataListener}">
<h:outputText value="&gt; "/>
<f:param name="#{pageNumber}" value="#{bean.pageCalc.pageIndex + 1}" />
<f:param name="maxResults" value="#{bean.maxResults}" />
</s:link>

<s:link reRender="#{dataListener}">
<h:outputText value="&gt;&gt; "/>
<f:param name="#{pageNumber}" value="#{bean.pageCalc.lastPageIndex}" />
<f:param name="maxResults" value="#{bean.maxResults}" />
</s:link>
</h:panelGroup>
</div>

</ui:composition>


Code for PageCalc.java:


package com.inventasoft.ui;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
* For number of steps=2, lastPageIndex=8:
* pageIndex=1 1 2 3 ... > >>
* pageIndex=2 << < 1 2 3 4 ... > >>
* pageIndex=3 << < 1 2 3 4 5 ... > >>
* pageIndex=4 << < ... 2 3 4 5 6 ... > >>
* pageIndex=5 << < ... 3 4 5 6 7 ... > >>
* pageIndex=6 << < ... 4 5 6 7 8 > >>
* pageIndex=7 << < ... 5 6 7 8 > >>
* pageIndex=8 << < ... 6 7 8
*
* Left arrows: pageIndex > 1
* Left dots: pageIndex > (#steps + 1)
* Right dots: pageIndex < (lastPageIndex - (#steps + 1))
* Right arrows: pageIndex < (lastPageIndex - 1)
*
* Loop start: pageIndex - #steps < 1 ? 1 : pageIndex - #steps
* Loop end: pageIndex + #steps > lastPageIndex ?
* lastPageIndex : pageIndex + #steps
*
* @author SN
*/
public class PageCalc implements Serializable {
public static final int DEFAULT_PAGE_SIZE = 8;
public static final int DEFAULT_STEP_SIZE = 10;
public static final int MIN_PAGE_SIZE = 2;
public static final int MAX_PAGE_SIZE = 100;
private int pageIndex;
private int pageSize;
private int lastPageIndex;
private Long total;
private int numSteps = DEFAULT_STEP_SIZE;
private boolean leftDots;
private boolean rightDots;
private boolean leftArrows;
private boolean rightArrows;
private int startIndex;
private int endIndex;
private int firstResult;
private List pageList;

public void setUpPaging(int index, int size) {
int myIndex = index;

if (myIndex <= 0) {
myIndex = 1;
}

pageIndex = myIndex;

// Make sure that the page size is within its limits
if (size < MIN_PAGE_SIZE || size > MAX_PAGE_SIZE) {
pageSize = DEFAULT_PAGE_SIZE;
} else {
pageSize = size;
}

// calculate the number of pages and set last page index
lastPageIndex = (int) Math.ceil(total.doubleValue() / pageSize);

// make sure that the page index in not out of scope
if (pageIndex > lastPageIndex) {
pageIndex = 1;
}

firstResult = pageSize * (pageIndex - 1);

leftArrows = pageIndex > 1;
leftDots = pageIndex > (numSteps + 1);
rightDots = pageIndex < (lastPageIndex - numSteps);
rightArrows = pageIndex < lastPageIndex;
if (pageIndex - numSteps < 1) {
startIndex = 1;
} else {
startIndex = pageIndex - numSteps;
}
if (pageIndex + numSteps > lastPageIndex) {
endIndex = lastPageIndex;
} else {
endIndex = pageIndex + numSteps;
}
setUpPageList(startIndex, endIndex);
}

private void setUpPageList(int start, int end) {
pageList = new ArrayList();
for (int i = start; i <= end; i++) {
pageList.add(new Integer(i));
}
}

public int getPageIndex() {
return pageIndex;
}

public int getLastPageIndex() {
return lastPageIndex;
}

public long getTotal() {
return total;
}

public boolean isRightDots() {
return rightDots;
}

public void setRightDots(boolean rightDots) {
this.rightDots = rightDots;
}

public int getPageSize() {
return pageSize;
}

public int getNumSteps() {
return numSteps;
}

public boolean isLeftDots() {
return leftDots;
}

public boolean isLeftArrows() {
return leftArrows;
}

public int getStartIndex() {
return startIndex;
}

public int getEndIndex() {
return endIndex;
}

public void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
}

public boolean isRightArrows() {
return rightArrows;
}

public List getPageList() {
return pageList;
}

public int getPageListSize() {
if (pageList != null) {
return pageList.size();
}
return 0;
}

public int getFirstResult() {
return firstResult;
}

public void setTotal(Long total) {
this.total = total;
}
}