Kishore
Kishore I am a Salesforce developer and a so-called blogger at SalesforceLwc.in. I love learning and sharing. SalesforceLWC aims to share relevant knowledge, examples and scenarios for understanding Salesforce Lightning Web Components and AURA.

Picklist and Dependent Picklist in Salesforce LWC and AURA


Picklist and Dependent Picklist in Salesforce LWC and AURA

We will utilize base components that Salesforce has provided to build custom dynamic Picklist and Dependent Picklist.

Base component that we will be using:

1
2
3
4
5
6
7
8
<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

1
2
3
4
5
6
7
8
9
10
11
12
<template>
    <lightning-combobox
        id="pickList"
        name="progress"
        label={label}
        value={value}
        variant={variant}
        placeholder="Select"
        options={options}
        onchange={handleChange}>
    </lightning-combobox>
</template>


Picklist.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/* 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

1
2
3
4
5
6
7
8
9
10
<?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:

1
2
3
4
5
6
7
8
9
10
11
<template>
    <lightning-combobox id="dependentPickList"
                        name="Hello"
                        label={label}
                        value={value}
                        placeholder="--None--"
                        variant={variant}
                        options={options}
                        onchange={handleChange}>
    </lightning-combobox>
</template>


dependentPickList.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/* 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:

1
2
3
4
5
6
7
8
9
10
<?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:

1
2
3
4
5
6
7
8
<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:**

1
2
3
4
5
6
7
8
9
10
<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.

Picklist:

Picklist source code github link

DependentPicklist:

Dependent Picklist source code github link

Icons made by itim2101 from www.flaticon.com