SObject Lookup – Lightning Component

Introduction

In salesforce lightning we don’t have any lookup field component like salesforce classic has <apex:inputfield>, which render as lookup for lookup and master detail relationships and also take care of , how to display lookup dialog or lookup result.

However , there is a force:inputfield tag in salesforce lightning, But lookup field is not yet supported by this tag.

This reusable lookup component will allow user to add lookup field in lightning component UI, as they do in visualforce.

Benefits

  • Single Lookup components can be used for any SObject lookup present in our org without changing any major code.
  • Its look & feel is exactly same as SLDS, which is device friendly

Pre-requisite:

Upload the above downloaded zip into the static resource.

Here in my code base I have used static resource Name as “SLDS_STATIC_RESOURCE”. You can replace this name with your actual static resource name.

Reusable Lightning Component Bundle

  1. svg Component:

 The Lightning Components framework doesn’t directly support SVG icons tag at this point.

So we need to build a component called “svg” which will generated SVG tag while loading in component.

This component is being used in “LightningSObjectLookup” reusable component to show the svg icon.

This component has been designed by taking help from below trailhead module.

https://developer.salesforce.com/trailhead/project/slds-lightning-components-workshop/slds-lc-4

Component Source Code

<aura:component >
<aura:attribute name="class" type="String" description="CSS classname for the SVG element" />
<aura:attribute name="xlinkHref" type="String" description="SLDS icon path. Ex: /assets/icons/utility-sprite/svg/symbols.svg#download" />
<aura:attribute name="ariaHidden" type="String" default="true" description="aria-hidden true or false. defaults to true" />
</aura:component>

Renderer Source Code


({
 render: function(component, helper) {
 //grab attributes from the component markup
 var classname = component.get("v.class");
 var xlinkhref = component.get("v.xlinkHref");
 var ariaHidden = component.get("v.ariaHidden");

 //return an svg element w/ the attributes
 var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
 svg.setAttribute('class', classname);
 svg.setAttribute('aria-hidden', ariaHidden);
 svg.innerHTML = '&amp;amp;amp;amp;lt;use xlink:href="'+xlinkhref+'"&amp;amp;amp;amp;gt;&amp;amp;amp;amp;lt;/use&amp;amp;amp;amp;gt;';
 return svg;
 }
})

2. lightningLookup Component:

This is the main reusable component. This component contains following attributes

  • sObjectAPIName : The API name of the SObject to search
  • label : The label to be displayed as lookup field Label, eg: Account
  • pluralLabel : The plural label to be displayed in Lookup Search result header, eg: <searchString> in Accounts”
  • listIconSVGPath : The static resource path to the svg icon to use.
  • listIconClass : The SLDS class to use for the icon.
  • searchString: The search string to find. It will be inputted by user
  • recordId: selected sObject record Id
  • matchingResult: The resulting matches returned by the Apex controller.

Controller Name : LookupSObjectController.

There is one event registered : “ClearLookupIdEvent” , which is fired when user clicks on cross icon in selection box.

This reusable lighnting component bundle have its own clientSide Controller and Helper js file.

ClearLookupId.evt Source Code

In Developer Console Goto File | New | Lightning Event


<aura:event type="COMPONENT" description="Clear the Lookup" />

LookupSObjectController.cls Class Source Code


/**
* Apex Controller for fetching the records as per search String inputted by user
*/
public with sharing class LookupSObjectController
{
/**
* Aura enabled method to search a specified SObject for a specific string
*/
@AuraEnabled
public static SearchResult[] lookup(String searchString, String sObjectAPIName)
{
// escaped the input String
String escapedSearchString = String.escapeSingleQuotes(searchString);
String escapedSObjectAPIName = String.escapeSingleQuotes(sObjectAPIName);

List<SearchResult> results = new List<SearchResult>();

// Build our SOQL query
String searchQuery = 'SELECT Id, Name FROM ' + escapedSObjectAPIName + ' WHERE Name LIKE ' + '\'%' + escapedSearchString + '%\'' + ' Limit 50';
List<SObject> searchList = DataBase.query(searchQuery);
// Create a list of matches to return
for (SObject so : searchList)
{
results.add(new SearchResult((String)so.get('Name'), so.Id));
}

return results;
}

/**
* Inner class to wrap up an SObject Label and its Id
*/
public class SearchResult
{
@AuraEnabled public String SObjectLabel {get; set;}
@AuraEnabled public Id SObjectId {get; set;}

public SearchResult(String sObjectLabel, Id sObjectId)
{
this.SObjectLabel = sObjectLabel;
this.SObjectId = sObjectId;
}
}
}

lightningLookup.cmp Source Code


<aura:component controller="LookupSObjectController" >
<!-- Required Scripts/Styles -->
<!-- Salesforce Lightning Design System : https://www.lightningdesignsystem.com/ -->
<ltng:require styles="/resource/SLDS_STATIC_RESOURCE/assets/styles/salesforce-lightning-design-system-ltng.css" />

<!-- Attributes -->
<aura:attribute name="sObjectAPIName" type="String" required="true"
description="The API name of the SObject to search" />
<aura:attribute name="label" type="String" required="true"
description="The label to be displayed as field Label, eg: Account" />
<aura:attribute name="pluralLabel" type="String" required="true"
description="The plural label to be displayed in Lookup Search result header, eg: xxx in Accounts" />
<aura:attribute name="listIconSVGPath" type="String" default="/resource/SLDS_STATIC_RESOURCE/assets/icons/custom-sprite/svg/symbols.svg#custom11"
description="The static resource path to the svg icon to use." />
<aura:attribute name="listIconClass" type="String" default="slds-icon-custom-11"
description="The SLDS class to use for the icon." />
<aura:attribute name="searchString" type="String"
description="The search string to find." />
<aura:attribute name="matchingResult" type="LookupSObjectController.SearchResult[]"
description="The resulting matches returned by the Apex controller." />
<aura:attribute name="recordId" type="Id"
description="selected soBject record Id" />
<!-- Events -->

<aura:registerEvent name="clearLookupIdEvent" type="c:ClearLookupId"/>

<!-- Lookup Markup : See https://www.lightningdesignsystem.com/components/lookups -->
<div class="slds">
<div aura:id="lookup-div" class="slds-lookup" data-select="single" data-scope="single" data-typeahead="true">
<!-- This is the Input form markup -->
<div class="slds-form-element">
<label class="slds-form-element__label" for="lookup">{!v.label}</label>
<div class="slds-form-element__control slds-input-has-icon slds-input-has-icon--right">
<c:svg class="slds-input__icon" xlinkHref="/resource/SLDS_STATIC_RESOURCE/assets/icons/utility-sprite/svg/symbols.svg#search" />
<!-- This markup is for when an item is currently selected -->
<div aura:id="lookup-pill" class="slds-pill-container slds-hide">
<span class="slds-pill slds-pill--bare">
<span class="slds-pill__label">
<c:svg class="{!'slds-icon ' + v.listIconClass + ' slds-icon--small'}" xlinkHref="{!v.listIconSVGPath}" />{!v.searchString}
</span>
<button class="slds-button slds-button--icon-bare" onclick="{!c.clear}">
<c:svg class="slds-button__icon" xlinkHref="/resource/SLDS_STATIC_RESOURCE/assets/icons/utility-sprite/svg/symbols.svg#close" />
<span class="slds-assistive-text">Remove</span>
</button>
</span>
</div>
<!-- This markup is for when searching for a string -->
<ui:inputText aura:id="lookup" value="{!v.searchString}" class="slds-input" updateOn="keyup" keyup="{!c.search}" />

</div>
</div>
<!-- This is the lookup list markup. Initially it's hidden -->
<div aura:id="lookuplist" class="slds-lookup__menu slds-hide" role="listbox">
<div class="slds-lookup__item">
<button class="slds-button">
<c:svg class="slds-icon slds-icon-text-default slds-icon--small" xlinkHref="/resource/SLDS_STATIC_RESOURCE/assets/icons/utility-sprite/svg/symbols.svg#search" />
&quot;{!v.searchString}&quot; in {!v.pluralLabel}
</button>
</div>
<ul aura:id="lookuplist-items" class="slds-lookup__list" role="presentation">
<aura:iteration items="{!v.matchingResult}" var="match">
	<li class="slds-lookup__item">
<a id="{!globalId + '_id_' + match.SObjectId}" href="javascript:void(0);" role="option" onclick="{!c.select}">
<c:svg class="{!'slds-icon ' + v.listIconClass + ' slds-icon--small'}" xlinkHref="{!v.listIconSVGPath}" />{!match.SObjectLabel}
</a></li>
</aura:iteration></ul>
</div>
</div>
</div>
</aura:component>

lightningLookupController.js Controller Source Code


({
 /**
 * Search an SObject for a match
 */
 search : function(cmp, event, helper) {
 console.log('--Inside Search method of Controller---' + event.getSource());
 helper.doSearch(cmp); 
 },

 /**
 * Select an SObject from a list
 */
 select: function(cmp, event, helper) {
 console.log('--Inside selection method of Controller---' + event.currentTarget.id);
 helper.doSelection(cmp, event);
 },

 /**
 * Clear the currently selected SObject
 */
 clear: function(cmp, event, helper) {
 helper.clearSelection(cmp); 
 }
})

 

lightningLookupHelper.js Helper Source Code


({
 /**
 * Search SObject
 */
 doSearch : function(cmp) {
 console.log('--Inside doSearch method---');
 // Get the search string, input element and the selection container
 var searchString = cmp.get("v.searchString");
 var inputElement = cmp.find('lookup');
 var lookupList = cmp.find("lookuplist");
 var lookupListItems = cmp.find("lookuplist-items");

 // Clear any errors 
 inputElement.set('v.errors', null);

 // min 2 characters is required for an effective search
 if (typeof searchString === 'undefined' || searchString.length &amp;lt; 2)
 {
 // Hide the lookuplist
 $A.util.addClass(lookupList, 'slds-hide');
 return;
 }

 // Show the lookuplist
 $A.util.removeClass(lookupList, 'slds-hide');

 // Get the API Name
 var sObjectAPIName = cmp.get('v.sObjectAPIName');

 // Create an Apex action
 var action = cmp.get("c.lookup");

 // Mark the action as abortable, this is to prevent multiple events from the keyup executing
 action.setAbortable();

 // Set the parameters
 action.setParams({ "searchString" : searchString, "sObjectAPIName" : sObjectAPIName});

 // Define the callback function
 action.setCallback(this, function(response) {
 var state = response.getState();

 // Callback succeeded
 if (cmp.isValid() &amp;amp;&amp;amp; state === "SUCCESS")
 {
 // Get the search matches
 var matchingResult = response.getReturnValue();

 // If we have no matches, return
 if (matchingResult.length == 0)
 {
 cmp.set('v.matchingResult', null);
 return;
 }

 // Set the results in matchingResult attribute of component
 console.log('--Result found--'+matchingResult);
 cmp.set('v.matchingResult', matchingResult);
 console.log('--Result found2--'+cmp.get('v.matchingResult'));
 }
 else if (state === "ERROR") // Handle any error 
 {
 var errors = response.getError();

 if (errors) 
 {
 if (errors[0] &amp;amp;&amp;amp; errors[0].message) 
 {
 this.displayErrorDialog('Error', errors[0].message);
 }
 }
 else
 {
 this.displayErrorDialog('Error', 'Unknown error.');
 }
 }
 });

 // Enqueue the action 
 $A.enqueueAction(action); 
 },

 /**
 * Handle the Selection of an searched Item
 */
 doSelection : function(cmp, event) {
 // Resolve the Object Id from the events Element Id (this will be the &amp;lt;a&amp;gt; tag)
 var recId = this.getSelectedRecordId(event.currentTarget.id);
 // The Object label is the 2nd child (index 1)
 var recLabel = event.currentTarget.innerText;
 // Log the Object Id and Label to the console
 console.log('recId=' + recId);
 console.log('recLabel=' + recLabel);
 //set the selected record Id
 cmp.set('v.recordId',recId); 

 // Update the Searchstring with the Label
 cmp.set("v.searchString", recLabel);

 // Hide the Lookup List
 var lookupList = cmp.find("lookuplist");
 $A.util.addClass(lookupList, 'slds-hide');

 // Hide the Input Element
 var inputElement = cmp.find('lookup');
 $A.util.addClass(inputElement, 'slds-hide');

 // Show the Lookup pill
 var lookupPill = cmp.find("lookup-pill");
 $A.util.removeClass(lookupPill, 'slds-hide');

 // Lookup Div has selection
 var inputElement = cmp.find('lookup-div');
 $A.util.addClass(inputElement, 'slds-has-selection');

 },

 /**
 * Clear the Selection
 */
 clearSelection : function(cmp) {
 // Create the ClearLookupId event
 var clearEvent = cmp.getEvent("clearLookupIdEvent");

 // Fire the event
 clearEvent.fire();

 // Clear the Searchstring
 cmp.set("v.searchString", '');
 cmp.set('v.recordId', null);
 // Hide the Lookup pill
 var lookupPill = cmp.find("lookup-pill");
 $A.util.addClass(lookupPill, 'slds-hide');

 // Show the Input Element
 var inputElement = cmp.find('lookup');
 $A.util.removeClass(inputElement, 'slds-hide');

 // Lookup Div has no selection
 var inputElement = cmp.find('lookup-div');
 $A.util.removeClass(inputElement, 'slds-has-selection');
 },

 /**
 * Resolve the Object Id from the Element Id by splitting the id at the _
 */
 getSelectedRecordId : function(recId)
 {
 var i = recId.lastIndexOf('_');
 return recId.substr(i+1);
 },

 /**
 * Display a message
 */
 displayErrorDialog : function (title, message) 
 {
 var toast = $A.get("e.force:showToast");

 // For lightning1 show the toast
 if (toast)
 {
 //fire the toast event in Salesforce1
 toast.setParams({
 "title": title,
 "message": message
 });

 toast.fire();
 }
 else // otherwise throw an alert
 {
 alert(title + ': ' + message);
 }
 }
})

 

That’s all! Now our component is ready for use.

Here I’m explaining how to use the above reusable lookup component.

Example: How to use above reusable Lightning Bundle

Here , you can find “AccountLookup” and “lightningLookupApp” bundle.

  1. AccountLookUp Component Bundle:

 In this component , a form is designed to create contact having an account lookup selection box, which uses above “lightningLookup” component

There is an attribute “recordId” which holds selected account record Id. By using below code , we can call the above “lightningLookup” component.

AccountLookup.cmp Component Source Code

<aura:component controller="AccountLookUpController" implements="force:appHostable,flexipage:availableForAllPageTypes">
<!-- Attributes -->
<aura:attribute name="recordId" type="Id" description="The current record Id to display" />
<aura:attribute name="firstName" type="String" description="First Name of Contact" />
<aura:attribute name="lastName" type="String" description="Last Name of Contact" />
<aura:attribute name="contId" type="Id" description="ID of Created Contact" />
<!-- Event handlers -->
<!--aura:handler name="updateLookupIdEvent" event="c:UpdateLookupId" action="{!c.handleAccountIdUpdate}"/-->
<aura:handler name="clearLookupIdEvent" event="c:ClearLookupId" action="{!c.handleAccountIdClear}"/>
<div class="slds">
<!-- Lookup component -->
<c:lightningLookup label="Account" pluralLabel="Accounts" sObjectAPIName="Account"
listIconSVGPath="/resource/SLDS_STATIC_RESOURCE/assets/icons/standard-sprite/svg/symbols.svg#account"
listIconClass="slds-icon-standard-account"
recordId="{!v.recordId}"
/>
<div class="slds-form-element">
<label class="slds-form-element__label" for="fName">First Name</label>
<div class="slds-form-element__control">
<ui:inputText aura:id="fName" class="slds-input" value="{!v.firstName}"/>
</div>
</div>
<div class="slds-form-element">
<label class="slds-form-element__label" for="lName">Last Name</label>
<div class="slds-form-element__control ">
<ui:inputText aura:id="lName" class="slds-input" value="{!v.lastName}"/>
</div>
</div>
<ui:button aura:id="submitButton" label="Submit" press="{!c.saveContact}"/>


<aura:if isTrue="{!v.contId!=null}">
<SPAN>
Created Record Id : {!v.contId}
<force:recordView recordId="{!v.contId}" />
</SPAN>
</aura:if>
</div>
</aura:component>

AccountLookupController.js Controller Source Code


({

 /**
 * receiving the clearLookupIdEvent event
 */
 handleAccountIdClear : function(cmp, event, helper) {
 // Clear the Id bound to the View
 cmp.set('v.recordId', null);
 cmp.set('v.contId', null);
 },

 saveContact : function(cmp,event,helper){

 helper.saveContactRecord(cmp, event);
 }
})

 

AccountLookupHelper.js Helper Source Code

({
 saveContactRecord : function(cmp,event) {
 var fName = cmp.get("v.firstName");
 var lName = cmp.get("v.lastName");
 var accId = cmp.get("v.recordId");
 console.log('---selected Account RecordId--'+accId);
 // Create an Apex action
 var createAction = cmp.get("c.createRecord");
 // Set the parameters
 createAction.setParams({ "fName" : fName, "lName" : lName, "accountId" : accId});
 // Define the callback
 createAction.setCallback(this, function(response) {
 var state = response.getState();

 // Callback succeeded
 if (cmp.isValid() &amp;amp;&amp;amp; state === "SUCCESS")
 {
 console.log('Contaxt Record Created-Successfully-'+response.getReturnValue());
 cmp.set('v.contId', response.getReturnValue());
 }
 else{
 console.log('Contaxt Record Created Failure--');
 cmp.set('v.contId', null);
 }
 });

 // Enqueue the action 
 $A.enqueueAction(createAction); 

 }
})

 

Component’s Apex Controller –  AccountLookupController.cls Source Code


public class AccountLookUpController {

/**
 * Aura enabled method to search a specified SObject for a specific string
 */
 @AuraEnabled
 public static Id createRecord(String fName, String lName, ID accountId )
 {
 try{
 Contact con = new Contact();
 con.FirstName = fName;
 con.LastName= lName;
 con.accountId = accountId;
 Insert con;
 return con.Id;
 }catch(Exception e){
 return null;
 }
 }
}

2. lightningLookupApp.app Component Bundle:

This app only use the above component by using below code base

<aura:application >
<c:AccountLookup />
</aura:application>

Now click on “Preview” button of this app  or access the below Url to show the output.

https://<mydomainInstance>.lightning.force.com/c/lightningLookupApp.app

 

 

Leave a comment