Hello folks, today we will discuss creating Reusable Custom LWC Multi-Select Lookup in Salesforce. We all know that single-select lookup is available in Salesforce but multi-select lookup not. So We will make this by custom code and use this.
Also, check this: Verify Phone HTTP Request in LWC Salesforce
Key Highlights :
- Multi Record Section (multi-select lookup).
- Prevent duplicate record selection.
- Reusable.
- Can query dynamic fields.
- Pills will be shown of selected records.
Code :
First, we create an Apex class where we search for the object records.
ReusableMultiSelectLookupCtrl.cls :
public class ReusableMultiSelectLookupCtrl { @AuraEnabled(cacheable=true) public static List<sObject> retriveSearchData(String ObjectName, String fieldName, String value, List<String> selectedRecId) { List<sObject> sObjectResultList = new List<sObject>(); if(selectedRecId == null) selectedRecId = new List<String>(); if(String.isNotEmpty(value)) { String query = 'Select '+fieldName+' FROM '+ObjectName+' WHERE Name LIKE \'%' + value.trim() + '%\' and ID NOT IN: selectedRecId order by createdDate DESC LIMIT 5'; for(sObject so : Database.Query(query)) { sObjectResultList.add(so); } } return sObjectResultList; } }
Now our next part is to create a Reusable LWC Custom multiselect lookup so we can use this in parent
reusableMultiSelectLookup.HTML :
<template> <div class="slds-form-element" onmouseleave={toggleResult} data-source="lookupContainer"> <div class="slds-combobox_container slds-has-selection"> <label class="slds-form-element__label" for="combobox-id-1">{label}</label> <div class="lookupInputContainer slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click" aria-expanded="false" aria-haspopup="listbox" role="combobox"> <!--------------- lookup field ----------------> <div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_left-right" role="none"> <div class="searchBoxWrapper"> <lightning-input type="search" data-id="userinput" name="searchText" is-loading={isSearchLoading} value={searchKey} variant="label-hidden" placeholder={placeholder} onchange={handleKeyChange} onclick={toggleResult} data-source="searchInputField"></lightning-input> </div> </div> <!--------------- lookup field ----------------> <!-- lookup Selected record pill container start--> <div class="slds-form-element__control slds-input-has-icon slds-input-has-icon slds-input-has-icon_left-right" role="none"> <template for:each={selectedRecords} for:item="serecord"> <span key={serecord.Id}> <lightning-pill label={serecord.Name} name={serecord.Id} onremove={removeRecord}> <lightning-icon icon-name={iconName} variant="circle" alternative-text={serecord.Name}></lightning-icon> </lightning-pill> </span> </template> </div> <!------ /lookup Selected record pill container end -----------> <!------ lookup search result part start------------> <div style="margin-top:0px" id="listbox-id-1" class="slds-dropdown slds-dropdown_length-with-icon-7 slds-dropdown_fluid" role="listbox"> <ul class="slds-listbox slds-listbox_vertical" role="presentation"> <template for:each={searchRecords} for:item="obj"> <li key={obj.Id} role="presentation" class="slds-listbox__item"> <div data-id={obj.Id} data-name={obj.Name} onclick={setSelectedRecord} class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta" role="option"> <span style="pointer-events: none;" class="slds-media__figure slds-listbox__option-icon" > <span class="slds-icon_container" > <lightning-icon icon-name={iconName} size="small" alternative-text="icon" ></lightning-icon> </span> </span> <span style="pointer-events: none;" class="slds-media__body" > <span class="slds-listbox__option-text slds-listbox__option-text_entity">{obj.Name}</span> <span class="slds-listbox__option-meta slds-listbox__option-meta_entity">Account • {obj.Name}</span> </span> </div> </li> </template> <!-- if there is no records..--> <template if:true={messageFlag}> <li class="slds-listbox__item" style="text-align: center; font-weight: bold;">No Records Found...</li> </template> </ul> </div> <!------ /lookup search result part start------------> </div> </div> </div> </template>
reusableMultiSelectLookup.JS :
import { LightningElement, api, track } from 'lwc'; import retriveSearchData from '@salesforce/apex/ReusableMultiSelectLookupCtrl.retriveSearchData'; export default class ReusableMultiSelectLookup extends LightningElement { @api objectname = 'Account'; @api fieldnames = ' Id, Name '; @api Label; @track searchRecords = []; @track selectedRecords = []; @api iconName = 'standard:account' @track messageFlag = false; @track isSearchLoading = false; @api placeholder = 'Search..'; @track searchKey; delayTimeout; searchField() { var selectedRecordIds = []; this.selectedRecords.forEach(ele=>{ selectedRecordIds.push(ele.Id); }) retriveSearchData({ ObjectName: this.objectname, fieldName: this.fieldnames, value: this.searchKey, selectedRecId: selectedRecordIds }) .then(result => { this.searchRecords = result; this.isSearchLoading = false; const lookupInputContainer = this.template.querySelector('.lookupInputContainer'); const clsList = lookupInputContainer.classList; clsList.add('slds-is-open'); if (this.searchKey.length > 0 && result.length == 0) { this.messageFlag = true; } else { this.messageFlag = false; } }).catch(error => { console.log(error); }); } // update searchKey property on input field change handleKeyChange(event) { // Do not update the reactive property as long as this function is this.isSearchLoading = true; window.clearTimeout(this.delayTimeout); const searchKey = event.target.value; this.delayTimeout = setTimeout(() => { this.searchKey = searchKey; this.searchField(); }, 300); } // method to toggle lookup result section on UI toggleResult(event) { const lookupInputContainer = this.template.querySelector('.lookupInputContainer'); const clsList = lookupInputContainer.classList; const whichEvent = event.target.getAttribute('data-source'); switch (whichEvent) { case 'searchInputField': clsList.add('slds-is-open'); this.searchField(); break; case 'lookupContainer': clsList.remove('slds-is-open'); break; } } setSelectedRecord(event) { var recId = event.target.dataset.id; var selectName = event.currentTarget.dataset.name; let newsObject = this.searchRecords.find(data => data.Id === recId); this.selectedRecords.push(newsObject); this.template.querySelector('.lookupInputContainer').classList.remove('slds-is-open'); let selRecords = this.selectedRecords; this.template.querySelectorAll('lightning-input').forEach(each => { each.value = ''; }); console.log(this.selectedRecords); const selectedEvent = new CustomEvent('selected', { detail: { selRecords }, }); // Dispatches the event. this.dispatchEvent(selectedEvent); } removeRecord(event) { let selectRecId = []; for (let i = 0; i < this.selectedRecords.length; i++) { console.log('event.detail.name--------->' + event.detail.name); console.log('this.selectedRecords[i].Id--------->' + this.selectedRecords[i].Id); if (event.detail.name !== this.selectedRecords[i].Id) selectRecId.push(this.selectedRecords[i]); } this.selectedRecords = [...selectRecId]; console.log('this.selectedRecords----------->' + this.selectedRecords); let selRecords = this.selectedRecords; const selectedEvent = new CustomEvent('selected', { detail: { selRecords }, }); // Dispatches the event. this.dispatchEvent(selectedEvent); } }
Now we can use the above LWC component in our parent component and send dynamic parameters
LWCMultiSelectLookup.HTML :
<template> <!--------card header -----------------> <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>Reusable Custom LWC Multi Select Lookup</span> <span class="slds-page-header__title slds-truncate" title="Recently Viewed">TechDicer</span> </h1> </div> </div> </div> </div> </div> </div> </div> </div> <br/> <!--------/card header end -----------------> <!------ lookup card -------------------------> <lightning-card variant="Narrow" title="Reusable Custom LWC Multi Select Lookup" icon-name="standard:apps"> <div class="slds-p-horizontal_small"> <c-reusable-multi-select-lookup objectname="Account" fieldnames=" Id, Name, Type, AccountNumber " onselected={handleselectedCompanyRecords} icon-name="standard:account" placeholder="Lookup Company..."> </c-reusable-multi-select-lookup> <template if:true={selectedRecordsLength}> <div class="slds-m-top_medium"> <lightning-datatable key-field="id" data={selectedRecords} hide-checkbox-column columns={columns}> </lightning-datatable> </div> </template> </div> </lightning-card> <!------ /lookup card -------------------------> </template>
LWCMultiSelectLookup.JS:
import { LightningElement, track } from 'lwc'; const columns = [ { label: 'Name', fieldName: 'Name' }, { label: 'Type', fieldName: 'Type', type: 'text' }, { label: 'Account Number', fieldName: 'AccountNumber'} ]; export default class LWCMultiSelectLookup extends LightningElement { @track selectedRecords = []; columns = columns; @track selectedRecordsLength; handleselectedCompanyRecords(event) { this.selectedRecords = [...event.detail.selRecords] this.selectedRecordsLength = this.selectedRecords.length; } }
Output :