Salesforce Lightning: Dependent Picklist - State Country picklist

Dependent picklist are not supported by default in the lightning components. There is no component provided by Salesforce through which we can have dependent picklist in custom lightning components.

Fot this example, I have created two custom fields of type picklist - Country and State on Account. After creating the fields, I have setup dependency between them where Country is the controlling picklist and State is the dependent picklist. Below are the components used:

  • Picklist.cmp - Generic component to display picklist in lightning component. Just pass in the object api name and field api name to display the picklist.
  • StateCountryPicklist.cmp - This component displays the state values as the per controlling country value passed to it.
Below is the code for all the components:

StateCountryPicklist.cmp
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" controller="StateCountryPicklistCtrl">
 
    <aura:attribute name="selectedCountry" type="String"/>
    <aura:attribute name="selectedState" type="String"/>
 
    <aura:attribute name="objectAPIName" type="String" default="Account"/>
    <aura:attribute name="countryPicklistAPIName" type="String" default="Country__c"/>
    <aura:attribute name="statePicklistAPIName" type="String" default="State__c"/>
 
    <!-- on change event to update states on change of country -->
    <aura:handler name="change" value="{!v.selectedCountry}" action="{!c.updateStates}"/>
 
    <!-- populated by init -->
    <aura:attribute name="states" type="List" />
 
    <!-- Country-->
    <c:Picklist objectName="Account" fieldName="Country__c" value="{!v.selectedCountry}"/>
 
    <!-- State -->
    <aura:if isTrue="{!v.selectedCountry}">
        <lightning:select name="state" value="{!v.selectedState}">
            <aura:iteration items="{!v.states}" var="item">
                <option value="{!item.value}" selected="{!item.value==v.value}">{!item.label}</option>
            </aura:iteration>
        </lightning:select>
    </aura:if>
 
</aura:component>
StateCountryPicklistController.js
({
    updateStates : function(component, event, helper) {
        if(component.get("v.selectedCountry")){
            helper.getStates(component, event, helper);
        }
}

})
StateCountryPicklistHelper.js
({
    getStates : function(component, event, helper) {
        var action = component.get("c.getStates");
        action.setParams({ "selectedCountry" : component.get("v.selectedCountry"),
                          "objectAPIName" : component.get("v.objectAPIName"),
                          "countryPicklistAPIName" : component.get("v.countryPicklistAPIName"),
                          "statePicklistAPIName" : component.get("v.statePicklistAPIName")
                         });
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                var stateList = response.getReturnValue();
                component.set("v.states", stateList);
            }
            else if (state === "ERROR") {
                console.error("Error calling action: "+ response.getState());
            }
        });
        $A.enqueueAction(action);
    }

})
StateCountryPicklistCtrl.apxc

public class StateCountryPicklistCtrl {
 
    @AuraEnabled
    public static List<PicklistOption> getStates(String selectedCountry, String objectAPIName,
                                                String countryPicklistAPIName, String statePicklistAPIName){
        List<PicklistOption> stateList = new List<PicklistOption>();
        if(selectedCountry == null || selectedCountry == ''){
            return stateList;
        }
        Map<String,List<String>> valueMap = DependentPicklistUtils.getFieldDependencies(
            objectAPIName,countryPicklistAPIName,statePicklistAPIName);
        if(valueMap != null){
            for(String state: valueMap.get(selectedCountry)){
                stateList.add(new PicklistOption(state,state));
            }
        }
        return stateList;
    }
 
     public class PicklistOption {
     
        @AuraEnabled
        public String label { get; set; }
     
        @AuraEnabled
        public String value { get; set; }
     
        public PicklistOption( String label, String value ) {
            this.label = label;
            this.value = value;
        }
    }

}
DependentPicklistUtils.apxc
public class DependentPicklistUtils {
 
    public class PickListInfo{
        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){
            controllingInfo.put(currControllingValue.getLabel(), new List<String>());
        }
     
        for(Schema.PicklistEntry currDependentValue : dependentValues){
            String jsonString = JSON.serialize(currDependentValue);
         
            PickListInfo info = (PickListInfo) JSON.deserialize(jsonString, PickListInfo.class);
         
            String hexString = EncodingUtil.convertToHex(EncodingUtil.base64Decode(info.validFor)).toUpperCase();
         
            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){
                    controllingInfo.get(controllingValues[baseCount + 0].getLabel()).add(currDependentValue.getLabel());
                }
                if((val & 4) == 4){
                    controllingInfo.get(controllingValues[baseCount + 1].getLabel()).add(currDependentValue.getLabel());                 
                }
                if((val & 2) == 2){
                    controllingInfo.get(controllingValues[baseCount + 2].getLabel()).add(currDependentValue.getLabel());                 
                }
                if((val & 1) == 1){
                    controllingInfo.get(controllingValues[baseCount + 3].getLabel()).add(currDependentValue.getLabel());                 
                }
             
                baseCount += 4;
            }         
        }
        return controllingInfo;
    }
}
Picklist.cmp
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" controller="PicklistController">
 
    <!-- required attributes-->
    <aura:attribute name="objectName" type="String" required="true" default="Account"/>
    <aura:attribute name="fieldName" type="String" required="true" default="Type"/>
 
    <!-- optional attributes-->
    <aura:attribute name="label" type="String" default="Label" description="Text that describes the desired select input. Default is the field's label."/>
    <aura:attribute name="value" type="String" description="The value of the select, also used as the default value to select the right option during init. If no value is provided, the first option will be selected."/>
 
    <!-- event handlers, as exposed by lightning:select component -->
    <aura:attribute name="onblur" type="Aura.Action" description="The action triggered when the element releases focus."/>
    <aura:attribute name="onfocus" type="Aura.Action" description="The action triggered when the element receives focus."/>
    <aura:attribute name="onchange" type="Aura.Action" description="The action triggered when a value attribute changes."/>
 
    <aura:attribute name="options" type="List" access="private" description="The picklist options to choose from. Populated during component initialization."/>
 
    <aura:handler name="init" value="{!this}" action="{!c.init}"/>
 
    <lightning:select name="picklist"
                      label="{!v.label}"
                      value="{!v.value}"
                      class="{!v.class}"
                      onblur="{!v.onblur}"
                      onfocus="{!v.onfocus}"
                      onchange="{!v.onchange}">
     
        <aura:iteration var="opt" items="{!v.options}">
            <option value="{!opt.value}" selected="{!opt.value==v.value}">{!opt.label}</option>
        </aura:iteration>
    </lightning:select>
 

</aura:component>
PicklistController.js
({
init : function(component, event, helper) {
helper.getPicklistOptions(component, event, helper);
}

})
PicklistHelper.js
({
    getPicklistOptions : function(component, event, helper) {
     
        var action = component.get("c.getPicklistOptions");
        action.setParams({ objectName : component.get("v.objectName"),
                          fieldName: component.get("v.fieldName")});
        action.setCallback( this, function( response ) {
            if (component.isValid() && response.getState() === "SUCCESS") {
                var data = response.getReturnValue();
                component.set("v.options", data);
                component.set("v.value", data[0].value);
            } else {
                console.error("Error calling action: "+ response.getState());
            }
        });
        $A.enqueueAction(action);
    }
 

})

Comments

  1. In Above Sample PicklistController.apxc class was not available. Please update it will be helpful to everyone.

    ReplyDelete

Post a Comment

Popular posts from this blog

Salesforce Hacks: System.LimitException: Too many queueable jobs added to the queue: 2

Salesforce Lightning: Countdown timer

Building an Org Role Hierarchy component in LWC