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 :
