Take the 2-minute tour ×
Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. It's 100% free, no registration required.

I have an interesting implementation request, and would like to pick the communities brains on the best way to design a solution.

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)

Thanks!

share|improve this question
    
Multi-select picklists cannot be controlling fields (your ParentMultiSelectField__c). –  user529 Sep 7 '12 at 14:24
add comment

1 Answer 1

up vote 9 down vote accepted

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...)

share|improve this answer
    
wow, this is a long answer. I'll review it over the weekend. Thanks for taking the time to answer JCD. :) –  Anup Sep 7 '12 at 21:38
    
Thanks so much for this code. I am working through this to create the multi-select picklists for my site. Just FYI, I found a couple of typos when I started trying to use it and thought you might want to update your example: 1) It s/b SelectOption not Select Options 2)The method .length() references s/b changed to .size() Now what I'm trying to do is a bit different. I want the third child picklist to be dependent on the second multi-select picklist. I'm going to try and get it going, I'll post my code if I do... –  user772 Oct 19 '12 at 14:40
    
u stub so well :P –  MnZ Jun 19 at 10:02
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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