Custom Dependent Picklist in LWC – Part1

I am exploring on lightning web components framework and it’s underground standard JS concepts. As a first POC in LWC , I will be sharing with you, reusable dependent picklist components which can be used in custom LWC or Aura record forms.

Developed the component in three approaches by exploring the ways the picklist entries can be pulled from server side to web component UI.

  • Dependent picklist using Apex
  • Dependent Picklist using UI ObjectInfo API
  • Dependent Picklist using Record Edit Form

Dependent picklist using Apex

First, I will develop a component which fetches the picklist entries through an apex method.

Step 1 – Create an apex method to fetch the picklist values.

Three parameters were shared to apex method to make it generic , they are object name, controlling and dependent picklist field api names. Wrapper class also be included with the class to share both picklist values in a useful format. Please go through PicklistValue class at the bottom of the class. Also, I have included the provisions for error handling but the wrapper should be modified accordingly (you can make the changes with respect to your requirement).

DependentPicklistController 
public with sharing class DependentPicklistController {
@AuraEnabled(cacheable = true)
public static List<PicklistValue> getPicklistValues(String objApiName, String controlField, String dependentField){
List<PicklistValue> pickListValues = new List<PicklistValue>();
if(String.isBlank(objApiName) || String.isBlank(controlField) || String.isBlank(dependentField)) {
//return pickListValues;
objApiName = 'Account';
controlField ='Country__c';
dependentField= 'State__c';
//enable the return statement and remove the above static assignment with some valid error value to update the UI
//return;
}
//Identify the sobject type from the object name using global schema describe function
Schema.SObjectType targetType = Schema.getGlobalDescribe().get(objApiName);
//Create an empty object based up on the above the sobject type to get all the field names
Schema.sObjectType objType = targetType.newSObject().getSobjectType();
//fetch the all fields defined in the sobject
Map<String, Schema.SObjectField> objFieldMap = objType.getDescribe().fields.getMap();
//Get the controlling and dependent picklist values from the objFieldMap
List<Schema.PicklistEntry> controlledPLEntries = objFieldMap.get(controlField).getDescribe().getPicklistValues();
List<Schema.PicklistEntry> dependentPLEntries = objFieldMap.get(dependentField).getDescribe().getPicklistValues();
// Wrap the picklist values using custom wrapper class – PicklistValue
for (Schema.PicklistEntry entry : controlledPLEntries) {
PicklistValue picklistValue = new PicklistValue(entry.isActive(), entry.isDefaultValue(), entry.getLabel(), entry.getValue());
pickListValues.add(picklistValue);
}
//ValidFor is an indicator value for the controlling field which is base64 encrypted
//base64 value should be convered to 6bit grouped binary and 1 indicate the controlling field in a certain order
//Also,validFor value only be shown when it is serialized so it should be serialized then deserialized using PicklistValue wrapper class
for(PicklistValue plVal : deserializePLEntries(dependentPLEntries)) {
String decodedInBits = base64ToBits(plVal.validFor);
for(Integer i = 0; i< decodedInBits.length(); i++) {
// For each bit, in order: if it's a 1, add this label to the dependent list for the corresponding controlling value
String bit = decodedInBits.mid(i, 1);
if (bit == '1') {
PicklistValue dependentPLValue = new PicklistValue(plVal.active, plVal.defaultValue, plVal.label, plVal.value);
//Dependent picklist value is mapped to its parent controlling value through 'dependents' attribute in the PicklistValue wrapper class
if(pickListValues.get(i).dependents == null ) {
pickListValues.get(i).dependents = new List<PicklistValue>{dependentPLValue};
}else{
pickListValues.get(i).dependents.add(dependentPLValue);
}
}
}
}
return pickListValues;
}
private static List<PicklistValue> deserializePLEntries(List<Schema.PicklistEntry> plEntries) {
return (List<PicklistValue>)
JSON.deserialize(JSON.serialize(plEntries), List<PicklistValue>.class);
}
//Available base64 charecters
private static final String BASE_64_CHARS = '' +'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +'abcdefghijklmnopqrstuvwxyz' +'0123456789+/';
// Convert decimal to binary representation (alas, Apex has no native method 🙁
// eg. 4 => '100', 19 => '10011', etc.
// Method: Divide by 2 repeatedly until 0. At each step note the remainder (0 or 1).
// These, in reverse order, are the binary.
private 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;
}
// Convert a base64 token into a binary/bits representation
// e.g. 'gAAA' => '100000000000000000000'
private 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 = BASE_64_CHARS.indexOf(thisChar);
String bits = decimalToBinary(val).leftPad(6, '0');
validForBits += bits;
}
return validForBits;
}
//Wrapper class
public class PicklistValue {
@AuraEnabled
public Boolean active { get; set; }
@AuraEnabled
public Boolean defaultValue { get; set; }
@AuraEnabled
public String label { get; set; }
@AuraEnabled
public String validFor { get; set; }
@AuraEnabled
public String value { get; set; }
@AuraEnabled
public List<PickListValue> dependents {get; set;}
public PicklistValue(){}
public PicklistValue(Boolean active, Boolean defaultValue, String label, String validFor, String value) {
this.active = active;
this.defaultValue = defaultValue;
this.label = label;
this.validFor = validFor;
this.value = value;
}
public PicklistValue(Boolean active, Boolean defaultValue, String label, String value) {
this.active = active;
this.defaultValue = defaultValue;
this.label = label;
this.validFor = validFor;
this.value = value;
}
public PicklistValue(String label, String value) {
this.label = label;
this.value = value;
}
}
}
Step 2 – Create a lightning web component

The LWC component will consume the apex controller method for picklist values by using wire decorator. Required apex method parameters are created as public attribute (@api) in the component side for user accessibility.

<!– template with two combobox or dropdown base component
to show controlling and dependent picklist fields with its attributes–>
<template>
<lightning-layout horizontal-align="space">
<lightning-layout-item padding="around-small">
<lightning-combobox
name="progress"
label="Country"
value={parentValue}
options={controlOptions}
onchange={handleControlChange} ></lightning-combobox>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-combobox
class="dependent"
name="progress"
label="State"
value={dependentValue}
options={dependentOptions}
onchange={handleDependentChange} disabled={isDisabled}></lightning-combobox>
</lightning-layout-item>
</lightning-layout>
</template>
import { api, LightningElement, track, wire } from 'lwc';
import getPicklistValues from '@salesforce/apex/DependentPicklistController.getPicklistValues';
const defaultOption = {label : '—None—',value: ''};
export default class DependentPicklist extends LightningElement {
//public attribute for object selection
@api
objectApiName;
//Public attribute for controlling picklist field
@api
controlFieldName;
//Public attribute for dependent picklist field
@api
dependentFieldName;
//Public attribute for selected dependent picklist value
@api
dependentValue = '';
//Public attribute for selected controlling picklist value
@api
parentValue ='';
//to stroe controlling picklist value options
controlOptions =[];
//to stroe dependent picklist value options based on the controlling picklist value
dependentOptions = [];
data = [];
//to enable/disable the dependent picklist field in the UI
isDisabled = true;
/*
Wiring to getPicklistValues method defined in the apex class – DependentPicklistController
which returns picklist values based on the paramteres object name, controlling Field and dependent field name */
@wire(getPicklistValues,{objApiName : '$objectApiName', controlField : '$controlFieldName', dependentField:'$dependentFieldName'})
picklistData({data,error}) {
if(data){
this.data = data;
this.updateOptions();
//console.log(JSON.stringify(data));
}
}
//Sets the controlling field options to show
updateOptions() {
let defaultValue = '';
this.controlOptions = this.data.map((option) => {
if(option.defaultValue) {
defaultValue = option.value;
}
return { label: option.label, value: option.value }
});
//show the default selected option as the first item in the dropdown otherwise –None–
if(defaultValue){
this.controlOptions.push(defaultOption);
this.parentValue = defaultValue;
this.updateDependentOptions();
this.isDisabled = false;
}else{
//default –None– Option is already set in the defaultOption constant
this.controlOptions.unshift(defaultOption);
}
}
//set the dependent options in the dropdown field
updateDependentOptions() {
let defaultValue = '';
//fetch the selected option in the controlling field with it's dependent picklist values
const selOptionDetail = this.data.filter(option => option.value === this.parentValue);
//check the dependent option list empty or not and sets in to the dependent field,
//filter function returns an array so fetching the value in the 0th index
if(selOptionDetail.length > 0 && selOptionDetail[0].dependents) {
this.dependentOptions = selOptionDetail[0].dependents.map((option) => {
if(option.defaultValue) {
defaultValue = option.value;
}
return { label: option.label, value: option.value }
});
//sets the default selected option or –None–
if(defaultValue){
this.dependentOptions.push(defaultOption);
this.dependentValue = defaultValue;
}else{
this.dependentValue = '';
this.dependentOptions.unshift(defaultOption);
}
//console.log(this.dependentOptions);
}
}
/*Handler for onchange event which initiate a custom event and set the value in the corresponding output attribute */
handleControlChange(event) {
this.parentValue = event.detail.value;
//enable the dependent picklist field – only needed for initial selection
this.isDisabled = false;
//update the dependent field option with newly selected control field value
this.updateDependentOptions();
this.dispatchEvent(new CustomEvent('controlchange',{detail : {value : this.parentValue}}));
}
/*Handler for onchange event which initiate a custom event and set the value in the corresponding output attribute */
handleDependentChange(event) {
this.dependentValue = event.detail.value;
this.dispatchEvent(new CustomEvent('dependentchange',{detail : {value : this.dependentValue}}));
}
}
view raw dependentPicklist.js hosted with ❤ by GitHub
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>49.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>

dependentValue’  and ‘parentValue’ are the output parameters for providing the dependent and controlling picklist field selected values. Also, the selections will be notified with separate custom events for both the fields, named ‘controlchange’ and ‘dependentchange’.

Please refer below documents for more details:

https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_picklist.htm

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_dynamic_describe_objects_understanding.htm

https://developer.salesforce.com/docs/component-library/documentation/en/lwc

Hope you guys are able to understand the implementation and the code is self explanatory. Also, I have included the necessary comments for most of the lines. Use the comment section for posting your valuable feedback and questions.

Read my next blog for same component implementation using Record edit form / UIObjectInfo api.

!!!Stay safe !!! Stay Home !!!

1 thought on “Custom Dependent Picklist in LWC – Part1”

  1. Pingback: Custom Dependent Picklist in LWC – Part 2 – Tech Evangel

Leave a Reply

%d bloggers like this: