1

Outside of a data table, a cascading select list (or drop down) is done generically like so:

<apex:selectList value="{!parentValue}">
    <apex:selectOptions value="{!ParentOptions}" />
    <apex:actionSupport event="onchange" rerender="children">
</apex:selectList>
<apex:outputPanel id="children"
    <apex:selectList value="{!childValue}">
        <apex:selectOptions value="{!ChildOptions}" />
    </apex:selectList>
</apex:outputPanel>

Where the controller has the following:

public String parentValue {get; set;}
public String childValue = {get; set;}
public List<SelectOption> getParentOptions() {
    List<SelectOption> options = new List<SelectOption>();
    for (obj__c obj : [select id, name from obj__c]) {
        options.add(new SelectOption(obj.id, obj.name));
    }
    return options;
}
public List<SelectOption> getParentOptions() {
    List<SelectOption> options = new List<SelectOption>();
    for (obj2__c obj : [select id, name from obj2__c where obj__c = :parentValue]) {
        options.add(new SelectOption(obj2.id, obj2.name));
    }
    return options;
}

How do you do this in a data table, where a SelectList in one column, when changed, rerenders a dependent SelectList in another column, given that apex:ouputPanel's id can't be dynamically created?

Basically I have a custom object that I wish to display in tabular form and allow the user to edit it. I use on all the String fields of the custom object to allow the user to double click and edit the field. Where there is essentially a picklist of available options for the user to chose from, I use to allow the user to change the option. However, one of these selectList's options will be dependent upon the selection of another SelectList's value. I would not want to refresh the entire data table each time one of the rows selectList's is changed as that would be slow.

<apex:dataTable value="{!objects}" var = "{!o}">
    <apex:column>
        <apex:selectList value="{!o.parent__c}">
            <apex:selectOptions value="{!parentItems}" />
            <!-- <apex:actionSupport> -->
        </apex:selectList>
    </apex:column>
    <apex:column>
        <apex:selectList value="{!o.dependent__c}">
            <apex:selectOptions value="{!depndentItems}" />
        </apex:selectList>
    </apex:column>
</apex:dataTable>

Thank you.

4

What you need is a Wrapper for each data row you want to display. the wrapper will take care of the SelectOptions.

Below is an example, that displays a list of Opportunities for which you can select a Contact based on the Opportunity's AccountId

To avoid to run into Governor Limits #getItems() is responsible for setting up the selectOptions in bulk initially.

public class SelectListController {

    ItemWrapper[] items;

    // account select options for the opportunities
    public List<SelectOption> getParentOptions() {

        List<SelectOption> options = new List<SelectOption>();

        for (Account record : [select Id, Name from Account LIMIT 100])
        {
            options.add(new SelectOption(record.Id,record.Name));
        }
        return options;
    }


    // returns list of ItemWrappers, each holding an opportuity record as well
    // as a list of contact selectOptions for the opportunity account id
    public ItemWrapper[] getItems(){

        if (items == null)
        {

            items = new ITemWrapper[]{};

            Opportunity[] records = [
                select Id
                     , Name
                     , AccountId
                  from Opportunity
                 where AccountID != null
                 LIMIT 10];

            Id[] accountIds = new Id[]{};
            for (Opportunity record:records)
            {
                accountIds.add(record.AccountId);
            }

            // query accounts with contacts to bulk setup select options
            Map<Id,Account> accountMap = new Map<Id,Account>([
                select Id
                     , (
                select Id
                     , Name
                  from Contacts)
                  from Account
                 where Id IN: accountIds]);

            // setup one ItemWrapper per Opportunity and generate the selectOptions using the Account Map
            for (Opportunity record:records)
            {
                ItemWrapper item = new ItemWrapper(record);
                items.add(item);

                // could by null if AccountId is null
                if (accountMap.containsKey(record.AccountId))
                {
                    for (Contact contact:accountMap.get(record.AccountId).Contacts)
                    {
                        item.contactOptions.add(new SelectOption(contact.Id,contact.Name));
                    }                
                }
            }
        }

        return items;
    }


    // Wrapper with Opportunity and Contact SelectOptions for the Opp's AccountId
    public class ItemWrapper {

        public Opportunity record {get;set;}

        public String contactValue {get;set;}

        public SelectOption[] contactOptions {get;set;}

        public ItemWrapper(Opportunity record){
            this.record = record;
            this.contactOptions = new SelectOption[]{};
        }


        public Id getParentValue(){
            return record.AccountId;
        }


        // changing the parent value, will replace the current contact options with
        // contacts from the for the new account
        public void setParentValue(Id value){

            if (value != record.AccountId)
            {
                contactOptions = new SelectOption[]{};

                for (Contact contact:[select Id, Name from Contact where AccountId =: value ORDER BY NAME])
                {
                    contactOptions.add(new SelectOption(contact.Id,contact.Name));
                }
            }
            record.AccountId = value;
        }
    }
}

Here's the VF Page

<apex:page controller="SelectListController">

<apex:form>
    <apex:pageBlock>

        <apex:pageBlockTable value="{!items}" var="i">

            <apex:column value="{!i.record.Name}"/>

            <apex:column>
                <apex:selectList value="{!i.ParentValue}" size="1">
                    <apex:selectOptions value="{!parentOptions}"/>
                    <apex:actionSupport event="onchange" reRender="contacts"/>
                </apex:selectList>
            </apex:column>

            <apex:column>
                <apex:selectList value="{!i.contactValue}" size="1" id="contacts">
                    <apex:selectOptions value="{!i.contactOptions}"/>
                </apex:selectList>
            </apex:column>


        </apex:pageBlockTable>

    </apex:pageBlock>
</apex:form>
</apex:page>
  • Thank you so much Mr. Wagner. I apologize for the very late response; I haven't had time to implement it until now. Everythinbg works except one thing: the dependent box does not default to the correct value for the row. I think it is defaulting to the first option available in the list of select options, because I updated my rows and all the data in the dependent list changed to the first option (oops!). The parent list defaults correctly. Any ideas why this would be? – Matthew Moisen Jun 11 '13 at 1:17
  • you'll have to set the value for contactValue/childValue, this will cause the HTML Select element to select the right value – Seb Wagner Jun 11 '13 at 10:51

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.