Picklist and Dependent Picklist in Salesforce LWC and AURA

Summary
We will utilize base components that Salesforce has provided to build custom dynamic Picklist and Dependent Picklist.
Base component that we will be using:
<lightning-combobox
name="progress"
label="Status"
value={value}
placeholder="Select Progress"
options={options}
onchange={handleChange}>
</lightning-combobox>
We will be using the above base component to build custom dynamic picklist and dependent picklist components, which can be re-used anywhere just by passing some parameters. We will see how we can use that custom component in Aura, which covers AURA inter-operability as well.
Main Logic for dependent picklist:
We will reset the dependent picklist value on onchange handler of picklist, by firing pubsub events.
Before you even see the code experience LIVE component here:
Picklist.html
<template>
<lightning-combobox
id="pickList"
name="progress"
label={label}
value={value}
variant={variant}
placeholder="Select"
options={options}
onchange={handleChange}>
</lightning-combobox>
</template>
Picklist.js
/* eslint-disable no-console */
import { LightningElement, track, wire, api } from 'lwc';
import { getPicklistValuesByRecordType } from 'lightning/uiObjectInfoApi';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { CurrentPageReference } from 'lightning/navigation';
import { fireEvent } from 'c/pubsub';
export default class Picklist2 extends LightningElement {
@wire(CurrentPageReference) pageRef;
@api objectApiName;
@api pickListfieldApiName;
@api label;
@api variant;
/*only for lwc for mapping values in list and
also for mapping this with dependent picklist(give unique = record Id while using in dependent picklist)*/
@api uniqueKey;
@track value;
recordTypeIdValue;
@track options = [
{ label: 'Default 1', value: 'Default1' },
{ label: 'Default 2', value: 'Default2' },
{ label: '--None--', value: "" }
];
@api
get recordTypeId() {
console.log("getter defaultRectype", this.recordTypeIdValue);
return this.recordTypeIdValue;
}
set recordTypeId(value) {
this.recordTypeIdValue = value;
console.log("setter defaultRectype", this.recordTypeIdValue);
}
@api
get selectedValue() {
console.log("getter", this.value);
return this.value;
}
set selectedValue(val) {
console.log("setter", val);
if (val === '' || val === undefined || val === null)
this.value = { label: '--None--', value: "" }.value;
else
this.value = val;
}
@wire(getObjectInfo, { objectApiName: '$objectApiName' })
getRecordTypeId({ error, data }) {
if (data) {
this.record = data;
this.error = undefined;
if(this.recordTypeId === undefined){
this.recordTypeId = this.record.defaultRecordTypeId;
}
console.log("Default Record Type Id", JSON.stringify(this.record.defaultRecordTypeId));
} else if (error) {
this.error = error;
this.record = undefined;
console.log("this.error",this.error);
}
}
@wire(getPicklistValuesByRecordType, { recordTypeId: '$recordTypeId', objectApiName: '$objectApiName' })
wiredOptions({ error, data }) {
if (data) {
this.record = data;
this.error = undefined;
/*
console.log("this.record picklist raw data", JSON.stringify(this.record));
console.log("this.record.picklistFieldValues", JSON.stringify(this.record.picklistFieldValues));
*/
if(this.record.picklistFieldValues[this.pickListfieldApiName] !== undefined) {
let tempOptions = [{ label: '--None--', value: "" }];
let temp2Options = this.record.picklistFieldValues[this.pickListfieldApiName].values;
temp2Options.forEach(opt => tempOptions.push(opt));
this.options = tempOptions;
}
console.log("this.options pick", JSON.stringify(this.options));
if(this.selectedValue === '' || this.selectedValue === undefined || this.selectedValue === null) {
this.value = { label: '--None--', value: "" }.value;
} else {
this.value = this.options.find(listItem => listItem.value === this.selectedValue).value;
}
} else if (error) {
this.error = error;
this.record = undefined;
console.log("this.error",this.error);
}
}
handleChange(event) {
let tempValue = event.target.value;
console.log("event.target.value",event.target.value);
console.log("this.value",tempValue);
let selectedValue = tempValue;
let key = this.uniqueKey;
//Firing change event for aura container to handle
//For Self
const pickValueChangeEvent = new CustomEvent('picklistchange', {
detail: { selectedValue, key },
});
this.dispatchEvent(pickValueChangeEvent);
//For dependent picklist
let eventValues = {selValue : selectedValue, uniqueFieldKey: `${this.pickListfieldApiName}${this.uniqueKey}`};
console.log("eventValues",JSON.stringify(eventValues));
console.log("eventValues",eventValues);
//Fire Pub/Sub Event, So that every other comp in the page knows the change
fireEvent(this.pageRef, 'controllingValue', eventValues);
}
}
Picklist.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="picklist2">
<apiVersion>46.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
Here we are using UI API to get record type id and picklist values based on that. UI API is helping us solve most of the problem, Otherwise previously should have to make an API call to get these results.
This is one more credit we can give to LWC for making our lives easier.
Above code is all about picklist what about dependent picklist ?? Hang on here comes it.
I have tried all different means to remove parent child hierarchy between picklist and dependent picklist, there is no direct relation, but has a bit of relation that is because of events. Dependent picklist depends on the event fired by picklist. When onchange event occurs in picklist it fires a pubsub event to notify dependent picklist that its value has changed, there by depdendent picklist changes its value to none. I have tries all different approaches to remove this relation but the final outcome wasn’t stable. That is the final outcome behaved differently in AURA and LWC, so had to to be content with this relation. Please leave your suggestions below to make this component even better. You can subscribe to the to my newsletter just by pressing on the ‘+’ plus button to bottom right corner, it is also a lightning web component which is inturn integrated to mailchimp which is a e-mail service.
Here comes dependent picklist code.
dependentPickList.html:
<template>
<lightning-combobox id="dependentPickList"
name="Hello"
label={label}
value={value}
placeholder="--None--"
variant={variant}
options={options}
onchange={handleChange}>
</lightning-combobox>
</template>
dependentPickList.js:
/* eslint-disable no-console */
import { LightningElement, track, wire, api } from 'lwc';
import { getPicklistValuesByRecordType } from 'lightning/uiObjectInfoApi';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { CurrentPageReference } from 'lightning/navigation';
import { registerListener, unregisterAllListeners } from 'c/pubsub';
import { NavigationMixin } from 'lightning/navigation';
import { fireEvent } from 'c/pubsub';
export default class DependentPickList4 extends NavigationMixin(LightningElement) {
@wire(CurrentPageReference) pageRef;
@api objectApiName;
@api pickListfieldApiName; //this field api name
@api controllingFieldApi; //parent field api name that is controlling field api name
//@api controllingFieldValue; //parent field value
@api label;
@api variant;
recordTypeIdValue;
controllingFieldVal;
previousValue;
prevControllingFieldVal;
@track value;
/*only for lwc for mapping values in list and
also for mapping this dependent picklist with another dependent picklist(give unique = record Id while using in dependent picklist)*/
@api uniqueKey;
connectedCallback() {
console.log("In connected callback depndent");
registerListener('controllingValue', this.handelControllingValue, this);
}
@api
get controllingFieldValue() {
return this.controllingFieldVal;
}
set controllingFieldValue(value) {
this.controllingFieldVal = value;
this.reinitiatemap();
}
disconnectedCallback() {
console.log("In disconnected callback depndent");
unregisterAllListeners(this);
}
@api
get recordTypeId() {
console.log("getter defaultRectype", this.recordTypeIdValue);
return this.recordTypeIdValue;
}
set recordTypeId(value) {
this.recordTypeIdValue = value;
console.log("setter defaultRectype", this.recordTypeIdValue);
}
@api
get selectedValue() {
console.log("getter dependent", this.value);
return this.value;
}
set selectedValue(val) {
console.log("setter dependent", val);
this.previousValue = this.value;
if (val === '' || val === undefined || val === null){
this.value = { label: '--None--', value: "" }.value;
}
else
this.value = val;
}
handelControllingValue(valuesObj) {
console.log("In handelControllingValue", JSON.stringify(valuesObj));
if (`${this.controllingFieldApi}${this.uniqueKey}` === valuesObj.uniqueFieldKey) {
if (valuesObj.selValue === '' || valuesObj.selValue === null || valuesObj.selValue === undefined) {
this.selectedValue = '';
this.options = [{ label: '--None--', value: "" }];
} else {
this.selectedValue = '';
if (this.myMap !== null && this.myMap !== undefined) {
let tempOptions = [{ label: '--None--', value: "" }];
console.log("valuesObj.selValue", valuesObj.selValue);
if(this.myMap.get(valuesObj.selValue)) {
this.myMap.get(valuesObj.selValue).forEach(opt => tempOptions.push(opt));
}
this.options = tempOptions;
}
}
let selectedValue = '';
let key = this.uniqueKey;
//Firing change event for aura container to handle
//For Self
const pickValueChangeEvent = new CustomEvent('picklistchange', {
detail: { selectedValue, key },
});
this.dispatchEvent(pickValueChangeEvent);
//For dependent picklist
let eventValues = { selValue: '', uniqueFieldKey: `${this.pickListfieldApiName}${this.uniqueKey}` };
//Fire Pub/Sub Event, So that every other comp in the page knows the change
fireEvent(this.pageRef, 'controllingValue', eventValues);
}
}
@track options = [
{label : 'Default 1', value : 'Default1'},
{label : 'Default 2', value : 'Default2'},
{label : '--None--', value : ''}
];
@track myMap = undefined;
@wire(getObjectInfo, { objectApiName: '$objectApiName' })
getRecordTypeId({ error, data }) {
if (data) {
this.record = data;
this.error = undefined;
if(this.recordTypeId === undefined){
this.recordTypeId = this.record.defaultRecordTypeId;
}
console.log("Default Record Type Id", this.record.defaultRecordTypeId);
} else if (error) {
this.error = error;
this.record = undefined;
console.log("this.error",this.error);
}
}
@wire(getPicklistValuesByRecordType, { recordTypeId: '$recordTypeId', objectApiName: '$objectApiName' })
wiredOptions({ error, data }) {
if (data) {
this.record = data;
this.error = undefined;
let pickMap = new Map();
if(this.record.picklistFieldValues[this.pickListfieldApiName] !== undefined) {
if(this.record.picklistFieldValues[this.pickListfieldApiName].controllerValues !== undefined) {
const controllerValues = this.record.picklistFieldValues[this.pickListfieldApiName].controllerValues;
Object.entries(controllerValues).forEach(([key, value]) => {
const picValues = this.record.picklistFieldValues[this.pickListfieldApiName].values;
picValues.forEach(pickValue => {
if(pickValue.validFor.includes(value)) {
if(pickMap.has(key)){
let temp = pickMap.get(key);
temp.push(pickValue);
pickMap.set(key, temp);
}else {
let temp2 = [];
temp2.push(pickValue);
pickMap.set(key, temp2);
console.log("In inner else", temp2);
}
}
});
});
console.log("MAP",pickMap);
this.myMap = pickMap;
console.log("In Wire", this.myMap);
console.log("etter controllingFieldValue",this.controllingFieldValue);
//Checking if selected and controlling values exist, and populating values accordingly
if (this.selectedValue) {
this.value = this.selectedValue;
if (!this.controllingFieldValue) {
this.options = [{ label: this.selectedValue, value: this.selectedValue }];
return;
}
}
else if (!this.controllingFieldValue) {
this.options = [{ label: '--None--', value: '' }];
this.value = this.options[0].value;
return;
}
else if(!this.selectedValue) {
this.value = { label: '--None--', value: '' }.value;
}
this.initiateMap(pickMap);
console.log("Initial selectedValue ", this.selectedValue);
}else {
console.log("Error: This field is not a dependent picklist!");
}
}else {
console.log("Error in fetching picklist values! Invalid picklist field");
}
console.log("***Initial Options*** ", JSON.stringify(this.options));
} else if (error) {
this.error = error;
this.record = undefined;
console.log("this.error",this.error);
}
}
handleChange(event) {
console.log("etter selected val in handle change", this.selectedValue);
this.value = event.target.value;
console.log("event.target.value",event.target.value);
console.log("this.value",this.value);
let selectedValue = this.value;
let key = this.uniqueKey;
//Firing change event for aura container to handle
//For Self
const pickValueChangeEvent = new CustomEvent('picklistchange', {
detail: { selectedValue, key },
});
this.dispatchEvent(pickValueChangeEvent);
//For dependent picklist
let eventValues = {selValue : selectedValue, uniqueFieldKey: `${this.pickListfieldApiName}${this.uniqueKey}`};
//Fire Pub/Sub Event, So that every other comp in the page knows the change
fireEvent(this.pageRef, 'controllingValue', eventValues);
}
initiateMap(thisMap) {
this.myMap = thisMap;
if (thisMap !== null && thisMap !== undefined) {
let tempOptions = [{ label: '--None--', value: "" }];
if (this.controllingFieldValue !== null && this.controllingFieldValue !== undefined && this.controllingFieldValue !== '') {
console.log("this.controllingFieldValue", this.controllingFieldValue);
if(this.myMap.get(this.controllingFieldValue)) {
this.myMap.get(this.controllingFieldValue).forEach(opt => tempOptions.push(opt));
}
}
this.options = tempOptions;
}
console.log("***Final Options dependent Picklist*** ", JSON.stringify(this.options));
}
reinitiatemap() {
if (this.myMap !== null && this.myMap !== undefined){
this.initiateMap(this.myMap);
console.log("this.myMap length", this.myMap.length);
}
}
}
dependentPickList.js-meta.xml:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="dependentPickList4">
<apiVersion>46.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
##Usage:
Picklist:
<c-picklist2
unique-key={account.Id}
object-api-name="Account"
record-type-id="0127F000000kyxEQAQ"
selected-value={account.Master_Picklist__c}
pick-listfield-api-name="Master_Picklist__c"
onpicklistchange={handleMasterPicklistChange}>
</c-picklist2>
** Dependent picklist:**
<c-dependent-pick-list4
unique-key={account.Id}
object-api-name="Account"
record-type-id="0127F000000kyxEQAQ"
pick-listfield-api-name="Controlling_Picklist__c"
controlling-field-value={account.Master_Picklist__c}
controlling-field-api="Master_Picklist__c"
selected-value={account.Controlling_Picklist__c}
onpicklistchange={handlePicklistChange}>
</c-dependent-pick-list4>
Hey guys, If you find this post interesting and helpful don’t forget to write your feedback down below in comments section. I am a social guy, you can find me in the apps mentioned below. Don’t forget to share this with other Salesforce folks. Don’t miss mentioning about Live components available in this site.
Github Links:
Picklist:
Picklist source code github link
DependentPicklist:
Dependent Picklist source code github link
Icons made by itim2101 from www.flaticon.com