Hey CafeForce Users,
Today we gonna create a cool re-usable custom lookup component in lightning.
Lightning comes with many pre-built components that are easy to use but still many components are missing by date. One of them is the lookup component. We will create this within a single component without using any child component or event. You can find the complete source code at the bottom of the page.
Custom Lookup component Features:
- Single component
- Will work on any object
- Spinner to show processing
- Ability to set sObject Icon
- Option to set placeholder & label
- Ability to show error in the dropdown if any occurred.
- You can set a number of records to show at once. Default is 5
Also Check, Multi Select Lookup Lightning Component
Demo GIF:
Step-1: Create an Apex controller
Our apex controller will fetch records on passing object and field name to it. Here we are using a wrapper class which will return records in the form of value and label.
Create CustomLookupController.cls Apex class.
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 |
public with sharing class CustomLookupController { @AuraEnabled public static List<RecordsData> fetchRecords(String objectName, String filterField, String searchString, String value) { try { List<RecordsData> recordsDataList = new List<RecordsData>(); String query = 'SELECT Id, ' + filterField + ' FROM '+objectName; if(String.isNotBlank(value)) { query += ' WHERE Id = \''+ value + '\' LIMIT 49999'; } else { query += ' WHERE '+filterField+ ' LIKE ' + '\'' + String.escapeSingleQuotes(searchString.trim()) + '%\' LIMIT 49999'; } for(SObject s : Database.query(query)) { recordsDataList.add( new RecordsData((String)s.get(filterField), (String)s.get('id')) ); } return recordsDataList; } catch (Exception err) { if ( String.isNotBlank( err.getMessage() ) && err.getMessage().contains( 'error:' ) ) { throw new AuraHandledException(err.getMessage().split('error:')[1].split(':')[0] + '.'); } else { throw new AuraHandledException(err.getMessage()); } } } public class RecordsData { @AuraEnabled public String label; @AuraEnabled public String value; public RecordsData(String label, String value) { this.label = label; this.value = value; } } } |
Step-2: Now we will create the CustomLookup 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 y default. In the right sidebar, click on Controller, Helper, and Style to create those files too.
Now since all the files are created, we will write markup for our component I have used standard (Salesforce Lightning Design System) SLDS here for styling.
Markup for CustomLookup.cmp
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 66 67 68 69 70 71 72 73 74 75 76 77 |
<!-- Code by CafeForce || www.cafeforce.com || support@cafeforce.com || Mandatory Header --> <aura:component controller="CustomLookupController" > <!-- 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="recordCount" type="string" default="5" description="Records visible at once"/> <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="searchString" type="string" access="private" default="" description="String to search"/> <aura:attribute name="selectedRecord" type="Object" access="private" default="" description="selected Record Details" /> <aura:attribute name="recordsList" type="List" access="private" description="List of Records having data" /> <aura:attribute name="message" type="String" access="private" default="" /> <!-- Component Markup --> <div> <aura:if isTrue="{!!empty(v.label)}"> <label class="slds-form-element__label">{!v.label}</label> </aura:if> <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.label}" name="{!v.selectedRecord.value}" 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') }"> <!-- Icon, Search Bar, Search Icon --> <lightning:icon class="slds-combobox__input-entity-icon" iconName="{!v.iconName}" size="large" alternativeText="icon"/> <lightning:input aura:id="inputLookup" class="inputBox" placeholder="{!v.placeholder}" onblur="{!c.blurEvent}" onclick="{!c.showRecords}" onkeyup="{!c.searchRecords}" value="{!v.searchString}" autoComplete="off" variant="label-hidden" id="combobox-id-1" /> <lightning:icon class="slds-input__icon" iconName="utility:search" size="x-small" alternativeText="search"/> <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" style="{! 'max-height:' + (8 + (v.recordCount * 40)) + 'px' }"> <ul class="slds-listbox slds-listbox_vertical recordListBox" role="presentation"> <aura:if isTrue="{!empty(v.message)}" > <!-- To display Drop down List --> <aura:iteration items="{!v.recordsList}" var="rec" > <li id="{!rec.value}" class="slds-listbox__item eachItem" onmousedown="{!c.selectItem}"> <div class="slds-media slds-listbox__option_entity"> <lightning:icon iconName="{!v.iconName}" size="medium" alternativeText="icon" /> <span class="verticalAlign slds-truncate">{!rec.label}</span> </div> </li> </aura:iteration> <!-- To display Error Message --> <aura:set attribute="else"> <li class="slds-listbox__item"> <span class="slds-media slds-listbox__option_entity">{!v.message}</span> </li> </aura:set> </aura:if> </ul> </div> </div> </div> </div> </aura:component> <!-- Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE --> |
Now open the controller file CustomLookupController.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 |
({ // 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.value')) ) { helper.searchRecordsHelper( component, event, helper, component.get('v.value') ); } }, // When a keyword is entered in search box searchRecords : function( component, event, helper ) { if( !$A.util.isEmpty(component.get('v.searchString')) ) { helper.searchRecordsHelper( component, event, 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.currentTarget.id)) { var recordsList = component.get('v.recordsList'); var index = recordsList.findIndex(x => x.value === event.currentTarget.id) if(index != -1) { var selectedRecord = recordsList[index]; } component.set('v.selectedRecord',selectedRecord); component.set('v.value',selectedRecord.value); $A.util.removeClass(component.find('resultsDiv'),'slds-is-open'); } }, showRecords : function( component, event, helper ) { if(!$A.util.isEmpty(component.get('v.recordsList')) && !$A.util.isEmpty(component.get('v.searchString'))) { $A.util.addClass(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'); }, }) /* Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE */ |
Open the helper file CustomLookupHelper.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 |
({ searchRecordsHelper : function(component, event, helper, value) { $A.util.removeClass(component.find("Spinner"), "slds-hide"); var searchString = component.get('v.searchString'); component.set('v.message', ''); component.set('v.recordsList', []); // Calling Apex Method var action = component.get('c.fetchRecords'); action.setParams({ 'objectName' : component.get('v.objectName'), 'filterField' : component.get('v.fieldName'), 'searchString' : searchString, 'value' : value }); action.setCallback(this,function(response){ var result = response.getReturnValue(); if(response.getState() === 'SUCCESS') { if(result.length > 0) { // To check if value attribute is prepopulated or not if( $A.util.isEmpty(value) ) { component.set('v.recordsList',result); } else { component.set('v.selectedRecord', result[0]); } } else { component.set('v.message', "No Records Found for '" + searchString + "'"); } } else { // If server throws any error var errors = response.getError(); if (errors && errors[0] && errors[0].message) { component.set('v.message', errors[0].message); } } // To open the drop down list of records if( $A.util.isEmpty(value) ) $A.util.addClass(component.find('resultsDiv'),'slds-is-open'); $A.util.addClass(component.find("Spinner"), "slds-hide"); }); $A.enqueueAction(action); } }) /* Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE */ |
Here helper calls the apex class method and fetches record data from there. Now finally paste the CSS in CustomLookup.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 34 35 36 37 |
.THIS .verticalAlign { cursor: pointer; margin: 6px 0px 0px 6px!important; } .THIS .fullWidth { width: 100% !important; } .THIS .recordListBox { margin-top:0px !important; overflow-y: scroll; } .THIS .inputBox input { padding-left: 30px; } .THIS .eachItem:hover { background-color: #EEE; cursor: pointer; } .THIS .inputIcon { z-index: 99; padding: 2px 0; } /* 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: Now we will call the component.
1 2 3 4 |
<!-- Code by CafeForce || www.cafeforce.com || support@cafeforce.com --> <c:CustomLookup objectName="Account" fieldName="Name" label="Account Name" iconName="standard:account" placeholder="Enter Value" /> |
Finally, our component is ready to use. Now while calling this component, you need to pass the objectName, fieldName, and iconName to this custom component. You can also set additional attributes.
You can check the list of available icons here LDS Icons.
As this component has used aura:iteration this will work slow. Aura:iterations are comparatively slower. To use a faster version of lookup component use Lookup Lite. Click the link below.
Test Class. Create CustomLookupControllerTest.cls Apex class.
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 |
@isTest public class CustomLookupControllerTest { @isTest static void fetchRecordsTest1() { Account acc = new Account(); acc.Name = 'Test Account'; acc.Phone = '123456789'; acc.Type = 'Prospect'; insert acc; List<CustomLookupController.RecordsData> result = CustomLookupController.fetchRecords('Account', 'Name', 'Test', ''); System.assertEquals(result != null, true); } @isTest static void fetchRecordsTest2() { Account acc = new Account(); acc.Name = 'Test Account'; acc.Phone = '123456789'; acc.Type = 'Prospect'; insert acc; List<CustomLookupController.RecordsData> result = CustomLookupController.fetchRecords('Account', 'Name', 'Test', acc.Id); System.assertEquals(result.size(), 1); } @isTest static void fetchRecordsNegativeTest() { try { List<CustomLookupController.RecordsData> result = CustomLookupController.fetchRecords('', 'Name', 'Test', ''); } catch(Exception ex) { System.assertNotEquals(ex.getMessage(), NULL); } } } |
Also Check:
For any queries or suggestions, comment below.
Cheers … 🙂
Edit: Code updated. Extra useless classes are removed, small bugs, and CSS fixed.
Thanks for the great post !
Please provide the test class for that apex controller please !
Sure Mike, will share the test class here soon.
Nice one. This will help a lot of people. Can you please provide the test class for the apex controller?
Sure Tubai, will share the same soon.
hi is it possible to use arrow keys in selecting?
Yes its definitely possible to use arrow keys. Although, this component doesn’t support the arrow keys for now, will share a new one with arrow keys support soon.
Any update on this? do we have to build javascript logic for this?
Hi,
I am working on this but its LWC version. Will release a custom lookup in LWC this month with and without arrow keys support.
Is it release ? I am waiting for this.
Hi Vivek,
LWC component is ready but keyboard support is still not available.
You can check the LWC component HERE.
Thanks for the post ……Always lifesaver…Please Provide Test Class of this
Hi Ganesh, sorry for the delay.
The test class is uploaded.
Thanks, this helps me a lot, please provide the test class thanks
Thanks John.
Yes, I’ll upload test class soon.
Thanks a lot for this post!!
Can you please help me with using the input value?? “selectedRecord” is made private, I am unable to work with the input value.
Welcome Sneha.
I’m unable to understand exactly. Do you want to pre-populate the lookup or get the selected record Id. Well, in both cases you can use ‘value’ attribute.
Thnks a lot for this post
but in input box list scroller not working propperly
can please update the code
Hi Noor,
I checked back in the code and it is working fine for me. Please check if the outer div have min height property. If outer box is small, dropdown will be inside it.
Thanks for your response
actually i put your code as it is ( markup,controller,helper,css). its not working
please can you tell me where i doing mistake
Hi Noor,
Can you please share the code snippet of how you are calling this component and where are you calling it? In another Custom component or VF Page or lightning App?
I tried to use this in aura : iteration in order to pre-populate the value from the controller list but doesn’t seem working.
Also when i try to remove the value attribute from the below code snippet and try to search the field on lookup field it throws me an error “invalid operator on id field”.
Product__c is a lookup field.
Please let me know what should i do in order to pre-populate the value from controller.
Please check if the value you are passing is a valid Id or not. Also, your custom lookup component loads before your outer component passes Id value in it. Make an aura:method in custom lookup which will call doInit again and trigger this aura method after you set value in listofCaseProducts. This will re-render the custom lookup and value will be populated automatically or you can also use this –
Thanks for the response.
I am able to search the value in lookup field but the expectation is the lookup field should autopopulate the value like other fields populate the values from the listOfCaseProducts .
I also tried the code snippet of <aura:if> you provided , but i am getting an error as “Message: The value of attribute “isTrue” associated with an element type “aura:if” must not contain the ‘<‘ character.: Source”.
I just need your help in order to populate the value in lookup field as soon the page loads.
Hi Dear,
There was some code issue in my previous comment. I have resolved that, please try adding the same <aura:if > condition again.
Thanks for sharing this! It helped me alot. Cheers!
Hi Iver,
Thanks for the Appreciation 🙂
Thanks for share those code, but how can i get the value i selected on controller?
You can get the selected value in selectedRecord attribute or value attribute.
If suppose I wanted to apply a filter on lookup for example i have to get only account which related to Record type of America, how can i apply in apex?
Hi Rakesh,
You can modify the query in Apex. Just add your filter after WHERE condition in Apex.