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 :
- You can search the picklist values
- In multiselect we create pills so you can remove easily on pills.
- this component is reusable
- There are 2 components so child is main and parent where we get selected options.
- 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; } } @api clearAll() { this.values = []; var optionData = this.options ? (JSON.parse(JSON.stringify(this.options))) : null; for (var i = 0; i < optionData.length; i++) { if (this.multiSelect) { optionData[i].selected = false; } } this.searchString = 0 + ' Option(s) Selected'; this.selectedValues = []; this.optionData = optionData; } 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); } }
What’s your Reaction?
+1
4
+1
2
+1
2
+1
1
+1
4
+1
2
54 comments
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.
You can check the parent component (MultiSelectPickListParent.Js) I am passing picklist values.
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!
yupp You can change the picklist values from parent components
vertical scroll bar getting closed whenever user click on scrollbar.
Hi @Uttam, add below CSS class in LWC CSS file
.slds-dropdown_fluid{
margin-top: 0px;
}
Hi RIJWAN MOHMMED, I tried this. Still the scroll bar is not working. Can you please help me on this.
Schedule a call on sunday
hi Rijwan, clicking the vertical scroll bar closes the dropdown menu, causing frustration on users not using the mouse wheel for scrolling. I tried using an onscroll event and preventDefault, however doesn’t look like the scroll event is getting fired when scrolling occurs by clicking the scrollbar. Is there a way to prevent this from happening? It appears that the lightning-combobox somehow managed to do it, as clicking the scrollbar there does not close the dropdown of options. Thanks
you can commented that code which fire on ommouseHover.
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.
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
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
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.
I’m running on the same issue, I can get a hardcoded option list to show up, but the options are not updating once I change the value of the option variable
How can I get an Array of multi-selected values?
Please check my parent component , I am getting array list of selected options.
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;
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);
}
”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.
How we can make picklist values darkest and bolder?
apply CSS on picklist options
How can we default the picklist value by default coming from Apex controller?
pass the value as selected values from the parent component
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.
create a child component method so you can call from the parent and clear the selected picklist values.
I cant find this component in lightning app builder under custom components. Am I missing something?
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?
Hi, Shab,
you can use a picklist of objects, you need to fetch that picklist from apex.
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 ?
Hi Alex, I have fixed the MultiSelectPickList.Html file now. now search option working.
Thanks for the posted bug.
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?
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); } }
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.
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/
Hi Rijwan,
First of all thanks a lot for posting this solution.
It seems when there is a duplicate in the list, and if we try to use keyword in the search which is present in the duplicated entry, then an exception is occurring and causing the abnormality in the functionality. By any chance, have you noticed this in your tests?
“Uncaught (in promise) TypeError: Failed to execute ‘removeChild’ on ‘Node’: parameter 1 is not of type ‘Node’.
at HTMLUListElement.removeChild”
Hi Farooq, thanks for caught the issue I will check and fix this
Hey is there any video available of the above code with the explanation??
You can arrange a meeting by clicking on book a meeting in right side button, I can explain you.
Hey!!
this is not working if I am using it in a same template for 2 times. why? I need it 2 times but it is not working 2 times?
ALso I am giving new values to all the params::
this is working fine, I am thankful for providing such a generic code.
but I am using twice in same template.
and here, it is not working. all the options in {optionsCityUnique} apperas in JS but not visible in html. I had applied @track optionsCityUnique = []; also. Please mention if I have to do alternate solutuion for getting it right.
I am not getting your questing, Can you please explain more
In HTML I am checking option have or not for this I am using If:true
so you can change this condition then it will work
here is the code:: 1st layout item is working, but 2nd one is not.
<!–
–>
here is the code:: 1st layout item is working, but 2nd one is not.
Hi Rijwan,
I am using ur child component same as it is and calling it from my parent html file. In my JS, I am fetching the options using getPicklistValues() and fetching the status picklist values.The UI works exactly fine. When selected each option , it shows the no of selcted options. But these selected values which are stored in this.values are passed from child to Parent through custom event and I am able to retrieve the values in the parent and I am trying to send the same to the apex controller as a parmaeter using $. but here my wire method doesnt work at all as the values passed from JS are not fetched in the controller. All the other fields that are passed from parent are being queried using wire. But the param which is sent from the child to parent and then to the controller is not working. Could yuou please help me. Have you ever faced this issue?
Appreciate your help.
Thanks.
I want to know how can we make the search functionality here. Thanks in advance
Hi @Prateek, Search functionality already there.
HI is it possible to change the name of options[] to any other lets say NumberOfFloors[];
I tried Doing that but my component just keeps rendering I think as you have used Options[] in your child component it only takes that vales not the updated NumberOfFloors[] value
Hi Chirag, you can use any variable. Just check the reference of that field.Also check the HTML files , I am passing the values as a options variable.
You are awesome, I’ve seen so many half assed/broken implementations of this one didn’t require much changes at all. The only weird thing was the mouse hover opening the drop down but that was easily disabled.
Thanks you Oscar
Hey Rijwan, What if I want use the component two or more time in a parent component for different option?
You can use , you only need to change in parent component that create different-2 method (handleSelectOptionList)