We are going to create Reusable Custom Lookup Component in Lightning Web Component. if you need to bypass the current user’s sharing and security or you need more flexibility then use this custom approach.
Also check this: Upload files in LWC Salesforce
Key Highlights :
- Reusable in any component.
- Fully Dynamic
- We can set default values
- All methods are very simple
Process & Code :
CustomLookup.cls : This class is used for search records and return a list of sobject records. Here we create two methods one for fetching the value on the lookup search and the other is getting records with default values.
public without sharing class CustomLookup {
@AuraEnabled(cacheable=true)
public static list<sObject> searchLookupData(string searchKey , string sObjectApiName) {
List < sObject > returnList = new List < sObject > ();
string sWildCardText = '%' + searchKey + '%';
string sQuery = 'Select Id,Name From ' + sObjectApiName + ' Where Name Like : sWildCardText order by createdDate DESC LIMIT 5';
for (sObject obj: database.query(sQuery)) {
returnList.add(obj);
}
return returnList;
}
// Method to fetch lookup default value
@AuraEnabled
public static sObject searchDefaultRecord(string recordId , string sObjectApiName) {
string sRecId = recordId;
string sQuery = 'Select Id,Name From ' + sObjectApiName + ' Where Id = : sRecId LIMIT 1';
for (sObject obj: database.query(sQuery)) {
return obj;
}
return null;
}
}
customLookup.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">
<div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_left-right" role="none">
<div class="searchBoxWrapper" if:false={isValueSelected}>
<!--Lookup Input Field-->
<lightning-input
type="search"
data-source="searchInputField"
onclick={toggleResult}
onchange={handleKeyChange}
is-loading={isSearchLoading}
value={searchKey}
variant="label-hidden"
placeholder={placeholder}
></lightning-input>
</div>
<!--Lookup Selected record pill container start-->
<div class="pillDiv" if:true={isValueSelected}>
<span class="slds-icon_container slds-combobox__input-entity-icon">
<lightning-icon icon-name={iconName} size="x-small" alternative-text="icon"></lightning-icon>
</span>
<input type="text"
id="combobox-id-1"
value={selectedRecord.Name}
class="slds-input slds-combobox__input slds-combobox__input-value"
readonly
/>
<button class="slds-button slds-button_icon slds-input__icon slds-input__icon_right" title="Remove selected option">
<lightning-icon icon-name="utility:close" size="x-small" alternative-text="close icon" onclick={handleRemove}></lightning-icon>
</button>
</div>
</div>
<!-- lookup search result part start-->
<div style="margin-top:0px" id="listbox-id-5" 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={lstResult} for:item="obj">
<li key={obj.Id} role="presentation" class="slds-listbox__item">
<div data-recid={obj.Id} onclick={handelSelectedRecord} 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>
</div>
</li>
</template>
<!--ERROR msg, if there is no records..-->
<template if:false={hasRecords}>
<li class="slds-listbox__item" style="text-align: center; font-weight: bold;">No Records Found....</li>
</template>
</ul>
</div>
</div>
</div>
</div>
</template>
customLookup.JS :
import { LightningElement,api,wire} from 'lwc';
import searchLookupData from '@salesforce/apex/CustomLookup.searchLookupData';
import searchDefaultRecord from '@salesforce/apex/CustomLookup.searchDefaultRecord';
export default class CustomLookup extends LightningElement {
// public properties with initial default values
@api label = 'label';
@api placeholder = 'search...';
@api iconName = 'standard:contact';
@api sObjectApiName = 'Contact';
@api defaultRecordId = '';
// private properties
lstResult = []; // to store list of returned records
hasRecords = true;
searchKey=''; // to store input field value
isSearchLoading = false; // to control loading spinner
delayTimeout;
isValueSelected;
selectedRecord = {}; // to store selected lookup record in object formate
// initial function to populate default selected lookup record if defaultRecordId provided
connectedCallback(){
if(this.defaultRecordId != ''){
searchDefaultRecord({ recordId: this.defaultRecordId , 'sObjectApiName' : this.sObjectApiName })
.then((result) => {
if(result != null){
this.selectedRecord = result;
this.handelSelectRecordHelper(); // helper function to show/hide lookup result container on UI
}
})
.catch((error) => {
this.error = error;
this.selectedRecord = {};
});
}
}
// wire function property to fetch search record based on user input
@wire(searchLookupData, { searchKey: '$searchKey' , sObjectApiName : '$sObjectApiName' })
searchResult(value) {
const { data, error } = value; // destructure the provisioned value
this.isSearchLoading = false;
if (data) {
this.hasRecords = data.length == 0 ? false : true;
this.lstResult = JSON.parse(JSON.stringify(data));
}
else if (error) {
console.log('(error---> ' + JSON.stringify(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;
}, 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');
break;
case 'lookupContainer':
clsList.remove('slds-is-open');
break;
}
}
// method to clear selected lookup record
handleRemove(){
this.searchKey = '';
this.selectedRecord = {};
this.lookupUpdateParenthandler(undefined); // update value on parent component as well from helper function
this.isValueSelected = false;
}
// update selected record from search result
handelSelectedRecord(event){
var objId = event.target.getAttribute('data-recid'); // get selected record Id
this.selectedRecord = this.lstResult.find(data => data.Id === objId); // find selected record from list
this.lookupUpdateParenthandler(this.selectedRecord); // update value on parent component
this.handelSelectRecordHelper(); // helper function to show/hide lookup result container on UI
}
//handle select record
handelSelectRecordHelper(){
this.template.querySelector('.lookupInputContainer').classList.remove('slds-is-open');
this.isValueSelected = true;
}
// send selected lookup record to parent component using custom event
lookupUpdateParenthandler(value){
console.log(value);
const oEvent = new CustomEvent('lookupupdate',
{
'detail': {selectedRecord: value}
}
);
this.dispatchEvent(oEvent);
}
}
customLookupParent.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:event" alternative-text="Event" title="Event"></lightning-icon>
</span>
</div>
<div class="slds-media__body">
<div class="slds-page-header__name">
<div class="slds-page-header__name-title">
<h1>
<span>Custom Reusable Lookup LWC</span>
<span class="slds-page-header__title slds-truncate" title="Recently Viewed">Lookup LWC</span>
</h1>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div> <br/>
<lightning-card variant="Narrow" title="Lookup" icon-name="standard:apps">
<div class="slds-p-horizontal_small">
<!--Custom Lookup without Pre-Populate Value for Contact Object-->
<c-custom-lookup icon-name="standard:contact" s-object-api-name="contact" label="Contact"
onlookupupdate={handleLookupSelection} placeholder="Search Contact...">
</c-custom-lookup>
<br/>
<!--Custom Lookup with Pre-Populate Value for User Object-->
<c-custom-lookup icon-name="standard:user" s-object-api-name="user" label="User"
onlookupupdate={handleLookupSelection} placeholder="Search User here..." default-record-id={userId}>
</c-custom-lookup>
</div>
</lightning-card>
</template>
customLookupParent.JS : In this we get lookup values in handleLookupSelection method.
import { LightningElement } from 'lwc';
import strUserId from '@salesforce/user/Id';
export default class CustomLookupParent extends LightningElement {
userId = strUserId;
handleLookupSelection(event){
if(event.detail.selectedRecord != undefined){
console.log('Selected Record Value on Parent Component is ' +
JSON.stringify(event.detail.selectedRecord));
alert(event.detail.selectedRecord.Id + ' '+ event.detail.selectedRecord.Name);
}
}
}