Reusable pagination in Visualforce page using standardsetcontroller

    It is a common requirement to paginate over a set of data in Visualforce page. There are different methods to implement a custom pagination in Visualforce pages. Most common methods are standardsetcontroller pagination and offset pagination. In this article we will go over standardsetcontroller pagination and its advantages.

Standardsetcontroller pagination

    In standardsetcontroller pagination, you reuse the methods of standardsetcontroller provided by Salesforce and extend it with your own features. This pagination helps you to navigate up to 10k records. You can make this approach very reusable by creating a generic parent class with option to set query and use standardsetcontroller methods. Then the controller of the page where you need pagination should inherit this utility class. You can find a sample for reusable utility class below,

/*****************************************************************************************************************
* Name : UTIL_ParentSetController
* @author : ####
* @date : 21/04/2016
* @group : Reusable class
* @description : Virtual class with reusable methods using standardsetcontroller
******************************************************************************************************************/
public virtual class UTIL_ParentSetController {
private ApexPages.StandardSetController controller;
private String baseQuery;
public String nameSearchFld {get; set;}
//** constructor
public UTIL_ParentSetController(String baseQuery) {
this.baseQuery = baseQuery;
query();
}
//** query methods
protected void query() {
// construct the query string
String queryString = baseQuery + ' ' + getWhereClause() + ' ' + getSortClause() + ' limit 10000';
System.debug('queryString: ' + queryString);
// save pageSize
Integer pageSize = this.pageSize;
// reboot standard set controller
controller = new ApexPages.StandardSetController(Database.getQueryLocator(queryString));
// reset pageSize
this.pageSize = pageSize;
}
//** search methods
public PageReference search() {
query();
// return to same page
return null;
}
// override to construct dynamic SOQL where clause
public virtual String getWhereClause() {
if (nameSearchFld != null && nameSearchFld.trim() != ''){
return 'where Name like :nameSearchFld ';
}
else{
return '';
}
}
//** sort methods
public String sortColumn {
get {
if (sortColumn == null){
sortColumn = '';
}
return sortColumn;
}
set {
if (sortColumn != value){
sortAsc = false;
}
sortColumn = value;
}
}
public Boolean sortAsc {
get {
if (sortAsc == null){
sortAsc = false;
}
return sortAsc;
}
set;
}
public PageReference sort() {
sortAsc = !sortAsc;
query();
// return to same page
return null;
}
protected virtual String getSortClause() {
if (sortColumn == '') return '';
else return ' order by ' + sortColumn + (sortAsc ? ' asc ' : ' desc ') + ' nulls last';
}
//** pageable methods
// get records on current page
protected List<SObject> getRecords() {
return controller.getRecords();
}
public void first() {
controller.first();
}
public void previous() {
controller.previous();
}
public void next() {
controller.next();
}
public void last() {
controller.last();
}
public Boolean getHasPrevious() {
return controller.getHasPrevious();
}
public Boolean getHasNext() {
return controller.getHasNext();
}
public Integer getResultSize() {
return controller.getResultSize();
}
public Integer getPageCount() {
Integer resultSize = getResultSize();
Integer oddRecordCount = Math.mod(resultSize, pageSize);
return ((resultSize - oddRecordCount) / pageSize) + (oddRecordCount > 0 ? 1 : 0);
}
public Integer getPageNumber() {
return controller.getPageNumber();
}
public void setPageNumber(Integer pageNumber) {
controller.setPageNumber(pageNumber);
}
public Integer pageSize {
get {
if (controller != null){
pageSize = controller.getPageSize();
}
else{
// default pagesize
pageSize = 20;
}
return pageSize;
}
set {
pageSize = value;
controller.setPageSize(pageSize);
}
}
public Boolean getRenderResults() {
return (getResultSize() > 0);
}
//** update methods
public virtual PageReference save() {
return controller.save();
}
public virtual PageReference cancel() {
return controller.cancel();
}
//** pass reference to UTIL_ParentSetController component
public UTIL_ParentSetController getController () {
return this;
}
}

Main query will be passed to the constructor of this class from child controller. Thereafter if required getWhereClause() and getSortClause() methods can be overridden from the child controller. A simple example of child controller reusing this pagination for pagination over account records can be found below,

/*****************************************************************************************************************
* Name : AccountListController
* @author : ####
* @date : 21/04/2016
* @group : Controller
* @description : Controller for page with pagination extending reusable utitlity class
******************************************************************************************************************/
public class AccountListController extends UTIL_ParentSetController {
public AccountListController() {
super('SELECT Id, CreatedDate, Name, Type, NumberOfEmployees, website FROM Account');
pageSize = 10;
}
// cast the resultset
public List<Account> getAccounts() {
return (List<Account>) getRecords();
}
}

You can see that the code in the controller is very less and we are just reusing most of the pagination from parent utility class. Actual page implementation can be found below,

<!--*****************************************************************************************************
*Page Name : AccountListController
*Version : 1.0
*Created Date : 26 APR 2015
*Function : Page used for demonstrating pagination on Account object.
* 1) Paginate over Account records
*Modification Log:
* Developer Date Description
* ----------------------------------------------------------------------------
* #### 11/14/2015 Initial Creation
******************************************************************************************************-->
<apex:page showHeader="true" sidebar="true" controller="AccountListController" id="page">
<apex:form rendered="{!$ObjectType.Account.accessible}" id="form">
<!--To display page messages -->
<apex:pageMessages id="messages"/>
<apex:pageBlock title="Account Pagination" mode="edit" id="pageBlock">
<apex:pageBlockSection title="Accounts" columns="1">
<!-- Action status to display when data is loading -->
<apex:actionStatus id="loading">
<apex:facet name="start">
<p>Updating....</p>
</apex:facet>
</apex:actionStatus>
<!-- Page block table to display Account records -->
<apex:pageBlockTable value="{!Accounts}" var="account" id="resultPanel">
<apex:column headerValue="Name">
<apex:outputField value="{!account.Name}"/>
</apex:column>
<apex:column headerValue="Created Date">
<apex:outputField value="{!account.createddate}"/>
</apex:column>
<apex:column headerValue="Type">
<apex:outputField value="{!account.Type}"/>
</apex:column>
<apex:column headerValue="Website">
<apex:outputField value="{!account.website}"/>
</apex:column>
<apex:column headerValue="Employee Count">
<apex:outputField value="{!account.NumberOfEmployees}"/>
</apex:column>
</apex:pageBlockTable>
<apex:outputPanel layout="block" id="buttonPanel" styleClass="pageButtonAlign" style="text-align:center;">
<apex:commandButton action="{!first}" disabled="{!NOT(hasPrevious)}" value="|< First" rerender="resultPanel,buttonPanel,messages" status="loading"/>
<apex:commandButton action="{!previous}" disabled="{!NOT(hasPrevious)}" value="< Previous" rerender="resultPanel,buttonPanel,messages" status="loading"/>
<apex:outputText >
&nbsp;page {!IF(pageCount=0, 0, pageNumber)} of {!pageCount}&nbsp;
</apex:outputText>
<apex:commandButton action="{!next}" disabled="{!NOT(hasNext)}" value="Next >" rerender="resultPanel,buttonPanel,messages" status="loading"/>
<apex:commandButton action="{!last}" disabled="{!NOT(hasNext)}" value="Last >|" rerender="resultPanel,buttonPanel,messages" status="loading"/>
</apex:outputPanel>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>

Conclusion

    It is a very reusable method of pagination. Here you are reusing many methods from standardsetcontroller. So there is very less chance for something to go wrong. Also in order to reuse the pagination for a different object, for example contacts, the process is very simple. Only things that you need to change in the controller are the query that is being passed to parent utility class, return type and casted type of getter method. Also standardsetcontroller pagination gives ability to paginate up to 10k records, whereas custom offset pagination can be used only up to 2k records.