Saturday, November 19, 2011

Custom Pagination in Visualforce made eady

This was an assignment given by one of my friend on Nov 12th, 2011. I thought it was an easy task, just making a visual force page with pagination it, but to my surprise, it was not -:(

So, here is the objective, putting it in exact words of my dear friend:

“You have accounts and a number of opportunities at different stages are associated to them. You have to design an inline Visualforce page on account detail page that shows a dropdown containing various opportunity stages. When user selects any of the listed stage, the PageBlockTable should show the all the Opportunities with the selected stage.”

This was the basic requirement, now comes the tough part:
      1. It should also use the pagination with first, last, previous, next and page size options.
      2. It should also use sorting and the entire functionality should be ajaxified.

I started designing the page and finished the basic functionality in 2 hours. Then came the pagination part for which I knew I cannot use standard list controllers, so I decided to use a custom class, SObjectPaginator from the package Apex-lang(http://code.google.com/p/apex-lang/). All thanks to Richard Vanhook for developing Apex-lang and making our lives easier.

It’s very easy to use to SObjectPaginator, you just need to implement the interface and the method handlePageChange(List newPage) and call the SObjectPaginator.setRecords(List) to set the records in pagination.

Solution: Here is the screenshot of what came out after 8 hours of hard work.

Inline visualforce page on account detail page




The Visualforce Page:
<apex:page standardController="Account" extensions="OpportunityOnAccountDetailExtension">
  <apex:form >
    <apex:pageBlock mode="edit" >
      <apex:pageBlockSection columns="1">
         <apex:outputText >
           <B>Select an Opportunity stage to view opportunities related to this account:</B>
         </apex:outputText>
         
   <apex:actionRegion >
            <apex:selectList size="1" multiselect="false" value="{!selectedStage}"  >
                <apex:selectOptions value="{!Items}"  />
                   <apex:actionSupport event="onchange"
                            action="{!handleAccountSelected}"
                            rerender="pbs1"
                            status="status"/ >
            </apex:selectList>
         </apex:actionRegion>
      </apex:PageBlockSection>
      
 <apex:pageBlockSection columns="1" id="pbs1">
      <apex:actionstatus id="status" startText="testing...">
        <apex:facet name="stop">
           Opportunity Stage Selected: <apex:outputText value="{!selectedStage}" />
    
  <apex:pageBlockTable value="{!accounts}" var="opp" id="pb1">
            <apex:column value="{!opp.obj.name}" />
            <apex:column value="{!opp.obj.StageName}" />
            <apex:column value="{!opp.obj.Amount}" />
            <apex:column value="{!opp.obj.CloseDate}" />                        
                    
            <apex:facet name="footer">
              <apex:outputPanel layout="block">
 
    <!-- PAGE X OF X IN X RESULTS -->
                Page {!IF(paginator.pageCount=0, 0, paginator.pageNumberDisplayFriendly)} of {!paginator.pageCount} in {!paginator.recordCount} results
                &nbsp;&nbsp;       
                
    <!-- FIRST --> 
                <apex:outputText value="First"
                                 rendered="{!NOT(paginator.hasPrevious)}" />
                    <apex:commandLink value="First"
                                      rendered="{!paginator.hasPrevious}"
                                      action="{!paginator.first}" />
                &nbsp;&nbsp;
                                    
     <!-- PREVIOUS -->    
                <apex:outputText value="Previous"
                                 rendered="{!NOT(paginator.hasPrevious)}" />
                    <apex:commandLink value="Previous"
                                      rendered="{!paginator.hasPrevious}"
                                      action="{!paginator.previous}" />
                &nbsp;&nbsp;
                                            
     <!-- PAGE SHORTCUTS -->
    <apex:repeat value="{!paginator.previousSkipPageNumbers}" var="skipPageNumber">
                    <apex:outputPanel >
                        <apex:commandLink value="{!skipPageNumber+1}" action="{!skipToPage}">
                           <apex:param name="pageNumber" id="pageNumber" value="{!skipPageNumber}" assignto="{!pageNumber}" />
                        </apex:commandLink>
              
           </apex:outputPanel>
                &nbsp;&nbsp;
                </apex:repeat>
                
    <apex:outputText style="text-decoration:none; font-weight:bold; background-color: #FFFF00"
                                 value="{!paginator.pageNumber+1}"/>
                &nbsp;&nbsp;
                <apex:repeat value="{!paginator.nextSkipPageNumbers}" var="skipPageNumber">
                    <apex:outputPanel >
                    <apex:commandLink value="{!skipPageNumber+1}" action="{!skipToPage}">
                       <apex:param name="pageNumber" id="pageNumber" value="{!skipPageNumber}" assignto="{!pageNumber}" />
                    </apex:commandLink>
                    </apex:outputPanel>
                &nbsp;&nbsp;
                </apex:repeat>
                                               
    <!-- NEXT -->
                <apex:outputText value="Next"
                                 rendered="{!NOT(paginator.hasNext)}" />
                    <apex:commandLink value="Next"
                                      rendered="{!paginator.hasNext}"
                                      action="{!paginator.next}" />
                &nbsp;&nbsp;
                                                      
    <!-- LAST -->
                <apex:outputText value="Last"
                                 rendered="{!NOT(paginator.hasNext)}" />
                    <apex:commandLink value="Last"
                                      rendered="{!paginator.hasNext}"
                                      action="{!paginator.last}" />
                &nbsp;&nbsp;
                                                                 
     <!-- Page Size Options -->
                Page Size:
                  <apex:actionRegion >
                    <apex:selectList value="{!paginator.pageSize}"
                                     size="1">
                      <apex:selectOptions value="{!paginator.pageSizeOptions}" />
                          <apex:actionSupport event="onchange"
                                              rerender="pbs1"
                                              status="status" />
                    </apex:selectList>
                  </apex:actionRegion>
              </apex:outputPanel>
            </apex:facet>
        </apex:pageBlockTable>
     </apex:facet>
      </apex:actionstatus>
 </apex:PageBlockSection>
 </apex:pageBlock>
 </apex:form>
</apex:page>


The Controller Code:



global class OpportunityOnAccountDetailExtension implements SObjectPaginatorListener {

String selectedStage;
Account acc;
    
    global List accounts   {get;set;}
    global SObjectPaginator paginator  {get;set;} 
    global Integer          pageNumber {get;set;}
    private String sortDirection = 'ASC';
    private String sortExp = 'name';
    
List oppList = new List();

    public OpportunityOnAccountDetailExtension(ApexPages.StandardController controller) {
        this.acc = (Account)controller.getRecord();
        oppList = null;
        this.accounts = new List();
        this.paginator = new SObjectPaginator(
            2,                          //pageSize
            new List{2,5,10, 25, 50, 100, 200},  //pageSizeIntegerOptions
            this                        //listener
        ); 
        //this.paginator.setRecords(getoppList());
        
    }
    
    public String getselectedStage() {
        return selectedStage;
    }
 
    public void setselectedStage(String selectedStage) {
        system.debug('%%%%%%%%%%%% ' + this.selectedStage);
        this.selectedStage = selectedStage;
        system.debug('%%%%%%%%%%%% ' + this.selectedStage);
    }
    
    public List getItems() {
            List options = new List();
            options.add(new SelectOption('','--SELECT Opportunity Stage--'));
            
            Schema.DescribeFieldResult field = Opportunity.StageName.getDescribe();
 
        for (Schema.PicklistEntry f : field.getPicklistValues())
          options.add(new SelectOption(f.getLabel(),f.getLabel()));
          
            return options;
        }
    
    /*    
    public List getoppList(){
        system.debug('$$$$$$$$$$$$$$$$$$ ' + selectedStage);
            if(selectedStage==null){
                oppList  = null;
            }else{
            oppList  = [select name, stageName, Amount, CloseDate from Opportunity where AccountId =: acc.id and StageName =: String.escapeSingleQuotes(selectedStage)];
            }
            
            return oppList;
                
    }
    */
    
   public PageReference handleAccountSelected(){
        system.debug('$$$$$$$$$$ in handleAccountSelected()');
        string sortFullExp = sortExpression  + ' ' + sortDirection;
        string query = 'select name, stageName, Amount, CloseDate from Opportunity where AccountId =';
        query = query + ' ' + acc.id + ' ' + ' ';
        query = query + 'and StageName =' + ' '  + String.escapeSingleQuotes(selectedStage) + ' '+ ' order by ' + sortFullExp;
        system.debug('################## ' + query);
        this.paginator.setRecords([select name, stageName, Amount, CloseDate from Opportunity where AccountId =: acc.id and StageName =: String.escapeSingleQuotes(selectedStage)]);
        
        return null;
    }
    
    
    public PageReference test(){
        return null;
    } 


    global void handlePageChange(List newPage){
        this.accounts.clear();
        if(newPage != null && newPage.size() > 0){
            for(Integer i = 0; i < newPage.size(); i++){
                this.accounts.add(
                    new OppWrapper(
                        (Opportunity)newPage.get(i)
                        , i + this.paginator.pageStartPosition + 1
                    )
                );
            }
        }
    }
    
    
    global PageReference skipToPage(){
        this.paginator.skipToPage(pageNumber);
        return null;
    }
    
       public String sortExpression
   {
     get
     {
        return sortExp;
     }
     set
     {
       //if the column is clicked on then switch between Ascending and Descending modes
       if (value == sortExp)
         sortDirection = (sortDirection == 'ASC')? 'DESC' : 'ASC';
       else
         sortDirection = 'ASC';
       sortExp = value;
     }
   }

 public String getSortDirection()
 {
    //if not column is selected 
    if (sortExpression == null || sortExpression == '')
      return 'ASC';
    else
     return sortDirection;
 }

 public void setSortDirection(String value)
 {  
   sortDirection = value;
 }
 
    
       
    public class OppWrapper{
        
        public Opportunity obj{get;set;}
        public Integer serialNumber{get;set;}
        public Boolean selected{get;set;}
        
        public OppWrapper(Opportunity obj, Integer serialNumber){
            this.obj = obj;
            this.serialNumber = serialNumber;
            system.debug('@@@@@@@@@@@@@@@@ ' + obj.Name);
        }

        
    }
    
    
}

16 comments:

  1. Good Job... Very common requirement for number of projects. It will be great help !! :)

    ReplyDelete
  2. Thanks for this awesome post!!

    ReplyDelete
  3. Error: OpportunityOnAccountDetailExtension Compile Error: Invalid type: SObjectPaginator at line 7 column 12

    ReplyDelete
  4. pls give me reply as early as possible

    ReplyDelete
  5. Hi Raja,
    Thanks for your interest in the post!
    Did you install the package Apex Lang(http://code.google.com/p/apex-lang/) which is a pre-requisite for the functionality to work?
    -Sankalp

    ReplyDelete
  6. Hi I need to Show the code for interface. I installed package still i did not have chance to see the inteface(al.SObjectPaginatorListener) code if possible please send it. al.SObjectPaginatorListener

    ReplyDelete
  7. kk, Sankalp I got output. It is excellent thank u

    ReplyDelete
  8. This is really good.
    Do you have test class for this? so we can move the controller to Production.
    Thanks,

    ReplyDelete
  9. Sorry for late reply, will create the test class and send it to you, let me know you emailId.

    ReplyDelete
    Replies
    1. This is so wonderful! Two questions:
      1. How can I change this to sort by CloseDate? I've tried changing where it says Name to CloseDate but that doesn't seem to be working...am I missing something obvious?
      2. Can you post or send me the test class to get this working? I'm at the point right now that I'm barely able to get a test class for a trigger working and this is far beyond that. My email ID if you need it is mrshowell @ gmail . com

      Thanks!
      Amanda

      Delete
  10. I got the sorting worked out but is there any way that you could please post your test class? I'm having a terrible time getting up to 75% coverage.

    Thanks!
    Amanda

    ReplyDelete
  11. hi only sorting is working i have installed the production unmanage package how to install zip file

    ReplyDelete

Your feedback is always appreciated, thanks!!