- Salesforce Lookup Component
This is a generic lookup component It does not rely on third party libraries and you have full control over its datasource.
Features
The lookup component provides the following features:
- customizable data source
- single or multiple selection mode
- client-side caching & request throttling
- great test coverage
- full accessibility (a11y) compliance
- keyboard navigation
- search term highlighting
- ability to setup actions
-
Define your apex data source
Create your apex class that implements the following interface
LookupResult.ILookupResult
public class ContactLookup implements LookupResult.ILookupResult { public List<LookupResult> getSelection()... public List<LookupResult> getDefaultSearchResults... public List<LookupResult> getSearchResults... }
-
Create the
LookupDefinition
recordGo to Setup > Custom Metadata Types > click in Manage Records (LookupDefinition) > add a new record
Label:
String
ApiName:String
Interface:String
(the name of your apex class)IMPORTANT
ApiName
should match yourunique-id
-
Use the lookup component
<c-lookup unique-id="Contact_Lookup_123" ></c-lookup>
IMPORTANT:
unique-id
should match yourApiName
This method is used to get the selected element when the @api value
is set
public List<LookupResult> getSelection(List<String> selectedIds, Map<String, Object> payload) {
List<LookupResult> result = new List<LookupResult>();
for (Contact contact : [SELECT Id FROM Contact WHERE Id IN: selectedIds]) {
result.add(buildResult(contact));
}
return result;
}
this method is responsible to build the results whenever the user types in the lookup
public List<LookupResult> getDefaultSearchResults(
String searchTerm,
List<String> selectedIds,
Map<String, Object> payload) {
List<LookupResult> result = new List<LookupResult>();
// selectedIds = list of selected records in the lookup
searchTerm = '%' + searchTerm + '%';
for (Contact contact : [
SELECT Name, Email
FROM Contact
WHERE
Id NOT IN :selectedIds
AND (Name LIKE :searchTerm OR Phone LIKE :searchTerm)]) {
result.add(buildResult(contact));
}
return result;
}
These are the results you display when there is no input in the lookup, typically you would use this to display record recomendations such as recently viewed or a plain set of records sorted by some priority. If you dont want to display anything just return an empty List
public List<LookupResult> getDefaultSearchResults(Map<String, Object> payload) {
List<LookupResult> result = new List<LookupResult>();
for (Contact contact : [SELECT Id FROM Contact LIMIT 10]) {
result.add(buildResult(contact));
}
return result;
}
RECOMMENDED: selectedIds
is the Id selected, use in getSelection()
method and getSearchResults()
method
When you build the LookupResults
you can use recordPayload to pass more information for each lookup result
LookupResult singleLookupResult = new LookupResult();
singleLookupResult.id = 'someNurseId';
singleLookupResult.recordPayload = new Map<String, Object> {
'type': 'nurse',
'name': 'johana',
};
<c-lookup
lwc:ref="lookup"
unique-id="Nurses_Lookup"
onchange={handleChange}>
</c-lookup>
handleChange(event) {
const value = event.detail.value;// ["someId", "someId2", "someId3"]
const payload = { someId : event.detail.value };// {"someId": {type: nurse, name : johana}, someId2: {ty...}}
//this create a copy of the payload of the id consulted
// TODO: do something with the selection
}
You can also access the record payload using the getRecordPayload()
public method
payload = {
AccountId: 'SomeAccountId',
Type: 'BusinessAccount',
Foo: {
Bar: 'Foo'
}
};
<c-lookup
unique-id="Contact_Lookup"
field-level-text="This lookup will filter by Account"
payload={payload}>
</c-lookup>
String accountId = (String) payload.get('AccountId'); // SomeAccountId
String type = (String) payload.get('Type'); // BusinessAccount
String foo = (String) payload.get('Foo').get('Bar'); // nestedProperties are allowed
You can customize how your lookup results display
the following example show you can build lookup result for a contact
it displays the name as the title with standard:contact icon and some subtitles with specific format options such as lightning-formatted-email or lightning-icon for more information see
LookupResult result = new LookupResult();
result.id = contact.Id;
result.icon = new Map<String, Object> {
'iconName' => 'standard:contact'
};
result.title = contact.Name;
result.recordPayload = contact.getPopulatedFieldsAsMap();
List<LookupResult.Subtitle> subtitles = new List<LookupResult.Subtitle>();
if (String.isNotBlank(contact.Email)) {
LookupResult.Subtitle email = new LookupResult.Subtitle();
email.type = 'lightning-formatted-email';
email.label = 'Email';
email.value = contact.Email;
email.props = new Map<String, Object>{ 'hideIcon' => true };
subtitles.add(email);
}
LookupResult.Subtitle optedOut = new LookupResult.Subtitle();
optedOut.type = 'lightning-icon';
optedOut.label = 'Opted out of email';
optedOut.props = new Map<String, Object>{
'iconName' => contact.HasOptedOutOfEmail
? 'utility:email'
: 'utility:end_chat'
};
subtitles.add(optedOut);
result.subtitles = subtitles;
use actions
to define custom actions on the component
you can handle the action
event to perform operations whenever an action is triggered
<c-lookup
unique-id="AccountAndOpportunitiesLookup"
actions={actions}
onaction={handleAction}
></c-lookup>
actions = [
{ name: "newAccount", label: "New Account" },
{ name: "newOpportunity", label: "New Opportunity" }
];
handleAction(event) {
if (event.detail === "newAccount") {
// do something such as navigate to the new record page
this[NavigationMixin.Navigate]({
type: "standard__objectPage",
attributes: {
objectApiName: "Account",
actionName: "new"
}
});
} else if (event.detail === "newOpportunity") {
// do something
}
}
Attribute | Type | Description | Default |
---|---|---|---|
uniqueId |
String |
(required) your metadata Lookup QualifiedApiName see Create the Lookup metadata | null |
disabled |
Boolean |
Whether the lookup selection can be changed. | false |
isMultiEntry |
Boolean |
Whether the lookup is single (default) or multi entry. | false |
label |
String |
Optional (but recommended) lookup label. Label is hidden if attribute is omitted but this breaks accessibility. If you don't want to display it, we recommend to provide a label but hide it with variant="label-hidden" . |
'' |
minSearchTermLength |
Number |
Minimum number of characters required to perform a search. | 2 |
actions |
[{ "name": String, "label": String }] |
List of actions that can be capture using the action event |
[] |
placeholder |
String |
Lookup placeholder text. | '' |
fieldLevelText |
String |
Text that gets displayed besides the label using a lightning-helptext. (there has to be a label ) |
'' |
required |
Boolean |
Whether the lookup is a required field. Note: Property can be set with <c-lookup required> . |
false |
scrollAfterNItems |
Number |
A null or integer value used to force overflow scroll on the result listbox after N number of items. Valid values are null , 5 , 7 , or 10 .Use null to disable overflow scrolling. |
null |
value |
[String] OR String |
and Array of ids or a single id | [] |
validity |
{ "valid": Boolean } |
Read-only property used for datatable integration. Reports whether there are errors or not (see errors ). |
false |
variant |
String |
Changes the appearance of the lookup. Accepted variants:label-stacked - places the label above the lookup.label-hidden - hides the label but make it available to assistive technology.label-inline - aligns horizontally the label and lookup. |
label-stacked |
messageWhenValueMissing |
String |
text to display when the input is required as is missing value | '' |
useRawInput |
boolean |
By default the user input get sanitized set to true if you want to get the raw user input | false |
payload |
Object |
Use it to pass data from your component to your apex class configuration | {} |
Function | Description |
---|---|
checkValidity() |
Places focus on the component an opens the search dropdown (unless this is a single selection lookup with a selection). |
reportValidity() |
Displays the error messages and returns false if the input is invalid If the input is valid, reportValidity() clears displayed error messages and returns true. |
setCustomValidity(message) |
Sets a custom error message to be displayed when the lookup value is submitted. |
showHelpMessageIfInvalid() |
Shows the help message if the lookup is in an invalid state. |
focus() |
Sets focus on the lookup |
blur() |
Removes focus from the lookup |
getRecordPayload() |
Returns the selectedRecords and their payloads |
Event | Description | event.detail Type |
---|---|---|
action |
event fired when user clicks on the actions (see actions) | { name: String } |
change |
Event fired when the selection of the lookup changes value holds an array with the selected ids and payload holds any aditional information passed from the apex class to the component using the payload attribute | { value: [String], payload: {} } |
invalid |
whenever the state of the lookup is invalid (see checkValidity or reportValidity) | {} |
Property | Description | Type |
---|---|---|
id |
unique identifier of the lookup result | String |
icon |
displays an icon using lightning-icon | Map<String, object> |
title |
main displayable title for the lookup | String |
recordPayload |
payload to pass record information to the component | Map<String, object> |
subtitles |
and array of subtitles see (#LookupResult.Subtitle) | List<LookupResult.Subtitle> |
Property | Description | Type |
---|---|---|
type |
the component used to format the data valid values are lightning-icon , lightning-formatted-number , lightning-formatted-text , lightning-formatted-time , lightning-formatted-date-time , lightning-formatted-email , lightning-formatted-url , lightning-formatted-rich-text |
String |
label |
a label for the subtitle | String |
value |
value to display | String |
props |
these props are applied to the component via lwc:spread | Map<String, object> |
highlightSearchTerm |
set to true if you want to hightlight your search input into the subtitle as well(only works with type lightning-formatted-rich-text ) |
Boolean |