Регистрация  |  Вход

Пагинация на Visualforce странице. Pagination using StandardSetController

В связи с тем, что salesforce нас жестко ограничивает в количестве (1000 единиц в списке) и размере (135 кб view state) данных отображаемых на visuslforce странице использование пагинации является жизненной необходимостью. Планировать ее использование необходимо сразу, чтобы в дальнейшем не оправдываться перед заказчиком когда страница свалится на проде при попытке отобразить реальные данные.





Пример использования пагинации приведу на нашей странице Books из приложения Library. Сделаем так, чтобы наша таблица с книгами содержала не более десяти книг. Вот сам код обновленной страницы и контроллера:

BooksController.cls

public with sharing class BooksController {


public String SearchBookKeyword { get; set; }
public Integer noOfRecords{get; set;}
public Integer size{get;set;}

public List<Book__c> Books { get{
return (List<Book__c>)setCon.getRecords();
} set; }

public ApexPages.StandardSetController setCon {
get{
if(setCon == null){
size = 10;
string queryString = 'SELECT Id, Name, ISBN__c, Date_Of_Publication__c, (SELECT Id, Author__c, Author__r.Name FROM Book_Author__r) FROM Book__c WHERE Name LIKE \'%'+(SearchBookKeyword == null ? '' : SearchBookKeyword)+'%\'';
SYSTEM.DEBUG('queryString - '+queryString);
setCon = new ApexPages.StandardSetController(Database.getQueryLocator(queryString));
setCon.setPageSize(size);
noOfRecords = setCon.getResultSize();
}
return setCon;
}set;
}

public void SearchBooks() {
setCon = null;
setCon.setPageNumber(1);
}

}

Books.page
<apex:page controller="BooksController" >


<h1>Books</h1>

<apex:form >

<apex:inputText value="{!SearchBookKeyword}" />
<apex:commandButton action="{!SearchBooks}" value="Search" rerender="BookTableBox" status="SearchStatus"/>
<apex:actionStatus id="SearchStatus">
<apex:facet name="start"><img src="/img/loading.gif" alt="" /></apex:facet>
<apex:facet name="stop"></apex:facet>
</apex:actionStatus>

<apex:outputPanel layout="block" id="BookTableBox">
<apex:panelGrid columns="7">
<apex:commandButton action="{!setCon.first}" status="PaginationStatus" reRender="BookTableBox" value="|<" disabled="{!!setCon.hasPrevious}" title="First Page"/>
<apex:commandButton action="{!setCon.previous}" status="PaginationStatus" reRender="BookTableBox" value="<" disabled="{!!setCon.hasPrevious}" title="Previous Page"/>
<apex:commandButton action="{!setCon.next}" status="PaginationStatus" reRender="BookTableBox" value=">" disabled="{!!setCon.hasNext}" title="Next Page"/>
<apex:commandButton action="{!setCon.last}" status="PaginationStatus" reRender="BookTableBox" value=">|" disabled="{!!setCon.hasNext}" title="Last Page"/>
<apex:outputText >{!(setCon.pageNumber * size)+1-size}-{!IF((setCon.pageNumber * size)>noOfRecords, noOfRecords,(setCon.pageNumber * size))} of {!noOfRecords}</apex:outputText>
<apex:actionStatus id="PaginationStatus">
<apex:facet name="start"><img src="/img/loading.gif" alt="" /></apex:facet>
<apex:facet name="stop"></apex:facet>
</apex:actionStatus>
</apex:panelGrid>
<table id="BookTable">
<tr>
<th>Name</th>
<th>ISBN</th>
<th>Date Of Publication</th>
<th>Author</th>
<th></th>
</tr>
<apex:repeat value="{!Books}" var="book">
<tr>
<td>
<apex:outputField value="{!book.Name}"/>
</td>
<td>
<apex:outputField value="{!book.ISBN__c}"/>
</td>
<td>
<apex:outputField value="{!book.Date_Of_Publication__c}"/>
</td>
<td>
<apex:repeat value="{!book.Book_Author__r}" var="author">
<apex:outputField value="{!author.Author__r.Name}"/> <br />
</apex:repeat>
</td>
<td>
<a href="#">Edit</a><br />
<a href="#">Delete</a>
</td>
</tr>
</apex:repeat>
</table>
</apex:outputPanel>

</apex:form>

</apex:page>

Собственно комментировать тут особо нечего. Рабочий пример рабочего кода. Берите и используйте.

!ОБНОВЛЕНО

Олег, мой опытный коллега по Salesforce, сделал несколько замечаний по поводу кода представленного выше. Спешу поделиться этими замечаниями с вами и произвести работу над ошибками.

- при формировании динамических SOQL запросов необходимо оборачивать строковые переменные в String.escapeSingleQuotes() для предотвращения SOQL injection

- и вообще динамические SOQL необходимо использовать в крайних случаях

Я заменил динамический SOQL в примере на обычный запрос. Ниже представлен новый пример контроллера страницы Books.cls:
public with sharing class BooksController {


public String SearchBookKeyword { get; set; }
public Integer noOfRecords{get; set;}
public Integer size{get;set;}

public List<Book__c> Books { get{
return (List<Book__c>)setCon.getRecords();
} set; }

public ApexPages.StandardSetController setCon {
get{
if(setCon == null){
size = 10;
String SearchBookKeywordCreteria = '%'+(SearchBookKeyword == null ? '' : SearchBookKeyword)+'%';
List<Book__c> BookList = [SELECT Id, Name, ISBN__c, Date_Of_Publication__c, (SELECT Id, Author__c, Author__r.Name FROM Book_Author__r) FROM Book__c WHERE Name LIKE :SearchBookKeywordCreteria LIMIT 10000];
setCon = new ApexPages.StandardSetController(BookList);
setCon.setPageSize(size);
noOfRecords = setCon.getResultSize();
}
return setCon;
}set;
}

public void SearchBooks() {
setCon = null;
setCon.setPageNumber(1);
}

}

!ОБНОВЛЕНО

Еще один интересный факт, который нужно знать. (Почему документацию нужно читать дословно, а не по диагонали :) ). StandardSetController может обрабатывать максимум 10 000 записей и всё! Если в параметры передать List объектов более 10 000, то List просто обрежется до данной величины, если же передать динамический SOQL, то получите исключение. На одном проекте я столкнулся с данной проблемой - мне надо было сделать навигацию по объекту содержащему более 50 000 записей. Сначала я ломал голову на данной задачей, придумывал пути обхода. А в итоге пришел к старому выводу - если попал в лимиты, значит плохо спроектировал приложением. И правда, какой пользователь будет постранично пролистывать более 10 000 записей. Лучше уточнить критерии поиска и сузить результат до 100-300 записей. Правильно - добавляем фильтрацию или поиск и скармливаем StandardSetController небольшую пачку записей, в итоге проблема разрулилась сама собой. Лимиты в Salesforce придуманы не для того чтобы помешать вам работать, а построены исходя из здравого смысла.