Flow Screen Component: Dependent picklists based on RecordType selection
Hola Ohana! 😀
In this article, we will see how we can create a lightning component for dependent picklists which renders the picklist values based on the record type selected. I have designed the component specifically for Visual flow. However, with slight changes, we can also use the component at other places.
But what’s the need to create this component?
Of everything we create, we should know the utility and significance of what we are creating, right? In winter’19 release, Salesforce released a lightning component, flowruntime:dependentPicklists, to display the dependent picklists on the flow screens. Pretty cool right! But here is a food for thought, what if your organization is using record types? That certainly limits the usability of flowruntime:dependentPicklists component big time.
Hence, to help our dear fellow Admins we will create lightning component so that they don’t have to worry about the record types.
Setting up elements in the org for our use case for demo
For demonstration purpose, I have created two picklist fields, namely;
– Session Category(Session_Category__c)
– Session Location(Session_Location__c)
Next, I created a field dependency between the two picklists, as shown.

Now, it’s time we create two record types and set up the values for both the picklist fields, as shown:




Now that we are done creating the different elements to demo our use case, we are ready to jump into the code.
Shout out to Piyush Soni, author of SFDC Monkey for an amazing article on Custom dependent picklists.
DependentPicklistController.apxc:
public class DependentPicklistController { //Wrapper class to return the recordtype object to component public class recordTypeWrapper{ @AuraEnabled public string rtName{get;set;} @AuraEnabled public string rtId{get;set;} @AuraEnabled public boolean rtIsMaster{get;set;} @AuraEnabled public boolean incorrectApiName{get;set;} } //Wrapper class to handle the JSON response public class picklistWrapper{ @AuraEnabled public piclistValues[] values; } //Wrapper class to handle the JSON response public class piclistValues { @AuraEnabled public String label; @AuraEnabled public String value; } /* Getting the list of record types accessible to the logged in user */ @AuraEnabled public static list<recordTypeWrapper> getRecordTypes(string ObjectApiName){ list<recordTypeWrapper> RecordTypeList=new list<recordTypeWrapper>(); Schema.SObjectType objectType = Schema.getGlobalDescribe().get(ObjectApiName); if(objectType!=null){ for(RecordTypeInfo rtInfo: objectType.getDescribe().getRecordTypeInfos()){ if(rtInfo.isAvailable() && !rtInfo.isMaster()){ recordTypeWrapper rtype= new recordTypeWrapper(); rtype.rtName=rtInfo.name; rtype.rtId=rtInfo.getRecordTypeId(); rtype.rtIsMaster=rtInfo.isMaster(); RecordTypeList.add(rtype); } } } else{ recordTypeWrapper rtype= new recordTypeWrapper(); rtype.incorrectApiName=true; RecordTypeList.add(rtype); } return RecordTypeList; } /* Making callout to the UI API to get the list of available picklist values based on the selected recordtype */ @AuraEnabled public static string[] getPicklistValues(string recordTypeId, string ObjectApiName,string fieldApiName){ string[] piclistValues=new string[]{}; string BaseUrl=system.Url.getOrgDomainUrl().toExternalForm(); httprequest request= new httprequest(); request.setMethod('GET'); request.setEndpoint(BaseUrl+'/services/data/v44.0/ui-api/object-info/'+ObjectApiName+'/picklist-values/'+recordTypeId+'/'+fieldApiName); request.setHeader('Authorization', 'Bearer ' + getSessionIdFromVfPage()); request.setHeader('Content-Type','application/json; charset=UTF-8'); request.setHeader('Accept','application/json'); http http = new http(); httpresponse response=http.send(request); picklistWrapper obj=new picklistWrapper(); obj=(picklistWrapper)JSON.deserialize(response.getBody(), picklistWrapper.class); for(piclistValues pickVal:obj.values){ piclistValues.add(pickVal.value); } return piclistValues; } //Getting the session Id from the VF page to make the callouts private static string getSessionIdFromVfPage(){ PageReference visualforcePage = Page.SessionIdPage; String content; try{ content = visualforcePage.getContent().toString(); } catch(VisualforceException e){ content='ID'+userinfo.getSessionId()+'END'; } Integer s = content.indexOf('ID') + 'ID'.length(); Integer e = content.indexOf('END'); return content.substring(s, e); } //Logic to get Field Dependency map between Controlling and Dependent Field private static final String base64Chars = '' +'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789+/'; @AuraEnabled public static Map<String, List<String>> getFieldDependencyMap(string ObjectApiName, string CfieldApiName,string DfieldApiName) { Map<String,List<String>> fieldDependencyMap = new Map<String,List<String>>(); Schema.SObjectType objType = Schema.getGlobalDescribe().get(ObjectApiName); if (objType==null){ return fieldDependencyMap; } String controllingField = CfieldApiName.toLowerCase(); String dependentField = DfieldApiName.toLowerCase(); Map<String, Schema.SObjectField> objFieldMap = objType.getDescribe().fields.getMap(); if (!objFieldMap.containsKey(controllingField) || !objFieldMap.containsKey(dependentField)){ return fieldDependencyMap; } Schema.SObjectField dependentPicklist = objFieldMap.get(dependentField); Schema.SObjectField controllingPicklist = objFieldMap.get(controllingField); List<Schema.PicklistEntry> controllingPicklistValues = controllingPicklist.getDescribe().getPicklistValues(); List<PicklistEntryWrapper> dependentPicklistValues = wrapPicklistEntries(dependentPicklist.getDescribe().getPicklistValues()); List<String> controllingValues = new List<String>(); for (Schema.PicklistEntry PicklistValue : controllingPicklistValues) { String label = PicklistValue.getLabel(); fieldDependencyMap.put(label, new List<String>()); controllingValues.add(label); } for (PicklistEntryWrapper obj : dependentPicklistValues) { String label = obj.label; String validForBits = base64ToBits(obj.validFor); for (Integer i = 0; i < validForBits.length(); i++) { String bit = validForBits.mid(i, 1); if (bit == '1') { fieldDependencyMap.get(controllingValues.get(i)).add(label); } } } return fieldDependencyMap; } public static String decimalToBinary(Integer val) { String bits = ''; while (val > 0) { Integer remainder = Math.mod(val, 2); val = Integer.valueOf(Math.floor(val / 2)); bits = String.valueOf(remainder) + bits; } return bits; } public static String base64ToBits(String validFor) { if (String.isEmpty(validFor)) return ''; String validForBits = ''; for (Integer i = 0; i < validFor.length(); i++) { String thisChar = validFor.mid(i, 1); Integer val = base64Chars.indexOf(thisChar); String bits = decimalToBinary(val).leftPad(6, '0'); validForBits += bits; } return validForBits; } private static List<PicklistEntryWrapper> wrapPicklistEntries(List<Schema.PicklistEntry> picklistEntries) { return (List<PicklistEntryWrapper>) JSON.deserialize(JSON.serialize(picklistEntries), List<PicklistEntryWrapper>.class); } //Wrapper class for picklist entries public class PicklistEntryWrapper{ public String active {get;set;} public String defaultValue {get;set;} public String label {get;set;} public String value {get;set;} public String validFor {get;set;} public PicklistEntryWrapper(){} }}
SessionIdPage.vfp:
<apex:page > <!-- The session Id is wrapped between two string literals 'ID' and 'END' --> ID{!$Api.Session_ID}END</apex:page>
DependentPicklistComponent.cmp:
<aura:component controller="DependentPicklistController" implements="lightning:availableForFlowScreens" access="global" > <aura:attribute name="recordtypeList" type="Object[]"/> <!-- List of RecordTypes accessible to the logged in user --> <aura:attribute name="selectedRecordTypeId" type="string"/> <aura:attribute name="selectedControllingPicklistValue" type="string"/> <aura:attribute name="selectedDependentPicklistValue" type="string"/> <aura:attribute name="objectApiName" type="string" required="true" default="Account"/> <aura:attribute name="controllingFieldApiName" type="string" required="true" default="Session_Category__c"/> <aura:attribute name="dependentFieldApiName" type="string" required="true" default="Session_Location__c"/> <aura:attribute name="invalidApiName" type="Boolean" default="false"/> <!-- To check the validity of the passed Object API name--> <aura:attribute name="fieldDependencMap" type="map"/> <!-- Map to store the field dependency values between controlling and dependent picklist --> <aura:attribute name="controllingPicklistValues" type="string[]"/> <!-- List of controlling picklist values --> <aura:attribute name="dependentPicklistValues" type="string[]"/> <!-- List of available dependent picklist values corresponding to the controlling picklist values --> <aura:attribute name="dependentPicklistValuesCache" type="string[]"/> <!-- List of all the dependent picklist values --> <aura:attribute name="controllingPicklistLabel" type="string"/> <aura:attribute name="dependentPicklistLabel" type="string"/> <aura:attribute name="showSpinner" type="boolean" default="false"/> <!-- Show/Hide spinner --> <aura:handler name="init" value="{!this}" action="{!c.doInit}"/> <!-- Record type selection --> <aura:if isTrue="{! not(v.invalidApiName)}"> <lightning:select name="recordType" label="Choose Record Type" value="{!v.selectedRecordTypeId}" onchange="{!c.handleRecordTypeChange}" required="true"> <option value="" text=" ---None--- "/> <aura:iteration items="{!v.recordtypeList}" var="rt"> <option value="{!rt.rtId}" text="{!rt.rtName}"/> </aura:iteration> </lightning:select> <aura:set attribute="else"> Invalid Api name of the object. </aura:set> </aura:if> <!-- Spinner --> <aura:if isTrue="{!v.showSpinner}"> <div aura:id="spinnerId" class="slds-spinner_container"> <div class="slds-spinner--brand slds-spinner slds-spinner--large slds-is-relative" role="alert"> <span class="slds-assistive-text">Loading</span> <div class="slds-spinner__dot-a"></div> <div class="slds-spinner__dot-b"></div> </div> </div> </aura:if> <aura:if isTrue="{! not(empty(v.selectedRecordTypeId))}"> <!-- Controlling picklist selection--> <lightning:select label="{!v.controllingPicklistLabel}" onchange="{!c.handleControllingPicklistChange}" value="{!v.selectedControllingPicklistValue}"> <option value="" text=" ---None--- "/> <aura:iteration items="{!v.controllingPicklistValues}" var="cp"> <option value="{!cp}" text="{!cp}"/> </aura:iteration> </lightning:select> <!-- Dependent picklist selection --> <lightning:select label="{!v.dependentPicklistLabel}" disabled="{! empty(v.selectedControllingPicklistValue)}" value="{!v.selectedDependentPicklistValue}"> <option value="" text=" ---None--- "/> <aura:iteration items="{!v.dependentPicklistValues}" var="dp"> <option value="{!dp}" text="{!dp}"/> </aura:iteration> </lightning:select> </aura:if> </aura:component>
DependentPicklistComponentController.js:
({ /* Calling helper method to get the field dependency map at initialization of component */ doInit : function(component, event, helper) { //Setting labels for the picklist var cFieldLabel=component.get("v.controllingFieldApiName"); cFieldLabel=cFieldLabel.replace('__c',''); component.set("v.controllingPicklistLabel",cFieldLabel.replace('_',' ')); var dFieldLabel=component.get("v.dependentFieldApiName"); dFieldLabel=dFieldLabel.replace('__c',''); component.set("v.dependentPicklistLabel",dFieldLabel.replace('_',' ')); helper.getFieldDependencyMap(component,event); }, /* Calling helper methods on Record type picklist change to get the Picklist values based on the record type */ handleRecordTypeChange : function(component, event, helper) { console.log('handleRecordTypeChange Called' ); //Show spinner component.set("v.showSpinner",true); helper.getPicklistValues(component,event,component.get("v.controllingFieldApiName"),'Controlling'); helper.getPicklistValues(component,event,component.get("v.dependentFieldApiName"),'Dependent'); var dependentPicklistValues=[]; component.set("v.dependentPicklistValues",dependentPicklistValues); component.set("v.selectedControllingPicklistValue",null); }, /* Based on the selected value in controlling picklist, getting the dependent picklist values from the field dependency map */ handleControllingPicklistChange : function(component, event, helper) { console.log('handleControllingPicklistChange Called' ); var selectedControllingPicklistValue=component.get("v.selectedControllingPicklistValue"); var availablePicklistValuesCacheSet=[]; availablePicklistValuesCacheSet=component.get("v.dependentPicklistValuesCache"); var dependencyMap=component.get("v.fieldDependencMap"); var availablePicklistValuesMapSet=dependencyMap[component.get("v.selectedControllingPicklistValue")]; var dependentPicklistValues=[]; if(availablePicklistValuesCacheSet.length>0){ for(var i=0;i<availablePicklistValuesCacheSet.length;i++){ if(availablePicklistValuesMapSet.includes(availablePicklistValuesCacheSet[i])) dependentPicklistValues.push(availablePicklistValuesCacheSet[i]); } } component.set("v.dependentPicklistValues",dependentPicklistValues); }})
DependentPicklistComponentHelper.js:
({ /* Calling the server side controller to get the list of record types accessible to the logged in user */ getRecordTypes : function(component,event) { console.log('getRecordTypes Called'); var action=component.get("c.getRecordTypes"); action.setParams({ ObjectApiName : component.get("v.objectApiName") }); action.setCallback(this,function(response){ var state=response.getState(); if(state==="SUCCESS"){ console.log(response.getReturnValue()); var rtlist=response.getReturnValue(); if(rtlist.length===0){ //No recordtype Available var rt={}; rt.rtName='No Record Type Available'; rt.rtId=null; rtlist.push(rt); component.set("v.recordtypeList",rtlist); } else{ //Check for Invalid Api Name if(rtlist[0].incorrectApiName===true){ component.set("v.invalidApiName",true); } else{ component.set("v.recordtypeList",rtlist); } } } else{ console.log('State: '+state); if(state==="ERROR") console.log(response.getError()); } }); $A.enqueueAction(action); }, /* Calling the server side controller to get the Picklist fields dependency map * & on successful response, calling 'getRecordTypes' helper method * to get the list of record types accessible to the logged in user */ getFieldDependencyMap : function(component,event) { console.log('getFieldDependencyMap Called'); var action=component.get("c.getFieldDependencyMap"); action.setParams({ ObjectApiName : component.get("v.objectApiName"), CfieldApiName : component.get("v.controllingFieldApiName"), DfieldApiName : component.get("v.dependentFieldApiName") }); action.setCallback(this,function(response){ var state=response.getState(); if(state==="SUCCESS"){ console.log(response.getReturnValue()); component.set("v.fieldDependencMap",response.getReturnValue()); this.getRecordTypes(component,event); } else{ console.log('State: '+state); if(state==="ERROR") console.log(response.getError()); } }); $A.enqueueAction(action); }, /* Calling server side controller to get picklist values based on the chosen record type */ getPicklistValues : function(component,event, fieldApiName,fieldtype){ console.log(' getPicklistValues Called: '+ fieldtype); var action=component.get("c.getPicklistValues"); action.setParams({ recordTypeId : component.get("v.selectedRecordTypeId"), ObjectApiName : component.get("v.objectApiName"), fieldApiName : fieldApiName }); action.setCallback(this,function(response){ var state=response.getState(); if(state==="SUCCESS"){ console.log(response.getReturnValue()); if(fieldtype==='Controlling') component.set("v.controllingPicklistValues",response.getReturnValue()); if(fieldtype==='Dependent') component.set("v.dependentPicklistValuesCache",response.getReturnValue()); component.set("v.showSpinner",false); } else{ console.log('State: '+state); if(state==="ERROR") console.log(response.getError()); } }); $A.enqueueAction(action); }})
DependentPicklistComponent.design:
<design:component> <design:attribute name="objectApiName" label="Object Api Name"/> <design:attribute name="controllingFieldApiName" label="Controlling Picklist Api Name"/> <design:attribute name="dependentFieldApiName" label="Dependent Picklist Api Name"/> <design:attribute name="selectedRecordTypeId" label="Selected RecordType Id"/> <design:attribute name="selectedControllingPicklistValue" label="Selected Controlling Picklist Value"/> <design:attribute name="selectedDependentPicklistValue" label="Selected Dependent Picklist Value"/> </design:component>
Demo:

Thank you! 🙂

.png)




Comments