- The class performs quite well because the sorting is done in memory (using Maps, Sets and Lists).
- The class also caches previous sorts, so if it detects that you are re-sorting on a previously sorted field, it uses the information from the cache and does not re-sort.
- If you sort in ascending order and then request a sort in descending order for the same field, the class uses the data in the cache.
<apex:page controller="SorterContact"> <apex:form > <apex:pageBlock > <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="{!SortByName}" value="Sort By Name" rerender="AjaxTable" /> </apex:facet> <apex:outputText value="{!acc.Name}"></apex:outputText> </apex:column> <apex:column > <apex:facet name="header"> <apex:commandButton action="{!SortByPhone}" value="Sort By Phone" rerender="AjaxTable" /> </apex:facet> <apex:outputText value="{!acc.Phone}"></apex:outputText> </apex:column> <apex:column > <apex:facet name="header"> <apex:commandButton action="{!SortByAccount}" value="Sort By Account" rerender="AjaxTable" /> </apex:facet> <apex:outputText value="{!acc.Account.Name}"></apex:outputText> </apex:column> </apex:datatable> </apex:pageBlockSection> </apex:pageBlock> </apex:form> </apex:page>Nothing fancy here... Just building a page like this: When the buttons on the header are clicked, the table is sorted either ascending or descending. If the user selects a different column from the previous one, then the table gest sorted ascending by that column. But when the user clicks on the button for the same column, then the sort order gets reversed from ascending to descending and viceversa. The controller:
Couple things going in here, but that is just to make the page look nice... Nothing really to do with the sorting.
public class SorterContact { private String sortedBy = null; private Boolean sortAscending = null; private AP_SortHelper sorter = new AP_SortHelper(); private List<Contact> sortedList = null; public SorterContact() { sorter.originalList = [SELECT Name, Phone, Account.Name FROM Contact]; } public PageReference SortByName() { setSortedBy('NAME'); sortedList = (List<Contact>) sorter.getSortedList('Name', sortAscending); return null; } public PageReference SortByAccount() { setSortedBy('ACCOUNT'); sortedList = (List<Contact>) sorter.getSortedList('Account.Name', sortAscending); return null; } public PageReference SortByPhone() { setSortedBy('PHONE'); sortedList = (List<Contact>) sorter.getSortedList('Phone', sortAscending); return null; } public List<Contact> getList() { if (sortedList == null) { SortByName(); } return sortedList; } private void setSortedBy(String value) { if (sortedBy == value) { sortAscending = !sortAscending; } else { sortAscending = true; } sortedBy = value; } }Let me talk about the easy part first... There are methods that answer the calls from the commandbuttons on the page:
- SortByName()
- SortByAccount()
- SortByPhone()
public PageReference SortByName() { setSortedBy('NAME'); sortedList = (List<contact>) sorter.getSortedList('Name', sortAscending); return null; }First, it calls a method setSortedBy() to find out the ascending or descending order. If the user clicks on a different button, the table is sorted ascending by that column, ortherwise the order is inverted from Ascending to descending and viceversa. Second, it calls the method in the Appex class that does the sorting. (I will explain on detail how to use the Appex class, keep reading) Finally, the controller's method returns a null value to the page. The controller's constructor gets the list from the database.
public SorterContact() { sorter.originalList = [SELECT Name, Phone, Account.Name FROM Contact]; }Since the buttons use the rerendered propery, and therefore a partial page refresh is performed using AJAX, the class constructor is only called at the initial page load rather than every time the buttons are clicked. This means the SOQL statement gets called only once regardless of how many times the data table gets sorted. AP_SortHelper: Finally, the more interesting part... This is the Appex class that does the sorting. Please note that you do not need to understand how this class is built in order to be able to use it (just grab the source code below and paste it into salesforce.com).
public class AP_SortHelper { private Map<String, Integer> listPosition = null; // <ID, Position> private Map<String, List<String>> sortedFieldValuesPerFieldName = null; // <FieldName, <FieldValues>> private Map<String, Map<String, List<String>>> sObjectIDsPerFieldNames = null; // <FieldName, <FieldValue, <IDs>>> // Properties public List<sObject> originalList {get; set;} // Constructor public AP_SortHelper() { originalList = null; } // Public Method public List<sObject> getSortedList(String fieldName, Boolean ascending) { 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 return makeSortedList(fieldName, ascending); } public List<sObject> getSortedList(List<sObject> originalList, String fieldName, Boolean ascending) { this.originalList = originalList; sortedFieldValuesPerFieldName = null; return getSortedList(fieldName, ascending); } public static 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])); } // Private Methods private void InitializeFieldName(String fieldName) { String sObjectID; Integer position; String fieldValue; List<String> sObjectIDs = null; Set<String> valuesForFieldSet = null; // Sets automatically omit duplicate values List<String> valuesForFieldList = null; Map<String, List<String>> sObjectIDsPerFieldValues = null; // Make sortedFieldValuesPerFieldName if (sortedFieldValuesPerFieldName == null) { listPosition = new Map<String, Integer>(); sortedFieldValuesPerFieldName = new Map<String, List<String>>(); sObjectIDsPerFieldNames = new Map<String, Map<String, List<String>>>(); } // Get (or create) map of sObjectIDsPerFieldValues sObjectIDsPerFieldValues = sObjectIDsPerFieldNames.get(fieldName); if (sObjectIDsPerFieldValues == null) { sObjectIDsPerFieldValues = new Map<String, List<String>>(); sObjectIDsPerFieldNames.put(fieldName, sObjectIDsPerFieldValues); } if (!sortedFieldValuesPerFieldName.keySet().contains(fieldName)) { // Objects need to be initialized position = 0; valuesForFieldSet = new Set<String>(); listPosition = new Map<String, Integer>(); 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<String>(); sObjectIDsPerFieldValues.put(fieldValue, sObjectIDs); } // Add ID to sObjectIDs sObjectIDs.add(sObjectID); } // Sort set items (Need to convert to list) valuesForFieldList = new List<String>(); valuesForFieldList.addAll(valuesForFieldSet); valuesForFieldList.sort(); // Now add it to the map. sortedFieldValuesPerFieldName.put(fieldName, valuesForFieldList); } } private List<sObject> makeSortedList(String fieldName, Boolean ascending) { Integer position; List<String> sObjectIDs = null; List<String> valuesForFieldList = null; // Initialize objects InitializeFieldName(fieldName); // Get a list of the same type as the "originalList" List<sObject> 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]); } } } return outputList; } // Unit testing /* static testMethod void testSortCustomObject() { List<TPValue__c> TPValues; AP_SortHelper sorter = new AP_SortHelper(); String fieldName; TPValues = [SELECT TPName__r.TPName__c, Value__c FROM TPValue__c LIMIT 50]; fieldName = 'Value__c'; testOrderedList(sorter.getSortedList(TPValues, fieldName, true), fieldName, true); fieldName = 'TPName__r.TPName__c'; testOrderedList(sorter.getSortedList(TPValues, fieldName, true), fieldName, true); } */ static testMethod void testSimpleField_Ascending() { testSortingContacts('Name', true); } static testMethod void testSimpleField_Descending() { testSortingContacts('Name', False); } static testMethod void testLookupField_Ascending() { testSortingContacts('Account.Name', True); } static testMethod void testLookupField_Decending() { testSortingContacts('Account.Name', False); } static testMethod void testMultipleCalls() { AP_SortHelper sorter; sorter = testSortingContacts(null, 'Name', true); testSortingContacts(sorter, 'Name', False); testSortingContacts(sorter, 'Account.Name', True); testSortingContacts(sorter, 'Account.Name', False); } static testMethod void testForgotOriginalList() { Boolean exceptionDetected = false; AP_SortHelper sorter = new AP_SortHelper(); try { sorter.getSortedList('Name', true); } catch (NullPointerException e) { exceptionDetected = true; } System.assert(exceptionDetected); } static testMethod void testPassingList() { AP_SortHelper sorter = new AP_SortHelper(); List<Contact> contacts = [SELECT Name, Phone, Account.Name FROM Contact LIMIT 50]; List<Contact> sortedList = (List<Contact>) sorter.getSortedList(contacts, 'Name', true); testOrderedList(sortedList, 'Name', true); } private static void testSortingContacts(string fieldName, Boolean isAscending) { testSortingContacts(null, fieldName, isAscending); } private static AP_SortHelper testSortingContacts(AP_SortHelper sorter, string fieldName, Boolean isAscending) { // If sorted is null,create it. if (sorter == null) { sorter = new AP_SortHelper(); sorter.originalList = [SELECT Name, Phone, Account.Name FROM Contact LIMIT 50]; } // Sort list List<Contact> sortedList = (List<Contact>) sorter.getSortedList(fieldName, isAscending); // Test sort order testOrderedList(sortedList, fieldName, isAscending); return sorter; } private static void testOrderedList(List<sObject> sortedList, string fieldName, Boolean isAscending) { String lastValue = null; String currentValue = null; for (sObject sObj : sortedList) { currentValue = getValue(sObj, fieldName); if ((lastValue != null) && (currentValue != null)) { String strDebug = ''; strDebug += '\n--------------------------------------------------------------'; strDebug += '\nSTART'; strDebug += '\n--------------------------------------------------------------'; strDebug += '\n[Ascending:'+isAscending+']'; strDebug += '\n[Previous:'+lastValue+'] [IsNull():'+(lastValue==null)+']'; strDebug += '\n[Current:'+currentValue+'] [IsNull():'+(currentValue==null)+']'; strDebug += '\n[CompareTo:'+(currentValue.compareTo(lastValue))+']'; strDebug += '\n--------------------------------------------------------------'; strDebug += '\nEND'; strDebug += '\n--------------------------------------------------------------'; System.debug(strDebug); if (isAscending) { System.assertEquals(currentValue.compareTo(lastValue)>=0, true); } else { System.assertEquals(currentValue.compareTo(lastValue)<=0, true); } } lastValue = currentValue; } } }Summary: How to use this class?
- Copy the source code above into Salesforce.com
- Create an instance of this class AP_SortHelper()
- Assign the list to sort (this list is created when you execute a valid SOQL statement)
- Call the getSortedList() method which takes two parameters:
- The name of the field as it was used in the SOQL
- The order (true for ascending, false for descending)
No comments:
Post a Comment