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

In genericDependentPicklist.html paste the following code

<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>

Next In genericDependentPicklist.js paste the following code

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();
   }
}

genericDependentPicklist meta xml file

<?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>

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 create the test web component. Use this web component in either app page, record detail page or home page.

Below is the output for the same.

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 value 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 Enhancment

  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 🙂

Leave a Reply