• nwallace
  • NEWBIE
  • 0 Points
  • Member since 2012

  • Chatter
    Feed
  • 0
    Best Answers
  • 0
    Likes Received
  • 1
    Likes Given
  • 2
    Questions
  • 6
    Replies

Hi,

 

There is an old thread here that presents some code to sort a list of sObjects by a field value without using ORDER BY in your query.

 

I recently wanted to do this very thing, so I took a look at that code, but I wasn't satisfied.  I couldn't read the code he presented.  Honestly, it probably works just fine, but since I couldn't read it, I didn't want to use it (and potentially have to maintain it over time).  So I made my own.

 

My implementation is much cleaner and easier to read.  All you have to give it is your List<sObject> and a String representing the field you want to sort on.  It handles Strings, Numbers, and Time type fields, as defined below:

    Strings:  Email, Id, Phone, Picklist (single-select), Reference (Id), String, URL

    Numbers:  Floating types, Integer, Percent

    Time:  Date, Datetime, Time

 

If the field is not of any of those types, the method throws an exception.  It will also fail if the field isn't included on all of the objects in the list.

 

The hard and potentially confusing part of this code is the dynamic behavior it requires to use a string (fieldName) to compare actual values on sObject records.  In the sortByField method, I retrieve the Schema.sObjectField that gets passed through the sort algorithm and used in the comparisons.  The comparison methods need this field to determine the field type so it will do the right kind of comparison (String, Number, or Time).

 

I use quicksort, because a related blog post already had it implemented for me, but you could easily swap out the algorithm for heapsort or something else guaranteed to be nlogn.

 

Here is the code!  I'm very open to suggestions/improvements, so leave a comment if you've got any improvements or if you find any bugs.  I hope this is useful to someone out there!

 

/*
Sorts a list of sObjects, sorted by the value of the specified field
Throws: SortingException
*/
public static void sortByField(List<sObject> objs, String fieldName) {
	if (objs.size() < 2) return;
	Schema.sObjectField field = objs.getsObjectType().getDescribe().fields.getMap().get(fieldName);
if (field == null) throw new SortingException('No such field ' + fieldName); Schema.DescribeFieldResult fieldDesc = field.getDescribe(); if (!fieldDesc.isSortable()) throw new SortingException('Type not sortable: ' + fieldDesc.getType()); quicksortByField(objs, 0, objs.size()-1, field, fieldDesc.gettype()); }
public class SortingException extends Exception {}
/* Implements quicksort on the list of sObjects given */ private static void quicksortByField(List<sObject> a, Integer lo0, Integer hi0, Schema.sObjectField field, Schema.DisplayType type) { Integer lo = lo0; Integer hi = hi0; if (lo >= hi) { return; } else if (lo == hi - 1) { if (compareFields(a[lo], a[hi], field, type) > 0) { sObject o = a[lo]; a[lo] = a[hi]; a[hi] = o; } return; } sObject pivot = a[(lo + hi) / 2]; a[(lo + hi) / 2] = a[hi]; a[hi] = pivot; while (lo < hi) { while (compareFields(a[lo], pivot, field, type) < 1 && lo < hi) { lo++; } while (compareFields(pivot, a[hi], field, type) < 1 && lo < hi) { hi--; } if (lo < hi) { sObject o = a[lo]; a[lo] = a[hi]; a[hi] = o; } } a[hi0] = a[hi]; a[hi] = pivot; quicksortByField(a, lo0, lo-1, field, type); quicksortByField(a, hi+1, hi0, field, type); }
/* Determines the type of primitive the field represents, then returns the appropriate comparison */ private static Integer compareFields(sObject a, sObject b, Schema.sObjectField field, Schema.DisplayType type) { if (type == Schema.DisplayType.Email || type == Schema.DisplayType.Id || type == Schema.DisplayType.Phone || type == Schema.DisplayType.Picklist || type == Schema.DisplayType.Reference || type == Schema.DisplayType.String || type == Schema.DisplayType.URL) { // compareTo method does the same thing as the compare methods below for Numbers and Time // compareTo method on Strings is case-sensitive. Use following line for case-sensitivity
// return String.valueOf(a.get(field)).compareTo(String.valueOf(b.get(field)));
return String.valueOf(a.get(field)).toLowerCase().compareTo(String.valueOf(b.get(field)).toLowerCase()); } else if (type == Schema.DisplayType.Currency || type == Schema.DisplayType.Double || type == Schema.DisplayType.Integer || type == Schema.DisplayType.Percent) { return compareNumbers(Double.valueOf(a.get(field)), Double.valueOf(b.get(field))); } else if (type == Schema.DisplayType.Date || type == Schema.DisplayType.DateTime || type == Schema.DisplayType.Time) { return compareTime(Datetime.valueOf(a.get(field)), Datetime.valueOf(b.get(field))); } else { throw new SortingException('Type not sortable: ' + type); } }
private static Integer compareNumbers(Double a, Double b) { if (a < b) { return -1; } else if (a > b) { return 1; } else { return 0; } }
private static Integer compareTime(Datetime a, Datetime b) { if (a < b) { return -1; } else if (a > b) { return 1; } else { return 0; } }

 

 

Update 2/26/2013:

  • Discovered sort of a bug with String comparisons:  using String.compareTo is case-sensitive.  I personally would prefer a case-insensitive comparison, so I have modified the code to do so.  If you prefer case-sensitivity, you can uncomment the original line of code and use that instead.
  • Since the method is destructive to the original list (changes the order of the elements), I made changed the return type of the method to 'void'.
  • Added error handling in case fieldName is invalid

Hi,

 

A client is hosting documents (about 50) as Content Documents on Salesforce that need to be accessible for download from a Visualforce page.

 

Right now, the VF page is creating a link of the format:

    https://<SERVER>.salesforce.com/sfc/servlet.shepherd/version/download/<CONTENT VERSION ID>

 

This link returns a page that causes the file to download automatically (via "Content-disposition: attachment" in the HTTP header).  This solution, while preferable in most situations, is not possible for mine.  Our documents are PDFs and they need to open in the browser (reasons are complicated -- just trust me, there is no negotiating this point).

 

So what I need to know, is there any way to dynamically get a link to the actual file of a Content Version, rather than this Content delivery servlet link?

 

Thanks!

Scenario: Concatenating four custom fields into one custom field, all fields belongs to the same custom Object called 'Claim__c'   

1.Claim_Invoice_Number__c

2.Claim_Invoice_Position__c

3.Claim_Document_Line_Item_Number__c

4. Claim_Line_Item__c  these four shoul be concatenate and putting into 'Invoice_Con__c'.I am getting an error in the below mentioned code

 

   Error Message: Illegal assignment from SObject:Claim__c to String

   This error message is giving in if{---}

    		Map<String,Claim__c> clmap = new Map<String,Claim__c>();  
    		for(Claim__c Invclm :[select ID,Claim_Invoice_Number__c,Claim_Invoice_Position__c
                        ,Claim_Document_Line_Item_Number__c,Claim_Line_Item__c from Claim__c ])
            {
            	clmap.put(Invclm.Claim_Invoice_Number__c+''+Invclm.Claim_Invoice_Position__c+''+
                         Invclm.Claim_Document_Line_Item_Number__c +''+ Invclm.Claim_Line_Item__c,Invclm);
            	System.debug('step 6:'+ Invclm.Claim_Invoice_Number__c+''+Invclm.Claim_Invoice_Position__c+''+
                         Invclm.Claim_Document_Line_Item_Number__c +''+ Invclm.Claim_Line_Item__c);
            } 
            
            for(SObject obj: Trigger.new)
            {
            	Claim__c cl = (Claim__c)obj;
            	if(clmap.containsKey(cl.Claim_Invoice_Number__c+''+cl.Claim_Invoice_Position__c+''+
                         cl.Claim_Document_Line_Item_Number__c +''+ cl.Claim_Line_Item__c))
            	{
            		cl.Invoice_Con__c=clmap.get(cl.Claim_Invoice_Number__c+''+cl.Claim_Invoice_Position__c+''+
                         cl.Claim_Document_Line_Item_Number__c +''+ cl.Claim_Line_Item__c);
            	}
            }  
               

 

 

Thanks in advance

          

Suppose I have multiple page block sections on my VF page created for Account object.

I want a solution to highlight a particular page block section based on a particular field value.

 

( Please Note :

I am aware of rendering a page block section based in a field value.

All I need is to highlight it based on a field value.

)

 

 

Thanks in advance,

Ishaan

This just started today (9/26/2011) and won't let me save the file to force.com. The file is a long existing apex class. Only making some minor changes.

 

 

Save error: Unable to perform save on all files:

 

com.salesforce.ide.api.metadata.types.Metadata$JaxbAccessorF_fullName cannot be cast to com.sun.xml.internal.bind.v2.runtime.reflect.Accessor

 

I deleted the whole project and re-added it. I was able to get everything back, then tried to make changes again. Same error. I tried to refresh my file from the ORG and received a pop up with the same error.

 

 

Hi,

Am unable to refresh any of the resources in the IDE from the server.

When am trying to refresh a page from the web, getting an Exception saying:

 

 

Unable to refresh resource 'MileaeExension.cls':
com.salesforce.ide.api.metadata.types.Metadata$JaxbAccessorF_fullName cannot be cast to com.sun.xml.bind.v2.runtime.reflect.Accessor

 

Unable to refresh resource 'MileaeExension.cls':


com.salesforce.ide.api.metadata.types.Metadata$JaxbAccessorF_fullName cannot be cast to com.sun.xml.bind.v2.runtime.reflect.Accessor

 

Please assist to rectify the issue. 

 

Thanks in advance,

VNath

Dear salesforce.com users,

 

I want to share with you one Appex class that sorts a List<sObject> by any field in ascending or descending order. The List<sObject> is generated by any SOQL statement, so it can be made of custom and/or standard objects. 

 

Using this class is quite simple, and because I have written unit tests that validates 100% of the code you can easily use it in production sytems.

 

The class performs quite well because the sorting is done in memory (using Maps, Sets and Lists). It also detects if the sort has been done for this field so it does not need to resort (even if it is in reverse order).

 

Before going into details of the Appex class, let me show you how the class is used...

 

The VisualForce page:

Nothing fancy here... Just a page building a datatable with three columns and command buttons on the table headers to sort the data.

<apex:page controller="aaSorterContact">
<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: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: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:column>
</apex:datatable>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>

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 aaSorterContact {
private String sortedBy = null;
private Boolean sortAscending = null;
private AP_SortHelper sorter = new AP_SortHelper();
private List<Contact> sortedList = null;

public aaSorterContact() {
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

 

These methods follow the same structure:

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) :smileywink:

 

Finally, the controller's method returns a null value to the page.

 

The controller's constructor gets the list from the database.

public aaSorterContact() {
sorter.originalList = [SELECT Name, Phone, Account.Name FROM Contact];
}

Since the buttons use the rerendered propery (using AJAX), the class constructor is only called at the initial page load rather than every time the buttons are clicked, therefore the SOQL gets called only once regardless of how many times the data table gets sorted.

 

Finally, the more interesting part...

 

The Appex class that sorts:

You don't really need to understand how this class works to use it, but those of you who are interested...

public class AP_SortHelper {     // <ID, Position>
private Map<String, Integer> listPosition = null; // <FieldName, <FieldValues>>
private Map<String, List<String>> sortedFieldValuesPerFieldName = null; // <FieldName, <FieldValue, <IDs>>>
private Map<String, Map<String, List<String>>> sObjectIDsPerFieldNames = null;

// 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);
}

// 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;
}
private 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]));
}

// 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;
}
}
}

 

How to use this class?

  1. Create an instance of this class AP_SortHelper()
  2. Assign the list to sort. Get this list using SOQL.
  3. Call the getSortedList() method which takes two fields:
    1. The name of the field as it was used in the SOQL
    2. The order (true for ascending, false for descending

 


For now, I have one question to the group...

 

This message is getting long... Is there a better place to post it? The way I see it, AppeXchange is applications not for independent utility classes.

Message Edited by andresperez on 01-28-2009 10:45 AM
Hi How do I disbale Inline editing.

I have two record types one record type is allows user to read /write the other is one read only.
Depending upon some condition I am setting changing the record type to the "Read Only" record type. In the (Read only) page layout  I have made all the fields readonly , I have also removed/hidden the "Edit" button. But using the inLine editing  users can still make changes even in the "read only" record type.

I read that If I over ride the "Edit" button then inline editing is disable. I wrote a sample S-Control that has a blank HTML page and used it to override the Edit button. Now the "Read Only record" type works file i.e InLine Editing is disable.

But now the Normal Page with "Read/Write" Record Type have Inline Disabled and When I Click on the Edit Button it displays the Blank Page (S-Control).

How do I overcome this problem?

How do I enable Inline Editing for Normal (RW) record types and make editing work for Normal (RW) record types

N

  • April 11, 2008
  • Like
  • 1