How to create single/multiple Picklist with search bar in Lightning Component Salesforce

by Rijwan Mohmmed
2 comments
create-single-multiple-picklist-with-search-bar-in-lightning-component-salesforce

Hello friends, today we will learn to create single or multi select picklist with search bar. We will customize the things and create this because standard combobox in Lightning Component 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.

MultiSelectCombobox.cmp :

<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" default="" description="String to search"/>
    <aura:attribute name="message"   type="String" 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.handleBlur}" 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.closePill}"/>
            </aura:if>
        </aura:iteration>
    </div>
</aura:component>

MultiSelectCombboxController.Js :

({
    doInit : function( component, event, helper ) {
        helper.doInitStartHelper(component);
    },
    
    //search option in picklist
    filterOptions : function( component, event, helper ) {
        if( !$A.util.isEmpty(component.get('v.searchString')) ) {
            helper.filterOptionsDataHelper(component);
        } else {
            $A.util.removeClass(component.find('resultsDiv'),'slds-is-open');
        }
    },
    
    // option selected
    selectItem : function( component, event, helper ) {
        if(!$A.util.isEmpty(event.currentTarget.id)) {
            helper.selectOptionHelper(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.
    closePill : function( component, event, helper ){
        helper.removeOptionPillHelper(component, event);
    },
    
    // To close the dropdown if clicked outside the dropdown.
    handleBlur : function( component, event, helper ){
        helper.handleBlurHelper(component, event);
    },
})

MultiSelectCombboxHelper.Js :

({
    doInitStartHelper : 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);
        }
    },
    
    filterOptionsDataHelper : 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');
    },
    
    selectOptionHelper : 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');
    },
    
    removeOptionPillHelper : 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);
    },
    
    handleBlurHelper : 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');
    }
})

MultiSelectCombobox.css :

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

We will put this on Parent component

MultiSelectComboboxParent.cmp :

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global">
    <aura:attribute name="options" type="List" default="[{'label':'Mango','value':'Mango'},
                                                        {'label':'Apple','value':'Apple'},
                                                        {'label':'Grapes','value':'Grapes'},
                                                        {'label':'Banana','value':'Banana'}]" />
    
    <aura:attribute name="options2" type="List" default="[{'label':'India','value':'India'},
                                                        {'label':'Usa','value':'Usa'},
                                                        {'label':'China','value':'China'},
                                                        {'label':'Japan','value':'Japan'}]" />
    
    <aura:attribute name="selectedValue" type="String" default="" description="Selected value in single Select" />
    <aura:attribute name="selectedValues" type="List" default="" description="Selected value in Multi Select" />

    
    <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 iconName="standard:event" alternativeText="Event" title="Event" />
                            </span>
                        </div>
                        <div class="slds-media__body">
                            <div class="slds-page-header__name">
                                <div class="slds-page-header__name-title">
                                    <h1>
                                        <span>Lightning Component 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="ComboBox">
        <div class="slds-p-horizontal_small">
            <c:MultiSelectCombobox options="{!v.options}" value="{!v.selectedValue}" label="Single Select Combobox"/>
            <br/>
            <c:MultiSelectCombobox options="{!v.options2}" values="{!v.selectedValues}" multiSelect="true" label="Multi Select Combobox"/>
        </div>
    </lightning:card>
</aura:component>
What’s your Reaction?
+1
0
+1
0
+1
0
+1
0
+1
1
+1
0

You may also like

2 comments

Frederic May 16, 2022 - 2:45 pm

Hi,Thanks for sharing that! Unfortunately, I cant change input parameter to define picklists values and Multi vs Single etc.. Did you forget to add the design ressource?

Reply
Rijwan Mohmmed May 23, 2022 - 3:35 pm

Hi @Frederic
Can you please explain more about this?

Reply

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.