Salesforce Generic sObject Lightning Lookup Component

In Salesforce Lightning we don’t have any lookup field component like Salesforce classic. So, here is a generic lookup Lightning Component, which can be used for any sObject lookup present in our org without changing any major code.

sObjectLookupController:
Create below apex controller to use it in sObjectLookup.cmp component.

/**
* @Author	:	Biswajeet Samal
* @Date		:	9 Dec 2017
* @Desc		:	Controller for sObject Lookup Lightning Component
* */
public with sharing class sObjectLookupController {
    @AuraEnabled
    public static List<sObject> searchRecord(String objectAPIName, String fieldAPIName, 
                                             List<String> moreFields, String searchText,
                                             Integer recordLimit)
    {
        
        List<sObject> objectList =  new List<sObject>();
        
        searchText='\'%' + String.escapeSingleQuotes(searchText.trim()) + '%\'';
        
        String soqlQuery = 'SELECT Id, Name';
        if(!moreFields.isEmpty()){
            soqlQuery = soqlQuery + ',' + String.join(moreFields, ',') ;
        }
        soqlQuery = soqlQuery + ' FROM ' + objectAPIName + ' WHERE ' +
            + fieldAPIName +' LIKE '+ searchText + ' LIMIT '+ recordLimit;
        objectList = Database.query(soqlQuery);
        return objectList;
    }
}

sObjectLookupSelectEvent.evt:
Create below Lightning Event, which is used to store and fill the input field with selected record Id and Name. Fired from sObjectLookupItem.cmp component, handled at Lookup component.

<!--sObjectLookupSelectEvent.evt-->
<aura:event type="COMPONENT" description="sObjectLookupSelectEvent">
    <aura:attribute name="recordId" type="String" required="true" description="Used to send selected record Id"/>
    <aura:attribute name="recordName" type="String" required="true" description="Used to send selected record Name" />
</aura:event>

sObjectLookup.cmp:
Create below Lightning Component, which will be used for sObject lookup.

<!--sObjectLookup.cmp-->
<aura:component controller="sObjectLookupController" description="Lightning component for lookup fields">
    
    <!--Declare Attributes-->
    <aura:attribute name="objectAPIName" type="String" required="true"
                    description="Object API name used for searching records"/>
    
    <aura:attribute name="fieldAPIName" type="String" required="true"
                    description="API Name of field to be searched"/>
    
    <aura:attribute name="lookupIcon" type="String" default="standard:contact"
                    description="Icon for lookup records"/>
    
    <aura:attribute name="placeholder" type="String" default="Search..."
                    description="Placeholder text for input search filed"/>
    
    <aura:attribute name="fieldLabel" type="String" required="true"
                    description="input search field Label"/>
    
    <aura:attribute name="selectedRecordId" type="String"
                    description="Used to store the selected record id.
                                 While calling this component from other component,
                                 set this attribute to the lookup field API name"/>
    
    <aura:attribute name="selectedRecordName" type="String"
                    description="This is used to show the selected record Name in search input"/>
    
    <aura:attribute name="subHeadingFieldsAPI" type="String[]"
                    description="Field API for the fields to be shown under the record Name.
                                 Must be comma separated. Example: Email,Phone"/>
    
    <aura:attribute name="matchingRecords" type="Object[]" access="private"
                    description="List of records returned from server side call"/>
    
    <aura:attribute name="recordLimit" type="Integer" access="public" default="5" 
                    description="Total number of record to be returned"/>
    
    <!--Declare Handlers-->
    <aura:handler name="lookupSelect" event="c:sObjectLookupSelectEvent" action="{!c.handleLookupSelectEvent}"
                  description="Event handler to get the selected record Id and Name from LookupItem component"/>
    
    <!--Component-->
    <div class="slds-form-element__control">
        <div class="slds-combobox_container slds-has-inline-listbox">
            <div aura:id="divLookup"
                 class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-combobox-lookup"
                 aria-expanded="false" aria-haspopup="listbox" role="combobox">
                
                <div class="slds-combobox__form-element">
                    <lightning:input type="search"
                                     aura:id="searchinput"
                                     label="{!v.fieldLabel}"
                                     name="{!v.fieldLabel}"
                                     value="{!v.selectedRecordName}"
                                     onchange="{!c.handleSearchRecords}"
                                     isLoading="false"
                                     placeholder="{!v.placeholder}"
                                     onfocus="{!c.handleSearchRecords}"
                                     onblur="{!c.hideList}"/>
                </div>
                <div id="listbox-unique-id" role="listbox">
                    <ul class="slds-listbox slds-listbox_vertical slds-dropdown slds-dropdown_fluid" role="presentation">
                        <aura:iteration var="rec" items="{!v.matchingRecords}">
                            <c:sObjectLookupItem record="{!rec}" subHeadingFieldsAPI="{!v.subHeadingFieldsAPI}" iconCategoryName="{!v.lookupIcon}"/>
                        </aura:iteration>
                    </ul>
                </div>
            </div>
        </div>
    </div>
</aura:component>

sObjectLookupController.js:

({
    handleSearchRecords : function (component, event, helper) {
        var searchText = component.find("searchinput").get("v.value");
        if(searchText){
            helper.searchRecord(component,searchText);
        }else{
            helper.searchRecord(component, '');
        }
    },
    
    handleLookupSelectEvent : function (component, event, helper) {
        var selectedRecordId = event.getParam("recordId");
        var selectedrecordName = event.getParam("recordName");
        component.set("v.selectedRecordId", selectedRecordId);
        component.set("v.selectedRecordName", selectedrecordName);
        helper.toggleLookupList(component, false, 'slds-combobox-lookup', 'slds-is-open');
    },
    
    hideList :function (component,event,helper) {
        window.setTimeout(
            $A.getCallback(function() {
                if (component.isValid()) {
                    helper.toggleLookupList(component, false, 'slds-combobox-lookup','slds-is-open');
                }
            }), 200
        );
    }
})

sObjectLookupHelper.js:

({
    toggleLookupList : function (component, ariaexpanded, classadd, classremove) {
        component.find("divLookup").set("v.aria-expanded", true);
        $A.util.addClass(component.find("divLookup"), classadd);
        $A.util.removeClass(component.find("divLookup"), classremove);
    },
    
    searchRecord : function (component, searchText) {
        component.find("searchinput").set("v.isLoading", true);        
        var action = component.get("c.searchRecord");
        action.setParams({
            "objectAPIName": component.get("v.objectAPIName"),
            "fieldAPIName":component.get("v.fieldAPIName"),
            "moreFields":component.get("v.subHeadingFieldsAPI"),
            "searchText":searchText,
            "recordLimit":component.get("v.recordLimit")
        });
        
        action.setCallback(this, function(response) {
            var state = response.getState();
            if(component.isValid() && state === "SUCCESS") {
                if(response.getReturnValue()){
                    component.set("v.matchingRecords", response.getReturnValue());
                    if(response.getReturnValue().length > 0){
                        this.toggleLookupList(component, true, 'slds-is-open', 'slds-combobox-lookup');
                    }
                    component.find("searchinput").set("v.isLoading", false);
                }
            }
        });
        $A.enqueueAction(action);
    }
})

sObjectLookupItem.cmp:
Create below lightning component, which will be used for creating list elements for records in sObjectLookup.cmp component.

<!--sObjectLookupItem.cmp-->
<aura:component description="Component used for creating list elements for records">
    
    <!--Declare Attributes-->
    <aura:attribute name="record" type="Object" required="true"
                    description="Holds the single record instance"/>
    
    <aura:attribute name="subHeadingFieldsAPI" type="String[]"
                    description="Holds the field API names to show as meta entity in list"/>
    
    <aura:attribute name="subHeadingFieldValues" type="String"
                    description="Used to construct the meta entity value. Works as subheading in record option"/>
    
    <aura:attribute name="iconCategoryName" type="String"
                    description="Lightning icon category and icon name to show with each record element"/>
    
    <!--Declare Events-->
    <aura:registerEvent name="lookupSelect" type="c:sObjectLookupSelectEvent"
                        description="Event used to send the selected record Id and Name to Lookup component"/>
    
    <!--Declare Handlers-->
    <aura:handler name="init" value="{!this}" action="{!c.getValues}"
                  description="standard init event to prepare the sub heading mete entity value"/>
    
    <!--Component-->
    <li role="presentation" class="slds-listbox__item" onclick="{!c.handleSelect}">
        <span class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta"
              role="option">
            <!--Lightning Icon-->
            <span class="slds-media__figure">
                <lightning:icon iconName="{!v.iconCategoryName}" size="small" alternativeText="{!v.record.Name}"/>
            </span>
            <!--Option-->
            <span class="slds-media__body">
                <span class="slds-listbox__option-text slds-listbox__option-text_entity">
                    {!v.record.Name}
                </span>
                <!--Option sub heading-->
                <span class="slds-listbox__option-meta slds-listbox__option-meta_entity">
                    {!v.subHeadingFieldValues}
                </span>
            </span>
        </span>
    </li>
</aura:component>

sObjectLookupItemController.js:

({
    getValues : function (component) {
        var record = component.get("v.record");
        var subheading = '';
        for(var i=0; i<component.get("v.subHeadingFieldsAPI").length; i++ ){
            if(record[component.get("v.subHeadingFieldsAPI")[i]]){
                subheading = subheading + record[component.get("v.subHeadingFieldsAPI")[i]] + ' - ';
            }
        }
        subheading = subheading.substring(0,subheading.lastIndexOf('-'));
        component.set("v.subHeadingFieldValues", subheading);
    },
    
    handleSelect : function (component,event) {
        var chooseEvent = component.getEvent("lookupSelect");
        chooseEvent.setParams({
            "recordId" : component.get("v.record").Id,
            "recordName":component.get("v.record").Name
        });
        chooseEvent.fire();
    }
})

Usage:

<!--sObjectLookupApp.app-->
<aura:application extends="force:slds">
    <c:sObjectLookup fieldLabel = "Contact" objectAPIName = "Contact"
                     fieldAPIName = "Name" subHeadingFieldsAPI = "Email,Phone"
                     lookupIcon = "standard:contact" placeholder = "Search Contact"/>
</aura:application>

Output:

  • Ashok Giri

    Hi as shown addition to that if there is one text box called first name and i want auto populate first name of Contact when i will select that lookup. how we can achieve this ?

  • Anamika Garg

    You did not talk about licence. Am I allowed to use your code?