Multi Select Combobox lightning component is easy to create and works for both single select and multi select.
Lightning comes with many pre-built components that are easy to use but still many components are missing by date. One of them is the multi-select Combobox/picklist component. We will create this within a single component without using any child component or event. You can find the complete source code in this post.
Multi Select Combobox component Features:
- Single component
- Options attribute to pass List
- Option to switch between Single Select and Multi Select.
- Separate attributes for single and multi select to get selected values.
- search functionality to filter options
- Option to set the minimum character to start searching
- Ability to set the label of combobox
- combobox disable functionality
Demo GIF:
Single Select Combobox:
Multi Select Combobox:
Step-1: Create the MultiSelectCombobox Component
For creating a new lightning component navigate to Developer Console > File > New > Lightning Component
Enter Name & Description and click Submit. Now component markup file will open y default. In the right sidebar, click on Controller, Helper, and Style to create those files too.
Now since all the files are created, we will write markup for our component. I have used standard (Salesforce Lightning Design System) SLDS here for styling.
Markup for MultiSelectCombobox.cmp
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 |
<!-- Code by CafeForce || www.cafeforce.com || support@cafeforce.com || Mandatory Header --> <aura:component> <!-- Attributes that can be set while component calling--> <aura:attribute name="options" type="string" default="" required="true" /> <aura:attribute name="value" type="String" default="" description="Selected value in single Select" /> <aura:attribute name="values" type="List" default="" description="Selected value in Multi Select" /> <aura:attribute name="label" type="string" default="" description="Label will be displayed above input Box" /> <aura:attribute name="minChar" type="Integer" default="1" description="Minimum character to type for search" /> <aura:attribute name="disabled" type="Boolean" default="false" description="Disable the combobox" /> <aura:attribute name="multiSelect" type="Boolean" default="false" description="Switch between single and multiSelect" /> <!-- Internal Use Attributes --> <aura:handler name="init" value="{!this}" action="{!c.doInit}"/> <aura:attribute name="searchString" type="string" access="private" default="" description="String to search"/> <aura:attribute name="message" type="String" access="private" default="" /> <!-- Component Markup --> <div> <aura:if isTrue="{!!empty(v.label)}"> <label class="slds-form-element__label">{!v.label}</label> </aura:if> <div class="slds-combobox_container"> <div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open" aura:id="resultsDiv" aria-expanded="true" aria-haspopup="listbox" role="combobox"> <div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none"> <lightning:input disabled="{!v.disabled}" aura:id="inputLookup" class="inputBox" placeholder="Select an Option" onblur="{!c.blurEvent}" onclick="{!c.showOptions}" onkeyup="{!c.filterOptions}" value="{!v.searchString}" autoComplete="off" variant="label-hidden" id="combobox-id-1" /> <lightning:icon class="slds-input__icon" iconName="utility:down" size="x-small" alternativeText="search"/> </div> <!-- Dropdown List --> <div id="listbox-id-1" class="slds-dropdown slds-dropdown_length-5 slds-dropdown_fluid" style="{! 'max-height:' + (8 + (v.recordCount * 40)) + 'px' }"> <ul class="slds-listbox slds-listbox_vertical recordListBox" role="presentation"> <aura:if isTrue="{!empty(v.message)}" > <!-- To display Drop down List --> <aura:iteration items="{!v.options}" var="option" > <aura:if isTrue="{!option.disabled}"> <li class="{!'slds-listbox__item disabledItem' + if(option.isVisible,'',' slds-hide')}"> <span class="slds-media slds-listbox__option_entity verticalAlign slds-truncate">{!option.label}</span> </li> <aura:set attribute="else"> <li id="{!option.value}" class="{!'slds-listbox__item eachItem' + if(option.isVisible,'',' slds-hide')}" onmousedown="{!c.selectItem}"> <lightning:icon class="{!if(option.selected,'','slds-hide')}" iconName="utility:check" size="x-small" alternativeText="icon" /> <span class="slds-media slds-listbox__option_entity verticalAlign slds-truncate">{!option.label}</span> </li> </aura:set> </aura:if> </aura:iteration> <!-- To display Error Message --> <aura:set attribute="else"> <li class="slds-listbox__item"> <span class="slds-media slds-listbox__option_entity verticalAlign slds-truncate">{!v.message}</span> </li> </aura:set> </aura:if> </ul> </div> </div> </div> <aura:iteration items="{!v.options}" var="option"> <aura:if isTrue="{!option.selected}"> <lightning:pill class="slds-m-around_xx-small" name="{!option.value}" label="{!option.label}" onremove="{!c.removePill}"/> </aura:if> </aura:iteration> </div> </aura:component> <!-- Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE --> |
Now open the controller file MultiSelectComboboxController.js and paste the below code.
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 |
({ // To prepopulate the seleted value pill if value attribute is filled doInit : function( component, event, helper ) { helper.doInitHelper(component); }, // When a keyword is entered in search box filterOptions : function( component, event, helper ) { if( !$A.util.isEmpty(component.get('v.searchString')) ) { helper.filterOptionsHelper(component); } else { $A.util.removeClass(component.find('resultsDiv'),'slds-is-open'); } }, // When an item is selected selectItem : function( component, event, helper ) { if(!$A.util.isEmpty(event.currentTarget.id)) { helper.selectItemHelper(component, event); } }, showOptions : function( component, event, helper ) { var disabled = component.get("v.disabled"); if(!disabled) { component.set("v.message", ''); component.set('v.searchString', ''); var options = component.get("v.options"); options.forEach( function(element,index) { element.isVisible = true; }); component.set("v.options", options); if(!$A.util.isEmpty(component.get('v.options'))) { $A.util.addClass(component.find('resultsDiv'),'slds-is-open'); } } }, // To remove the selected item. removePill : function( component, event, helper ){ helper.removePillHelper(component, event); }, // To close the dropdown if clicked outside the dropdown. blurEvent : function( component, event, helper ){ helper.blurEventHelper(component, event); }, }) /* Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE */ |
Open the helper file MultiSelectComboboxHelper.js and paste the below code.
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 |
({ doInitHelper : function(component) { $A.util.toggleClass(component.find('resultsDiv'),'slds-is-open'); var value = component.get('v.value'); var values = component.get('v.values'); if( !$A.util.isEmpty(value) || !$A.util.isEmpty(values) ) { var searchString; var count = 0; var multiSelect = component.get('v.multiSelect'); var options = component.get('v.options'); options.forEach( function(element, index) { if(multiSelect) { if(values.includes(element.value)) { element.selected = true; count++; } } else { if(element.value == value) { searchString = element.label; } } }); if(multiSelect) component.set('v.searchString', count + ' options selected'); else component.set('v.searchString', searchString); component.set('v.options', options); } }, filterOptionsHelper : function(component) { component.set("v.message", ''); var searchText = component.get('v.searchString'); var options = component.get("v.options"); var minChar = component.get('v.minChar'); if(searchText.length >= minChar) { var flag = true; options.forEach( function(element,index) { if(element.label.toLowerCase().trim().startsWith(searchText.toLowerCase().trim())) { element.isVisible = true; flag = false; } else { element.isVisible = false; } }); component.set("v.options",options); if(flag) { component.set("v.message", "No results found for '" + searchText + "'"); } } $A.util.addClass(component.find('resultsDiv'),'slds-is-open'); }, selectItemHelper : function(component, event) { var options = component.get('v.options'); var multiSelect = component.get('v.multiSelect'); var searchString = component.get('v.searchString'); var values = component.get('v.values') || []; var value; var count = 0; options.forEach( function(element, index) { if(element.value === event.currentTarget.id) { if(multiSelect) { if(values.includes(element.value)) { values.splice(values.indexOf(element.value), 1); } else { values.push(element.value); } element.selected = element.selected ? false : true; } else { value = element.value; searchString = element.label; } } if(element.selected) { count++; } }); component.set('v.value', value); component.set('v.values', values); component.set('v.options', options); if(multiSelect) component.set('v.searchString', count + ' options selected'); else component.set('v.searchString', searchString); if(multiSelect) event.preventDefault(); else $A.util.removeClass(component.find('resultsDiv'),'slds-is-open'); }, removePillHelper : function(component, event) { var value = event.getSource().get('v.name'); var multiSelect = component.get('v.multiSelect'); var count = 0; var options = component.get("v.options"); var values = component.get('v.values') || []; options.forEach( function(element, index) { if(element.value === value) { element.selected = false; values.splice(values.indexOf(element.value), 1); } if(element.selected) { count++; } }); if(multiSelect) component.set('v.searchString', count + ' options selected'); component.set('v.values', values) component.set("v.options", options); }, blurEventHelper : function(component, event) { var selectedValue = component.get('v.value'); var multiSelect = component.get('v.multiSelect'); var previousLabel; var count = 0; var options = component.get("v.options"); options.forEach( function(element, index) { if(element.value === selectedValue) { previousLabel = element.label; } if(element.selected) { count++; } }); if(multiSelect) component.set('v.searchString', count + ' options selected'); else component.set('v.searchString', previousLabel); if(multiSelect) $A.util.removeClass(component.find('resultsDiv'),'slds-is-open'); } }) /* Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE */ |
Finally paste the CSS in MultiSelectCombobox.css file.
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 |
.THIS .verticalAlign { cursor: pointer; padding: 0px 5px !important; } .THIS .slds-dropdown { padding:0px !important; } .THIS .fullWidth { width: 100% !important; } .THIS .disabledItem { color: #bbb; cursor: no-drop; } .THIS .recordListBox { margin-top:0px !important; overflow-y: scroll; } .THIS .slds-listbox li { padding: .45rem 0.7rem !important; display: flex; } .THIS .inputBox input { padding-left: 10px; } .THIS .eachItem:hover { background-color: #F1F1F1; cursor: pointer; } .THIS .inputIcon { z-index: 99; padding: 2px 0; } /* For Scrolling */ .THIS ::-webkit-scrollbar { width: 7px; height: 7px; } .THIS ::-webkit-scrollbar-track { display: none !important; } .THIS ::-webkit-scrollbar-thumb { border-radius: 10px; background: rgba(0,0,0,0.4); } |
Step-2: Now we will call the component.
For Single Select:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!-- Code by CafeForce || www.cafeforce.com || support@cafeforce.com --> <aura:component> <aura:attribute name="options" type="List" default="[{'label':'Bob','value':'123'}, {'label':'Shubham','value':'234'}, {'label':'Chrissey','value':'345'}, {'label':'Jessica','value':'456','disabled': true}, {'label':'Sunny','value':'567'}]" /> <aura:attribute name="selectedValue" type="String" default="" description="Selected value in single Select" /> <c:MultiSelectCombobox options="{!v.options}" value="{!v.selectedValue}" label="Single Select Combobox"/> </aura:component> |
For Multi Select:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!-- Code by CafeForce || www.cafeforce.com || support@cafeforce.com --> <aura:component> <aura:attribute name="options" type="List" default="[{'label':'Bob','value':'123'}, {'label':'Shubham','value':'234'}, {'label':'Chrissey','value':'345'}, {'label':'Jessica','value':'456','disabled': true}, {'label':'Sunny','value':'567'}]" /> <aura:attribute name="selectedValues" type="List" default="" description="Selected value in Multi Select" /> <c:MultiSelectCombobox options="{!v.options}" values="{!v.selectedValues}" multiSelect="true" label="Multi Select Combobox"/> </aura:component> |
Finally, our component is ready to use. Now while calling this component, you need to pass the options list and attribute in which selected value will be stored. You can also set the additional attributes and switch between single and multi select component.
How to pass the data (Option list format):
Data should be passed as Object List in the form of label and value.
Eg: [{‘label’: ‘University of Texas’, ‘value’:’UTT112′}]
To disable a value in the list:
To disable a particular element in the options list, simply add ‘disabled’: true in that element object.
Also, Check:
For any queries or suggestions regarding this post, comment below:
Cheers … 🙂
Hi,
Using the above component, i am able select multiple picklist values…but pre populating them is a issue for me.
Could you please explain that as well?
Hi Rishika,
Thanks for your query. If you are using multiSelect attribute as true (Multi select combobox). You just have to pass list of values to prepopulate in the values attribute.
Eg – values=”[‘123’, ‘234’]”
Hi,
How to get the Selected Values in Parent Component, There is either Application or Component Event Declared in your Component.
Hi Dear,
You can get list of Ids of selected values in the ‘values’ attribute. This attribute has dual behavior. To prepopulate values or to get selected values list. There is no event fired right now.
Hi,
I used above component for multi select picklist, i passed values it is showing my values currectly which is fine, but on selecting a value i am getting error “This page has an error. You might just need to refresh it. First, would you give us some details? (We’re reporting this as error ID: -2081392871)”
I checked by debugging “selectItemHelper” helper method i can see selected values currectly but I don’t why that error is occuring each time i select value.
Please help on this.
Hi Shaikh,
Can you please tell me how you are passing the data or can you please share the component calling and data passing code here. Besides, try using try-catch and console the issue.
It has a call to event.preventDefault();, But the function hasn’t ‘event’ parameter so I get an ‘[NoErrorObjectAvailable] Script error.’ error message. The function Helper => blurEventHelper function.
blurEventHelper : function(component) Transform to blurEventHelper : function(component,event)
Hi Fatih,
You caught the error buddy. Thanks for the Help! Appreciated 🙂
Hi,
It is working for me. How can i use event on this?
Hi Kirit,
Create your own component type event and register it in the component file of the component where you declare attributes.
After registering the event, fire this event from the ‘selectItemHelper’ function of Helper class.
How can we use 2 multiselect picklists in one lightening component?
Hi Amol, you can use this component twice in your lightning component.
how to disable any of these options by using controller js.
Hi Ravi,
You just need to set attribute disabled = true in the list which you are binding to component.
Eg-
hi,
can u pls explain how can i pass dynamic list instead of default hadcoding values and labels
Hi Kiarn,
You can dynamically create the options list. Just iterate your dataset and push the data into a list type variable and bind that variable to the options attribute this way.
Hello I want to call the answers from My apex class and to use them in the multiselect to show a list of persons who are related to the choices that exists in the options of the multiselect but I dont know how to do that
Hi Sara,
You can create a List of String in Apex and send it to your lightning component. In lightning Controller/Helper, iterate your list and push the data into the list type variable.
Hi Suyash Thank you for you answer but Iwas talking about after getting dynamic options and selecting one option between the options of the dynamic list how to use it and show the apex response related to each selected option.
I need to show a list of things each time I select an option in the multiselect.
Thank you
Hi Sara,
The simple way is to fire an event from selectItemHelper method of Helper class on selecting a value. Handle that event in your parent component and fetch data back from Apex against that selected value.
Hii,
While Using the above Component Iam getting an Error like
Uncaught Action failed: c:MultiSelectCombobox$controller$blurEvent [Cannot read properties of undefined (reading ‘value’)]
Can You help me with this
Hi Aravind,
Try to see data by adding consoles. You will be able to find which variables are having a null value in the blurEventHelper method.
Hi Suyash. Very great work here which I have been using in one of my pages for over a year already. I modified it a little to accommodate some further requirements. Only suggestion that I’d have is that you cannot really populate the pills (in Multi Select) dynamically right now (e.i. if you build a feature where you can store and re-use your filters) because the “.selected” attribute of the options (that you iterate through in aura) is only turned true during the selectItem function which obviously is not executed when you set the values from the outside. Thanks and have a good day!
Hi Arian,
Apologies for the delay. Very Thanks for your appreciation.
Yes, I got your point. I will add the feature soon to pre-populate the values during load.
I tried using this component as a child component on another Form component, used 8 of these child components but the problem is, when two child components are using the same options, then the values are duplicated on the second child even when none of them are selected on the second child component.
Hi Rohit,
When you are fetching options from the backend, please check if you are binding data to the same variable twice as you are using the same variable in options.