Hello Dear #Trailblazers,
In this blog post, we are doing to learn how we can create reusable Custom Lookup using Lightning Web Component and we will use SOSL to develop the Lightning Web Component
Features
- Custom Lookup for All Stadard, Custom and External Object
- Ability to Create a New Record from the Lookup Component itself
- Ability to pass the pre selected record Id and Name
- Ability to use under Iteration in a Custom Table or any custom development
- Displays Recently Viewed Record when focus is on input
Step1 – Create an Apex Class and name it “SearchController”
This class will have a method to search the records and return the List<List<sObject>> here is the code for the same
/**
* @description :
* @author : Amit Singh
* @group :
* @last modified on : 12-21-2021
* @last modified by : Amit Singh
**/
public with sharing class SearchController {
@AuraEnabled
public static List<sObject> search(String objectName, List<String> fields, String searchTerm){
String searchKeyword = searchTerm + '*';
String returningQuery = '';
returningQuery = objectName+' ( Id, '+String.join(fields,',')+')';
String query = 'FIND :searchKeyword IN ALL FIELDS RETURNING '+returningQuery+' LIMIT 20';
List<List<sObject>> searchRecords = new List<List<sObject>>();
List<SObject> sobjList = new List<SObject>();
if(String.isBlank(searchTerm)){
String soqlQuery = 'SELECT Id, Name, Type, LastViewedDate FROM RecentlyViewed WHERE Type =\''+objectName+'\' ORDER BY LastViewedDate DESC LIMIT 5';
sobjList = Database.query( soqlQuery );
searchRecords.add(sobjList);
}else{
searchRecords = Search.Query(Query);
}
return searchRecords.get(0);
}
@AuraEnabled
public static sObject getRecentlyCreatedRecord(String recordId, List<String> fields, String objectName){
sObject createdRecord;
try {
String query = 'SELECT Id, '+String.join(fields,',')+' FROM '+objectName+' WHERE Id = \''+recordId+'\'';
List<SObject> sobjList = Database.query( query );
createdRecord = sobjList.get(0);
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
return createdRecord;
}
}
/**
* @description :
* @author : Amit Singh
* @group :
* @last modified on : 12-29-2021
* @last modified by : Amit Singh
**/
@IsTest
public with sharing class SearchControllerTest {
@IsTest
public static void searchTest(){
Account accuntRecord = createAccountRecord();
List<String> fields = new List<String>();
fields.add('Name');
fields.add('Phone');
fields.add('Rating');
List<String> searchResultsIds = new List<String>();
searchResultsIds.add(accuntRecord.Id);
Test.startTest();
Test.setFixedSearchResults(searchResultsIds);
SearchController.search('Account', fields, 'Salesforce');
Test.stopTest();
}
@IsTest
public static void searchTest1(){
Account accuntRecord = createAccountRecord();
List<String> fields = new List<String>();
fields.add('Name');
fields.add('Phone');
fields.add('Rating');
List<String> searchResultsIds = new List<String>();
searchResultsIds.add(accuntRecord.Id);
Test.startTest();
Test.setFixedSearchResults(searchResultsIds);
SearchController.search('Account', fields, '');
Test.stopTest();
}
@IsTest
public static void getRecentlyCreatedRecordTest(){
Account accuntRecord = createAccountRecord();
List<String> fields = new List<String>();
fields.add('Name');
fields.add('Phone');
fields.add('Rating');
String accountId = accuntRecord.Id;
Test.startTest();
SObject account = SearchController.getRecentlyCreatedRecord( accountId , fields, 'Account');
String fetchedAccountId = (String)account.get('Id');
System.assertEquals( fetchedAccountId , accountId , 'Id is not matching' );
Test.stopTest();
}
private static Account createAccountRecord(){
Account acc = new Account();
acc.Name = 'Salesforce.com';
acc.Rating = 'Hot';
acc.Industry = 'Technology';
acc.Description = 'This is a test account';
acc.BillingCity = 'San Francisco';
acc.BillingState = 'CA';
acc.BillingPostalCode = '94105';
acc.BillingCountry = 'USA';
acc.Phone = '4158889999';
acc.Type = 'Customer';
insert acc;
return acc;
}
}
Step2 Create a Lightning Web Component
- selectedRecord Component – First, we need to create a Child Component that will display the selected record in the lookup component.
Create an LWC Component and name it selectedRecord and use the code from the below files
<!-- sldsValidatorIgnore --> | |
<!-- sldsValidatorIgnore --> | |
<!-- | |
@description : | |
@author : Amit Singh | |
@group : | |
@last modified on : 12-21-2021 | |
@last modified by : Amit Singh | |
--> | |
<template> | |
<div class="slds-form-element"> | |
<label if:false={showLabel} class="slds-form-element__label" for="combobox-id-5" id="combobox-label-id-32">{objectLabel}</label> | |
<div class="slds-form-element__control"> | |
<div class="slds-combobox_container slds-has-selection"> | |
<div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click"> | |
<div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_left-right" role="none"> | |
<span class="slds-icon_container slds-icon-standard-account slds-combobox__input-entity-icon" title={objectLabel}> | |
<svg class="slds-icon slds-icon_small" aria-hidden="true"> | |
<use xlink:href={iconUrl}></use> | |
</svg> | |
<!-- sldsValidatorIgnoreNextLine --> | |
<span class="slds-assistive-text">Account</span> | |
<!-- sldsValidatorIgnoreNextLine --> | |
</span> | |
<button type="button" class="slds-input_faux slds-combobox__input slds-combobox__input-value" aria-labelledby="combobox-label-id-32 combobox-id-5-selected-value" id="combobox-id-5-selected-value" aria-controls="listbox-id-5" aria-expanded="false" | |
aria-haspopup="listbox"> | |
<span class="slds-truncate" id="combobox-value-id-19"> | |
{record.FIELD1} | |
</span> | |
</button> | |
<button onclick={handleRemove} class="slds-button slds-button_icon slds-input__icon slds-input__icon_right" title="Remove selected option"> | |
<svg class="slds-button__icon" aria-hidden="true"> | |
<use xlink:href="/apexpages/slds/latest/assets/icons/utility-sprite/svg/symbols.svg#close"></use> | |
</svg> | |
<span class="slds-assistive-text">Remove selected option</span> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> |
/** | |
* @description : This component is used to display the selected record in the selected record section. | |
* @author : Amit Singh | |
* @group : | |
* @last modified on : 12-21-2021 | |
* @last modified by : Amit Singh | |
**/ | |
import { LightningElement, api } from 'lwc'; | |
export default class SelectedRecord extends LightningElement { | |
@api iconUrl; | |
@api objectLabel; | |
@api record; | |
@api index; | |
@api showLabel = false; | |
handleRemove = (event) => { | |
event.preventDefault(); | |
const closeEvent = new CustomEvent('close', { | |
bubbles : true, | |
composed : true, | |
cancelable : true, | |
detail: { | |
data : { | |
record : undefined, | |
recordId : undefined | |
} | |
} | |
}); | |
this.dispatchEvent(closeEvent); | |
} | |
} |
2. searchComponent
This is the main component that will have complete functionality for the lookup. This component has below main public property which is important to use
- valueId; – This field holds the Selected Record Id if the parent record is already there
- valueName; – This field holds the Selected Record Name if the parent record is already there
- objName= ‘Account’; – The Parent Object API Name
- iconName= ‘standard:account’; – The Icon which you wanted to display
- labelName; – The Label for the Search
- currentRecordId; – The child record Id if applicable
- placeholder= ‘Search’; – Search Place holder
- fields= [‘Name’]; – The Fields that You wanted to Query
- displayFields= ‘Name, Rating, AccountNumber’; – The Fields that You wanted to use in the Lookup
- onlookup – the custom event which holds the selected record, SelectedRecordId & the child recorded if applicable
- createRecord – This variable accepts the boolean value that controls either to display the new button in the list or Not.
- recordTypeId – Id of the Record Type if need to create a new Record.
- fieldsToCreate – Accepts the list of fields which needs to be displayed on New Record Form
- index – The Row no if the Lookup component is used inside the Custom data tables
Usage
Below is the code on how you can use this component inside your parent component.
<c-search-component
obj-name={typeAttributes.object}
icon-name={typeAttributes.icon}
label-name={typeAttributes.label}
placeholder={typeAttributes.placeholder}
fields={typeAttributes.fields}
display-fields={typeAttributes.displayFields}
value-id={typeAttributes.valueId}
value-name={typeAttributes.valueName}
onlookup={handleLookup}
fields-to-create={fieldsToCreate}
index={index}
create-record=false
current-record-id={typeAttributes.currentRecordId} >
</c-search-component>
You can find the complete Code here
<!-- | |
@File Name : demo.html | |
@Description : | |
@Author : A Singh | |
@Group : | |
@Last Modified By : Amit Singh | |
@Last Modified On : 12-29-2021 | |
@Modification Log : | |
Ver Date Author Modification | |
1.0 6/7/2020 A Singh Initial Version | |
--> | |
<template> | |
<lightning-card variant="Narrow" title="Lookup Demo" icon-name="standard:record"> | |
<div class="slds-var-m-around_small"> | |
<p>Search Component</p> | |
<c-search-component | |
onlookup={handleLookup} | |
fields-to-create={fieldsToCreate} | |
create-record=true | |
parent-a-p-i-name="ParentId" | |
obj-name="Account" | |
display-ields="Name, Rating, AccountNumber" | |
fields = {fields} | |
show-label=true | |
label-name="Account Name" | |
data-index={index} | |
index={index}> | |
</c-search-component> | |
</div> | |
<div class="slds-var-m-around_small"> | |
<p>Lookup Component With Pre Selcted </p> | |
<c-search-component | |
onlookup={handleLookup} | |
fields-to-create={fieldsToCreate} | |
create-record=false | |
data-index={index} | |
parent-a-p-i-name="ParentId" | |
obj-name="Account" | |
display-ields="Name, Rating, AccountNumber" | |
show-label=true | |
label-name="Account Name" | |
fields = {fields} | |
value-id="001sdf4424" | |
value-name="Salesforce.org" | |
index={index}> | |
</c-search-component> | |
</div> | |
</lightning-card> | |
</template> |
/** | |
* @description : | |
* @author : Amit Singh | |
* @group : | |
* @last modified on : 12-29-2021 | |
* @last modified by : Amit Singh | |
**/ | |
import { LightningElement } from 'lwc'; | |
export default class Lookupdemol extends LightningElement { | |
fieldsToCreate = ['Name','Rating','Phone','Industry'] | |
fields = ['Name']; | |
handleLookup = (event) => { | |
let data = event.detail.data; | |
if(data && data.record){ | |
// populate the selected record in the correct parent Id field | |
// this.allRecord[data.index][data.parentAPIName] = data.record.Id; | |
}else{ | |
// clear the parent Id field | |
//this.allRecord[data.index][data.parentAPIName] = undefined; | |
} | |
} | |
} |
<!-- sldsValidatorIgnore --> | |
<!-- | |
@description : | |
@author : Amit Singh | |
@group : | |
@last modified on : 12-29-2021 | |
@last modified by : Amit Singh | |
Modifications Log | |
Ver Date Author Modification | |
1.0 12-20-2020 Amit Singh Initial Version | |
--> | |
<template> | |
<lightning-spinner if:true={isLoading} alternative-text="Loading" size="small"></lightning-spinner> | |
<div class="slds-form-element"> | |
<template if:true={showLabel}> | |
<label class="slds-form-element__label" for="combobox-id-1">{labelName}</label> | |
</template> | |
<!-- Display the create new record modal --> | |
<div if:true={showModal}> | |
<section if:true={showModal} role="dialog" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true" | |
aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open"> | |
<div class="slds-modal__container"> | |
<header class="slds-modal__header"> | |
<button onclick={handleCancel} class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse" title="Close"> | |
<svg class="slds-button__icon slds-button__icon_large" aria-hidden="true"> | |
<use xlink:href={ICON_URL_CLOSE}></use> | |
</svg> | |
<span class="slds-assistive-text">Close</span> | |
</button> | |
<h2 id="modal-heading-01" class="slds-modal__title slds-hyphenate">Add New Record</h2> | |
</header> | |
<div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1"> | |
<lightning-record-form | |
object-api-name={objName} | |
record-type-id={recordTypeId} | |
oncancel={handleCancel} | |
fields={fieldsToCreate} | |
onsuccess={handleSuccess} | |
columns="2"> | |
</lightning-record-form> | |
</div> | |
</div> | |
</section> | |
<div class="slds-backdrop slds-backdrop_open"></div> | |
</div> | |
<div class="slds-form-element__control"> | |
<div class="slds-combobox_container"> | |
<div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click" aria-expanded="false" aria-haspopup="listbox" role="combobox"> | |
<template if:false={selectedRecord}> | |
<div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none"> | |
<input type="text" onfocus={handleInputChange} onchange={handleInputChange} onkeyup={handleInputChange} onkeydown={handleInputChange} | |
class="slds-input slds-combobox__input" | |
id="combobox-id-1" aria-autocomplete="list" aria-controls="listbox-id-1" autocomplete="off" | |
role="textbox" placeholder={placeholder} /> | |
<span class="slds-icon_container slds-icon-utility-search slds-input__icon slds-input__icon_right"> | |
<svg class="slds-icon slds-icon slds-icon_x-small slds-icon-text-default" aria-hidden="true"> | |
<use xlink:href="/apexpages/slds/latest/assets/icons/utility-sprite/svg/symbols.svg#search"></use> | |
</svg> | |
</span> | |
</div> | |
<div style="background: white;" id="listbox-id-1" class="slds-dropdown_length-with-icon-7 slds-dropdown_fluid" role="listbox"> | |
<ul class="slds-listbox slds-listbox_vertical" role="presentation"> | |
<li onclick={handleNewRecord} if:true={showButton} role="presentation" class="slds-listbox__item"> | |
<div class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta" role="option"> | |
<span class="slds-media__figure slds-listbox__option-icon"> | |
<span class="slds-icon_container slds-icon-standard-account"> | |
<svg class="slds-icon slds-icon_small" aria-hidden="true"> | |
<use xlink:href={ICON_URL_NEW}></use> | |
</svg> | |
</span> | |
</span> | |
<span class="slds-media__body"> | |
<span class="slds-listbox__option-text slds-listbox__option-text_entity"> | |
New {objectLabel} | |
</span> | |
</span> | |
</div> | |
</li> | |
<template if:true={searchRecords} for:each={searchRecords} for:item="record" for:index="index"> | |
<li onclick={handleSelect} role="presentation" class="slds-listbox__item" data-record-id={record.Id} key={record.Id}> | |
<div data-id={record.Id} class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta" role="option"> | |
<span class="slds-media__figure slds-listbox__option-icon"> | |
<span class="slds-icon_container slds-icon-standard-account"> | |
<svg class="slds-icon slds-icon_small" aria-hidden="true"> | |
<use xlink:href={ICON_URL}></use>0 | |
</svg> | |
</span> | |
</span> | |
<span class="slds-media__body"> | |
<span class="slds-listbox__option-text slds-listbox__option-text_entity"> | |
{record.FIELD1} | |
</span> | |
<span class="slds-listbox__option-meta slds-listbox__option-meta_entity"> | |
{objectLabel} • {record.FIELD2} {record.FIELD3} | |
</span> | |
</span> | |
</div> | |
</li> | |
</template> | |
</ul> | |
</div> | |
</template> | |
<template if:true={selectedRecord}> | |
<c-selected-record icon-url={ICON_URL} | |
record={selectedRecord} | |
onclose={handleClose} | |
show-label=false | |
index={index} object-label={objectLabel} > | |
</c-selected-record> | |
</template> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> |
/** | |
* @description : | |
* @author : Amit Singh | |
* @group : | |
* @last modified on : 12-29-2021 | |
* @last modified by : Amit Singh | |
* Modifications Log | |
* Ver Date Author Modification | |
* 1.0 12-19-2020 Amit Singh Initial Version | |
**/ | |
import { LightningElement, api, track, wire } from 'lwc'; | |
import search from '@salesforce/apex/SearchController.search'; | |
import getRecentlyCreatedRecord from '@salesforce/apex/SearchController.getRecentlyCreatedRecord'; | |
const DELAY = 10; | |
import { NavigationMixin } from 'lightning/navigation'; | |
export default class SearchComponent extends NavigationMixin(LightningElement) { | |
/* values for an existing selected record */ | |
@api valueId; | |
@api valueName; | |
@api objName = 'Account'; | |
@api iconName = 'standard:account'; | |
@api labelName; | |
@api currentRecordId; | |
@api placeholder = 'Search'; | |
@api fields = ['Name']; | |
@api displayFields = 'Name, Rating, AccountNumber'; | |
@api showLabel = false; | |
@api parentAPIName = 'ParentId'; | |
@api createRecord = false; | |
/* values to be passed to create the new record */ | |
@api recordTypeId; | |
@api fieldsToCreate = []; | |
/* Create fields for using in Datatable for Multiple In-line Edit */ | |
@api index; | |
@track error; | |
searchTerm; | |
delayTimeout; | |
searchRecords; | |
selectedRecord; | |
objectLabel; | |
isLoading = false; | |
showButton = false; | |
showModal = false; | |
field; | |
field1; | |
field2; | |
ICON_URL = '/apexpages/slds/latest/assets/icons/{0}-sprite/svg/symbols.svg#{1}'; | |
ICON_URL_NEW = '/apexpages/slds/latest/assets/icons/utility-sprite/svg/symbols.svg#add'; | |
ICON_URL_CLOSE = '/apexpages/slds/latest/assets/icons/utility-sprite/svg/symbols.svg#close'; | |
connectedCallback(){ | |
let icons = this.iconName.split(':'); | |
this.ICON_URL = this.ICON_URL.replace('{0}',icons[0]); | |
this.ICON_URL = this.ICON_URL.replace('{1}',icons[1]); | |
if(this.objName.includes('__c')){ | |
let obj = this.objName.substring(0, this.objName.length-3); | |
this.objectLabel = obj.replaceAll('_',' '); | |
}else{ | |
this.objectLabel = this.objName; | |
} | |
if( this.valueId && this.valueName ){ | |
this.selectedRecord = { | |
FIELD1 : this.valueName, | |
Id : this.valueId | |
} | |
} | |
this.objectLabel = this.titleCase(this.objectLabel); | |
let fieldList; | |
if( !Array.isArray(this.displayFields)){ | |
fieldList = this.displayFields.split(','); | |
}else{ | |
fieldList = this.displayFields; | |
} | |
if(fieldList.length > 1){ | |
this.field = fieldList[0].trim(); | |
this.field1 = fieldList[1].trim(); | |
} | |
if(fieldList.length > 2){ | |
this.field2 = fieldList[2].trim(); | |
} | |
let combinedFields = []; | |
fieldList.forEach(field => { | |
if( !this.fields.includes(field.trim()) ){ | |
combinedFields.push( field.trim() ); | |
} | |
}); | |
this.fields = combinedFields.concat( JSON.parse(JSON.stringify(this.fields)) ); | |
if(this.valueId && this.valueName){ | |
this.selectedRecord = { | |
FIELD1 : this.valueName, | |
recordId : this.valueId | |
} | |
} | |
} | |
handleInputChange(event){ | |
window.clearTimeout(this.delayTimeout); | |
const searchKey = event.target.value; | |
//this.isLoading = true; | |
this.delayTimeout = setTimeout(() => { | |
//if(searchKey.length >= 2){ | |
search({ | |
objectName : this.objName, | |
fields : this.fields, | |
searchTerm : searchKey | |
}) | |
.then(result => { | |
let stringResult = JSON.stringify(result); | |
let allResult = JSON.parse(stringResult); | |
allResult.forEach( record => { | |
record.FIELD1 = record[this.field]; | |
record.FIELD2 = record[this.field1]; | |
if( this.field2 ){ | |
record.FIELD3 = record[this.field2]; | |
}else{ | |
record.FIELD3 = ''; | |
} | |
}); | |
this.searchRecords = allResult; | |
}) | |
.catch(error => { | |
console.error('Error:', error); | |
}) | |
.finally( ()=>{ | |
this.showButton = this.createRecord; | |
}); | |
//} | |
}, DELAY); | |
} | |
handleSelect(event){ | |
let recordId = event.currentTarget.dataset.recordId; | |
let selectRecord = this.searchRecords.find((item) => { | |
return item.Id === recordId; | |
}); | |
this.selectedRecord = selectRecord; | |
const selectedEvent = new CustomEvent('lookup', { | |
bubbles : true, | |
composed : true, | |
cancelable : true, | |
detail: { | |
data : { | |
record : selectRecord, | |
recordId : recordId, | |
currentRecordId : this.currentRecordId, | |
parentAPIName : this.parentAPIName, | |
index : this.index | |
} | |
} | |
}); | |
this.dispatchEvent(selectedEvent); | |
} | |
handleClose(){ | |
this.selectedRecord = undefined; | |
this.searchRecords = undefined; | |
this.showButton = false; | |
const selectedEvent = new CustomEvent('lookup', { | |
bubbles : true, | |
composed : true, | |
cancelable : true, | |
detail: { | |
data : { | |
record : undefined, | |
recordId : undefined, | |
currentRecordId : this.currentRecordId, | |
parentAPIName : this.parentAPIName, | |
index : this.index | |
} | |
} | |
}); | |
this.dispatchEvent(selectedEvent); | |
} | |
titleCase(string) { | |
var sentence = string.toLowerCase().split(" "); | |
for(var i = 0; i< sentence.length; i++){ | |
sentence[i] = sentence[i][0].toUpperCase() + sentence[i].slice(1); | |
} | |
return sentence; | |
} | |
handleNewRecord = event => { | |
event.preventDefault(); | |
this.showModal = true; | |
} | |
handleCancel = event => { | |
event.preventDefault(); | |
this.showModal = false; | |
} | |
handleSuccess = event => { | |
event.preventDefault(); | |
this.showModal = false; | |
let recordId = event.detail.id; | |
this.hanleCreatedRecord(recordId); | |
} | |
hanleCreatedRecord = (recordId) => { | |
getRecentlyCreatedRecord({ | |
recordId : recordId, | |
fields : this.fields, | |
objectName : this.objName | |
}) | |
.then(result => { | |
if(result){ | |
this.selectedRecord = { | |
FIELD1 : result[this.field], | |
Id : recordId | |
}; | |
const selectedEvent = new CustomEvent('lookup', { | |
bubbles : true, | |
composed : true, | |
cancelable : true, | |
detail: { | |
data : { | |
record : this.selectedRecord, | |
recordId : recordId, | |
currentRecordId : this.currentRecordId, | |
parentAPIName : this.parentAPIName, | |
index : this.index | |
} | |
} | |
}); | |
this.dispatchEvent(selectedEvent); | |
} | |
}) | |
.catch(error => { | |
console.error('Error: \n ', error); | |
}) | |
.finally( ()=>{ | |
this.showModal = false; | |
}); | |
} | |
} |
/** | |
* @description : | |
* @author : Amit Singh | |
* @group : | |
* @last modified on : 12-21-2021 | |
* @last modified by : Amit Singh | |
**/ | |
public with sharing class SearchController { | |
@AuraEnabled | |
public static List<sObject> search(String objectName, List<String> fields, String searchTerm){ | |
String searchKeyword = searchTerm + '*'; | |
String returningQuery = ''; | |
returningQuery = objectName+' ( Id, '+String.join(fields,',')+')'; | |
String query = 'FIND :searchKeyword IN ALL FIELDS RETURNING '+returningQuery+' LIMIT 20'; | |
List<List<sObject>> searchRecords = new List<List<sObject>>(); | |
List<SObject> sobjList = new List<SObject>(); | |
if(String.isBlank(searchTerm)){ | |
String soqlQuery = 'SELECT Id, Name, Type, LastViewedDate FROM RecentlyViewed WHERE Type =\''+objectName+'\' ORDER BY LastViewedDate DESC LIMIT 5'; | |
sobjList = Database.query( soqlQuery ); | |
searchRecords.add(sobjList); | |
}else{ | |
searchRecords = Search.Query(Query); | |
} | |
return searchRecords.get(0); | |
} | |
@AuraEnabled | |
public static sObject getRecentlyCreatedRecord(String recordId, List<String> fields, String objectName){ | |
sObject createdRecord; | |
try { | |
String query = 'SELECT Id, '+String.join(fields,',')+' FROM '+objectName+' WHERE Id = \''+recordId+'\''; | |
List<SObject> sobjList = Database.query( query ); | |
createdRecord = sobjList.get(0); | |
} catch (Exception e) { | |
throw new AuraHandledException(e.getMessage()); | |
} | |
return createdRecord; | |
} | |
} |
/** | |
* @description : | |
* @author : Amit Singh | |
* @group : | |
* @last modified on : 12-29-2021 | |
* @last modified by : Amit Singh | |
**/ | |
@IsTest | |
public with sharing class SearchControllerTest { | |
@IsTest | |
public static void searchTest(){ | |
Account accuntRecord = createAccountRecord(); | |
List<String> fields = new List<String>(); | |
fields.add('Name'); | |
fields.add('Phone'); | |
fields.add('Rating'); | |
List<String> searchResultsIds = new List<String>(); | |
searchResultsIds.add(accuntRecord.Id); | |
Test.startTest(); | |
Test.setFixedSearchResults(searchResultsIds); | |
SearchController.search('Account', fields, 'Salesforce'); | |
Test.stopTest(); | |
} | |
@IsTest | |
public static void searchTest1(){ | |
Account accuntRecord = createAccountRecord(); | |
List<String> fields = new List<String>(); | |
fields.add('Name'); | |
fields.add('Phone'); | |
fields.add('Rating'); | |
List<String> searchResultsIds = new List<String>(); | |
searchResultsIds.add(accuntRecord.Id); | |
Test.startTest(); | |
Test.setFixedSearchResults(searchResultsIds); | |
SearchController.search('Account', fields, ''); | |
Test.stopTest(); | |
} | |
@IsTest | |
public static void getRecentlyCreatedRecordTest(){ | |
Account accuntRecord = createAccountRecord(); | |
List<String> fields = new List<String>(); | |
fields.add('Name'); | |
fields.add('Phone'); | |
fields.add('Rating'); | |
String accountId = accuntRecord.Id; | |
Test.startTest(); | |
SObject account = SearchController.getRecentlyCreatedRecord( accountId , fields, 'Account'); | |
String fetchedAccountId = (String)account.get('Id'); | |
System.assertEquals( fetchedAccountId , accountId , 'Id is not matching' ); | |
Test.stopTest(); | |
} | |
private static Account createAccountRecord(){ | |
Account acc = new Account(); | |
acc.Name = 'Salesforce.com'; | |
acc.Rating = 'Hot'; | |
acc.Industry = 'Technology'; | |
acc.Description = 'This is a test account'; | |
acc.BillingCity = 'San Francisco'; | |
acc.BillingState = 'CA'; | |
acc.BillingPostalCode = '94105'; | |
acc.BillingCountry = 'USA'; | |
acc.Phone = '4158889999'; | |
acc.Type = 'Customer'; | |
insert acc; | |
return acc; | |
} | |
} |
<!-- sldsValidatorIgnore --> | |
<!-- sldsValidatorIgnore --> | |
<!-- | |
@description : | |
@author : Amit Singh | |
@group : | |
@last modified on : 12-21-2021 | |
@last modified by : Amit Singh | |
--> | |
<template> | |
<div class="slds-form-element"> | |
<label if:false={showLabel} class="slds-form-element__label" for="combobox-id-5" id="combobox-label-id-32">{objectLabel}</label> | |
<div class="slds-form-element__control"> | |
<div class="slds-combobox_container slds-has-selection"> | |
<div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click"> | |
<div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_left-right" role="none"> | |
<span class="slds-icon_container slds-icon-standard-account slds-combobox__input-entity-icon" title={objectLabel}> | |
<svg class="slds-icon slds-icon_small" aria-hidden="true"> | |
<use xlink:href={iconUrl}></use> | |
</svg> | |
<!-- sldsValidatorIgnoreNextLine --> | |
<span class="slds-assistive-text">Account</span> | |
<!-- sldsValidatorIgnoreNextLine --> | |
</span> | |
<button type="button" class="slds-input_faux slds-combobox__input slds-combobox__input-value" aria-labelledby="combobox-label-id-32 combobox-id-5-selected-value" id="combobox-id-5-selected-value" aria-controls="listbox-id-5" aria-expanded="false" | |
aria-haspopup="listbox"> | |
<span class="slds-truncate" id="combobox-value-id-19"> | |
{record.FIELD1} | |
</span> | |
</button> | |
<button onclick={handleRemove} class="slds-button slds-button_icon slds-input__icon slds-input__icon_right" title="Remove selected option"> | |
<svg class="slds-button__icon" aria-hidden="true"> | |
<use xlink:href="/apexpages/slds/latest/assets/icons/utility-sprite/svg/symbols.svg#close"></use> | |
</svg> | |
<span class="slds-assistive-text">Remove selected option</span> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> |
/** | |
* @description : This component is used to display the selected record in the selected record section. | |
* @author : Amit Singh | |
* @group : | |
* @last modified on : 12-21-2021 | |
* @last modified by : Amit Singh | |
**/ | |
import { LightningElement, api } from 'lwc'; | |
export default class SelectedRecord extends LightningElement { | |
@api iconUrl; | |
@api objectLabel; | |
@api record; | |
@api index; | |
@api showLabel = false; | |
handleRemove = (event) => { | |
event.preventDefault(); | |
const closeEvent = new CustomEvent('close', { | |
bubbles : true, | |
composed : true, | |
cancelable : true, | |
detail: { | |
data : { | |
record : undefined, | |
recordId : undefined | |
} | |
} | |
}); | |
this.dispatchEvent(closeEvent); | |
} | |
} |
Step3 – Demo Time
Create a Lightning Web Component and use the SearchComponent that you have created
<!--
@description :
@author : Amit Singh
@group :
@last modified on : 03-24-2021
@last modified by : Amit Singh
Modifications Log
Ver Date Author Modification
1.0 03-24-2021 Amit Singh Initial Version
-->
<template>
<lightning-card variant="Narrow" title="Custom Lookup" icon-name="standard:account">
<p class="slds-p-horizontal_small">
<c-search-component
obj-name="Contact"
icon-name="standard:contact"
label-name="Contact"
placeholder="Search"
fields={fields}
display-fields={displayFields}
onlookup={handleLookup} >
</c-search-component>
</p>
</lightning-card>
</template>
/**
* @description :
* @author : Amit Singh
* @group :
* @last modified on : 03-24-2021
* @last modified by : Amit Singh
* Modifications Log
* Ver Date Author Modification
* 1.0 03-24-2021 Amit Singh Initial Version
**/
import { LightningElement } from 'lwc';
export default class CustomLookup extends LightningElement {
fields = ["Name","Email","Phone"];
displayFields = 'Name, Email, Phone'
handleLookup(event){
console.log( JSON.stringify ( event.detail) )
}
}

Once you select any record below is the JSON that you get using event.detail
{
"data": {
"record": {
"Id": "0034x000004r7FvAAI",
"Name": "Ashley James",
"Email": "ajames@uog.com",
"Phone": "+44 191 4956203",
"FIELD1": "Ashley James",
"FIELD2": "ajames@uog.com",
"FIELD3": "+44 191 4956203"
},
"recordId": "0034x000004r7FvAAI"
}
}
Thanks for reading 🙂
#Salesforce #DeveloperGeeks #Trailblazer
Please do like share subscribe to the blog post and YouTube channel.
Hi @Amit, thank you for sharing with us.
It’s beneficial
Thank You 🙂
Man, you nailed it. !!! Awesome !!!
Looking forward to more such reusable components.
Hi Amit,
This is awesome. Thank you very much. I recently purchased your Udemy course and I just love it. Just a quick question.
How can I prepopulate a record in the Search component? For example I’m using the Search component inside another component and if a certain account exists in the org I want to have it selected when I launch the component otherwise I want to be able to search for an account. Is that possible?
Thanks in advance.
Hello,
We have covered the same in the other mode of conversation.
[…] You can get the complete code for the Search Component from here […]
Thanks Amit for sharing 🙂
Hi Amit, while using custom object(__c), I am getting error as undefined property to lowercase. Can u pls provide custom object with custom fields code in the same way.Thanks
There was a small change in the JavaScript which has been addressed now. Use the updated JS code and it will work as expected.
Hi Thanku so much, but if we are removing one selected record and typing another input..getting error as record id not defined. Please help
Unfortunately, I can not write the code on behalf of you. You need to debug it at your side and see what is the error. As this code is being used at multiple projects and working fine with small modifications based on the need.
Thanks, I have changed it in my org.
Thanks. It is really good and reusable component.
Just note that I also got the error above. Should fix the code in handleClose function to something like:
detail: {
record : undefined,
recordId : undefined,
currentRecordId : this.currentRecordId
}
What error are you getting? I have tested for all the objects including standard and custom objects and it is working fine.
Simply select record, and then remove the selection by clicking ‘X’
There is no Error from our side.
Hi Amit,
Using this inside a for:each, whenever I try to open the Lookup component on a specific record, it opens the component in all of the records of the for:each record List.
Do you know how can this be avoided?
Thank you
What is your requirement to use inside for each?
This was awesome – thank you so much.
Do you possibly have the test class you used for the Search Component? I’m fairly new to dev and I cannot get my test to pass so this would be a good learning opportunity for me if you could post it
Here is the Link which will help you.
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_SOSL.htm
Can we create multi select look up field. If yes please help Me in finding code.
Check this Link
Can we prepopulate a record in this component?
Yes. You made to make a tweak to code according to yourself
how can I create a table with lookfield list at 1 column and 2 column with checkbox. Want to create a record . please help
For this, you need to develop the custom HTML Table
When I’m using this inside an Aura component the dropdown doesn’t close when clicked outside. Can you tell me how to fix this?
Hello,
You need to use some browser events like onblur, onfocuschange like that and see is that works.
[…] You can get the complete code for the Search Component from here […]