function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Curry-ManCurry-Man 

4-tier related lists

I have an object that has a series of related lists in a model as follows:

 

Opportunity>Document>Section>Term

 

Is there any way in VF to display the Terms as sets of Sections as sets of a Document within a page displaying the Opportunity as the root element?

michaelforcemichaelforce

I think you should check out the "repeat" component.  What you can do is define the VF markup for one Term inside one Section inside one Document, and get it looking the way you want (using pageBlocks or however you want it to display).  Then add the three levels of nested repeat tags that will get the appropriate objects and display all of them in the proper parent.

 

For instance, your outermost repeat tags would get all Documents for the given Opportunity and display all of them.  Then for each of those, the next level of repeat tags will want to query the Sections for the given document. etc.

 

Hope that helps.

Curry-ManCurry-Man

Michael,

 

I tried that before posting.  VF doesn't seem to allow nesting repeats, data lists, data tables, etc. to avoid soql queries maxing out.  I guess this makes sense, since they (SF) can't actually control user input, and if you use nested repeats it is possible to have it set so that Object B is related to Object A, which also has a lookup to Object B.  So if you repeated A and for each A found the Bs, it would, if it was a recursive function, create an endless loop.

 

What I'm thinking of doing is writing a custom controller that would do this, but from a bottom-up method instead of a top-down (fewer queries that way).  I'll post as I get further along.  Thanks!

ThomasTTThomasTT

apex:repeat does allow nesting data.

 

<apex:repeat value="{!data}" var="a">
<apex:outputText value="{!a.name}"/>
<apex:repeat value"{!a.data}" var="b">
<apex:outputText value="{b}"/>
</apex:>
</apex>

 

public List<Data> getData(){

//
return data;

}

 

public class Data {

public string name {get; set;}
public List<string> data {get; set;}

}

 This should work.

Your post worrying about infinite loop seems to assume that SFDC somehow automatically display nested list based on the definition, but michaelforce is not talking about such a convenient function. It's you to specify the nested structure and render the tables by using apex:repeat and nested data structure (of course, you can re-use SObject parent-child relationship for the nested data structure like List<Account>, Account.Contacts. (value="{!accounts}" var="a", value="{!a.contacts}" var="c", value="{!c.name}")

 

But, for datalist, pageBlockTable, I don't think SFDC "assumes" nested data. The way to control header and style depends on css/javascript/html class name. If you nest pageBlockTable, it will be rendreed, but the class name will be duplicated and headers are not rendered appropriately.

If you use apex:repeat to generate your own list, I don't see any problem (except the amount of your effort...)

ThomasTT

 

Message Edited by ThomasTT on 11-09-2009 11:42 AM
Curry-ManCurry-Man

[code] 

<apex:page standardController = "Account">
    <apex:repeat value="{!Account.Opportunities}" var="opp">
        <apex:repeat value="{!opp.OpportunityLineItems}" var="oli">
            {!oli.unitprice}
        </apex:repeat>
    </apex:repeat>
</apex:page>
[/code]

produces

 

Error: ; nested exception is: common.exception.ApiException: SOQL statements cannot query aggregate relationships more than 1 level away from the root entity object.

 

The method that I ended up using is as follows:

 

[code]

public class ContractPackageExtender {
    private final Opportunity opp;
    public ContractPackageExtender(ApexPages.StandardController controller) {
        this.opp = (Opportunity)controller.getRecord();
    }
   
    public list<String> getcontractpackage(){
            Term__c[] terms = [select id, section__c, name, order__c, term__c from term__c where section__r.agreement__r.opportunity__c = :opp.id order by order__c asc];
            Section__c[] sections = [select id, name, agreement__c, order__c from section__c where agreement__r.opportunity__c = :opp.id order by order__c asc];
            Agreement__c[] agreement = [select id, type__c, document_intro__c from agreement__c where opportunity__c = :opp.id order by type__c];
            Deliverable__c[] deliverable = [select SOW__c, title__c, Name from Deliverable__c where opportunity__c = :opp.id order by Name];
            Deliverable_Step__c[] step = [select deliverable__c, Description__c, Prefix__c, Responsible_Party__c from Deliverable_Step__c where Deliverable__r.opportunity__c = :opp.id];
List<string> contractpackage = new List<string>();

Map<ID, Term__c[]> sectionMap = new Map<ID, Term__c[]>();
for(Section__c s : sections){
  list<term__c> termsBySection = new list<Term__c>();
  for(Term__c t : terms){
    if(t.section__c == s.id){
      termsBySection.add(t);
    }
      sectionMap.put(s.id, termsBySection);
  }
}

Map<ID, Section__c[]> agreementMap = new Map<ID, Section__c[]>();
for(Agreement__c a : agreement){
  list<Section__c> sectionsByAgreement = new list<Section__c>();
  for(Section__c s : sections){
    if(s.agreement__c == a.id){
      sectionsByAgreement.add(s);
    }
      agreementMap.put(a.id, sectionsByAgreement);
  }
}

Map<ID, Deliverable_Step__c[]> stepsMap = new Map<ID, Deliverable_Step__c[]>();
for(Deliverable__c d : deliverable){
  list<Deliverable_Step__c> stepsByDeliverable = new list<Deliverable_Step__c>();
  for(Deliverable_Step__c ds : step){
    if(ds.deliverable__c == d.id){
      stepsByDeliverable.add(ds);
    }
      stepsMap.put(d.id, stepsByDeliverable);
  }
}


//piece of code to generate the document package
for(Agreement__c document : agreement){
    String doc;
    Integer i = 1;
    doc = '\r\n<!--img src="some.url"/-->\r\n'+'<h3>'+document.type__c+'</h3>'+'\r\n<p>'+document.document_intro__c+'</p>';
    Section__c[] buildSections = agreementMap.get(document.id);
    for(Section__c getTerms : buildSections){
        Integer n = 1;
        doc = doc+'\r\n<h5>'+i+'. '+getTerms.name+'</h5>';
        i++;
        Term__c[] buildTerms = sectionMap.get(getTerms.id);
        for(Term__c printTerms : buildTerms){
            doc=doc+'\r\n<strong>'+i+'.'+n+' '+printTerms.name+'</strong>\r\n<p>'+printTerms.term__c+'</p>';
            n++;
        }
    }
    if(document.type__c == 'Statement of Work'){
        doc = doc+'<h3>'+i+' Exhibit A:  Professional Services and Deliverables</h3>\r\n<p>This exhibit defines blah blah blah</p>\r\n';
        integer n = 1;
        for(Deliverable__c services : deliverable){
            doc = doc+'<h4>'+i+'.'+n+' '+services.name+'</h4>\r\n<p>'+services.title__c+'</p>\r\n';
            Deliverable_Step__c[] buildSteps = stepsMap.get(services.id);
            integer z = 1;
            for(Deliverable_Step__c steps : buildSteps){
                doc = doc+steps.prefix__c+'-'+z+'\r\n';
            }
        }
    } 
system.debug(doc);
contractpackage.add(doc);
}
        return contractpackage;
        }
}

[/code]

 

This allows me to do an apex:repeat on the {!contractpackage} list<string> as many levels as I need to.

ThomasTTThomasTT

Oh, that's true. You can show nested data, but you can't query more than 1 level in 1 SQOL.

I strongly recommend to define Wapper classes (not only for this issue) and use multiple queries to get all required data for the nested data.

If you have to add a checkbox to each opportunity for multiselection, this wrapper will help you so much.

 

ThomasTT

 



class AccountWrapper {
public Account account {get; set;}
public List opportunities {get; set;}
}

class OpportunityWrapper {
public Opportunity oppourtunity {get; set;}
}

public List getAccounts(){
// create List inlduing OpprtunityWrappers.
return accounts;
}

<apex:repeat value="{!accounts}" var="a">
{!a.account.name}
<apex:repeat value="{!a.opportunities}" var="o">
<apex:repeat value="{!o.OpportunityLineItems}" var="oli">
{!oli.unitprice}
</apex:repeat>
<apex:repeat>
<apex:repeat>