72

I have a pair of dependent picklists. In Apex code, how can I determine what options are valid in the dependent field for each option in the controlling field?

I've tried using getPicklistValues(), but there doesn't seem to be any way of getting dependency information.

Example

Controlling_Field__c

Fruit, Vegetable, Dairy

Dependent_Field__c

(Fruit): Apple, Banana, Pear

(Vegetable): Tomato, Egglant, Lettuce

(Dairy): Milk, Cheese, Yogurt

What I'm Looking For

(In pseudocode, something like:)

controllingOptions = dependentfield.getPicklistValues();
for (option : controllingOptions) {
    dependentOptions = dependentfield.getPicklistValuesFor(option);
    do_something_with_it();
}
1
  • Seems like fairly major oversite on the apex side but understandable since the apex and api teams are different. I'm probably bias since I'm writing apex but I don't think there should be anything you can do in the api that you can't do in apex.
    – user917
    Commented Jan 8, 2013 at 19:30

8 Answers 8

61

Salesforce doesn't reveal information about dependent picklists in normal Describe calls in Apex, but it is included in calls via the API, and can also be accessed through Apex by serializing and deserializing PicklistEntry objects (see gist linked below for an example of how to do that).

Each PicklistEntry in a dependent field has a property called validFor which contains a Base64 encoded string. When decoded to bits, each bit read left to right corresponds to an option in the controlling field. For example, if you have a validFor of "gAAA"

example pke.validFor: g      A      A      A
displayed as bits:    100000 000000 000000 000000
rearranged as bytes:  10000000 00000000 00000000

As such, you can use loop through all of the picklist values and use bitwise operators to check whether each dependent value is valid for each controlling value.

I wrote a javascript function and Apex Class to abstract all this, which is available as a gist. You can use it like this (assumes you've correctedly loaded the Ajax toolkit in a visualforce page):

/* Build an Object in which keys are valid options for the controlling field
 * and values are lists of valid options for the dependent field.
 */
var OBJ_NAME = 'Custom_Object__c';
var CTRL_FIELD_NAME = "Controlling_Field__c";
var DEP_FIELD_NAME = "Dependent_Field__c";
var options = getDependentOptions(OBJ_NAME, CTRL_FIELD_NAME, DEP_FIELD_NAME);
console.debug(options);
6
  • I sent you an update to your gist code in email. It handles the case where a dependent value is assigned to more than one master value. Commented Jan 16, 2013 at 2:22
  • @DerekLansing, I never got your email, but I've made a similar change to the gist.
    – Benj
    Commented Jan 30, 2013 at 20:22
  • Hi I am facing an issue same type. Dependent values are not coming for corresponding controlling values. Can you please help ? salesforce.stackexchange.com/questions/81204/… Commented Jun 25, 2015 at 9:45
  • 1
    This is a great solution! The only issue is that it does not take into account checkbox controlling fields. Picklists can be controlled by other picklists and checkbox. This gist is a slight update on your 2017 version that takes into account checkboxes. (The formatting got kind of wonky) Commented Dec 19, 2017 at 21:12
  • 1
    Big thank-you for posting this. I did notice that for the case of the default state and country picklists where there are 330 states these have only 9 different validFor strings so caching those 9 outcomes reduces the generation time from 1400 ms to 150ms.
    – Keith C
    Commented Apr 14, 2018 at 14:28
14

Approaches had been well discussed in the above thread. Following is an apex utility function that provides the controlling field value and its dependent field values provided object name, controlling field and dependent field.

//By SharSolutions
   public class MyPickListInfo
   {
        public String validFor;
   }

public static Map<String, List<String>> getFieldDependencies(String objectName, String controllingField, String dependentField)
{
    Map<String, List<String>> controllingInfo = new Map<String, List<String>>();

    Schema.SObjectType objType = Schema.getGlobalDescribe().get(objectName);

    Schema.DescribeSObjectResult describeResult = objType.getDescribe();
    Schema.DescribeFieldResult controllingFieldInfo = describeResult.fields.getMap().get(controllingField).getDescribe();
    Schema.DescribeFieldResult dependentFieldInfo = describeResult.fields.getMap().get(dependentField).getDescribe();

    List<Schema.PicklistEntry> controllingValues = controllingFieldInfo.getPicklistValues();
    List<Schema.PicklistEntry> dependentValues = dependentFieldInfo.getPicklistValues();

    for(Schema.PicklistEntry currControllingValue : controllingValues)
    {
        System.debug('ControllingField: Label:' + currControllingValue.getLabel());
        controllingInfo.put(currControllingValue.getLabel(), new List<String>());
    }

    for(Schema.PicklistEntry currDependentValue : dependentValues)
    {
        String jsonString = JSON.serialize(currDependentValue);

        MyPickListInfo info = (MyPickListInfo) JSON.deserialize(jsonString, MyPickListInfo.class);

        String hexString = EncodingUtil.convertToHex(EncodingUtil.base64Decode(info.validFor)).toUpperCase();

        System.debug('DependentField: Label:' + currDependentValue.getLabel() + ' ValidForInHex:' + hexString + ' JsonString:' + jsonString);

        Integer baseCount = 0;

        for(Integer curr : hexString.getChars())
        {
            Integer val = 0;

            if(curr >= 65)
            {
                val = curr - 65 + 10;
            }
            else
            {
                val = curr - 48;
            }

            if((val & 8) == 8)
            {
                System.debug('Dependent Field: ' + currDependentValue.getLabel() + ' Partof ControllingField:' + controllingValues[baseCount + 0].getLabel());
                controllingInfo.get(controllingValues[baseCount + 0].getLabel()).add(currDependentValue.getLabel());
            }
            if((val & 4) == 4)
            {
                System.debug('Dependent Field: ' + currDependentValue.getLabel() + ' Partof ControllingField:' + controllingValues[baseCount + 1].getLabel());
                controllingInfo.get(controllingValues[baseCount + 1].getLabel()).add(currDependentValue.getLabel());                    
            }
            if((val & 2) == 2)
            {
                System.debug('Dependent Field: ' + currDependentValue.getLabel() + ' Partof ControllingField:' + controllingValues[baseCount + 2].getLabel());
                controllingInfo.get(controllingValues[baseCount + 2].getLabel()).add(currDependentValue.getLabel());                    
            }
            if((val & 1) == 1)
            {
                System.debug('Dependent Field: ' + currDependentValue.getLabel() + ' Partof ControllingField:' + controllingValues[baseCount + 3].getLabel());
                controllingInfo.get(controllingValues[baseCount + 3].getLabel()).add(currDependentValue.getLabel());                    
            }

            baseCount += 4;
        }            
    } 

    System.debug('ControllingInfo: ' + controllingInfo);

    return controllingInfo;
}
0
12

So, this awesome dude, wrote exactly what you were looking for. Might as well pimp his code and spread the word. This was a life save for something I was working on this week!

http://titancronus.com/blog/2014/05/01/salesforce-acquiring-dependent-picklists-in-apex/

11

I've recently come up with this concise version of the dependent picklist code. One method, self-contained, about 30 lines of code. The method returns a map of lists, keyed by controlling values, the lists contain all valid dependent values for the controlling value. This works when the controlling field is a checkbox (boolean) as well as a picklist.

public static Map<Object,List<String>> getDependentPicklistValues( Schema.sObjectField dependToken )
{
    Schema.DescribeFieldResult depend = dependToken.getDescribe();
    Schema.sObjectField controlToken = depend.getController();
    if ( controlToken == null ) return null;
    Schema.DescribeFieldResult control = controlToken.getDescribe();
    List<Schema.PicklistEntry> controlEntries =
    (   control.getType() == Schema.DisplayType.Boolean
    ?   null
    :   control.getPicklistValues()
    );

    String base64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    Map<Object,List<String>> dependentPicklistValues = new Map<Object,List<String>>();
    for ( Schema.PicklistEntry entry : depend.getPicklistValues() ) if ( entry.isActive() )
    {
        List<String> base64chars =
            String.valueOf
            (   ((Map<String,Object>) JSON.deserializeUntyped( JSON.serialize( entry ) )).get( 'validFor' )
            ).split( '' );
        for ( Integer index = 0; index < (controlEntries != null ? controlEntries.size() : 2); index++ )
        {
            Object controlValue =
            (   controlEntries == null
            ?   (Object) (index == 1)
            :   (Object) (controlEntries[ index ].isActive() ? controlEntries[ index ].getLabel() : null)
            );
            Integer bitIndex = index / 6, bitShift = 5 - Math.mod( index, 6 );
            if  (   controlValue == null
                || (base64map.indexOf( base64chars[ bitIndex ] ) & (1 << bitShift)) == 0
                ) continue;
            if ( !dependentPicklistValues.containsKey( controlValue ) )
            {
                dependentPicklistValues.put( controlValue, new List<String>() );
            }
            dependentPicklistValues.get( controlValue ).add( entry.getLabel() );
        }
    }
    return dependentPicklistValues;
}

I've put this all into a blog post here: Glyn Talks Salesforce. The blog post includes details of how it works and test code for 100% coverage with assertions.

I hope it helps!

4
  • I loved this post! Commented Aug 17, 2018 at 23:18
  • 2
    This is great, and the blog post explanation is very helpful too! I discovered one edge case where it fails though. If a PicklistEntry is not valid for any of the control entries, then validFor returns an empty string, causing an index out of bounds error on line 30. The error can be avoided by checking if validFor is blank (String.isBlank) prior to line 17 and skipping that PicklistEntry iteration if it is.
    – kacrouse
    Commented Sep 25, 2019 at 18:16
  • The blog post is fantastic ! Thank you for the detailed explanation in the post. Helped me understand the bitIndex and bitShift calculation better than the titancronus post.
    – Nagen Sahu
    Commented Jan 16, 2023 at 18:42
  • loved this answer, this support the controlling field type: boolean. the accepted answer only support picklist controlling field
    – Rival
    Commented Mar 17, 2023 at 4:33
7

I had the same problem a while ago and I have posted on ideaexchange:

https://sites.secure.force.com/success/ideaView?id=08730000000h1y6AAA

At this point in time this is the only solution that I have found:

http://iwritecrappycode.wordpress.com/2012/02/23/dependent-picklists-in-salesforce-without-metadata-api-or-visualforce/

Hope that helps!

6

I have had an opportunity to check the solution from Tommy answer (http://titancronus.com/blog/2014/05/01/salesforce-acquiring-dependent-picklists-in-apex/), and while it worked for some simple picklist dependency picklist entries, it did not work for some specific dependent picklist options. Specifically, if the dependent list of picklist options is large, or if the controlling picklist has many options - some values were marked as dependent, and others were not...

I have managed to implement the Bitset helper class, which is based on the approach provided in the Salesforce documentation (see PicklistEntry, Java code sample), see below:

public class BitsetChecker {
    public String validFor {get;private set;}
    public String vfDecoded {get;private set;}
    public String[] hexBytes {get;private set;}
    public Integer[] bytes {get;private set;}

    public BitsetChecker(String validFor) {
        this.validFor = validFor;
        this.vfDecoded = null;
        hexBytes = new String[] {};
        bytes = new Integer[] {};
        if (String.isNotBlank(validFor)) {
            this.vfDecoded = String.isNotBlank(validFor) ?
                EncodingUtil.convertToHex(EncodingUtil.base64Decode(validFor)).toLowerCase() : '';
            if (String.isNotBlank(vfDecoded) && Math.mod(vfDecoded.length(), 2) == 0) {
                for (Integer i = 0; i < vfDecoded.length(); i += 2) {
                    String hexByte = vfDecoded.substring(i, i + 2);
                    hexBytes.add(hexByte);
                    bytes.add(hexToDecimal(hexByte).intValue());
                }
            }
        }
    }

    public Boolean testBit(Integer n) {
        Boolean result = false;
        if (n != null && n < size() && hexBytes != null) {
            Integer bytesPos = n >> 3;
            Integer targetByte = bytesPos < bytes.size() ? bytes[bytesPos] : null;
            if (targetByte != null) {
                Integer mask = 128 >> Math.mod(n, 8);
                Integer maskedByte = targetByte & mask;
                result = maskedByte != 0;
            }
        }
        return result;
    }

    public Integer size() {
        return bytes.size() * 8;
    }

    public static Decimal hexToDecimal(String sourceHex) {
        String hex = '0123456789abcdef';
        String[] hexValue = sourceHex.split('');
        Decimal result = 0;
        for(Integer index = 0; index < hexValue.size(); index++) {
            result = (result * 16) + hex.indexOf(hexValue[index]);
        }
        return result;
    }
}
3

You can take help from https://www.minddigital.com/how-to-create-dynamic-dependent-picklist-of-objects-within-salesforce/ where the apex class is there to create the logic. You just need to replace the custom object with the object on which you want to work for the dependent picklist. To implement that logic we have a VF page.

2

Thank for your solution i was using code from https://developer.salesforce.com/forums/?id=906F0000000BIVqIAO which worked fine but not for the dependent values of the last value of the controller Picklist. In this case, the validfor.length of the dependent values is 8.

With your solution it works. Here is an exemple of Apex code calling your Class "BitsetChecker"

for(Integer ppli=0; ppli<pplvalues.size() ; ppli++) {
   list <string> DependentItemList = new list <string>();
   for (PicklistEntry dplv: dplvalues) {
       String jsonstr = JSON.serialize(dplv);
       Map<String,String> jMap = (Map<String,String>)     
       JSON.deserialize(jsonstr, Map<String,String>.class);
        String validFor = jMap.get('validFor');
       String dplvalue = jMap.get('value');
       // using class Bitsetchecker
       BitsetChecker checkdependency=new BitsetChecker(validfor);
        if (checkdependency.testbit(ppli)) {
           DependentItemList.add(dplv.getValue());                     
        } // dependent found
     } // for dependent picklist
} // for parent picklist

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .