How to Create Reusable Dependent Picklist in LWC?

7
1258

Hi Folks,

Today I came up with a new blog for the reusable dependent picklist. In this blog, you guys will see how to create a reusable generic picklist without using apex so let’s get started.

Before we start development, let’s talk about the use case & Solution.

Use Case: –

The company called XYZ is using many ( 100+ ) lightning web components and out of 20+ LWC are using dependent pick-list. As a developer, you have been asked to develop a generic ( reusable ) component which can work for any objects & for any component.

Solution: Create a web component which uses inbuilt JavaScript API to get the pick-list details and creates a generic dependent pick-list for any object. ( There are some limitations of the ui*API, check the supported object Here ).

Step 1 – Create a Lightning Web Component and name it GenericDependentPicklist

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
<template>
<div class="slds-grid slds-wrap" if:true={isError}>
<div class="slds-p-horizontal--small slds-size--1-of-1" style="padding-top: 12px;">
<div class="slds-form-element slds-hint-parent">
<div class="slds-form-element__control">
<div class="slds-notify slds-notify_alert slds-theme_alert-texture slds-theme_error" role="alert" >
<span class="slds-assistive-text">error</span>
<p class="slds-align_absolute-center">{errorMessage}</p>
</div>
</div>
</div>
</div>
</div>
<div class="slds-grid slds-wrap" if:false={isError}>
<div class="slds-p-horizontal--small slds-size--1-of-2" >
<div class="slds-form-element slds-hint-parent">
<lightning-combobox label={controllingPicklistLabel}
name="Controlling Picklist"
options={optionValues.controlling}
onchange={handleControllingChange}
placeholder="None"
value={selectedValues.controlling}
>
</lightning-combobox>
</div>
</div>
<div class="slds-p-horizontal--small slds-size--1-of-2">
<div class="slds-form-element slds-hint-parent">
<div class="slds-form-element__control">
<lightning-combobox label={dependentPicklistLabel}
name="Dependent Picklist"
options={optionValues.dependent}
placeholder="None"
onchange={handleDependentChange}
disabled={isDisabled}
value={selectedValues.dependent}
>
</lightning-combobox>
</div>
</div>
</div>
</div>
</template>
import { LightningElement, api, track, wire } from 'lwc';
import { getPicklistValuesByRecordType, getObjectInfo } from 'lightning/uiObjectInfoApi';
export default class GenericDependentPicklist extends LightningElement {
@api
objectApiName;
//An Api Name for Controlling PickList Field
@api
controllingPicklistApiName;
//An Api Name for Dependent Picklist for any Object
@api
dependentPicklistApiName;
// to show the label for the dependent field
@api
dependentPicklistLabel;
// to show the label for the controlling field
@api
controllingPicklistLabel;
//An Object to fill show user all available options
@track
optionValues = {controlling:[], dependent:[]};
//To fill all controlling value and its related valid values
allDependentOptions={};
//To hold what value, the user selected.
@track
selectedValues = {controlling:undefined, dependent:undefined};
//Invoke in case of error.
isError = false;
errorMessage;
//To Disable Dependent PickList until the user won't select any parent picklist.
isDisabled = true;
@wire(getObjectInfo, {objectApiName : '$objectApiName'})
objectInfo;
@wire(getPicklistValuesByRecordType, { objectApiName: '$objectApiName', recordTypeId: '$objectInfo.data.defaultRecordTypeId'})
fetchValues({error, data}){
if(!this.objectInfo){
this.isError = true;
this.errorMessage = 'Please Check You Object Settings';
return;
}
if(data && data.picklistFieldValues){
try{
this.setUpControllingPicklist(data);
this.setUpDependentPickList(data);
}catch(err){
this.isError = true;
this.errorMessage = err.message;
}
}else if(error){
this.isError = true;
this.errorMessage = 'Object is not configured properly please check';
}
}
//Method to set Up Controlling Picklist
setUpControllingPicklist(data){
this.optionValues.controlling = [{ label:'None', value:'' }];
if(data.picklistFieldValues[this.controllingPicklistApiName]){
data.picklistFieldValues[this.controllingPicklistApiName].values.forEach(option => {
this.optionValues.controlling.push({label : option.label, value : option.value});
});
if(this.optionValues.controlling.length == 1)
throw new Error('No Values Available for Controlling PickList');
}else
throw new Error('Controlling Picklist doesn\'t seems right');
}
//Method to set up dependent picklist
setUpDependentPickList(data){
if(data.picklistFieldValues[this.dependentPicklistApiName]){
if(!data.picklistFieldValues[this.dependentPicklistApiName].controllerValues){
throw new Error('Dependent PickList does not have any controlling values');
}
if(!data.picklistFieldValues[this.dependentPicklistApiName].values){
throw new Error('Dependent PickList does not have any values');
}
this.allDependentOptions = data.picklistFieldValues[this.dependentPicklistApiName];
}else{
throw new Error('Dependent Picklist Doesn\'t seems right');
}
}
handleControllingChange(event){
const selected = event.target.value;
if(selected && selected != 'None'){
this.selectedValues.controlling = selected;
this.selectedValues.dependent = null;
this.optionValues.dependent = [{ label:'None', value:'' }];
let controllerValues = this.allDependentOptions.controllerValues;
this.allDependentOptions.values.forEach( val =>{
val.validFor.forEach(key =>{
if(key === controllerValues[selected]){
this.isDisabled = false;
this.optionValues.dependent.push({label : val.label, value : val.value});
}
});
});
const selectedrecordevent = new CustomEvent(
"selectedpicklists", {
detail : { pickListValue : this.selectedValues}
}
);
this.dispatchEvent(selectedrecordevent);
if(this.optionValues.dependent && this.optionValues.dependent.length > 1){
}
else{
this.optionValues.dependent = [];
this.isDisabled = true;
}
}else{
this.isDisabled = true;
this.selectedValues.dependent = [];
this.selectedValues.controlling = [];
}
}
handleDependentChange(event){
this.selectedValues.dependent = event.target.value;
const selectedrecordevent = new CustomEvent(
"selectedpicklists",
{
detail : { pickListValue : this.selectedValues}
}
);
this.dispatchEvent(selectedrecordevent);
//sendDataToParent();
}
}

Step 2 – Test the Pick-list.

Create a web component and give it a name of your choice. For .html file use below code

<template>
    <div class="">
        <lightning-card  variant="Narrow"  
            title="Generic Solutions" icon-name="standard:record">
                <div class="slds-m-around_small" style="color: red;" >
                    Note:- Task Object is not supported by lightning/*Api 
                </div>
                <div class="slds-m-around_small">
                    Account Object
                    <c-generic-dependent-picklist 
                        object-api-name="Account"
                        dependent-picklist-api-name="CustomerPriority__c"
                        dependent-picklist-label="Customer Priority"
                        controlling-picklist-api-name="Industry"
                        controlling-picklist-label="Industry"
                        onselectedpicklists={handlePicklist} >
                    </c-generic-dependent-picklist>
                    <template if:true={selectedvalues}>
                        <div class="slds-m-around_small">
                            <p style="color: red;"> 
                                Selected Controlling Picklist : {selectedvalues.controlling}
                            </p>
                            <p style="color: red;"> 
                                Selected Dependent Picklist   : {selectedvalues.dependent} 
                            </p>
                        </div>
                    </template>
                </div>
        </lightning-card>
    </div>
</template>

Code Comments: – In our test component, we are using our generic component.

<c-generic-dependent-picklist 
                        object-api-name="Account"
                        dependent-picklist-api-name="CustomerPriority__c"
                        dependent-picklist-label="Customer Priority"
                        controlling-picklist-api-name="Industry"
                        controlling-picklist-label="Industry"
                        onselectedpicklists={handlePicklist} >
</c-generic-dependent-picklist>

object-api-name : – is used to get the Object API Name. So, make sure that you are passing the valid API name for the object.

dependent-picklist-api-name :- The API name for the dependent picklist field

dependent-picklist-label :- Field Label for the dependent pick-list field.

controlling-picklist-api-name :- API name for the controlling pick-list field

controlling-picklist-label : – Field Label for the Controlling pick-list field.

onselectedpicklists – Event handler which accepts the JS method in the calling component ( in our case it is test component ) to handle and get the selected values.

for .js file use below code between { }

selectedvalues;
    handlePicklist(event) {
        let selectedValues = event.detail.pickListValue;
        window.console.log('\n **** selectedValues **** \n ', selectedValues);
        this.selectedvalues = JSON.parse(JSON.stringify(selectedValues));
    }

for meta XML file using below code

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>48.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

Once you have created the test web component. Use this web component in either app page, record detail page or home page.

Approach

  • In this solution, we have 5 @api decorators which are used to receive setup information such as object name, controlling picklist API name, and dependent picklist API names. 2 @api decorators are used to get the Label for Dependent & Controlling picklist field.
  • We are using uiObjectInfoApi to get metadata info of objects such as picklist values.
  • Methods like setUpControllingPicklist and setUpDependentPickList are used to set up picklist values each individually.
  • In the setUpControllingPicklist method, we are filling all values available for Controlling Picklist Options whereas in the setUpDependentPicklist we are storing the picklist partial and required response which will be later used for filling dependent picklist options based on controlling picklist.
  • One can handle events which are generated when the user selects options.

Future Enhancement

  1. Add Validation
  2. Ask to the user which type of Dependent Pick-list want to display either single-select or multi-select
  3. Incorporate the changes for record type
  4. Make the same component work for simple ( Non-Dependent ) pick-list

At Last I wanna thanks Amit for giving this platform to share with you guys, see you until next blog 🙂

 

7 COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here