Pages

Monday, September 15, 2008

How to support paging of data in your Visualforce Pages?

A while back I was working on a project in which I had to a design a search interface for my Custom object. The search result could get pretty big at times and I always needed to over come the limitations and show large number of rows in the screen.

At that point I was like "gee if I could use Paging with the pageBlockTable tag" and then I decided to write it myself. So here is goes:

As we all know we should have two pieces of code, one for the Controller and the other is for the page tags.


The whole idea here is to create two lists on the controller object which one keeps the original records (all records) and the second one temporarily keeps only what should be shown to the user.
With defining a few parameters such as Total Pages, Current Page Number and Page size I managed to simulate paging successfully.

Here is what goes to the page:


<apex:page controller="MyPagingController" tabStyle="Account">
<apex:sectionHeader title="Accounts List with Paging"></apex:sectionHeader>
<apex:form >
<apex:pageBlock title="" id="pageBlock">
<apex:pageBlockButtons location="top">
<apex:commandButton value="View" action="{!ViewData}" id="theButton" rerender="pageBlock"></apex:commandButton>
</apex:pageBlockButtons>
<apex:pageMessages ></apex:pageMessages>
<div align="right" style="display:{!IF(NOT(ISNULL(accounts)),'block','none')}">
<font size="1pt">Page #:&nbsp;<apex:outputLabel value="{!PageNumber}"/>&nbsp;out of&nbsp;<apex:outputLabel value="{!totalPageNumber}"/>&nbsp;&nbsp;&nbsp;&nbsp;</font>
<apex:commandButton value="Previous" action="{!previousBtnClick}" disabled="{!previousButtonEnabled}" reRender="pageBlock"></apex:commandButton>
<apex:commandButton value="Next" action="{!nextBtnClick}" reRender="pageBlock" disabled="{!nextButtonDisabled}" ></apex:commandButton>
</div>
<br/><br/>
<apex:pageBlockTable value="{!accounts}" var="a" rendered="{!NOT(ISNULL(accounts))}" rows="{!PageSize}">
<apex:column >
<apex:facet name="header">Account Name</apex:facet>
<apex:outputLink value="/{!a.Id}" target="_blank">{!a.Name}</apex:outputLink>
</apex:column>
<apex:column value="{!a.Phone}"></apex:column>
<apex:column value="{!a.Fax}"></apex:column>
</apex:pageBlockTable>
<div align="right" style="display:{!IF(NOT(ISNULL(accounts)),'block','none')}">
<br/>
<font size="1pt">Page #:&nbsp;<apex:outputLabel value="{!PageNumber}"/>&nbsp;out of&nbsp;<apex:outputLabel value="{!totalPageNumber}"/>&nbsp;&nbsp;&nbsp;&nbsp;</font>
<apex:commandButton value="Previous" action="{!previousBtnClick}" disabled="{!previousButtonEnabled}" reRender="pageBlock"></apex:commandButton>
<apex:commandButton value="Next" action="{!nextBtnClick}" reRender="pageBlock" disabled="{!nextButtonDisabled}" ></apex:commandButton>
</div>

</apex:pageBlock>
</apex:form>
</apex:page>




Below is the code for Controller:


public class MyPagingController {

private List accounts;

private List pageAccounts;

private Integer pageNumber;

private Integer pageSize;

private Integer totalPageNumber;

public Integer getPageNumber()

{

return pageNumber;

}

public List getAccounts()

{

return pageAccounts;

}

public Integer getPageSize()

{

return pageSize;

}

public Boolean getPreviousButtonEnabled()

{

return !(pageNumber > 1);

}

public Boolean getNextButtonDisabled()

{

if (accounts == null) return true;

else

return ((pageNumber * pageSize) >= accounts.size());

}

public Integer getTotalPageNumber()

{

if (totalPageNumber == 0 && accounts !=null)

{

totalPageNumber = accounts.size() / pageSize;

Integer mod = accounts.size() - (totalPageNumber * pageSize);

if (mod > 0)

totalPageNumber++;

}

return totalPageNumber;

}

public MyPagingController()

{

pageNumber = 0;

totalPageNumber = 0;

pageSize = 20;

ViewData();

}

public PageReference ViewData()

{

accounts = null;

totalPageNumber = 0;

BindData(1);

return null;

}

private void BindData(Integer newPageIndex)

{

try

{

if (accounts == null)

accounts = [Select id, Name, Phone, Fax from Account limit 1000];

pageAccounts = new List();

Transient Integer counter = 0;

Transient Integer min = 0;

Transient Integer max = 0;

if (newPageIndex > pageNumber)

{

min = pageNumber * pageSize;

max = newPageIndex * pageSize;

}

else

{

max = newPageIndex * pageSize;

min = max - pageSize;

min = (min <>

}

for(Account a : accounts)

{

counter++;

if (counter > min && counter <= max)

pageAccounts.add(a);

}

pageNumber = newPageIndex;

if (pageAccounts == null || pageAccounts.size() <= 0)

ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Data not available for this view.'));

}

catch(Exception ex)

{

ApexPages.addmessage(new ApexPages.message(ApexPages.severity.FATAL,ex.getMessage()));

}

}

public PageReference nextBtnClick() {

BindData(pageNumber + 1);

return null;

}

public PageReference previousBtnClick() {

BindData(pageNumber - 1);

return null;

}

}




25 comments:

  1. Nice code, Sam. Thank you!
    I appreciate the work you've done!

    BTW, is there a way to bind a custom non-SF table (say MyProduct) to the List<> collection?

    ReplyDelete
  2. Sure you can create an Apex class and keep them in a collection. Apex class are drived from sObject so you can bind them with pageBlockTable or other components.

    ReplyDelete
  3. Hi again, Sam.
    It seems that I'm going in the wrong direction, and I can't figure out how to make the custom list work. Could you please have a look at my code?

    Here's the Apex class that is supposed to represent the collection item:

    public class MyProduct {
    private String id;
    private String name;
    private String price;
    public String getId() { return id; }
    public String getName() { return name; }
    public String getPrice() { return price; }

    public MyProduct()
    {
    id = '0';
    name = '';
    price = '';
    }
    }

    Here's the S-Control controller code:
    public class ProductsController {
    private List<MyProduct> products;
    public List<MyProduct> getProducts() { return products; }

    private void BindData()
    {
    String strQuery = 'SELECT Id, Name, Price FROM MyProduct';
    products = database.query(strQuery); // error occurs here!
    }

    }

    I get an error when I try to bind the DB query results to the products collection:
    Error: Compile Error: Illegal assignment from LIST:SObject to LIST:MyProduct at line...

    Can you tell what am I doing wrong?

    ReplyDelete
  4. Trying to run your code for paging of data. Receiving a compile error for the controller. "Compile Error: expecting a left angle bracket, found 'accounts' at line 2 column 14". Just starting with VF, I need this functionality but I am not yet skilled enough to see the trouble in the code.

    ReplyDelete
  5. Nice post, appreciate the code samples to get ideas from.

    Btw, I saw paging is supported natively from Winter'09. =)

    ReplyDelete
  6. Nice blog Sam.

    Paging is now native - check out my blog post on this - it simplifies your code quite a bit :-)

    Regards,
    Jon

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Hello Sam nice post!

    What about the code if records are more than 1000?

    thanks

    ReplyDelete
  9. Thats a good question.. what if the count of records is > 1000. How can we fetch 10,000 records from SOQL. Becoz the max limit is 10000

    ReplyDelete
  10. Great job bro....I appreciate your effort

    ReplyDelete
  11. cool man,i like your articles,great work

    ReplyDelete
  12. I have been visiting various blogs for my term papers writing research. I have found your blog to be quite useful. Keep updating your blog with valuable information... Regards

    ReplyDelete
  13. can u give me code to show records in multiple pages using next and previous buttons?

    ReplyDelete
  14. Sorry boss, Thank u boss... Great job.....

    ReplyDelete
  15. Hi
    I want to display a blank table with 10 rows and 3 columns ... Is there any wayto do this?

    ReplyDelete
  16. you can use readonly="true" in your page

    == It will increase your data from 1000 to 10000.

    ReplyDelete
  17. Hey great post. This is very useful indeed.
    Also do you know if this can be used with a Apex:dataTable instead or does Apex:datatable provide any out of the box means to provide a paging mechanism.

    ReplyDelete
  18. You Rock man!! Proud of you!

    ReplyDelete
  19. Hey Really Good Post.
    Just wanted to know.There is a missin code in controller class.

    min = min<>....which is giving compliation error.Can u plz rectify it....Can i omit it.Would it effect functionality.

    ReplyDelete
  20. Hi All,
    I am using jstree.js to display hierarachical data on treeview.
    However because of the size of the data, tree takes ages to load on the
    browser.
    For that reason, I want to build custom paging solution similar to custom
    paging in datagrid/gridview...
    Any pointer/help for how to build custom paging for Tree?
    Thanks,
    Phuong vo

    ReplyDelete
  21. For some reason, Previous Page for me doesn't respond, but Next page does. I look at debug and it doesn't even show it as Invoking. But the action and other settings are precisely bound to the Controller. Any ideas?

    ReplyDelete
  22. BTW, here is the VF code used for Next/Previous page
    Previous Page
              
    Next Page

    and the APEX code for them:
    public void NextPage() {
    GetSelected();
    nPage += 1;
    system.debug('nPage: ' + nPage);
    } // END NextPage
    public void PreviousPage() {
    GetSelected();
    nPage -= 1;
    system.debug('nPage: ' + nPage);
    } // END PreviousPage

    ReplyDelete