How to create custom polymorphic field using LWC ?
In this blog we will see how we can make polymorphic lookup field using LWC.
What is Polymorphic field?
A polymorphic field is one where related object might be one of the several different types of objects.
There can be a use case where a customer wants to connect one object with multiple objects- i.e, relationships between objects or relate a child object to multiple parents's ojects.
For example, in task Object we have three such polymorphic fields.
- The WhoId(Name) relationship of Task can be with Contact or a Lead.
- Assigned To field can be a User or a Queue.
- Similarly, a WhatId(Related To) relationship field of Task can be with many other objects like Accounts, Opportunities etc.
In Salesforce, currently we do not have any OOTB option or may be we can say we do not have a datatype for polymorphic field which we can create but if required we can create a custom component to facilitate the same functionality.
So let's get started and see step by step how we can create custom Polymorphic Lookup field in Salesforce.
So let's get started and see step by step how we can create custom Polymorphic Lookup field in Salesforce.
Step by Step approach to build Custom Polymorphic field component
Step 1: Create a new Custom Metadata
Step 1: Create a new Custom Metadata
- PolymorphicLookupObject__mdt
When we say dynamic it means we can add or remove any object as needed. The objects that are displayed as dropdown will come from this defined custom Metadata records in PolymorphicLookupObject__mdt custom metadata.
If you have noticed we also have a 'New Record' option for the user. this New Record feature can also be controlled according to the requirement. If we DO NOT want to show this New Record button to the end user we can simply switch off this by making the IsCreateNewRecordAllowed__c field in custom metadata as FALSE and the New Record button will be hidden .
Step 2: Create Apex Classes
- PolymorphicLookupObjects - This class is used to retrieve the Custom Metadata records created in Step 1.
- searchLooupResult - This Class is used to fetch the records of the selected object from the drop down.
Code snippet:- PolymorphiLookupObjects.cls
PolymorphicLookupObjects.cls -
Code snippet:- genericLookupCompWrapper.cls
/****
@description :
@author : Amit Agarwal
@group :
@last modified on :
@last modified by : Amit Agarwal
****/
public with sharing class PolymorphicLookupObjects {
public PolymorphicLookupObjects() {
}
@AuraEnabled(cacheable=true)
public static List<sObject> getObjectLookupList(){
String Query = '';
Query = 'SELECT Id, IsCreateNewRecordAllowed__c,sObjectAPIName__c, sObjectLabel__c, sObjectPluralName__c, sObjectRecords_Search_by_field__c, sObjectLookup_subtitle_field__c, sObject_Display_IconName__c FROM PolymorphicLookupObject__mdt ';
List<SObject> sObjectList = Database.query(Query);
return sObjectList;
}
}
genericLookupCompWrapper.cls -
Code :- searchLookupResult.cls
/****
@description :
@author : Amit Agarwal
@group :
@last modified on :
@last modified by : Amit Agarwal
****/
public with sharing class genericLookupCompWrapper {
private Id id;
private String sObjectType;
private String icon;
private String title;
private String subtitle;
public genericLookupCompWrapper(Id id, String sObjectName, String icon, String title, String subtitle) {
this.Id = id;
this.sObjectType = sObjectName;
this.icon = icon;
this.title = title;
this.subtitle = subtitle;
}
@AuraEnabled
public Id getId(){
return id;
}
@AuraEnabled
public String getsObjectType(){
return sObjectType;
}
@AuraEnabled
public String getIcon(){
return icon;
}
@AuraEnabled
public String getTitle(){
return title;
}
@AuraEnabled
public String getSubtitle(){
return subtitle;
}
}
searchLooupResult.cls-
/****
@description :
@author : Amit Agarwal
@group :
@last modified on :
@last modified by : Amit Agarwal
****/
public class searchLooupResult {
private final static Integer MAX_RESULTS = 5000;
@AuraEnabled(cacheable =true)
public static List<genericLookupCompWrapper> searchRecords(String searchTerm, List<String> selectedIds, String sObjectName, String[] filterCriteria, String[] titleFields, String[] subtitleFields){
System.debug(' searchTerm => ' + searchTerm);
System.debug('selectedIds => ' + selectedIds);
System.debug('sObjectName => ' + sObjectName);
System.debug('filterCriteria => ' + filterCriteria);
System.debug(' titleFields => ' + titleFields );
System.debug(' subTitleFieldse => ' + subtitleFields);
List<sObject> queryRecords = new List<sObject>();
String strQuery = '';
String whereClause = '';
String whereClauseTemp = '';
String fieldAPI = 'id,' + string.join(titleFields,',') + ',' + string.join(subTitleFields,',') ;
System.debug(' fieldAPI => ' + fieldAPI);
if(selectedIds != null && !selectedIds.isEmpty()){
whereClause = whereClause + 'id NOT IN :selectedIds';
}
System.debug(' whereClause => ' + whereClause);
if(String.isNotBlank(searchTerm)){
if(titleFields != null && titleFields.size() > 0){
for(String titlefield : titleFields){
whereClauseTemp += titlefield + ' like \'%' + searchTerm + '%\'' + ' OR ' ;
}
}
if(subTitleFields != null && subTitleFields.size() > 0){
for(String subtitlefield : subTitleFields){
whereClauseTemp += subtitlefield + ' like \'%' + searchTerm + '%\'' + ' OR ' ;
}
}
System.debug(' fieldAPI => ' + fieldAPI);
whereClauseTemp = (String.isNotBlank(whereClauseTemp) ? (whereClauseTemp.removeEnd(' OR ')) : ' ');
if(String.isNotBlank(whereClause)){
whereClause += ' AND ' + + ' ('+ whereClauseTemp + ')' ;
}else{
whereClause += ' ('+ whereClauseTemp + ')' ;
}
}
if(filterCriteria != null && filterCriteria.size() > 0 ){
if(String.isNotBlank(whereClause)){
whereClause = whereClause + ' AND ';
}
whereClause += String.join(filterCriteria, ' AND ');
}
System.debug(' whereClause => ' + whereClause);
System.debug(' whereClauseTemp => ' + whereClauseTemp);
fieldAPI = fieldAPI.removeEnd(',');
System.debug(' fieldAPI => ' + fieldAPI);
System.debug(' Final whereClause => ' + whereClause);
strQuery = 'SELECT ' + fieldAPI + ' FROM ' + sObjectName +(String.isNotBlank(whereClause) ? ' WHERE ' + whereClause : ' ') + ' WITH SECURITY_ENFORCED' + ' LIMIT ' + MAX_RESULTS;
System.debug(' Final Query => ' + strQuery);
queryRecords = Database.query(strQuery);
// Now make it a Wrapper Class to send teh result in specific format
List<genericLookupCompWrapper> searchResult = new List<genericLookupCompWrapper>();
for(sObject record : queryRecords ){
String title = '';
String subtitle = '';
if(titleFields != null && titleFields.size() > 0){
for(String titlefield : titleFields){
String titleVal = (String)record.get(titlefield) ;
if(titleVal != null){
title = title + titleVal + ',' ;
}
}
}
title = title.removeEnd(',');
if(subTitleFields != null && subTitleFields.size() > 0){
for(String subtitlefield : subTitleFields){
String subtitleVal = (String)record.get(subtitlefield) ;
if(subtitleVal != null){
subtitle = subtitle + subtitleVal + ',' ;
}
}
}
subtitle = subtitle.removeEnd(',') ;
searchResult.add(new genericLookupCompWrapper(
(String)record.get('id'),
sObjectName,
getIcon(sObjectName),
title,
subtitle
));
}
return searchResult;
}
private static String getIcon(String sObjectName){
switch on sObjectName{
when 'Contact' {
return 'standard:contact';
}
when 'User'{
return 'action:user';
}
when null{
return 'custom:custom101';
}
when else {
return 'custom:custom101';
}
}
}
}
Step 3: Create Lightning Web components
- polymorphiSelectObject.cmp - This LWC is the child component where we will create a dropdown options for the sObject Selection. We will retrieve the metadata records and show the Sobjects that are configured in the custom metadata object in Step 1.
- polymorphicSearchRecords.cmp - This LWC component is also a child component . In this we will implement the Lookup search box to show the records for the selected object. In this we will also implement the New Record button .
- polymorphicLookup.cmp - This LWC is the parent component which will hold the polymorphicSearchRecords.cmp and polymorphicSearchRecords.cmp components. This will help the child components to pass event and values needed.
polymorphicSelectObject.html -
<template>
<div class="slds-combobox_object-switcher slds-combobox-addon_start">
<div class="slds-form-element">
<label class="slds-form-element__label slds-assistive-text" for="combobox-id-2"
id="combobox-label-id-34">Filter Search by:</label>
<div class="slds-form-element__control">
<div class="slds-combobox_container">
<div class={getDropDownClass} aria-controls="primary-combobox-id-1">
<!--Dropdown Button to show Object List -->
<div style="border-color: 2px solid rgb(201, 201, 201);border-radius: 0.25em;"
class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right"
role="none" onclick={handleObjListToSelect}>
<button type="button"
class="slds-button slds-button_icon slds-button_icon-container-more"
aria-expanded="false" title="More Options"
aria-autocomplete="list"
aria-labelledby="combobox-label-id-34 combobox-id-2-selected-value"
id="combobox-id-2-selected-value"
aria-controls="objectswitcher-listbox-id-01"
aria-haspopup="listbox"
onblur={handleInputRecordBlur} >
<span class="slds-truncate">
<lightning-icon icon-name={selectedObject.iconName}
alternative-text={selectedObject.iconName} size='medium'
title={selectedObject.iconName}>
</lightning-icon>
</span>
<span>
<lightning-icon icon-name='utility:down'
class="slds-p-left_x-small" alternative-text='down'
size='x-small' title='down'>
</lightning-icon>
</span>
</button>
</div>
<!--Dropdown Button to show Object List-->
<!-- Object List to be selected-->
<div id="objectswitcher-listbox-id-01"
class="slds-dropdown slds-dropdown_length-5 slds-dropdown_x-small slds-dropdown_left"
role="listbox">
<ul class="slds-listbox slds-listbox_vertical" role="group"
aria-label="Suggested for you">
<!--Iterate over the Object List from custom metadata -->
<template for:each={objectList} for:item="object">
<li role="presentation" key={object.APIName}
data-objapiname={object.APIName}
onclick={handleObjectSelection}
class="slds-listbox__item">
<div class="slds-media slds-listbox__option slds-listbox__option_plain slds-media_small"
role="option">
<span
class="slds-media__figure slds-listbox__option-icon">
<lightning-icon icon-name={object.iconName}
alternative-text={object.iconName}
size='small' title={object.iconName}>
</lightning-icon>
</span>
<span class="slds-media__body">
<span class="slds-truncate"
title={object.LabelName}>{object.LabelName}</span>
</span>
</div>
</li>
</template>
<!--Iterate over the Object List from custom metadata -->
</ul>
</div>
<!-- Object List to be selected-->
</div>
</div>
</div>
</div>
</div>
</template>
import { LightningElement,api,wire } from 'lwc';
import getObjectLookupList from '@salesforce/apex/PolymorphicLookupObjects.getObjectLookupList';
const DELAY = 100;
export default class PolymorphicSelectObject extends LightningElement {
openObjectList= false;
hasInputfocus = false;
blurTimeout;
@api selectedObject={};
@api objectList=[];
// This is a wire method to fetch the List of Objects to be diaplyed on UI as options to select
@wire(getObjectLookupList)
wiredRecords({data,error}){
if(data) {
console.log("data wiredRecords => " + JSON.stringify(data));
let items = [];
// loop over each record and make a wrapper class
data.forEach(ele => {
let item = {};
item.APIName = ele.sObjectAPIName__c;
item.LabelName = ele.sObjectLabel__c;
item.searchTitle = ele.sObjectRecords_Search_by_field__c;
item.subtitleFields = ele.sObjectLookup_subtitle_field__c;
item.iconName = ele.sObject_Display_IconName__c;
item.createRecord = ele.IsCreateNewRecordAllowed__c;
items.push(item);
});
// store the items in objectList variable
this.objectList = items;
// store the default selected Object as the first object from the list
this.selectedObject = this.objectList[0];
console.log("selectedObject wiredRecords => " + JSON.stringify(this.selectedObject));
//fire an event on object selection
// to pass the selected object to parent component
this.fireselectedObjectEvent();
this.errors = undefined;
}
if(error) {
console.log("error wiredRecords => " + JSON.stringify(error));
this.objectList = undefined;
this.errors = error;
};
}
// toggle the dropdown list onclick of dropdown icon
handleObjListToSelect() {
this.openObjectList = !this.openObjectList;
}
// remove/hide the dropdown options on blur
handleInputRecordBlur() {
this.blurTimeout = window.setTimeout(() => {
this.hasInputfocus = false;
this.openObjectList = false;
this.blurTimeout = null;
},
DELAY
);
}
handleObjectSelection(event) {
let apiname = event.currentTarget.dataset.objapiname;
this.openObjectList = false;
let objectFound = this.objectList.find(function (ele, index) {
if (ele.APIName === apiname)
return true;
});
this.selectedObject = objectFound;
//fire an event on object selection
this.fireselectedObjectEvent();
}
//fire an event on object selection
// to pass the selected object to parent component
fireselectedObjectEvent(){
const selectedObjectEvent = new CustomEvent('selectionchange',{
detail:{
selectedObject: this.selectedObject
}
});
this.dispatchEvent(selectedObjectEvent);
}
// remove/hide the dropdown options on blur
get getDropDownClass() {
let css = 'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click ';
return this.openObjectList ? css + 'slds-is-open' : css;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>false</isExposed>
</LightningComponentBundle>
polymorphicSearchRecords.html -
<template>
<div class="slds-combobox_container slds-combobox-addon_end">
<div class={getInputRecordsDropdownclass} id="primary-combobox-id-1">
<!-- This block shows the lookup searh box => start -->
<div class={getComboboxClass} role="none">
<!-- Toggle input lookup search box -->
<!-- This block will display if no record is selected -->
<template if:false={isRecordSelected}>
<input type="text" class={getInputClass} id="combobox-id-1" aria-autocomplete="list"
aria-controls="listbox-id-1" aria-expanded="true" aria-haspopup="listbox" autocomplete="off"
role="combobox" onfocus={handleInputRecordFocus} onblur={handleInputRecordBlur}
value={getInputValue} placeholder={searchObjectPlaceholder} oninput={updateSearchTerm} />
<lightning-icon icon-name="utility:search" size="x-small" alternative-text="search icon"
class={getSearchIconClass}>
</lightning-icon>
</template>
<!-- This block will display when a record is selected -->
<!-- Toggle input lookup search box -->
<template if:true={isRecordSelected}>
<div class="slds-p-top_xx-small">
<lightning-pill label={recSelected.title} onremove={removeSelectedRecord}>
<lightning-icon icon-name={recSelected.icon} alternative-text={recSelected.title}>
</lightning-icon>
</lightning-pill>
</div>
</template>
</div>
<!-- This block shows the lookup searh box => End-->
<!-- This block shows the dropdown records and the New Record button on searhing => start -->
<div 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">
<!-- User New Record accessibility dynamically => start-->
<template if:true={hasCreateAllowed}>
<li role="presentation" class="slds-listbox__item" onclick={handleNewRecordClick}
data-objselectedapi={selectedObject.APIName}>
<div aria-selected="true" id="option0"
class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_term slds-has-focus"
role="option">
<lightning-icon icon-name='utility:add' alternative-text='Create New Record'
size='xx-small' title='Create New Record'></lightning-icon>
<span class="slds-media__body">
<span class="slds-listbox__option-text slds-listbox__option-text_entity slds-p-left_x-small"
title="Create New Record">New Record</span>
</span>
</div>
</li>
</template>
<!-- User New Record accessibility dynamically => end-->
<!-- Iterate and display the Records of the selected obejct => start-->
<template for:each={searchRecords} for:item="rec">
<li role="presentation" title={rec.title} key={rec.id} data-recordid={rec.id} class="slds-listbox__item" onclick={handleResultClick}>
<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" data-recordid={rec.id}>
<lightning-icon icon-name={rec.icon} alternative-text={rec.title} size='medium'></lightning-icon>
</span>
<span class="slds-media__body">
<span class="slds-listbox__option-text slds-listbox__option-text_entity">{rec.title}</span>
<span class="slds-listbox__option-meta slds-listbox__option-meta_entity">{rec.subtitle}</span>
</span>
</div>
</li>
</template>
<!-- Iterate and display the Records of the selected obejct => end-->
</ul>
</div>
<!-- This block shows the dropdown records and the New Record button on searhing => end -->
</div>
</div>
</template>
polymorphicSearchRecords.js -
import { LightningElement,api,track,wire } from 'lwc';
import fetchLookupRecords from '@salesforce/apex/searchLooupResult.searchRecords';
const DELAY = 100;
const MINIMAL_SEARCH_TERM_LENGTH = -1;
export default class PolymorphicSearchRecords extends LightningElement {
@api selectedObject;
@api searchRecords;
@api searchPlaceholder;
@track searchTerm = '';
selectedItem = [];
recSelected;
isRecordSelected = false;
hasInputfocus = false;
blurTimeout;
cleanSearchTerm;
searchThrottingTimeout;
// On load of the component this will fecth the records of the default selected Object
connectedCallback() {
// console.log('selectedObject in searchRecords '+JSON.stringify(this.selectedObject));
if (this.selectedObject.APIName != null) {
this.searchPlaceholder = 'Search ' + this.selectedObject.LabelName + '....';
this.fetchsObjectRecords({
searchTerm: '',
selectedIds: null,
sObjectName: this.selectedObject.APIName,
filterCriteria: [],
titleFields: this.selectedObject.searchTitle,
subtitleFields: this.selectedObject.subtitleFields
});
}
}
// this will toggle and show if 'New Record' button should be visible or not
get hasCreateAllowed(){
if(this.selectedObject.createRecord === true)
return true;
else
return false;
}
// if search box has is focused
handleInputRecordFocus() {
this.hasInputfocus = true;
}
// if a record is selected or not from the search lookup dropdown box
hasSelection() {
return this.selectedItem.length > 0;
}
// if a record is already selected or selection is allowed from the search lookup dropdown box
isSelectionAllowed() {
return !this.hasSelection();
}
// remove/hide the dropdown options on blur
handleInputRecordBlur() {
this.blurTimeout = window.setTimeout(() => {
this.hasInputfocus = false;
this.blurTimeout = null;
},
DELAY
);
}
//search and fetch the records of the selected object on focus or click of the search box
updateSearchTerm(event) {
//check if selection is allowed or not
if (!this.isSelectionAllowed()) {
return;
}
//stores the current input values on search box
this.searchTerm = event.target.value;
const newCleansearchterm = event.target.value.trim().replace(/\*/g, '').toLowerCase();
if (this.cleanSearchTerm === newCleansearchterm) {
return;
}
this.cleanSearchTerm = newCleansearchterm;
if (newCleansearchterm.length < MINIMAL_SEARCH_TERM_LENGTH) {
this.searchRecords = [];
return;
}
if (this.searchThrottingTimeout) {
clearTimeout(this.searchThrottingTimeout);
}
// check if minimum character is entered and delay teh search a few miliseconds
this.searchThrottingTimeout = setTimeout(() => {
if (this.cleanSearchTerm.length > MINIMAL_SEARCH_TERM_LENGTH) {
console.log("this.searchTerm Updated to => " + this.searchTerm);
this.fetchsObjectRecords({
searchTerm: this.searchTerm,
selectedIds: null,
sObjectName: this.selectedObject.APIName,
filterCriteria: [],
titleFields: this.selectedObject.searchTitle,
subtitleFields: this.selectedObject.subtitleFields
});
}
this.searchThrottingTimeout = null;
},
DELAY
);
}
//to fetch the records from apex class
fetchsObjectRecords(detail) {
//console.log('detail fetchsObjectRecords => ' + JSON.stringify(detail));
fetchLookupRecords(detail)
.then(results => {
// console.log("results => " + results);
let items = [];
results.forEach(ele => {
let item={};
item.id = ele.id;
item.icon = this.selectedObject.iconName;
item.title = ele.title;
item.subtitle = ele.subtitle;
items.push(item);
});
this.searchRecords = items;
})
.catch(error => {
this.searchRecords = [];
console.log('error => ' + (error));
console.log('error => ' + JSON.stringify(error));
});
}
// to remove the current selected record
handleRemoveSelection(){
this.selectedItem = [];
}
// on click of the record from teh search box dropdown
handleResultClick(event){
const recordId = event.currentTarget.dataset.recordid;
this.selectedItem = this.searchRecords.filter(result => result.id === recordId);
console.log('selectedItem => ' + JSON.stringify(this.selectedItem) );
if(this.selectedItem.length === 0){
return;
}
if(this.selectedItem.length > 0){
this.isRecordSelected=true;
this.recSelected = this.selectedItem[0];
}
console.log(' this.isRecordSelected => ' + this.isRecordSelected );
console.log(' this.recSelected => ' + JSON.stringify(this.recSelected) );
this.searchTerm = '';
this.dispatchEvent(new CustomEvent('selectionchange'));
}
// fire an event to parent component if a New Record is to be created
handleNewRecordClick() {
this.dispatchEvent(new CustomEvent('createnewrecord'));
}
// removing the current selected item/record
removeSelectedRecord(){
this.isRecordSelected=false;
this.recSelected = '';
this.selectedItem =[];
}
get getSearchIconClass(){
let css = 'slds-input__icon slds-input__icon_right ' + (!this.hasSelection() ? '' : 'slds-hide');
return css;
}
get getClearSelectionButtonClass() {
return 'slds-button slds-button_icon slds-input__icon slds-input__icon_right ' + (this.hasSelection() ? '' : 'slds-hide');
}
get getInputValue() {
return this.searchTerm;
}
get searchObjectPlaceholder() {
return this.searchPlaceholder;
}
get getInputRecordsDropdownclass() {
let css = 'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click ';
return this.hasInputfocus ? css + 'slds-is-open' : css;
}
get getComboboxClass(){
let css ='slds-combobox__form-element slds-input-has-icon ';
css += (this.hasSelection()? 'slds-input-has-icon_left-right' : 'slds-input-has-icon_right');
return css;
}
get getInputClass() {
let css = 'slds-input slds-combobox__input';
css += 'slds-has-focus';
return this.hasInputfocus ? css + 'slds-has-focus' : css;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>false</isExposed>
</LightningComponentBundle>
polymorphicLookUp.html -
<template>
<lightning-card title="Custom Polymorphic LookUp field using LWC" icon-name="standard:bundle_config">
<lightning-layout multiple-rows="true">
<lightning-layout-item padding="around-small" size="12">
<div class="slds-form-element">
<label class="slds-form-element__label" for="combobox-id-1"><b>Related To</b></label>
<div class="slds-form-element__control">
<div class="slds-combobox-group">
<!-- Select the Object -->
<c-polymorphic-select-object onselectionchange={handleObjectSelection}>
</c-polymorphic-select-object>
<!-- Select the Object -->
<!-- Look Up records for selected Object-->
<template if:true={selectedObject}>
<c-polymorphic-search-records search-placeholder={searchObjectPlaceholder}
search-records={sObjectRecords} selected-object={selectedObject}
oncreatenewrecord={handleNewRecordCreation}>
</c-polymorphic-search-records>
</template>
<!-- Look Up records for selected Object-->
<!--Start Modal Pop up to select the recordtype -->
<template if:true={selectRecordType}>
<div>
<section 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" style="z-index:9999;">
<div class="slds-modal__container">
<!--header of the select Record type Modal pop-->
<header class="slds-modal__header">
<button
class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse"
title="Close">
<lightning-button-icon icon-name="utility:close"
variant="bare-inverse" size="large" onclick={closeModal}>
</lightning-button-icon>
<span class="slds-assistive-text">Close</span>
</button>
<h2 id="modal-heading-01"
class="slds-text-heading_medium slds-hyphenate">New
{selectedObject.LabelName}
</h2>
</header>
<!--content of the select Record type Modal pop-->
<div class="slds-modal__content slds-p-around_medium"
id="modal-content-id-1">
<template if:true={errorStatus}>
<div class="slds-box slds-box_small slds-theme_error">
<p>Review the errors on this page.</p>
</div>
<div class="slds-m-left_large">
<p style="color: red;">
Select at least one recordtype to proceed.
</p>
</div>
</template>
<div class="slds-m-left_xx-large slds-m-top_medium">
<lightning-radio-group name="recordType"
label="Select a record type" options={recordTypeOptions}
value={recordTypeId} type="radio"
onchange={handleRecordTypeChange}>
</lightning-radio-group>
</div>
</div>
<!--footer of the select Record type Modal pop-->
<footer class="slds-modal__footer">
<lightning-button label="Cancel" class="slds-p-right_small" onclick={closeModal}>
</lightning-button>
<lightning-button label="Next" onclick={handleNext}
variant="brand"></lightning-button>
</footer>
</div>
</section>
<div class="slds-backdrop slds-backdrop_open"></div>
</div>
</template>
<!--End Pop up to select the recordtype -->
<!--Start Modal pop up to Create New Record -->
<template if:true={openCreateRecord}>
<div>
<section role="dialog" tabindex="-1"
class="slds-modal slds-fade-in-open slds-modal_medium"
aria-labelledby="modal-heading-02" aria-modal="true"
aria-describedby="modal-content-id-2">
<div class="slds-modal__container">
<!--header if the New Record Modal pop-->
<header class="slds-modal__header">
<button
class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse"
title="Close">
<lightning-button-icon icon-name="utility:close"
variant="bare-inverse" size="large" onclick={closeModal}>
</lightning-button-icon>
<span class="slds-assistive-text">Close</span>
</button>
<h2 id="modal-heading-02"
class="slds-text-heading_medium slds-hyphenate">New {selectedObject.LabelName}
</h2>
</header>
<!--content of the New Record Modal pop-->
<div class="slds-modal__content slds-p-around_medium" id="modal-content-id-2">
<div class="slds-m-left_xx-large slds-m-top_medium">
<lightning-record-form class="recordForm" object-api-name={selectedObject.APIName}
record-type-id={recordTypeId} layout-type="Compact" columns="2"
onload={handleLoad} density="comfy" onerror={handleError}
onsuccess={handleSuccess}>
</lightning-record-form>
</div>
</div>
<!--footer of the New Record Modal pop-->
<footer class="slds-modal__footer">
<lightning-button label="Save" class="slds-p-right_small" variant="brand" onclick={handleSubmit}>
</lightning-button>
<lightning-button label="Cancel" onclick={closeModal}></lightning-button>
</footer>
</div>
</section>
<div class="slds-backdrop slds-backdrop_open"></div>
</div>
</template>
<!--End Modal pop up to Create New Record -->
</div>
</div>
</div>
</lightning-layout-item>
</lightning-layout>
</lightning-card>
</template>
import { LightningElement, track, api, wire } from 'lwc';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import fetchLookupRecords from '@salesforce/apex/searchLooupResult.searchRecords';
export default class PolymorphicLookUp extends LightningElement {
@track searchTerm = '';
sObjectRecords = [];
searchPlaceholder;
selectedObject = {};
isError=false;
record;
mainRecord;
openCreateRecord;
selectRecordType;
recordTypeOptions;
recordTypeId;
error;
//this holds the selected object
handleObjectSelection(event) {
this.selectedObject = event.detail.selectedObject;
this.searchPlaceholder = 'Search ' + this.selectedObject.LabelName + '....';
this.fetchsObjectRecords({
searchTerm: '',
selectedIds: null,
sObjectName: this.selectedObject.APIName,
filterCriteria: [],
titleFields: this.selectedObject.searchTitle,
subtitleFields: this.selectedObject.subtitleFields
});
}
//this is used to fetch the lokup records of the selected object
fetchsObjectRecords(detail) {
console.log('detail this.selectedObject => ' + JSON.stringify(this.selectedObject));
console.log('detail fetchsObjectRecords => ' + JSON.stringify(detail));
fetchLookupRecords(detail)
.then(results => {
console.log("results => " + results);
let items = [];
results.forEach(ele => {
let item = {};
item.id = ele.id;
item.icon = this.selectedObject.iconName;
item.title = ele.title;
item.subtitle = ele.subtitle;
items.push(item);
});
this.sObjectRecords = items;
})
.catch(error => {
this.sObjectRecords = [];
console.log('error => ' + error);
console.log('error => ' + JSON.stringify(error));
});
}
// placeholder of search box
get searchObjectPlaceholder() {
return this.searchPlaceholder;
}
//to show error
get errorStatus() {
return this.isError;
}
//Used to get the object information of the selected object to create New record
@wire(getObjectInfo, { objectApiName: '$selectedObject.APIName' })
wiredObjectInfo({ error, data }) {
if (data) {
this.record = data;
console.log("getObjectInfo this.record ", this.record);
this.error = undefined;
this.getRecordTypeOptions();
} else if (error) {
this.error = error;
this.record = undefined;
console.log("this.error", this.error);
}
}
//Get the all record Types for the selected Object
getRecordTypeOptions() {
let recordTypeInfos = Object.entries(this.record.recordTypeInfos);
console.log("recordTypeInfos length", recordTypeInfos.length);
if (recordTypeInfos.length > 1) {
let temp = [];
recordTypeInfos.forEach(([key, value]) => {
console.log(key);
if (value.available === true && value.master !== true) {
temp.push({ "label": value.name, "value": value.recordTypeId });
}
});
this.recordTypeOptions = temp;
console.log("recordTypeOptions", this.recordTypeOptions);
} else {
this.recordTypeId = this.record.defaultRecordTypeId;
}
console.log("recordTypeOptions", this.recordTypeOptions);
}
// handles the chnage of the recordtype selection
handleRecordTypeChange(event) {
console.log("In handleRecTypeChange", event.target.value);
this.recordTypeId = event.target.value;
}
// open modal pop up to show recordtype optons to select for the selected object
handleNewRecordCreation() {
console.log(" this.recordTypeId ", this.recordTypeId);
if (this.recordTypeOptions !== undefined && this.recordTypeOptions.length > 0) {
this.recordTypeId = '';
this.selectRecordType = true;
this.openCreateRecord = false;
} else {
this.handleNext();
}
}
// open modal pop up to create new records
handleNext() {
console.log(" this.recordTypeId ", this.recordTypeId);
if (this.recordTypeId !== '') {
this.selectRecordType = false;
this.openCreateRecord = true;
} else
this.isError = true;
}
handleSubmit() {
this.template.querySelector('lightning-record-form').submit();
this.isError = false;
}
handleSuccess(event) {
this.selectRecordType = false;
this.openCreateRecord = false;
this.isError = false;
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: `Record saved successfully with id: ${event.detail.id}`,
variant: 'success',
}),
)
}
handleError() {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error',
message: 'Error saving the record',
variant: 'error',
}),
)
}
closeModal() {
this.isError = false;
this.recordTypeOptions = '';
this.recordTypeId = '';
this.selectRecordType = false;
this.openCreateRecord = false;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Custom Polymorphic LookUp Field</masterLabel>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__RecordPage</target>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>
Components Used:
We can select any object from the drop down and then from the selected object we can lookup the records from the lookup box to the right.
1. Lightning Web Components
- polymorphicLookup.cmp
- polymorphiSelectObject.cmp
- polymorphicSearchRecords.cmp
2. Apex Classes
- polymorphicLookupObjects.cls
- searchLookupResults.cls
- genericLookupCompWrapper.cls
3. Custom Metadata
- PolymorphicLookupObject__mdt
Component View:
In the above screen you can see we have four objects as an example to select. Account , Contact and Case are the standard objects whereas Student is a custom object.
Object Dropdown List |
In the above screen you can see we have four objects as an example to select. Account , Contact and Case are the standard objects whereas Student is a custom object.
We can select any object from the drop down and then from the selected object we can lookup the records from the lookup box to the right.
Steps to be followed to use the custom polymorphic field functionality
Step 1: Create custom metadata records as follows
go to Setup => Type Custom Metadata => Click PolymorphicLookupObject => Click Manage PolymorphicLookup Objects=> Click New and fill data as below
Similarly for other objects as required
go to Setup => Type Custom Metadata => Click PolymorphicLookupObject => Click Manage PolymorphicLookup Objects=> Click New and fill data as below
Step 3: Select the Object from dropdown
Select object from dropdown list |
If we select Account we can see the records but we cannot see New Record button . this is because we created the CMDT record for Account with IsCreateNewRecordAllowed__c as False
Example :- Account ObjectIf we select any other object we can see the records as well as New Record button . This is because we created the CMDT records for all other objects with IsCreateNewRecordAllowed__c as True.
Example :- Student Object
Example :- Student Object
Select record from dropdown list |
Step 4: Select the Record from dropdown and we are done.
Resources:
Salesforce Documents- Lightning Design System
Salesforce Documents - Component Library
Salesforce Documents - Component Library
Thank you all !
#Keep learning #Keep Sharing :)
#Keep learning #Keep Sharing :)
Comments
Post a Comment