Create Single/Multi-Select Combobox / Picklist in LWC Salesforce

by Rijwan Mohmmed
30 comments
how-to-create-single-/-multi-select-combobox-/-picklist-in-lwc-salesforce

Hello friends, today we will learn to create single/Multi-Select Combobox / picklist with the search bar in LWC Salesforce. We will customize the things and create this because standard combobox in LWC doesn’t have such type of functionality .

Key Heighlights :

  1. You can search the picklist values
  2. In multiselect we create pills so you can remove easily on pills.
  3. this component is reusable
  4. There are 2 components so child is main and parent where we get selected options.
  5. Multi Select Combobox component is advance then dual-listbox because this cover small space and dual cover big space.

MultiSelectPickList.Html :

<template>
    
    <!-- Start Header Label Passed from Parent -->
    <template if:true={label}>
        <label class="slds-form-element__label">{label}</label>
    </template>
    <!-- End Header Label Passed from Parent -->
    <div class="slds-combobox_container">
        <div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open" aria-expanded="true" aria-haspopup="listbox" role="combobox" onmouseleave={handleMouseOut} onmouseenter={handleMouseIn}>
            <!-- Search Input -->
            <div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none">
                <lightning-input disabled={disabled} class="inputBox" placeholder="Select an Option" onblur={handleBlur} onclick={showOptions} onkeyup={filterOptions} value={searchString}  variant="label-hidden" id="combobox-id-1" ></lightning-input>
                <lightning-icon class="slds-input__icon" icon-name="utility:down" size="x-small" alternative-text="downicon"></lightning-icon>
            </div>
            <!-- Dropdown List -->
            <template if:true={showDropdown}>
                <div id="listbox-id-1" class="slds-dropdown slds-dropdown_length-5 slds-dropdown_fluid">
                    <ul class="slds-listbox slds-listbox_vertical recordListBox" role="presentation">
                        <template if:false={noResultMessage} >
                            <template for:each={optionData} for:item="option">
                                <li key={option.value} data-id={option.value} onmousedown={selectItem} class="slds-listbox__item eachItem" if:true={option.isVisible}>
                                    <template if:true={option.selected}>
                                        <lightning-icon icon-name="utility:check" size="x-small" alternative-text="icon" ></lightning-icon>
                                    </template>
                                    <span class="slds-media slds-listbox__option_entity verticalAlign slds-truncate">{option.label}</span>
                                </li>
                            </template>
                        </template>
                        <template if:true={noResultMessage} >
                            <li class="slds-listbox__item">
                                <span class="slds-media slds-listbox__option_entity verticalAlign slds-truncate">{noResultMessage}</span>
                            </li>
                        </template>
                    </ul>
                </div>
            </template>
        </div>
    </div>
    <!-- Multi Select Pills -->
    <template for:each={optionData} for:item="option">
		<template if:true={option.selected}>
			<lightning-pill label={option.label} key={option.value} name={option.value} onremove={closePill}>
				<lightning-icon icon-name="custom:custom11" alternative-text="Account"></lightning-icon>
			</lightning-pill>
		</template>
	</template>

</template>

MultiSelectPickList.Js :

import { LightningElement, track, api } from 'lwc';
 
export default class MultiSelectPickList extends LightningElement {
    
    @api options;
    @api selectedValue;
    @api selectedValues = [];
    @api label;
    @api disabled = false;
    @api multiSelect = false;
    @track value;
    @track values = [];
    @track optionData;
    @track searchString;
    @track noResultMessage;
    @track showDropdown = false;
 
    connectedCallback() {
        this.showDropdown = false;
        var optionData = this.options ? (JSON.parse(JSON.stringify(this.options))) : null;
        var value = this.selectedValue ? (JSON.parse(JSON.stringify(this.selectedValue))) : null;
        var values = this.selectedValues ? (JSON.parse(JSON.stringify(this.selectedValues))) : null;
        if(value || values) {
            var searchString;
            var count = 0;
            for(var i = 0; i < optionData.length; i++) {
                if(this.multiSelect) {
                    if(values.includes(optionData[i].value)) {
                        optionData[i].selected = true;
                        count++;
                    }  
                } else {
                    if(optionData[i].value == value) {
                        searchString = optionData[i].label;
                    }
                }
            }
            if(this.multiSelect)
                this.searchString = count + ' Option(s) Selected';
            else
                this.searchString = searchString;
        }
        this.value = value;
        this.values = values;
        this.optionData = optionData;
    }
 
    filterOptions(event) {
        this.searchString = event.target.value;
        if( this.searchString && this.searchString.length > 0 ) {
            this.noResultMessage = '';
            if(this.searchString.length >= 2) {
                var flag = true;
                for(var i = 0; i < this.optionData.length; i++) {
                    if(this.optionData[i].label.toLowerCase().trim().startsWith(this.searchString.toLowerCase().trim())) {
                        this.optionData[i].isVisible = true;
                        flag = false;
                    } else {
                        this.optionData[i].isVisible = false;
                    }
                }
                if(flag) {
                    this.noResultMessage = "No results found for '" + this.searchString + "'";
                }
            }
            this.showDropdown = true;
        } else {
            this.showDropdown = false;
        }
    }
 
    selectItem(event) {
        var selectedVal = event.currentTarget.dataset.id;
        if(selectedVal) {
            var count = 0;
            var options = JSON.parse(JSON.stringify(this.optionData));
            for(var i = 0; i < options.length; i++) {
                if(options[i].value === selectedVal) {
                    if(this.multiSelect) {
                        if(this.values.includes(options[i].value)) {
                            this.values.splice(this.values.indexOf(options[i].value), 1);
                        } else {
                            this.values.push(options[i].value);
                        }
                        options[i].selected = options[i].selected ? false : true;   
                    } else {
                        this.value = options[i].value;
                        this.searchString = options[i].label;
                    }
                }
                if(options[i].selected) {
                    count++;
                }
            }
            this.optionData = options;
            if(this.multiSelect){
                this.searchString = count + ' Option(s) Selected';

                let ev = new CustomEvent('selectoption', {detail:this.values});
                this.dispatchEvent(ev);
            }
                

            if(!this.multiSelect){
                let ev = new CustomEvent('selectoption', {detail:this.value});
                this.dispatchEvent(ev);
            }

            if(this.multiSelect)
                event.preventDefault();
            else
                this.showDropdown = false;
        }
    }
 
    showOptions() {
        if(this.disabled == false && this.options) {
            this.noResultMessage = '';
            this.searchString = '';
            var options = JSON.parse(JSON.stringify(this.optionData));
            for(var i = 0; i < options.length; i++) {
                options[i].isVisible = true;
            }
            if(options.length > 0) {
                this.showDropdown = true;
            }
            this.optionData = options;
        }
    }
 
    closePill(event) {
        var value = event.currentTarget.name;
        var count = 0;
        var options = JSON.parse(JSON.stringify(this.optionData));
        for(var i = 0; i < options.length; i++) {
            if(options[i].value === value) {
                options[i].selected = false;
                this.values.splice(this.values.indexOf(options[i].value), 1);
            }
            if(options[i].selected) {
                count++;
            }
        }
        this.optionData = options;
        if(this.multiSelect){
            this.searchString = count + ' Option(s) Selected';
            
            let ev = new CustomEvent('selectoption', {detail:this.values});
            this.dispatchEvent(ev);
        }
    }
 
    handleBlur() {
        var previousLabel;
        var count = 0;

        for(var i = 0; i < this.optionData.length; i++) {
            if(this.optionData[i].value === this.value) {
                previousLabel = this.optionData[i].label;
            }
            if(this.optionData[i].selected) {
                count++;
            }
        }

        if(this.multiSelect){
            this.searchString = count + ' Option(s) Selected';
        }else{
            this.searchString = previousLabel;
        }

        this.showDropdown = false;
    }

    handleMouseOut(){
        this.showDropdown = false;
    }

    handleMouseIn(){
        this.showDropdown = true;
    }
}

MultiSelectPickList.css :

.verticalAlign {
 cursor: pointer;
    padding: 0px 5px !important;
}
.slds-dropdown {
    padding:0px !important;
}
.recordListBox {
 margin-top:0px !important;
 overflow-y: scroll;
}
.slds-listbox li {
    padding: .45rem 0.7rem !important;
    display: flex;
}
.inputBox input {
 padding-left: 10px;
}
.eachItem:hover {
    background-color: #F1F1F1;
    cursor: pointer;
}
 
/* For Scrolling */
::-webkit-scrollbar {
    width: 7px;
    height: 7px;
}
::-webkit-scrollbar-track {
   display: none !important;
}
::-webkit-scrollbar-thumb {
    border-radius: 10px;
    background: rgba(0,0,0,0.4);
}
.slds-dropdown_fluid{
	margin-top: 0px;
}

We will put this component any parent component so we can reuse this

MultiSelectPickListParent.Html :

<template>
    <div class="slds-tabs_card">
        <div class="slds-page-header">
            <div class="slds-page-header__row">
                <div class="slds-page-header__col-title">
                    <div class="slds-media">
                        <div class="slds-media__figure">
                            <span class="slds-icon_container slds-icon-standard-opportunity">
                                 <lightning-icon icon-name="standard:recipe" alternative-text="recipe" title="recipe"></lightning-icon>
                            </span>
                        </div>
                        <div class="slds-media__body">
                            <div class="slds-page-header__name">
                                <div class="slds-page-header__name-title">
                                    <h1>
                                        <span>LWC Combobox</span>
                                        <span class="slds-page-header__title slds-truncate" title="TechDicer">TechDicer</span>
                                    </h1>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div> <br/>
     <lightning-card  variant="Narrow"  title="Pick List" if:true={options}>
        <div class="slds-p-horizontal_small"> 
            <c-multi-select-pick-list onselectoption={handleSelectOption} options={options} selected-value={selectedValue} label="Single Select Pick List"></c-multi-select-pick-list>
        </div>
        <div class="slds-p-horizontal_small"> 
            <c-multi-select-pick-list multi-select="true" onselectoption={handleSelectOptionList} options={options} selected-values={selectedValueList} label="multiSelect Pick List"></c-multi-select-pick-list>
        </div>
   </lightning-card>
</template>

MultiSelectPickListParent.Js :

import { LightningElement, track, wire } from 'lwc';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import TYPE_FIELD from '@salesforce/schema/Account.Type';
import { getPicklistValues, getObjectInfo } from 'lightning/uiObjectInfoApi';
const options = [
                    {'label':'India','value':'India'},
                    {'label':'USA','value':'USA'},
                    {'label':'China','value':'China'},
                    {'label':'Rusia','value':'Rusia'}
                ];
 
export default class MultiSelectPickListParent extends LightningElement {
    @track selectedValue = 'Customer - Direct';//selected values
    @track selectedValueList = ['Customer - Direct'];//selected values
    @track options; //= options;

    @wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
    objectInfo;
 
    //fetch picklist options
    @wire(getPicklistValues, {
        recordTypeId: "$objectInfo.data.defaultRecordTypeId",
        fieldApiName: TYPE_FIELD
    })
    wirePickList({ error, data }) {
        if (data) {
            this.options = data.values;
        } else if (error) {
            console.log(error);
        }
    }
     
    //for single select picklist
    handleSelectOption(event){
        console.log(event.detail);
        this.selectedValue = event.detail;
    }
 
    //for multiselect picklist
    handleSelectOptionList(event){
        console.log(event.detail);
        this.selectedValueList = event.detail;
        console.log(this.selectedValueList);
    }
}

You may also like

30 comments

Mark June 10, 2022 - 5:43 am

How to get the exact selected values from here? I want to pass it to Apex Controller in a string format with “;” as separator. Please help. Thank you.

Reply
Rijwan Mohmmed June 20, 2022 - 3:32 pm

You can check the parent component (MultiSelectPickListParent.Js) I am passing picklist values.

Reply
Fred June 20, 2022 - 3:22 pm

Hi, this version is not searchable. Can we somehow change the picklist values and use it within a flow? I am only able to install the static and fixed component containing both picklist fields. How can I customize it?
Thanks a lot for your help!

Reply
Rijwan Mohmmed June 20, 2022 - 3:30 pm

yupp You can change the picklist values from parent components

Reply
Uttam Singh July 29, 2022 - 4:34 am

vertical scroll bar getting closed whenever user click on scrollbar.

Reply
Rijwan Mohmmed July 31, 2022 - 4:43 pm

Hi @Uttam, add below CSS class in LWC CSS file
.slds-dropdown_fluid{
margin-top: 0px;
}

Reply
Priyanka August 3, 2022 - 12:01 pm

Rijwan, I am trying to get the picklist values from object using apex controller method. But the connectedcallback of the child component gets called first and then apex controller is getting called. this results into empty array in the options attribute of the multiselect picklist.

Reply
Rijwan Mohmmed August 3, 2022 - 4:59 pm

Hi @Priyanka
Pass the picklist values from the parent component to the child component. In my component, you just need to assign the picklist values to the child component.

Just put the values in options field

Reply
Carlos February 1, 2023 - 10:45 am

Hello,
I found the same issue. I am passing the values from Apex, but the child component gets called first.
There is a way to refresh the child component after that?
Thanks

Reply
Carlos February 1, 2023 - 11:04 am

Just found the fix.

I had to add is before the adding the child component, so every thing that I change options it will refresh it.

Reply
Mohsin August 7, 2022 - 1:43 pm

How can I get an Array of multi-selected values?

Reply
Rijwan Mohmmed August 7, 2022 - 4:03 pm

Please check my parent component , I am getting array list of selected options.

Reply
Mohsin August 7, 2022 - 2:05 pm

handleMultiSelectValues(event){
try {
this.fields = event.detail.payload.values;

} catch (error) {
console.log(error);
}
}

This is how I am trying to get all multi-selected values But I am getting an empty array.

for reference;

Reply
Rijwan Mohmmed August 7, 2022 - 4:04 pm

Hi @Mohsin, below is code for get select options array
//for multiselect picklist
handleSelectOptionList(event){
console.log(event.detail);
this.selectedValueList = event.detail;
console.log(this.selectedValueList);
}

Reply
Mohsin August 7, 2022 - 2:12 pm

”handleMultiSelectValues(event){
try {
this.fieldObjectValues = event.detail.payload.values;
});

console.log( this.fieldObjectValues);

} catch (error) {
console.log(error);
}
}”


This is how I am trying to get multi-selected values but I am getting an empty array.

Reply
Hil September 25, 2022 - 9:58 pm

How we can make picklist values darkest and bolder?

Reply
Rijwan Mohmmed September 27, 2022 - 5:34 pm

apply CSS on picklist options

Reply
Ravi September 27, 2022 - 3:20 pm

How can we default the picklist value by default coming from Apex controller?

Reply
Rijwan Mohmmed September 27, 2022 - 5:38 pm

pass the value as selected values from the parent component

Reply
Miggy November 17, 2022 - 6:58 am

HI, I need to clear selected muti-picklist and the pills when parent was trigger change by another field.

I found the problem when I send blank value from parent to clear picklist, the text in combobox was clear to 0 selected but in the dropdown it still show selected icon and below is show selected pill from latest.

Reply
Rijwan Mohmmed November 18, 2022 - 3:29 pm

create a child component method so you can call from the parent and clear the selected picklist values.

Reply
shab November 17, 2022 - 11:16 pm

I cant find this component in lightning app builder under custom components. Am I missing something?

Reply
shab November 17, 2022 - 11:43 pm

I fixed the previous issue by adding targets in XML File.
Another question: Instead of static options(like India, USA etc), can we fetch the list of all objects in salesforce?

Reply
Rijwan Mohmmed November 18, 2022 - 3:24 pm

Hi, Shab,
you can use a picklist of objects, you need to fetch that picklist from apex.

Reply
Alex December 2, 2022 - 9:03 pm

hello Rijwan,
In search string, it doesn’t find the items in the list or sort them?However, it returns the right message that the list doesn’t have the records. How can I do it ?

Reply
Rijwan Mohmmed December 3, 2022 - 5:22 am

Hi Alex, I have fixed the MultiSelectPickList.Html file now. now search option working.
Thanks for the posted bug.

Reply
Juan December 14, 2022 - 8:11 am

Hi Rijwan,
is it possibe to put clear all button at the bottom of the multi-select picklist so that it clear all the selected values and removes all the pills? If yes, how to achieve it?

Reply
Rijwan Mohmmed December 14, 2022 - 8:24 am

Hi @Juan,
Yupp that is possible. Below is the code


closeAllPill(event) {

var count = 0;
var options = JSON.parse(JSON.stringify(this.optionData));
for(var i = 0; i < options.length; i++) { if(options[i].selected) { options[i].selected = false; this.values.splice(this.values.indexOf(options[i].value), 1); } if(options[i].selected) { count++; } } this.optionData = options; if(this.multiSelect){ this.searchString = count + ' Option(s) Selected'; let ev = new CustomEvent('selectoption', {detail:this.values}); this.dispatchEvent(ev); } }

Reply
Juan December 14, 2022 - 8:32 am

Thanks for your quick reply, Rijwan.
When will closeAllPIll() will be called? I want it as a button. Where should I put in MultiselectPicklist.html?
you can put in the parent LWC component and call from there.

Reply
Rijwan Mohmmed December 15, 2022 - 5:07 pm

above code should be put in: MultiSelectPickList.JS
and create a button in the parent component: MultiSelectPickListParent.HTML
Call this child component method from parent .
https://techdicer.com/call-child-method-from-parent-component-in-lightning-web-component-lwc-salesforce/

Reply

Leave a Comment