Tuesday, August 6, 2013

Custom Sort feature at Salesforce - Part I

In Salesforce we often face cases where we need to customize Sort feature. Sort means sorting of records in related list of any parent record. For example: Opportunity has its child records of Opportunity Line Items. In related list of OLIs at an Opportunity detail page, standard 'Sort' button is present. Clicking on this sort button navigates to a Salesforce standard page where we can sort based on only Product names.


But we can not override Sort button with custom Visualforce page for our customized features. So I took a workaround where I can sort records based on say 3 fields of OLI while click sort button .
First, create 2 custom fields at Opportunity.Sorted_By_field__c and Sorted_By_Asc__c


1- Override detail page of Opportunity with custom to display custom Related list
2- Click on custom button 'Sort OLI' at that related list. Go to your page.
N.B. You may add custom Sort button at standard related list of OLI also which will lead to your page for custom sort. But when you sort there and come back then sorted order will not be shown in standard related list.
3- Sort then. click Save Sort there.
4- back to Opportunity detail page
[Also, See bottom most section of this post]


1- Override detail page of Opportunity with custom VF page, where my custom button ' Sort OLI' will be present.






VF page 'CustomRelatedListForOLI':


<!-- VisualForce Page Responsible for Displaying Related Lists of OLI along with Action Buttons for Page -->
<apex:page standardController="Opportunity" extensions="SorterController">   
  <apex:form >   
    <!-- To display the Detail Page Record -->
    <apex:detail relatedList="true" inlineEdit="true" showChatter="true" />
   
    <!-- To Display Page Block -->
    <apex:pageblock id="pageBlock" >
        <apex:pageblockButtons>
            <apex:commandButton value="Sort OLI" action="{!gotoCustomSortPage}"/>
        </apex:pageblockButtons>
        <!-- To Display Page Block Table -->
        <apex:pageBlockTable id="pbtable" value="{!sortedList}" var="s" >
           
            <!-- Displaying the Field Values -->
            <apex:column value="{!s.Quantity}"/>
            <apex:column value="{!s.TotalPrice}"/>
        </apex:pageBlockTable>
    </apex:pageblock>
  </apex:form>
</apex:page>


And the controller of this page where code for button lies is as follow:

public class SorterController{    

    private String sortedBy = null;
    private Boolean sortAscending = null;
    private AP_SortHelper sorter = new AP_SortHelper();
    public List sortedList {get;set;}

    private String oppid;
    
    /*standard controller*/
    public SorterController(ApexPages.StandardController controller) {
     oppid = ApexPages.CurrentPage().getParameters().get('Id');
     Opportunity currOpp = [SELECT Id,Sorted_By_field__c, Sorted_By_Asc__c FROM Opportunity WHERE Id = :oppid ];
     sorter.originalList = [SELECT Quantity, TotalPrice FROM OpportunityLineItem WHERE OpportunityId =:oppid];
     if(currOpp.Sorted_By_field__c != null &&  currOpp.Sorted_By_field__c != '')
         sortedList = (List)sorter.getSortedList(currOpp.Sorted_By_field__c, currOpp.Sorted_By_Asc__c, oppid);
    
    }
    /*controller . This class is as controller of page 'DemoSort'*/
    public SorterController() {
        oppid = ApexPages.CurrentPage().getParameters().get('Id');
        sorter.originalList = [SELECT Quantity, TotalPrice FROM OpportunityLineItem WHERE OpportunityId =:oppid];
    }
    /*Method for Sort button at VF 'CustomRelatedListForOLI'*/
    public PageReference gotoCustomSortPage() {
       return new PageReference('/apex/DemoSort?Id='+oppid);
    }
    /*Method for save button at VF page 'DemoSort'*/
    public PageReference Save() {
        PageReference pr = new PageReference('/apex/CustomRelatedListForOLI?id='+oppid+'&sfdc.override=1') ;
        pr.setRedirect(false);
        sorter.saveOpp(oppid);
        return pr;
    }
    public PageReference SortByQuantity() {
        setSortedBy('Quantity');
        sortedList = (List) sorter.getSortedList('Quantity', sortAscending, oppid);
        return null;
    }
    public PageReference SortByTotalPrice() {
        setSortedBy('TotalPrice');
        sortedList = (List) sorter.getSortedList('TotalPrice', sortAscending,oppid);
        return null;
    }
    public List getList() {
        if (sortedList == null) {
            SortByQuantity();
        }
        return sortedList;
    }
    private void setSortedBy(String value) {
        if (sortedBy == value) {
             sortAscending = !sortAscending;
        } else {
            sortAscending = true;
        }
        sortedBy = value;
    }
}


The class called from this class is:

public class AP_SortHelper {     // 
    public Map listPosition = null;    // >
    public Map> sortedFieldValuesPerFieldName = null;     // >>
    public Map>> sObjectIDsPerFieldNames = null;
    public String OpportuniId;
    public Boolean ascIs;
    public String fieldIs;
// Properties
    public List originalList {get; set;}
    
// Constructor
    public AP_SortHelper() {
        originalList = null;
    }
    // Public Method
    public List getSortedList(String fieldName, Boolean ascending, String oppId) {
        if (originalList == null) {
            // Assume that originalList has a not NULL value.
            // If the class who uses this method has not assigned a value it will get an Exception which
            //    needs to be handled by the calling class.            // Force the exception...
            originalList.clear();
        }        // Make field name uppercase
        fieldName = fieldName.toUpperCase();        // Get sorted list
        OpportuniId = oppId;
        return makeSortedList(fieldName, ascending);
    }
    public List getSortedList(List originalList, String fieldName, Boolean ascending, String oppId) {
        this.originalList = originalList;
        sortedFieldValuesPerFieldName = null;
        OpportuniId = oppId;
        return getSortedList(fieldName, ascending,OpportuniId);
    }
    
// Private Methods
    public void InitializeFieldName(String fieldName) {
        String sObjectID;
        Integer position;
        String fieldValue;
        List sObjectIDs = null;
        Set valuesForFieldSet = null;    // Sets automatically omit duplicate values 
        List valuesForFieldList = null;
        Map> sObjectIDsPerFieldValues = null;
        
        // Make sortedFieldValuesPerFieldName
        if (sortedFieldValuesPerFieldName == null) {
            listPosition = new Map();
            sortedFieldValuesPerFieldName = new Map>();
            sObjectIDsPerFieldNames = new Map>>();
        }
        
        // Get (or create) map of sObjectIDsPerFieldValues
        sObjectIDsPerFieldValues = sObjectIDsPerFieldNames.get(fieldName);
        if (sObjectIDsPerFieldValues == null) {
            sObjectIDsPerFieldValues = new Map>();
            sObjectIDsPerFieldNames.put(fieldName, sObjectIDsPerFieldValues);
        }
        if (!sortedFieldValuesPerFieldName.keySet().contains(fieldName)) {
            // Objects need to be initialized
            position = 0;
            valuesForFieldSet = new Set();
            listPosition = new Map();
            
            for (sObject sObj : originalList) {
                sObjectID = sObj.ID;
                fieldValue = getValue(sObj, fieldName);
                
                // Add position to list
                listPosition.put(sObjectID, position++);
                
                // Add the value to the set (sets rather than lists to prevent duplicates)
                valuesForFieldSet.add(fieldValue);
                
                // Get (or create) map of sObjectIDs
                sObjectIDs = sObjectIDsPerFieldValues.get(fieldValue);
                if (sObjectIDs == null) {
                    sObjectIDs = new List();
                    sObjectIDsPerFieldValues.put(fieldValue, sObjectIDs);
                }
                
                // Add ID to sObjectIDs
                sObjectIDs.add(sObjectID);
            }
            
            // Sort set items (Need to convert to list)
            valuesForFieldList = new List();
            valuesForFieldList.addAll(valuesForFieldSet);
            valuesForFieldList.sort();
            
            // Now add it to the map.
            sortedFieldValuesPerFieldName.put(fieldName, valuesForFieldList);
        }
    }
    public List makeSortedList(String fieldName, Boolean ascending) {
        Integer position;
        List sObjectIDs = null;
        List valuesForFieldList = null;        // Initialize objects
        InitializeFieldName(fieldName);        // Get a list of the same type as the "originalList"
        List outputList = originalList.clone();
        outputList.clear();        // Get a list of sorted values
        valuesForFieldList = sortedFieldValuesPerFieldName.get(fieldName);
        
        // for each sorted value
        for (String fieldValue : valuesForFieldList) {
            // Get lisft of IDs
            sObjectIDs = sObjectIDsPerFieldNames.get(fieldName).get(fieldValue);
            
            // for each ID
            for (String ID : sObjectIDs) {
                // Get position in originalList
                position = listPosition.get(ID);                // Add each sObject to the list.
                if ((ascending) || (outputList.size()==0)) {
                    outputList.add(originalList[position]);
                } else {
                    outputList.add(0, originalList[position]);
                }
            }
        }
        System.debug('\n\nI am returning the output = '+fieldName+'and = \n'+ascending+'\n');
       
        fieldIs = fieldName;
        ascIs = ascending;
        

        return outputList;
    }
    public void saveOpp(String OpportuniIdp){
        Opportunity currOpp = [SELECT Id FROM Opportunity WHERE Id =:OpportuniIdp];
        currOpp.Sorted_By_field__c = fieldIs;
        currOpp.Sorted_By_Asc__c = ascIs;
        
        upsert currOpp;
        
    
    }
    public String getValue(sObject sObj, String fieldName) {
        // This returns the sObject desired in case the fieldName refers to a linked object.
        Integer pieceCount;
        String[] fieldNamePieces;
        
        fieldNamePieces = fieldName.split('\\.');
        pieceCount = fieldNamePieces.size();
        for (Integer i = 0; i < (pieceCount-1); i++) {
            sObj = sObj.getSObject(fieldNamePieces[i]);
        }
        return String.valueOf(sObj.get(fieldNamePieces[pieceCount-1]));
    }
 
    
}

2- Notice that Sort button click will navigate to DemoSort, a VF page where customization for sorting based on 2/3 columns present. In below image, you can see table at DemoSort page where OLIs care there and each column has a button which gives facility to sort by that field.Finally clicking on save returns to original Opportunity detail page and related list shows sorted list by our choice which we have made at DemoSort page.
VF page DemoSort:
<apex:page  controller="SorterController"  >

 <apex:form >
    <apex:pageBlock >
    <apex:commandButton action="{!Save}"  value="Save Sort" />
      <apex:pageBlockSection columns="1" ID="AjaxTable">
        <apex:datatable value="{!List}" var="acc" Border="1" cellspacing="1" cellpadding="5">
          <apex:column >
            <apex:facet name="header">
              <apex:commandButton action="{!SortByQuantity}" value="Sort By Quantity" rerender="AjaxTable" />
            </apex:facet>
            <apex:outputText value="{!acc.Quantity}" />
            </apex:column>
          <apex:column >
            <apex:facet name="header">
            <apex:commandButton action="{!SortByTotalPrice}" value="Sort By Price" rerender="AjaxTable" />
            </apex:facet>
            <apex:outputText value="{!acc.TotalPrice}" />
          </apex:column>
        </apex:datatable>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>


Controller of this class is same as above class 'SorterController' .
Drawback of this method:
1- as you override , so inline edit of Opportunity detail page is gone
2- Edit Layout of Opportunity detail page link is gone.
So, another way may be:
1- Add a section from page layout of Opportunity, then add a VF page at that section ,which shows custom Related list of OLI. And there is button 'Sort Oli'.
2- Click on custom button 'Sort OLI' at that related list. Go to your page.
N.B. You may add custom Sort button at standard related list of OLI also which will lead to your page for custom sort. But when you sort there and come back then sorted order will not be shown in standard related list.
3- Sort then. click Save Sort there.
4- back to Opportunity detail page.
Read post: Custom Sort feature at Salesforce - Part II

No comments:

Post a Comment