+ Start a Discussion
JoeyDJoeyD 

Sorting Tables copy/paste (development noob)

Hi all!

 

I have only one or two weeks experience with Apex and Visualforce, and no coding experience, so please bear with me.

I altered the MassEdit visualforce page to the specifications of my supervisor, but we now we need to be able to sort the fields.

Having no experience with Apex or Java or anything, I was hoping I could sort of copy/paste the code from this Sorting Tables tutorial and replace the components that made sense.  It's not really working out.

 

Not being in Developer Edition, I'm having to use the Force.com IDE through Eclipse.

Here's the class:

 

 

public with sharing class massEditSort {

    List<Product_Forecasting__c> fcs;
    public String sortField {get; set;}
    public String previousSortField {get; set;}
    
    public List<Product_Forecasting__c> getFcs() {
        if(fcs == null){
            fcs = [select Forecast_For_Month__c, Style__c, Color_Code__c, Product_Description__c, Color__c, LQ2__c, Forecasted_Units_Q2_c__c, LQ3__c, Forecasted_Units_Q3_2010__c, Forecasted_Units_Q4_2010__c from Product_Forecasting__c];
        }
        return fcs;
    }
    
    public void doSort(){
        String order = 'asc';
        
        /*This checks to see if the same header was click two times in a row, if so 
        it switches the order.*/
        if(previousSortField == sortField){
            order = 'desc';
            previousSortField = null;
        }else{
            previousSortField = sortField;
        }
       
        //To sort the table we simply need to use this one line, nice!
        superSort.sortList(fcs,sortField,order);
    }
}

 

 

Now when I try to save this, I get an error that reads: Average test coverage across all Apex Classes and Triggers is 74%, at least 75% test coverage is required

And also the error: Save error: Method does not exist or incorrect signature: superSort.sortList(LIST:SOBJECT:Product_Forecasting__c, String, String)

 

Here is the Visualforce Page I am trying to get to work with this controller:

 

 

<apex:page controller="massEditSort">
    <apex:form >
        
	<apex:pageBlock >
		Note: All modifications made on the page will be lost if Cancel button is clicked without clicking the Save button first.<br />
		<p style="color:red"><u>Do not click the "Agree With Forecast" checkbox if you update or change any other field.</u></p>
	</apex:pageBlock>
	<apex:pageBlock >
	<apex:pageBlockButtons >
		<apex:commandButton value="Save" action="{!save}"/>
		<apex:commandButton value="Cancel" action="{!cancel}"/>
	</apex:pageBlockButtons>            

	<apex:pageBlockTable value="{!selected}" var="o" id="table">
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.Forecast_For_Month__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="Forecast For Month:" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:outputField value="{!o.Forecast_For_Month__c}"/>
                </apex:column>
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.Style__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="Style#" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:outputField value="{!o.Style__c}"/>
                </apex:column>
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.Color_Code__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="Color Code" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:outputField value="{!o.Color_Code__c}"/>
                </apex:column>
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.Product_Description__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="Product Description" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:outputField value="{!o.Product_Description__c}"/>
                </apex:column>
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.Color__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="Color" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:outputField value="{!o.Color__c}"/>
                </apex:column>
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.LQ2__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="LQ2 2009" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:outputField value="{!o.LQ2__c}"/>
                </apex:column>
                 <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.Forecasted_Units_Q2_c__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="Forecasted Units Q2 2010" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:inputField value="{!o.Forecasted_Units_Q2_c__c}"/>
                </apex:column>
		 <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.LQ3__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="LQ3 2009" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:outputField value="{!o.LQ3__c}"/>
                </apex:column>
		 <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.Forecasted_Units_Q3_2010__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="Forecasted Units Q3 2010" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:inputField value="{!o.Forecasted_Units_Q3_2010__c}"/>
                </apex:column>
		 <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.LQ4__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="LQ4 2009" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:outputField value="{!o.LQ4__c}"/>
                </apex:column>
		 <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Product_Forecasting__c.Fields.Forecasted_Units_Q4_2010__c.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="Forecasted Units Q4 2010" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:inputField value="{!o.Forecasted_Units_Q4_2010__c}"/>
                </apex:column>
		<apex:column headerValue="Agree With Forecast">
			<apex:inputField value="{!a.Agree_With_Forecast__c}"/>
		</apex:column>
		<apex:column headerValue="Comments on Style Forecast">
			<apex:inputTextarea value="{!a.Comments_on_Style_Forecast__c}" style="width:160px"/>
		</apex:column>
            </apex:pageBlockTable>
        </apex:pageBlock>
    </apex:form>
</apex:page>

 

Any help will be greatly appreciated!

I understand if it's one of those "This guy has no idea what's going on, and it would be way too much work to fix this garbage for him." :)

 

 

Best Answer chosen by Admin (Salesforce Developers) 
bob_buzzardbob_buzzard

I think your null pointer exception is being caused by this line in your page:

 

<apex:param name="sortField" value="Name::" assignTo="{!sortField}"/>

The code will eventually end up trying to get describe information for the "Name::" field on the account sObject, which won't exist.  Presumably this should be just "Name" (without the colons), which is a field that exists on the sobject.

 

Where I've used this superSort class, I've broken this line up:

 

(items[0].getSObjectType().getDescribe().fields. getMap().get(sortField).getDescribe().getType().Na me() == 'REFERENCE')

 

as it makes a lot of assumptions that return values will be non-null.

 

I don't quite understand your final issue - if you are trying to use a standard list view page then I don't think you will be able to capture the checkboxes for your custom button.  That said, its not too hard to create pages that mimic list views so you should be able to at least replicate with VisualForce.

 

All Answers

bob_buzzardbob_buzzard

When you say you are using the force.com ide, does that mean you are developing in a sandbox and then promoting the code through to a production edition?

 

Your use of superSort.sortList looks correct to me - that's certainly how I'm using it.

 

It may be that your lack of unit tests is stopping you deploying the superSort class, and that leads to the nosuchmethod exception.

 

Try deploying superSort in isolation first, then your code that uses it.

JoeyDJoeyD

Thanks for your reply.

 

I'm not developing in a sandbox, as after I put that class into Eclipse, it does show up in the production edition.

Sorry, I'm unfamiliar with unit tests and deploying in isolation, so I'm not sure what you mean exactly.

 

I may be making what I'm trying to accomplish more difficult than it needs to be. Our problem is that the MassEdit visualpage that we installed does not appear to sort the selected records in any useful way. I had searched everywhere and wasn't able to figure out how it was sorting the records. So I took the next step and tried looking into how to make the list sortable, and this was the only solution I was able to find.  The ability for the end user to sort the list is just an adding convenience; if we actually knew how MassEdit was already sorting the records, we could add a field or adjust the records as we see fit, as all the data for this object is still test data.

 

If this Apex class is the only way to do this, I'll probably need some heavier support to get it working, as I'm really green with nearly every aspect of development.

 

I really appreciate your response and I'll play with it some more!

Thanks!

bob_buzzardbob_buzzard

I'd suggest you get yourself a free developer edition, develop on that and then use the Force.com IDE to deploy to production.

 

We've had experience in the past of being able to save classes to production without unit tests, but then not being able to update them without full unit test coverage.

JoeyDJoeyD

I'm making some significant progress!

 

I got myself setup with the free Dev Edition, and I tweaked the classes to work with only the Name field from the Account object, and adjusted the VisualForce page to only display that column.

 

I am able to display the page by going to my salesforce.com/apex/testList, but when I try to sort, I get an error page and email is sent with the following error:

 

-----------------------------------------------------

 

Apex script unhandled exception by user/organization:

 

Visualforce Page: /apex/testList

 

 

 

caused by: System.NullPointerException: Attempt to de-reference a null object

 

Class.superSort.sortList: line 16, column 68

Class.massEditSort.doSort: line 26, column 9 External entry point

 

Debug Log:

 

-----------------------------------------------------

 

Here is the superSort class.

 

 

public class superSort {

    /*This method takes 3 arguments, the List of objects to sort, the field to sort, 
    and the order, asc or desc*/
    
    public static void sortList(List<sObject> items, String sortField, String order){
        /*I must give credit where it is due as the sorting algorithm I am using is the 
        one supplied by Andrew Waite here: http://blog.sforce.com/sforce/2008/09/sorting-collect.html */
        
        Boolean isSortFieldReference = false;
        Map<Id,String> Name;
         
        /*Determine the type of the field that needs to be sorted, if it is a 
        reference we will want sort by the name of the related object, not the 
        ID itself*/
        if(items[0].getSObjectType().getDescribe().fields.getMap().get(sortField).getDescribe().getType().Name() == 'REFERENCE'){
            isSortFieldReference = true;
            Name = new Map<Id,String>();
            
            /*Determine the type of this object and populate the Id to Name map*/
            Set<Id> referenceIds = new Set<Id>();
            for(sObject s : items){
               referenceIds.add((Id)s.get(sortField));
            }
            
            String objectID = (String)items[0].get(sortField);
            String prefix = objectID.substring(0,3);
            String objectType;
            Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe();
            for(Schema.SObjectType s : gd.values()){
                if(prefix == s.getDescribe().getKeyPrefix()){
                    objectType = s.getDescribe().Name;
                }
            }
            
            //Query the related objects for the name and populate the Id -> Name map
            String queryString = 'select Id, Name from ' + objectType + ' where ID IN :referenceIDs';
            for(sObject s : Database.query(queryString )){
                Name.put((Id)s.get('Id'),(String)s.get('Name'));
            }
        }
                
        /*Declare a list that will contain the sorted results. I think this is one of the 
        coolest parts of this method as the system will not let you declare a list of 
        sObjects (List<sObject> objects = new List<sObjects>();) but using a 
        wrapper class you can bypass this system limitation to create this type of list */
        List<cObject> resultList = new List<cObject>();
    
        //Create a map that can be used for sorting 
        Map<object, List<cObject>> objectMap = new Map<object, List<cObject>>();
        
        for(sObject ob : items){
            if(isSortFieldReference == false){
                if(objectMap.get(ob.get(sortField)) == null){ 
                    objectMap.put(ob.get(sortField), new List<cObject>()); 
                }
                cObject o = new cObject(ob);
                objectMap.get(ob.get(sortField)).add(o);
            }else{
                if(objectMap.get(Name.get((Id)ob.get(sortField))) == null){ 
                    objectMap.put(Name.get((Id)ob.get(sortField)), new List<cObject>()); 
                }
                cObject o = new cObject(ob);
                objectMap.get(Name.get((Id)ob.get(sortField))).add(o);
            }
        }
        
        //Sort the keys
        List<object> keys = new List<object>(objectMap.keySet());
        keys.sort();
        
        for(object key : keys){ 
            resultList.addAll(objectMap.get(key)); 
        }
        
        //Apply the sorted values to the source list
        items.clear();
        if(order.toLowerCase() == 'asc'){
            for(cObject ob : resultList){
                items.add(ob.obj);  
            }
        }else if(order.toLowerCase() == 'desc'){
            for(integer i = resultList.size()-1; i >= 0; i--){
                items.add(resultList[i].obj);   
            }
        }
    }
    
    public class cObject{
        sObject obj {get; set;}
        
        public cObject(sObject obj){
            this.obj = obj; 
        }
    }
    
    /*Some test methods that provide 100% coverage */
    public static testMethod void sortAscendingTest(){
        
        List<Opportunity> opps = new List<Opportunity>();
        for(integer i = 0; i<1000; i++){
            opps.add(new Opportunity(Name = 'test' + i, Amount = 1000 * Math.random()));
        }
        
        Test.startTest();
        Long start = system.currentTimeMillis();
        sortList(opps,'Amount','asc');
        system.debug(system.currentTimeMillis() - start);
        Test.stopTest();
        
        //Assert the list was sorted correctly
        Decimal assertValue = -1;
        for(Opportunity o : opps) {
            System.debug('Opp value: ' + o.amount);
            System.assert(assertValue <= o.amount);
            assertValue = o.amount;
        }  
    }
    
    public static testMethod void sortDescendingTest(){
        
        List<Opportunity> opps = new List<Opportunity>();
        for(integer i = 0; i<1000; i++){
            opps.add(new Opportunity(Name = 'test' + i, Amount = 1000 * Math.random()));
        }
        
        Test.startTest();
        sortList(opps,'Amount','desc');
        Test.stopTest();
        
        //Assert the list was sorted correctly
        Decimal assertValue = 1001;
        for(Opportunity o : opps) {
            System.debug('Opp value: ' + o.amount);
            System.assert(assertValue >= o.amount);
            assertValue = o.amount;
        }  
    }
}

 And the massEditSort class:

 

 

public with sharing class massEditSort {
    List<Account> acct;
    public String sortField {get; set;}
    public String previousSortField {get; set;}
    
    public List<Account> getAcct() {
        if(acct == null){
            acct = [select Name, AccountNumber from Account];
        }
        return acct;
    }
    
    public void doSort(){
        String order = 'asc';
        
        /*This checks to see if the same header was click two times in a row, if so 
        it switches the order.*/
        if(previousSortField == sortField){
            order = 'desc';
            previousSortField = null;
        }else{
            previousSortField = sortField;
        }
       
        //To sort the table we simply need to use this one line, nice!
        superSort.sortList(acct,sortField,order);
    }
}

 

 

And finally the visualforce page:

 

 

<apex:page controller="massEditSort">
    <apex:form >
    <apex:pageBlock >
    <apex:pageBlockTable value="{!acct}" var="o" id="table">
                <apex:column >
                    <apex:facet name="header">
                        <apex:commandLink value="{!$ObjectType.Account.Fields.Name.Label}" action="{!doSort}" rerender="table">
                            <apex:param name="sortField" value="Name::" assignTo="{!sortField}"/>
                        </apex:commandLink>
                    </apex:facet>
                    <apex:inputField value="{!o.Name}"/>
                </apex:column>
            </apex:pageBlockTable>
        </apex:pageBlock>
    </apex:form>
</apex:page>

 

 

I have another concern aside from the error I'm receiving when the column header is clicked.

 

I'd like this visualforce page to act similar to the MassEdit page we have. That is, checking records in the list view, clicking the "Mass Edit" button, and getting to the VP page.

I know I can create a custom button that will simply link to the url, but I don't believe that will work through the selection of records.

In the MassEdit page I was using, the pageBlockTable value="{!selected}", and it worked great. I'm nto sure how I'll get this to work with custom class...

 

 

bob_buzzardbob_buzzard

I think your null pointer exception is being caused by this line in your page:

 

<apex:param name="sortField" value="Name::" assignTo="{!sortField}"/>

The code will eventually end up trying to get describe information for the "Name::" field on the account sObject, which won't exist.  Presumably this should be just "Name" (without the colons), which is a field that exists on the sobject.

 

Where I've used this superSort class, I've broken this line up:

 

(items[0].getSObjectType().getDescribe().fields. getMap().get(sortField).getDescribe().getType().Na me() == 'REFERENCE')

 

as it makes a lot of assumptions that return values will be non-null.

 

I don't quite understand your final issue - if you are trying to use a standard list view page then I don't think you will be able to capture the checkboxes for your custom button.  That said, its not too hard to create pages that mimic list views so you should be able to at least replicate with VisualForce.

 

This was selected as the best answer
JoeyDJoeyD

Thank you so much! Taking those colons out definitely solved the problem!

I actually put those colons there to see if that value was a customizable field that only related to what text would be displayed on the column header, then I completely forgot I had done that. :smileytongue:

 

 

Regarding my second issue, let me show you the visualforce page we are currently using, and then I'll describe the pipeline so you can get a better idea of what we're trying to accomplish.

 

 

<apex:page standardController="Product_Forecasting__c" recordSetVar="unused" sidebar="false">
<apex:includeScript value="{!$Resource.UtilJS}" />
   <apex:form >
      <apex:pageBlock >
      <apex:pageMessages />
      <apex:pageBlock >
            Note: All modifications made on the page will be lost if Cancel button is clicked without clicking the Save button first.<br />
            <p style="color:red"><u>Do not click the "Agree With Forecast" checkbox if you update or change any other field.</u></p>
      </apex:pageBlock>
      <apex:pageBlockButtons >
         <apex:commandButton value="Save" action="{!save}"/>
         <apex:commandButton value="Cancel" action="{!cancel}"/>
      </apex:pageBlockButtons>

      <apex:pageBlockTable value="{!selected}" var="a" id="table">
         <apex:column headerValue="Style#">
            <apex:outputField value="{!a.Style__c}"/>
         </apex:column>
         <apex:column headerValue="Color Code">
            <apex:outputField value="{!a.Color_Code__c}" style="width:24px" />
         </apex:column>
         <apex:column headerValue="Product Description">
            <apex:outputField value="{!a.Product_Description__c}"/>
         </apex:column>
         <apex:column headerValue="Color">
            <apex:outputField value="{!a.Color__c}"/>
         </apex:column>
         <apex:column headerValue="Forecast for Month:">
            <apex:outputField value="{!a.Forecast_For_Month__c}" style="width:24px" />
         </apex:column>
         <apex:column headerValue="LQ2 2009">
            <apex:outputField value="{!a.LQ2__c}"/>
         </apex:column>
         <apex:column headerValue="Forecasted Units Q2 2010">
            <apex:inputField value="{!a.Forecasted_Units_Q2_c__c}" />
         </apex:column>
         <apex:column headerValue="LQ3 2009">
            <apex:outputField value="{!a.LQ3__c}"/>
         </apex:column>
         <apex:column headerValue="Forecasted Units Q3 2010">
            <apex:inputField value="{!a.Forecasted_Units_Q3_2010__c}"/>
         </apex:column>
         <apex:column headerValue="LQ4 2009">
            <apex:outputField value="{!a.LQ4__c}"/>
         </apex:column>
         <apex:column headerValue="Forecasted Units Q4 2010">
            <apex:inputField value="{!a.Forecasted_Units_Q4_2010__c}"/>
         </apex:column>
         <apex:column headerValue="Agree With Forecast">
            <apex:inputField value="{!a.Agree_With_Forecast__c}"/>
         </apex:column>
         <apex:column headerValue="Comments on Style Forecast">
            <apex:inputTextarea value="{!a.Comments_on_Style_Forecast__c}" style="width:160px"/>
         </apex:column>
      </apex:pageBlockTable>
   </apex:pageBlock>
</apex:form>
</apex:page>

 

 

So the idea here is to allow our reps to navigate to this page from the Product Forecast custom object's list view. We have set up a view for them that will only list records they own.

 

We wanted something similar to the listview itself, but rather than having to double click a field, edit, and click save (through inline editing), the reps can go field by field and save all the values with one click. This MassEdit page does everything we want it to, except sort the records in a useful way.

 

I basically copied this from the standard MassEdit page that comes with the MassEdit/MassUpdate package.

 

I don't think I'd have too much trouble recreating the list view with a visualforce page, however it needs to have the same functionality: only showing the reps records they own and allowing them to go line by line changing values.

 

But I can see how to adopt the visualforce page and apex class to reference our custom object and fields instead of the standard Account object I'm usnig in the developer edition, I just don't see how I can connect this page back to the object, or anything else for that matter, so the reps can click a button to get there (and only see the records they own when they do, or will that be default?).

 

I hope you see where I'm coming from! And again, thank for you all the help!

 

 

JoeyDJoeyD

Also, I'm not exactly sure how to get the records to save after they are edited.

When I had the

 

 

<apex:pageBlockButtons >
<apex:commandButton value="Save" action="{!save}"/>
<apex:commandButton value="Cancel" action="{!cancel}"/>
</apex:pageBlockButtons>

 

in the page and tried to save, I got an error saying massEditSort.save() did not exist. Next to the error was link asking me if I wanted to create it, so I did, and it allowed me to save the page, however when I change the records in the list and hit the save button, nothing happens.

 

JoeyDJoeyD

I fixed this saving issue.

cashworthcashworth

Been working on this for a few days myself and then ran across your post. 

Do you have a final code sample of your customer controller and VF page that is working for massEdit with sorting? I emailed the developer of massEdit but he did not have anything developed for that.

 

JoeyDJoeyD

Unfortunately I don't.  I did at one point, but the process went through a few iterations and we ended up using a set sort through the SOQL query.