1

I have this query:

Account[] IPList = [SELECT Name,
                       (SELECT Product_Family__c 
                        FROM Product_Releases_del__r 
                        ORDER BY Product_Family__c ASC) 
                    FROM Account a 
                    WHERE ParentID =: AccountID];

I tried to turn it into a map so I can list the map values in a VF page. The map works, but the VF Page double lists the values.

This is what's happening: I have a map of >. The Key is the Account Name, the entry is a list of field entries from the child object. Some accounts have no child objects, and the following results occur with this code:

I need the VF to display:

Account Name ------ ChildField1,ChildField2,etc.

Account Name ------ Blank(no child)

What it displays with this code:

Account Name ----- CField1,Cfield2

Account Name ----- Cfield1(same as above),Cfield2

So it repeats the fields when the child doesn't exist.

public with sharing class whitespace{

    transient public String AccountID{get;set;}
    transient public String PAccountID{get;set;}
    transient public List<Account>TIPlist{get;set;}
    transient public String t{get;set;}
    transient Map<String,List<Installed_Products__c>> IPList{get;set;}
    transient public String Account_Name {get;set;}
    transient public list<Installed_Products__c> P{get;set;}


    public whitespace(ApexPages.StandardController controller){
        }
    public Map<String,List<String>> IPListM{
        get{
        //Selects Account ID and Parent ID
            AccountID = ApexPages.currentPage().getParameters().get('id');
            PAccountID = [select ParentId from Account where ID = :AccountID].ParentId;

        //Queries Account info based on criteria
            if (PAccountID == null){
                TIPList = [select Name,(select Account__c,Product_Family__c from Product_Releases_del__r ORDER BY Product_Family__c ASC) 
                          from Account a where ParentID =: AccountID];
            }
            else {
                TIPList = [select Name,(select  Account__c,Product_Family__c from Product_Releases_del__r ORDER BY Product_Family__c ASC) 
                              from Account a  where ParentID =: PAccountID];   
            }
            //Declares Map and List
            Map<String,List<String>> IPList = new Map<String,List<String>>();
            List <String> P = new List<String>{};
            for(integer j = 0; j < TIPList.size(); j++){ 
            //sets temporary variable t, Account Name as String and clears the product list
                t = '';
                Account_Name = TIPList[j].Name;
            P.clear();

            if(TIPList[j].Product_Releases_del__r != null){
                    for(Integer i = (TIPList[j].Product_Releases_del__r.size()-1) ; i >= 0; i--){
                    Installed_Products__c x = TIPList[j].Product_Releases_del__r[i];

                    if(t == x.Product_Family__c){
                        }
                    else{
                                system.debug(TIPList[j].Product_Releases_del__r[i].Product_Family__c);
                system.debug(TIPList[j]);
                        P.add(TIPList[j].Product_Releases_del__r[i].Product_Family__c);
                        t = x.Product_Family__c;
                        }
                    }
                }
            else{
            P.add(' ');
                }
            system.debug(P);
            system.debug(Account_Name);
            IPList.put(Account_Name, P);
            }  
        return IPList;
            }
        set;
    }
}

VF:

</apex:repeat>

  • Are you sure it needs to remain a list of accounts? There is not requirement for the page to contain only accounts, unless you have some other dependency – Caspar Harmer Jul 28 '15 at 23:17
  • Before you get too far along in writing code against this data model, the field that looks up to Account on the child object Product_Release__c has previously been deleted and then later undeleted. You can see this by the inclusion of the _del in the API name of both the field and the relationship name. You should rename both the field on the child as well as the relationship name to the parent. Allowing this type of naming to make it into production would not be wise long-term. – Mark Pond Jul 28 '15 at 23:48
  • Hi @Ramin Mohammadi, a lot of people have contributed a lot of time to your three linked questions. While perhaps none have completely solved your problem, there have been a lot of good ideas. I suggest you acknowledge this by upvoting at least some of the answers. You have enough reputation to do this. – Caspar Harmer Jul 29 '15 at 0:37
  • @CaspNZ I think it needs to remain a list of accounts because the VF page that uses this extension ends up listing the "Product_Family__c" field. The whole issue stems from the fact that I dont want it to list two product families of the same kind. I'm looking into other ways of doing this. – Ramin Mohammadi Jul 29 '15 at 12:43
  • @RaminMohammadi, you said your controller is a standard Account controller. Salesforce doesn't really care what object you're querying when you're running your repeat. Give the code I suggested a try, it should do what you're looking for. – Jason Hardy Jul 29 '15 at 16:46
0

Use a Set to store the values already found, if it contains the value remove the object from your list. The first occurrence will always remain since you are populating the set after the check for it being in the set

FTemp = '';
Set<String> tmp = New Set<String>();

For(integer i = 0; i<IPList.size(); i++){

    if(tmp.contains(IPList[i].Product_Releases_del__r.Product_Family__c)){

        IPList.remove(i);
        i--;
    }


       tmp.add(Product_Releases_del__r.Product_Family__c);

 }

Here is an example using Opportunities that most can use and show that it is working:

Opportunity[] opps = [Select ID, Name, Account.Name From Opportunity Limit 10];
Set<String> tmp = New Set<String>();
for(Opportunity o : opps){
        tmp.add(o.Account.name);
}
system.debug(logginglevel.error,opps.size());

for(integer x =0;x<opps.size();x++){
    if(tmp.contains(opps[x].Account.Name)){
        opps.remove(x);
        x--;
    }
}
system.debug(logginglevel.error,opps.size());

Strangely enough this does not produce an error as expected, it simply just does not remove it if you do a remove on a subquery:

//Get an Account ID From an Opp
Opportunity[] o = [Select AccountID From Opportunity limit 10];
Set<ID>tmp = new Set<ID>();
for(opportunity opp : o)
    tmp.add(opp.AccountID);

//get the accounts    
Account[] accts = [Select ID, Name, (Select ID, Name From Opportunities) From Account Where ID In :tmp limit 10];

//try to remove an pop - Not doing a loop as it will result in CPU Time Limit as the pop is never removed
Account a = accts[0];
system.debug(logginglevel.error,a.opportunities.size());
a.Opportunities.remove(0);
system.debug(logginglevel.error,a.opportunities.size());
  • 1
    I don't think you can modify the results of the query - apparently it's read-only, from what I've been reading. – Caspar Harmer Jul 28 '15 at 22:11
  • @CaspNZ No, you can modify the List that is a result of a query. It is not one of the read only collections (where you can modify data but not the list). See updated example with code you can use to replicate. Also, overlooked the OP mistake in the loop and I fixed that – Eric Jul 28 '15 at 23:00
  • @CaspNZ - I forget which objects exactly where you cannot modify the collection but can modify the row field values. (Other than trigger.new and trigger.old......Wish I could find the doc... – Eric Jul 28 '15 at 23:02
  • You are right - I was confusing the regular list with the subquery list. It's the subquery list that's readonly. – Caspar Harmer Jul 28 '15 at 23:14
  • 1
    Yes - And strangely Enough - Doing it on a subquery in Dev console did not produce and error, it just simply did not remove it - See updated code – Eric Jul 28 '15 at 23:19
0

Based on your last comment, it sounds like you need to get a map of accounts to object records. Here is an example of getting multiple "repeated" last names from the contact object to Account. I'm including a method from a utility class that I use which will take a list and create a map. In this case, I'm creating the map based on the "Account Name", the method is a bit different if you want to use something like the "AccountId" instead.

public class TestingVFController {
    public map<String,set<sObject>> mapAcct2ParentName {Get;private Set;}
    public map<String,set<String>> mapAcct2ParentByNameString {Get;private Set;}
    public map<String,set<sObject>> mapAcct2ParentNameDeDuped {Get;private Set;}

    private Id currId {get;private set;}
    public TestingVFController(ApexPages.StandardController sc)
    {
        currId = sc.getId();
        list<Contact> contactList = [SELECT AccountId, Account.Name, LastName 
                                     FROM Contact 
                                     WHERE AccountId in 
                                     (select id from Account where parentId = :currId)];
        mapAcct2ParentName = convertObjectList2Map(contactList,'Account','Name');
        mapAcct2ParentNameDeDuped = new map<String,Set<sObject>>();
        mapAcct2ParentByNameString = convertSet2MapStringSet(mapAcct2ParentName,'lastName');
    }

    /**
     * @description Will return a map containing a list of sobjects and a common key
     * @param objectRecords The list of records that will be processed into a map
     * @param objectFld Relationship object (e.g. Account.Name), should be defined as just the object being referenced (Account)
     * @param keyFldNm The API name of the field that will be used as the key value, typically an ID.
     * @return A map of a key value to a list of related records
     */
    private map<String, set<sObject>> convertObjectList2Map(list<sObject> objectRecords, String ObjectFld, String keyFldNm){
        map<String, set<sObject>> returnMap = new map<String, set<sObject>>();
        set<sObject> tempSet = null;
        String key;
        //Looping through all of the sObject records
        for(sObject so : objectRecords){
            //Getting the key based on the key field name var
            try{
                key = (String)so.getSOBject(ObjectFld).get(keyFldNm);
            }
            catch(exception e){
                key = keyFldNm;
            }
            if(key != keyFldNm){
                //Checking if there is already a set
                if(returnMap.containsKey(key)){
                    tempSet = returnMap.get(key);
                }
                else{
                    tempSet = new set<sObject>();
                }
                //Adding the sobject record to the temp set
                tempSet.add(so);
                //Adding the combination to the map
                returnMap.put(key,tempSet);
            }
        }
        return returnMap;
    }

    private map<String, set<String>> convertSet2MapStringSet(map<String,Set<sObject>> map2Convert, String fldName){
        map<String, set<String>> returnMap = new map<String, set<String>>();
        for(String s : map2Convert.keySet()){
            set<String> converted = new set<String>();
            //Can remove variable below if no need to prevser sObjects
            set<sObject> soRecs = new set<sObject>();
            for(sObject so : map2Convert.get(s)){
                String deDupVal = (String)so.get(fldName);
                //Can remove if statement if no need to preserve sObject
                if(!converted.contains(deDupVal)){
                    soRecs.add(so);
                }
                converted.add(deDupVal);
            }
            returnMap.put(s,converted);
            //Can remove line below if no need to preserve sObject
            mapAcct2ParentNameDeDuped.put(s,soRecs);
        }
        return returnMap;
    }
}

You should then be able to access the field in a visualforce like this:

    <apex:page standardController="Account" extensions="TestingVFController">
        <apex:pageBlock title="Contact Account Map by Parent Name (raw)">
            <apex:repeat value="{!mapAcct2ParentName}" var="mkey">
                <apex:pageBlockSection title="{!mkey}" columns="1">
                    <apex:repeat value="{!mapAcct2ParentName[mkey]}" var="sKey">
                        <apex:outputText value="{!sKey['LastName']}"/>
                    </apex:repeat>
                </apex:pageBlockSection>
            </apex:repeat>
        </apex:pageBlock>
        <apex:pageBlock title="Contact Last Name Account Map by Parent Name (Deduped w/Strings)">
            <apex:repeat value="{!mapAcct2ParentByNameString}" var="mkey">
                <apex:pageBlockSection title="{!mkey}" columns="1">
                    <apex:repeat value="{!mapAcct2ParentByNameString[mkey]}" var="sKey">
                        <apex:outputText value="{!sKey}"/>
                    </apex:repeat>
                </apex:pageBlockSection>
            </apex:repeat>
        </apex:pageBlock>
        <apex:pageBlock title="Contact Last Name Account Map by Parent Name (Deduped w/sObject preserved)">
            <apex:repeat value="{!mapAcct2ParentNameDeDuped}" var="mkey">
                <apex:pageBlockSection title="{!mkey}" columns="1">
                    <apex:repeat value="{!mapAcct2ParentNameDeDuped[mkey]}" var="sKey">
                        <apex:outputText value="{!sKey['LastName']}"/>
                    </apex:repeat>
                </apex:pageBlockSection>
            </apex:repeat>
        </apex:pageBlock>
    </apex:page>

And here are the results from my sandbox:

enter image description here

Thus the power of maps and sets

  • You will not run into the index out of bound if you properly account for the removal of an item - and you did not address the OP's issue of removing items from the list. It is not about coming up with a final Set of products, rather a list that contains sObject records with no duplicate products – Eric Jul 28 '15 at 23:03
  • Based on the sub query, it looks like he already know what the account is. If you take the fact that the account ID is known, then he needs a list "Products". If the list is the ID's (which I'm guessing that is, or it's a regular string), then he can further query the actual object that he needs to reference based on the items passed in. Also, the logic he setup doesn't account for anything special other than the name is not repeated. – Jason Hardy Jul 28 '15 at 23:10
  • Added an alternative that would accommodate preserving sObjects incase it's needed. – Jason Hardy Jul 28 '15 at 23:59
0

Solved.

My inner List definition had be inside the loop.

public with sharing class whitespace{

transient public String AccountID{get;set;}
transient public String PAccountID{get;set;}
transient public List<Account>TIPlist{get;set;}
transient public String t{get;set;}
transient Map<String,List<Installed_Products__c>> IPList{get;set;}
transient public String Account_Name {get;set;}
transient public list<Installed_Products__c> P{get;set;}


public whitespace(ApexPages.StandardController controller){
    }
    public Map<String,List<String>> IPListM{
        get{
            //Selects Account ID and Parent ID
            AccountID = ApexPages.currentPage().getParameters().get('id');
            PAccountID = [select ParentId from Account where ID = :AccountID].ParentId;

            //Queries Account info based on criteria
            if (PAccountID == null){
                TIPList = [select Name,(select Account__c,Product_Family__c from Product_Releases_del__r ORDER BY Product_Family__c ASC) from Account a where ParentID =: AccountID];
            }
            else {
                TIPList = [select Name,(select  Account__c,Product_Family__c from Product_Releases_del__r ORDER BY Product_Family__c ASC) from Account a  where ParentID =: PAccountID];   
            }
            //Declares Map and List
            Map<String,List<String>> IPList = new Map<String,List<String>>();

            for(integer j = 0; j < TIPList.size(); j++){ 
                List <String> P = new List<String>{};
                //sets temporary variable t, Account Name as String and clears the product list
                P.clear();
                t = '';
                Account_Name = TIPList[j].Name;



                for(Integer i = (TIPList[j].Product_Releases_del__r.size()-1) ; i >= 0; i--){
                    system.debug(TIPList[j]);
                    Installed_Products__c x = TIPList[j].Product_Releases_del__r[i];

                    if(t == x.Product_Family__c){
                        }
                    else{
                        system.debug(TIPList[j].Product_Releases_del__r[i].Product_Family__c);
                        system.debug(TIPList[j]);
                        P.add(TIPList[j].Product_Releases_del__r[i].Product_Family__c);
                        t = x.Product_Family__c;
                            }
                    }




                system.debug(P);
                system.debug(Account_Name);
                IPList.put(Account_Name, P);
                system.debug(IPlist);
                }  
            return IPList;
            }



        set;
}

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.