Custom Dependent Picklist in LWC – Part 2

In this blog, I would like to share with you the same reusable custom dependent pick-list component which was explained in my previous part1 blog. Current component is fetching pick-list values from org using uiObjectInfoApi which was released in winter 21 instead of custom apex class.

Object-info API is flexible enough to provide complete single or multiple information as well as pick-list values based on the record type by consuming dynamic values through wire method. In our current scenario, we have consumed getObjectInfo, getPicklistValues api’s to get the necessary data to bind pick-list values to the component.

The below component code explains everything as it is simple and consumed only the above custom apis to bind the data. ‘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’.

<template>
<lightning-layout horizontal-align="space">
<lightning-layout-item padding="around-small">
<lightning-combobox
name="progress"
label="Status"
value={parentValue}
options={controlOptions}
onchange={handleControlChange} ></lightning-combobox>
</lightning-layout-item>
<lightning-layout-item padding="around-small">
<lightning-combobox
class="dependent"
name="progress"
label="Status"
value={dependentValue}
options={dependentOptions}
onchange={handleDependentChange} disabled={isDisabled}></lightning-combobox>
</lightning-layout-item>
</lightning-layout>
</template>
import { LightningElement,api,wire, track } from 'lwc';
import { getObjectInfo,getPicklistValues,getPicklistValuesByRecordType } from 'lightning/uiObjectInfoApi';
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 recordtype id
@api
recordTypeId='';
//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 – disabled it initially
isDisabled = true;
@track
parentField = '';
dependentField = '';
//Wire method which fetches the object detatils with all the dependent picklist fields, record types, field details etc
//based on the objectAPIName parameter
@wire(getObjectInfo, { objectApiName: '$objectApiName' })
objectInfo({error,data}) {
if (data) {
//set the default recordtype id if there is no given value
if(!this.recordTypeId) {
this.recordTypeId = data.defaultRecordTypeId;
}
//Check if any dependent picklist fields available for the consdiered object
if(!(Object.keys(data.dependentFields).length === 0 && data.dependentFields.constructor === Object)){
//check the given controlling field name is valid for this object or not, otherwise throw an appropriate error
if(data.dependentFields.hasOwnProperty(this.controlFieldName)){
//console.log(data.dependentFields.hasOwnProperty(this.controlFieldName));
//check the given dependent field name is valid for this object or not, otherwise throw an appropriate error
if(!data.dependentFields[this.controlFieldName].hasOwnProperty(this.dependentFieldName)) {
console.log('error')
return;
}
//if the fields are valid, then pass the fieldAPIName in appropriate for the getPicklistValue object info wire method
//basically, controlling the wire parameters – no possibilty for imperative accessibility to UIObjectInfo API
this.parentField = this.generateFieldAPIName(this.controlFieldName);
this.dependentField = this.generateFieldAPIName(this.dependentFieldName);
}
else {
console.log('error')
return;
}
}
}
}
//fethch the dependent picklist values based on the recordtype id and fieldAPI details
@wire(getPicklistValues, { recordTypeId: '$recordTypeId', fieldApiName: '$dependentField' })
fetchDependentOptions( {error , data }) {
if(data) {
const controlKeys = {};
//Handling the validFor value which denotes the controlling fields.
//check the json format, controllerValues in field:index format and converts it into index:field format
Object.keys(data.controllerValues).forEach((key) => {
Object.defineProperty(controlKeys, data.controllerValues[key], {
value: key,
writable: false
});
//create a dependent data skelton
Object.defineProperty(this.data, key, {
value : {values : [], default: false , defaultValue : ''},
writable : true
});
});
//dependent data should be formatted as controllingField value as the key and related dependent options in an array
//no need to iterate again when the controlling field value changes so this format would be helpful
data.values.forEach((val) => {
let option = {label : val.label, value : val.value};
let isDefault = val.value === data.defaultValue.value ? true : false ;
val.validFor.forEach((key) => {
this.data[controlKeys[key]].values.push(option);
if(isDefault) {
this.data[controlKeys[key]].default = isDefault;
this.data[controlKeys[key]].defaultValue = val.value;
}
});
});
//set the dependent options once the dependent data is ready
this.setDependentOptions();
//console.log(controlKeys);
//console.log(this.data);
}else{
//handle the errors
console.log(JSON.stringify(error));
}
}
//fethch the controlling picklist values based on the recordtype id and fieldAPI details
@wire(getPicklistValues,{ recordTypeId: '$recordTypeId', fieldApiName: '$parentField'})
fetchControlOption( {error , data }) {
if(data) {
//sets the options to contriolling field
this.controlOptions = data.values.map((option) => {
return {label : option.label, value : option.value};
});
//default value for the controlling field
this.parentValue = data.defaultValue.hasOwnProperty('value') ? data.defaultValue.value : '';
//initating to set the dependent option in the field
this.setDependentOptions();
}else{
//handle the errors in an appropriate way
console.log(JSON.stringify(error));
}
}
setDependentOptions(){
//only sets the dependent picklist options only if there any valid selection, valid depdendent data for the selected value
if(this.parentValue && this.data && this.data.hasOwnProperty(this.parentValue)) {
this.isDisabled = false;
//fetching the options from the data array using the controlling value as index
let selectOptions = this.data[this.parentValue];
//sets the dependent options to the field
this.dependentOptions = selectOptions.values;
//set the default value
if(selectOptions.default){
this.dependentValue = selectOptions.defaultValue;
}
}
}
/*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;
this.setDependentOptions();
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}}));
}
//define the fieldAPIName for the getPickListValue UI objectinfo api
generateFieldAPIName(fieldName) {
return {"objectApiName": this.objectApiName,"fieldApiName": fieldName };
}
}
view raw dependentPicklist.js hosted with ❤ by GitHub
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"&gt;
<apiVersion>49.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
<targets>
</LightningComponentBundle>
view raw gistfile1.txt hosted with ❤ by GitHub
  • getObjectInfo api fetches the record type and dependent field information based on the public attributes through a wire method objectInfo
  • if the recordtypeid is not given, then assigns it from the objects default recordtypeId
  • Next, verify the dependent pick-list fields passed through controlFieldName, dependentFieldName are valid for the given object.
  • After successful validation, parentField and dependentField populates necessary field information to pull the pick-list values from salesforce org using getPicklistValues api.
  • parentField and dependentField attributes acts as a controller for other two wire methods as we don’t have any option to call the ui apis in an imperative way. If the picklist fields are not valid, then both the attributes have only an empty string which restricts from pulling the data from server.
  • fetchControlOption method sets the options for the controlling combobox field. Also, considering the default value selection as well.
  • fetchDependentOptions method sets the options, not in a straight forward way as it contains validFor and ControllingFields attributes to indicate its controlling field. Please go through the JSON then you will be understand the logic.
  • setDependentOptions is private method. which invokes multiple places to fill the dependent options only if the value is selected in the controlling field and valid data available for dependent field (as we don’t have any control on order of the wire method execution).

Please go through the below links for more details :

Custom Dependent Picklist in LWC – Part1

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

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

Leave a Reply

%d bloggers like this: