Hey CafeForce Users,
Have you ever tried custom lookup component fetching thousands of records from apex? If so, you are well aware of the speed it offers. Custom lookup components having aura:iteration is extremely slow to display records. It will take more than 20sec to fetch and display over 5000 records.
To resolve the speed issues we gonna create a fast lookup component lookup lite. In this we not use aura:iteration instead we will access HTML DOM and append HTML tags dynamically.
Lookup Lite component Features:
- Single component
- Lightning fast speed
- Will work on any object
- Ability to set sObject Icon
- Storable Action
- Ability to set placeholder & label
Step-1: Create an Apex controller
Here our main focus is on faster lookup component so I m creating a simple Apex controller without any (Field Level Security) FLS or Error checks. Create LookupLiteController.cls Apex class and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public with sharing class LookupLiteController { @AuraEnabled public static List<sObject> fetchRecords( String objectName, String filterField, String searchString ) { String query = 'SELECT Id, ' + filterField+ ' FROM '+objectName+ ' WHERE '+filterField+' LIKE ' + '\'' + String.escapeSingleQuotes(searchString.trim()) + '%\'' + ' LIMIT 50000'; return Database.query(query); } } |
Step-2: Now we will create the LookupLite lightning component.
For creating a new lightning component navigate to Developer Console > File > New > Lightning Component
Enter Name & Description and click Submit. Now component markup file will open by default. In the right sidebar, click on Controller, Helper, and Style to create these files too.
Now open LookupLite.cmp and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
<aura:component controller="LookupLiteController" > <!-- Attributes that can be set while component calling--> <aura:attribute name="objectName" type="string" default="" required="true" /> <aura:attribute name="fieldName" type="string" default="" required="true" /> <aura:attribute name="value" type="String" default="" description="To pre-populate a value" /> <aura:attribute name="iconName" type="string" default="standard:drafts" description="complete icon name eg. standard:account" /> <aura:attribute name="label" type="string" default="" description="Label will be displayed above input Box" /> <aura:attribute name="placeholder" type="string" default="Search..." description="placeholder for input Box" /> <!-- Internal Use Attributes --> <aura:handler name="init" value="{!this}" action="{!c.doInit}"/> <aura:attribute name="uniqueId" type="Integer" default="" description="Uniquely identifies loopup list" /> <aura:attribute name="searchString" type="string" default="" description="String to search" /> <aura:attribute name="selectedRecord" type="String" default="" description="selected Record Id" access="private" /> <aura:attribute name="recordsList" type="List" description="List of Records having data" access="private" /> <!-- Component Markup --> <div class="slds-form-element"> <label class="slds-form-element__label" for="combobox-id-1">{!if(v.label != '',v.label,v.fieldName)}</label> <div class="slds-form-element__control"> <div class="slds-combobox_container"> <div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open" aura:id="resultsDiv" aria-expanded="true" aria-haspopup="listbox" role="combobox"> <div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none"> <div aura:id="lookup-pill" class="{! 'slds-pill-container ' + if(v.selectedRecord == '','slds-hide','') }"> <!-- Selected Value Pill --> <lightning:pill class="fullWidth" label="{!v.selectedRecord}" name="{!v.selectedRecord}" onremove="{! c.removeItem }"> <aura:set attribute="media"> <lightning:icon iconName="{!v.iconName}" size="x-small" alternativeText="icon"/> </aura:set> </lightning:pill> </div> <div aura:id="lookupField" class="{! if(v.selectedRecord == '','slds-show','slds-hide') }"> <!-- Dynamic Icon, Search Bar, Search Icon --> <span class="slds-icon_container slds-combobox__input-entity-icon inputIcon" title="record"> <lightning:icon class="slds-icon slds-icon slds-icon_small slds-icon-text-default" iconName="{!v.iconName}" size="x-small" alternativeText="icon"/> <span class="slds-assistive-text"></span> </span> <lightning:input aura:id="inputLookup" id="combobox-id-1" role="textbox" class="inputBox" placeholder="{!v.placeholder}" onkeyup="{!c.searchRecords}" value="{!v.searchString}" onblur="{!c.blurEvent}" aria-controls="listbox-id-1" autoComplete="off" /> <span class="slds-icon_container slds-icon-utility-down slds-input__icon slds-input__icon_right"> <lightning:icon iconName="utility:search" size="x-small" alternativeText="search"/> </span> <lightning:spinner class="slds-hide" aura:id="Spinner" alternativeText="Loading" size="small" variant="brand"/> </div> </div> <!-- Dropdown List --> <div id="listbox-id-1" class="slds-dropdown slds-dropdown_length-5 slds-dropdown_fluid" role="listbox" style="max-height:220px;"> <ul id="{!'record-'+v.uniqueId}" class="slds-listbox slds-listbox_vertical recordListBox" onmousedown="{!c.selectItem}"></ul> </div> </div> </div> </div> </div> </aura:component> |
Open the controller file LookupLiteController.js and paste the below code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
({ // To prepopulate the seleted value pill if value attribute is filled doInit : function( component, event, helper ) { $A.util.toggleClass(component.find('resultsDiv'),'slds-is-open'); if( $A.util.isEmpty(component.get('v.uniqueId')) ) { component.set('v.uniqueId',Math.random()); } if( !$A.util.isEmpty(component.get('v.value')) ) { helper.searchRecordsHelper( component, helper ); } }, // When a keyword is entered in search box searchRecords : function( component, event, helper ) { if(!$A.util.isEmpty(component.get('v.searchString'))) { helper.searchRecordsHelper( component, helper ); } else { $A.util.removeClass(component.find('resultsDiv'),'slds-is-open'); } }, // When an item is selected selectItem : function( component, event, helper ) { if(!$A.util.isEmpty(event.target.id)) { var recordsList = component.get('v.recordsList'); var selectedRecord; var index = recordsList.findIndex(x => x.Id === event.target.id) if(index != -1) { selectedRecord = recordsList[index]; } component.set('v.selectedRecord',selectedRecord[component.get('v.fieldName')]); component.set('v.value',selectedRecord.Id); $A.util.removeClass(component.find('resultsDiv'),'slds-is-open'); } }, // To remove the selected item. removeItem : function( component, event, helper ) { component.set('v.selectedRecord',''); component.set('v.value',''); component.set('v.searchString',''); setTimeout( function() { component.find( 'inputLookup' ).focus(); }, 250); }, // To close the dropdown if clicked outside the dropdown. blurEvent : function( component, event, helper ) { $A.util.removeClass(component.find('resultsDiv'),'slds-is-open'); }, }) |
Our helper will enqueue an action and will fetch the data from the server so open the helper file LookupLiteHelper.js and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
({ searchRecordsHelper : function(component, helper) { $A.util.removeClass(component.find("Spinner"), "slds-hide"); var fieldName = component.get('v.fieldName'); var action = component.get('c.fetchRecords'); action.setStorable(); action.setParams({ 'objectName' : component.get('v.objectName'), 'filterField' : fieldName, 'searchString' : component.get('v.searchString') }); action.setCallback(this,function(response){ var result = response.getReturnValue(); if(response.getState() === 'SUCCESS') { // To check if records are present or not if(result.length > 0) { var value = component.get('v.value'); // To check if value is prepopulated in value attribute or not if( $A.util.isEmpty(value) ) { $A.util.addClass(component.find('resultsDiv'),'slds-is-open'); helper.dynamicCreation(component, result, fieldName); component.set('v.recordsList',result); } else { var index = result.findIndex(x => x.Id === value) if(index != -1) { var selectedRecord = result[index]; } component.set('v.selectedRecord',selectedRecord[fieldName]); } } else { helper.dynamicCreation(component, 'No Data Found', fieldName); $A.util.addClass(component.find('resultsDiv'),'slds-is-open'); } } else { helper.dynamicCreation(component, 'No Server Response or client is offline', fieldName); } $A.util.addClass(component.find("Spinner"), "slds-hide"); }); $A.enqueueAction(action); }, // Dynamic creation of div for dropdown list dynamicCreation : function(component, recordsList, fieldName) { var recordId = 'record-'+component.get('v.uniqueId'); var recordDiv = document.getElementById(recordId); while (recordDiv.firstChild) recordDiv.removeChild(recordDiv.firstChild); if(Array.isArray(recordsList) && recordsList.length) { const len = recordsList.length; for(let k = 0; k<len; k++) { var li = document.createElement("li"); li.id = recordsList[k].Id; li.appendChild(document.createTextNode(recordsList[k][fieldName])); recordDiv.appendChild(li); } } else { var li = document.createElement("li"); li.id = ''; li.appendChild(document.createTextNode(recordsList)); recordDiv.appendChild(li); } } }) |
Finally, paste the CSS in LookupLite.css file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
.THIS .fullWidth { width: 100% !important; } .THIS .recordListBox { margin-top:0px !important; overflow-y: scroll; } .THIS .inputBox input { padding-left: 30px; } .THIS .inputIcon { z-index: 99; } .THIS .recordListBox li { height:35px; padding: 7px 12px 7px 12px; cursor:pointer; } /* For Scrolling */ .THIS ::-webkit-scrollbar { width: 7px; height: 7px; } .THIS ::-webkit-scrollbar-track { display: none !important; } .THIS ::-webkit-scrollbar-thumb { border-radius: 10px; background: rgba(0,0,0,0.4); } |
Step-3: Call the component in lightning App
1 2 3 4 5 |
<aura:application extends="force:slds" access="global"> <c:LookupLite objectName="Account" fieldName="Name" label="Account Name" iconName="standard:account" placeholder="Enter Value" /> </aura:application> |
Finally, Our component is now ready to use. Just call the component and pass the necessary values.
To use a fancy lookup, see the Custom Lookup component using aura:iteration.
Does this work, when i use this multiple times on the same component.
let’s say, i have 5 rows in table, i will be creating Contact, and when i choose account, it should allow me to select different account each contact that i am going to create.
Hi Sagar,
Yes, you can use it multiple times in a component with different or the same object. This Lookup opens dropdown by changing DOM & I already gave a unique DOM Id to it when the component loads.
it doesnt showing the result ?
Hi Vaibhav,
What error you are getting? Can you share some code or images?
Hi,
Thank you for sharing this component. I wanted to use this on a contact lookup field, however it seems that we cannot use the like operator with the “FirsName” or “LastName” field due to encryption: “The operator like is not supported on an encrypted field”.
Any ideia on how I can solve this?
Hi Joao,
It should work fine on firstname or lastname, try adding debugs to the class.