[SalesForce] Dependent multi-select pick lists

I'd like to add 3 picklist fields to an Account object in the following manner:

  1. Parent multi-select Picklist field (let's call it ParentMultiSelectField__c)
  2. A dependent mutli-select Picklist field (let's call it ChildMultiSelectField__c) is dependent on the values in
    ParentMultiSelectField__c field
  3. And a third single picklist field (let's call it ChildSinglePiclistField__c) is also dependent on the values in
    ParentMultiSelectField__c field

For the sake of clarity, when I say dependent I mean that the values in the child fields will be filtered depending on the value(s) selected in the parent multi-select picklist.

My question is in 2 parts:

  1. Is this setup possible using standard salesforce point and click?
  2. If not, how do I implement this using visualforce and apex? (High level design should suffice)

Best Answer

Safe Harbor: this is a really long answer...read at your own risk. Also, none of this sample code has been tested, this is just me stubbing it out in Notepad++.

This is not currently possible with standard Salesforce configuration, as multi-select picklists cannot be selected as the Controlling Field in a Field Dependency. So, you're definitely looking at a VisualForce solution; your approach here really depends on your requirements. Does it need to be easily configurable, or is it feasible to hardcode the dependencies in your controller?

If it's acceptable to hardcode the dependencies in your controller, then you'll probably want to set up maps of the dependencies to simplify your code (I use the word "simplify" loosely here)...

public class someController {

    public String[] parentPicklistVal {public get; public set;}
    public String[] childMultiPicklistVal {public get; public set;}
    public String childSinglePicklistVal {public get; public set;}

    // maps to hold your dependencies between picklists
    private Map<String, List<String>> parentDepMap;
    private Map<String, List<String>> childDepMap;

    private String[] parentOpts = new String[] { 'parent option 1', 'parent option 2' };
    private String[] childMultiOpts = new String[] { 'child multi 1', 'child multi 2', 'child multi 3' };
    private String[] childSingleOpts = new String[] { 'child single 1', 'child single 2', 'child single 3' };

    public someController() {
        // init dependency maps
        parentDepMap = new Map<String, List<String>>();
        childDepMap = new Map<String, List<String>>();

        // pick which child options to display for which parent value
        parentDepMap.put(parentOpts[0], (new String[]{childMultiOpts[0], childMultiOpts[1]}));
        parentDepMap.put(parentOpts[1], (new String[]{childMultiOpts[1], childMultiOpts[2]}));

        // pick which single-select options to display for which child value
        childDepMap.put(childMultiOpts[0], (new String[]{childSingleOpts[0], childSingleOpts[2]}));
        childDepMap.put(childMultiOpts[1], (new String[]{childSingleOpts[1], childSingleOpts[2]}));
        childDepMap.put(childMultiOpts[2], childSingleOpts);    // or if you want to show them all?
    }

    public List<SelectOption> getParentPicklistOptions() {
        List<SelectOption> selectOpts = new List<SelectOption>();
        for ( String s : parentOpts )
            selectOpts.add(new SelectOption(s, s));
        return selectOpts;
    }

    public List<SelectOption> getChildMultiPicklistOptions() {
        List<SelectOption> selectOpts = new List<SelectOption>();
        if ( parentPicklistVal != null && parentPicklistVal.size() > 0 ) {
            // build a set of values to avoid dupes, since there may be overlapping dependencies
            Set<String> possibleOpts = new Set<String>();
            for ( String val : parentPicklistVal )
                possibleOpts.addAll(parentDepMap.get(val));
            for ( String s : possibleOpts )
                selectOpts.add(new SelectOption(s, s));
        }
        return selectOpts;
    }

    public List<SelectOption> getChildSinglePicklistOptions() {
        List<SelectOption> selectOpts = new List<SelectOption>();
        if ( childMultiPicklistVal != null && childMultiPicklistVal.size() > 0 ) {
            // build a set of values to avoid dupes, since there may be overlapping dependencies
            Set<String> possibleOpts = new Set<String>();
            for ( String val : childMultiPicklistVal )
                possibleOpts.addAll(childDepMap.get(val));
            for ( String s : possibleOpts )
                selectOpts.add(new SelectOption(s, s));
        }
        return selectOpts;
    }

    public PageReference actionUpdatePicklistVals() {
        // this doesn't really need to do anything, since the picklists should be updated when their getters call after returning
        return null;
    }

}

And then your VF page...

<apex:page controller="someController">
    <apex:form>
        <apex:outputPanel id="panel1">
            <apex:selectList value="{!parentPicklistVal}" multiselect="true" size="3">
                <apex:selectOptions value="{!parentPicklistOptions}" />
                <apex:actionSupport event="onchange" action="{!actionUpdatePicklistVals}" rerender="panel1" />
            </apex:selectList>
            <apex:selectList value="{!childMultiPicklistVal}" multiselect="true" size="3">
                <apex:selectOptions value="{!childMultiPicklistOptions}" />
                <apex:actionSupport event="onchange" action="{!actionUpdatePicklistVals}" rerender="panel1" />
            </apex:selectList>
            <apex:selectList value="{!childSinglePicklistVal}" multiselect="false" size="1">
                <apex:selectOptions value="{!childSinglePicklistOptions}" />
            </apex:selectList>
        </apex:outputPanel>
    </apex:form>
</apex:page>

However, if this needs to be easily configurable, then you're looking at a custom object with a lookup relationship to itself. You can then query the dependencies and build the maps from them instead of hardcoding them. Just throwing some ideas out there, let's say this custom object uses a Name field for its value, a FieldDep__c field as a lookup to it's controlling value, and a WhichPicklist__c field to control which picklist it belongs to (for this I'll just say it's an integer with possible values of 1, 2, and 3).

public class someController {

    public String[] parentPicklistVal {public get; public set;}
    public String[] childMultiPicklistVal {public get; public set;}
    public String childSinglePicklistVal {public get; public set;}

    private Map<String, List<String>> parentDepMap;
    private Map<String, List<String>> childDepMap;

    public someController() {
        FieldDep__c[] deps = [select id, name, fielddep__c, fielddep__r.name, whichpicklist__c from FieldDep__c order name asc];
        // init the same maps that we used in the example above
        parentDepMap = new Map<String, List<String>>();
        childDepMap = new Map<String, List<String>>();
        for ( FieldDep__c dep : deps ) {
            if ( dep.whichpicklist__c == 1 ) {
                // shouldn't really need to do anything here...
            } else if ( dep.whichpicklist__c == 2 ) {
                // add to the dependency map between the parent and child multi-select picklist
                String parentVal = dep.fielddep__r.name;
                if ( parentDepMap.containsKey(parentVal) )
                    parentDepMap.get(parentVal).add(dep.Name);
                else
                    parentDepMap.put(parentVal, new String[]{dep.Name});
            } else if (dep.whichpicklist__c == 3 ) {
                // add to the dependency map between the child multi-select and child single-select picklist
                String parentVal = dep.fielddep__r.name;
                if ( childDepMap.containsKey(parentVal) )
                    childDepMap.get(parentVal).add(dep.Name);
                else
                    childDepMap.put(parentVal, new String[]{dep.Name});
            }
        }
    }

    public List<SelectOption> getParentPicklistOptions() {
        List<SelectOption> selectOpts = new List<SelectOption>();
        String[] parentOpts = parentDepMap.keySet();
        for ( String s : parentOpts )
            selectOpts.add(new SelectOption(s, s));
        return selectOpts;
    }

    public List<SelectOption> getChildMultiPicklistOptions() {
        List<SelectOption> selectOpts = new List<SelectOption>();
        if ( parentPicklistVal != null && parentPicklistVal.size() > 0 ) {
            // build a set of values to avoid dupes, since there may be overlapping dependencies
            Set<String> possibleOpts = new Set<String>();
            for ( String val : parentPicklistVal )
                possibleOpts.addAll(parentDepMap.get(val));
            for ( String s : possibleOpts )
                selectOpts.add(new SelectOption(s, s));
        }
        return selectOpts;
    }

    public List<SelectOption> getChildSinglePicklistOptions() {
        List<SelectOption> selectOpts = new List<SelectOption>();
        if ( childMultiPicklistVal != null && childMultiPicklistVal.size() > 0 ) {
            // build a set of values to avoid dupes, since there may be overlapping dependencies
            Set<String> possibleOpts = new Set<String>();
            for ( String val : childMultiPicklistVal )
                possibleOpts.addAll(childDepMap.get(val));
            for ( String s : possibleOpts )
                selectOpts.add(new SelectOption(s, s));
        }
        return selectOpts;
    }

    public PageReference actionUpdatePicklistVals() {
        // this doesn't really need to do anything, since the picklists should be updated when their getters call after returning
        return null;
    }

}

You would then want to populate FieldDep__c records from the top down so that you can add a lookup to the controlling value for each record.

I hope I didn't go too overboard with this! If I get a chance sometime today I'll put this together in an org to see how valid my code works, but it sounded like you were just looking for a push in the right direction (although this answer may have been slightly more than that...)

Related Topic