Imagine having a single component that allows you to search on any object you want. Multi object lookup lightning component have all this with additional capabilities. Also, it’s easy to create and really cool stuff.
Lightning has many pre-built components but still, many utility components are missing. One of them is the multi-object lookup component. We will create this within a single component without using any child component or event. Multi-Object Lookup is easy to create and use. You can find the complete source code in this post.
Multi Object Lookup component Features:
- Single component
- Append any object of your choice
- Spinner to show processing
- Ability to set sObject Icon
- Label & placeholder functionality
- Ability to show error in the dropdown if any occurred.
- Pills of selected record will be shown
- Search functionality to filter results
- You can set a number of records to show at once. Default is 5
Spinner
A Spinner will be shown when you will enter a keyword in the search box until the search results are fetched and shown.
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 to the lightning component. If Apex throws any error or exception, that too will be shown in the dropdown itself.
Create MultiObjectLookupController.cls Apex class 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 |
public class MultiObjectLookupController { @AuraEnabled public static List<RecordsData> fetchRecords( String objectName, String filterField, String searchString ) { List<RecordsData> recordsDataList = new List<RecordsData>(); try { String query = 'SELECT Id, ' + filterField+ ' FROM '+objectName+ ' WHERE '+filterField+' LIKE ' + '\'' + String.escapeSingleQuotes(searchString.trim()) + '%\'' + ' LIMIT 50000'; for(SObject s : Database.query(query)){ recordsDataList.add( new RecordsData((String)s.get(filterField), (String)s.get('id')) ); } } 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()); } } return recordsDataList; } 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 Multi Object Lookup 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 MultiObjectLookup.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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
<!-- Code by CafeForce || www.cafeforce.com || support@cafeforce.com || Mandatory Header --> <aura:component controller="MultiObjectLookupController" > <!-- Attributes that can be set while component calling--> <aura:attribute name="objectList" type="List" default="" description="Objects whose records to show" /> <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="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" default="" access="private" description="String to search"/> <aura:attribute name="selectedObject" type="Object" default="" access="private" description="selected Object" /> <aura:attribute name="selectedRecord" type="Object" default="" access="private" description="selected Record Id" /> <aura:attribute name="recordsList" type="List" access="private" description="List of Records having data" /> <aura:attribute name="showObjectList" type="boolean" default="false" access="private" /> <aura:attribute name="message" type="String" default="" access="private" /> <!-- Component Markup --> <div class="slds-form-element"> <aura:if isTrue="{!!empty(v.label)}"> {! v.label } </aura:if> <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-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.selectedObject.iconName }" size="x-small" alternativeText="objectIcon"/> </aura:set> </lightning:pill> </div> <div aura:id="lookupField" class="{! if(v.selectedRecord == '','slds-show','slds-hide') }"> <!-- Icon, Search Bar, Search Icon --> <span class="slds-icon_container slds-combobox__input-entity-icon inputIcon"> <div aura:id="objectDataDiv" class="slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open" aria-haspopup="objectBox"> <button class="slds-button slds-button_icon" title="Show More" style="display: inline-flex;" onclick="{!c.showObjects}" onblur="{!c.blurObjectList}"> <lightning:icon class="objectIcons" iconName="{! v.selectedObject.iconName }" alternativeText="objectIcon"/> <lightning:icon class="dropdownIcon" iconName="utility:down" alternativeText="downIcon"/> </button> <aura:if isTrue="{!v.showObjectList}" > <div class="slds-dropdown slds-dropdown_left" role="objectBox"> <ul class="slds-dropdown__list" role="menu" aria-label="Show More"> <aura:iteration items="{! v.objectList }" var="obj" > <li id="{!obj.APIName}" class="slds-dropdown__item" role="presentation" onmousedown="{!c.selectObject}"> <a href="javascript:void(0);" role="menuitem"> <lightning:icon class="objectIcons" iconName="{!obj.iconName}" alternativeText="objectIcon"/> <span class="slds-truncate objectName">{!obj.label}</span> </a> </li> </aura:iteration> </ul> </div> </aura:if> </div> </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.blurRecordList}" 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:' + (8 + (v.recordCount * 40)) + 'px' }"> <ul class="slds-listbox slds-listbox_vertical recordListBox" role="presentation"> <aura:if isTrue="{! v.message == '' }" > <!-- To display Drop down List --> <aura:iteration items="{!v.recordsList}" var="rec" > <li id="{!rec.value}" class="slds-listbox__item" onmousedown="{!c.selectItem}"> <div class="slds-media slds-listbox__option_entity"> <span> <lightning:icon iconName="{! v.selectedObject.iconName }" alternativeText="objectIcon"></lightning:icon> </span> <span class="verticalAlign"> <span class="slds-truncate">{!rec.label}</span> </span> </div> </li> </aura:iteration> <!-- To display Message --> <aura:set attribute="else"> <li class="slds-listbox__item"> <div id="option1" class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta"> <span class="slds-media__body">{!v.message}</span> </div> </li> </aura:set> </aura:if> </ul> </div> </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 MultiObjectLookupController.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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
({ // 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'); $A.util.removeClass(component.find('objectDataDiv'), 'slds-is-open'); var objectList = component.get('v.objectList'); objectList.push( {label:'Account', APIName:'account', fieldName: 'name', iconName: 'standard:account'} ); objectList.push( {label:'Contact', APIName:'contact', fieldName: 'name', iconName: 'standard:contact'} ); objectList.push( {label:'Lead', APIName:'lead', fieldName: 'name', iconName: 'standard:lead'} ); objectList.push( {label:'Opportunity', APIName:'opportunity', fieldName: 'name', iconName: 'standard:opportunity'} ); component.set('v.objectList', objectList); component.set('v.selectedObject', component.get('v.objectList')[0]); }, showObjects : function( component, event, helper ) { component.set('v.showObjectList', true); $A.util.toggleClass(component.find('objectDataDiv'), 'slds-is-open'); }, selectObject : function( component, event, helper ) { component.set('v.showObjectList', false); if(!$A.util.isEmpty(event.currentTarget.id)) { var objectList = component.get('v.objectList'); var index = objectList.findIndex(x => x.APIName === event.currentTarget.id) if(index != -1) var selectedObject = objectList[index]; component.set('v.selectedObject', selectedObject); component.set('v.searchString', ''); } }, // When a keyword is entered in search box searchRecords : function( component, event, helper ) { $A.util.removeClass(component.find('objectDataDiv'), 'slds-is-open'); 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'); } }, // 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. blurRecordList : function( component, event, helper ){ $A.util.removeClass(component.find('resultsDiv'), 'slds-is-open'); }, blurObjectList : function( component, event, helper ){ $A.util.removeClass(component.find('objectDataDiv'), 'slds-is-open'); component.set('v.showObjectList', false); } }) /* Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE */ |
Now open the helper file MultiObjectLookupHelper.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 |
({ searchRecordsHelper : function(component, event, helper) { $A.util.removeClass(component.find("Spinner"), "slds-hide"); component.set('v.message', ''); component.set('v.recordsList', null); var selectedObject = component.get('v.selectedObject'); // Calling Apex Method var action = component.get('c.fetchRecords'); action.setParams({ 'objectName' : selectedObject.APIName, 'filterField' : selectedObject.fieldName, 'searchString' : component.get('v.searchString') }); action.setCallback(this,function(response){ var result = response.getReturnValue(); if(response.getState() === 'SUCCESS') { // To check if any records are found for searched keyword if(result.length > 0) { component.set('v.recordsList', result); } else { component.set('v.message', 'No Records Found'); } } else if(response.getState() === 'INCOMPLETE') { component.set('v.message','No Server Response or client is offline'); } else if(response.getState() === 'ERROR') { // 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 $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 MultiObjectLookup.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 38 39 40 41 42 43 44 45 |
.THIS .inputBox input{ padding-left: 52px; } .THIS .objectName { padding-left: 5px; } .THIS .dropdownIcon svg{ width: 1rem !important; height: 1rem !important; margin: 4px 0px 0px 4px !important; } .THIS .objectIcons { border-radius: 0.1rem !important; } .THIS .objectIcons svg{ width: 1.4rem !important; height: 1.4rem !important; } .THIS .recordListBox { margin-top:0px !important; overflow-y: scroll; } .THIS .fullWidth { width: 100% !important; } .THIS .verticalAlign { cursor: pointer; margin: 6px 0px 0px 6px!important; } /* 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 5 6 7 8 |
<aura:component> <aura:attribute name="objectList" type="List" default="[{label:'Apex Class', APIName:'ApexClass', fieldName: 'name', iconName: 'standard:drafts'}]" /> <aura:attribute name="value" type="String" default="" description="To pre-populate a value" /> <c:MultiObjectLookup objectList="{!v.objectList}" value="{!v.value}" label="Multi Object Lookup" /> </aura:component> |
Our component is now ready to use. Now while calling this component, you need to pass the additional objects list and attribute in which selected value will be stored. You can also set the additional attributes like setting label or placeholder or minimum records count to show at once.
How to pass the object list (ObjectList format):
Data should be passed as Object List in the attribute. Attributes to pass in each object are label, APIName, fieldName and iconName.
Label: Label to display in object selection dropdown.
APIName: API Name of the Object
fieldName: Searching to be done on the given field.
iconName: Icon displayed in object dropdown
Eg: [{‘label’: ‘University of Texas’, ‘APIName’: ‘custom_object__c’, ‘fieldName’:’Name’, ‘iconName’: ‘standard:drafts’}]
Also, Check:
For any queries or suggestions regarding this post, comment below:
Cheers … 🙂
Nice development
Thanks Pruthvi for the Appreciation 🙂
Amazing Component! Nice post.
Thanks, for the Appreciation Pooja 🙂
Very nice post 🙂
But actually, i have a conflict between the icons and the drop-down menu. This is visible by placing two components one below the other.
I tried to fix this in the css with z-index, without success.
Do you have suggestion for resolve this ?
Hi, Thanks for appreciation.
Can you share an image of the UI?
Sure, it’s like this :
https://ibb.co/hDhzcjW
Hi,
You can do a workaround. Put the z-index just on the very outer div but here the z-index of the top component should be more than the bottom one. Check out the image. You can also make this dynamic. Put z-index through JS on div on button click.
Hello, thank’s so much,
z-index work with class=“slds-form-element”
Hi Suyash,
I have implemented the above code for multi object lookup, however I am not able to pre populate the field value. I have tried passing the v.value attribute to parent component but its not showing up in child component. Can you please help me here.
Hi Bhawna,
Right now prepopulate is not present in the component. You need to modify the code and pass the SF Id to Apex and prepopulate the record.
Thanks Suyash !! That works !!
Great. Anytime?
How did you manage that?