├── .gitignore ├── README.md ├── metadata └── aura │ ├── AccountLookup │ ├── AccountLookup.cmp │ ├── AccountLookup.design │ └── AccountLookupController.js │ ├── ClearLookupId │ └── ClearLookupId.evt │ ├── LookupSObject │ ├── LookupSObject.cmp │ ├── LookupSObjectController.js │ └── LookupSObjectHelper.js │ ├── UpdateLookupId │ └── UpdateLookupId.evt │ └── svg │ ├── svg.cmp │ └── svgRenderer.js └── src └── classes ├── LookupSObjectController.cls └── LookupSObjectControllerTest.cls /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .settings 3 | salesforce.schema 4 | Referenced Packages 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightning-Lookup 2 | Salesforce Lightning Lookup Component 3 | -------------------------------------------------------------------------------- /metadata/aura/AccountLookup/AccountLookup.cmp: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /metadata/aura/AccountLookup/AccountLookup.design: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /metadata/aura/AccountLookup/AccountLookupController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * (c) Tony Scott. This code is provided as is and without warranty of any kind. 3 | * 4 | * This work by Tony Scott is licensed under a Creative Commons Attribution 3.0 Unported License. 5 | * http://creativecommons.org/licenses/by/3.0/deed.en_US 6 | */ 7 | ({ 8 | /** 9 | * Handler for receiving the updateLookupIdEvent event 10 | */ 11 | handleAccountIdUpdate : function(cmp, event, helper) { 12 | // Get the Id from the Event 13 | var accountId = event.getParam("sObjectId"); 14 | 15 | // Get the Instance Id from the Event 16 | var instanceId = event.getParam("instanceId"); 17 | 18 | // Determine the instance Id of the component that fired the event 19 | if (instanceId == "MyAccount") 20 | { 21 | // Set the Id bound to the View 22 | cmp.set('v.recordId', accountId); 23 | } 24 | else 25 | { 26 | console.log('Unknown instance id: ' + instanceId); 27 | } 28 | }, 29 | 30 | /** 31 | * Handler for receiving the clearLookupIdEvent event 32 | */ 33 | handleAccountIdClear : function(cmp, event, helper) { 34 | // Get the Instance Id from the Event 35 | var instanceId = event.getParam("instanceId"); 36 | 37 | // Determine the instance Id of the component that fired the event 38 | if (instanceId == "MyAccount") 39 | { 40 | // Clear the Id bound to the View 41 | cmp.set('v.recordId', null); 42 | } 43 | else 44 | { 45 | console.log('Unknown instance id: ' + instanceId); 46 | } 47 | } 48 | }) -------------------------------------------------------------------------------- /metadata/aura/ClearLookupId/ClearLookupId.evt: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /metadata/aura/LookupSObject/LookupSObject.cmp: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 17 | 19 | 21 | 23 | 25 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | {!v.label} 40 | 41 | 42 | 43 | 44 | 45 | 46 | {!v.searchString} 47 | 48 | 49 | 50 | Remove 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | "{!v.searchString}" in {!v.pluralLabel} 64 | 65 | 66 | 67 | 68 | 69 | 70 | {!match.SObjectLabel} 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /metadata/aura/LookupSObject/LookupSObjectController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * (c) Tony Scott. This code is provided as is and without warranty of any kind. 3 | * 4 | * This work by Tony Scott is licensed under a Creative Commons Attribution 3.0 Unported License. 5 | * http://creativecommons.org/licenses/by/3.0/deed.en_US 6 | */ 7 | ({ 8 | /** 9 | * Search an SObject for a match 10 | */ 11 | search : function(cmp, event, helper) { 12 | helper.doSearch(cmp); 13 | }, 14 | 15 | /** 16 | * Select an SObject from a list 17 | */ 18 | select: function(cmp, event, helper) { 19 | helper.handleSelection(cmp, event); 20 | }, 21 | 22 | /** 23 | * Clear the currently selected SObject 24 | */ 25 | clear: function(cmp, event, helper) { 26 | helper.clearSelection(cmp); 27 | } 28 | }) -------------------------------------------------------------------------------- /metadata/aura/LookupSObject/LookupSObjectHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * (c) Tony Scott. This code is provided as is and without warranty of any kind. 3 | * 4 | * This work by Tony Scott is licensed under a Creative Commons Attribution 3.0 Unported License. 5 | * http://creativecommons.org/licenses/by/3.0/deed.en_US 6 | */ 7 | ({ 8 | /** 9 | * Perform the SObject search via an Apex Controller 10 | */ 11 | doSearch : function(cmp) { 12 | // Get the search string, input element and the selection container 13 | var searchString = cmp.get('v.searchString'); 14 | var inputElement = cmp.find('lookup'); 15 | var lookupList = cmp.find('lookuplist'); 16 | 17 | // Clear any errors and destroy the old lookup items container 18 | inputElement.set('v.errors', null); 19 | 20 | // We need at least 2 characters for an effective search 21 | if (typeof searchString === 'undefined' || searchString.length < 2) 22 | { 23 | // Hide the lookuplist 24 | $A.util.addClass(lookupList, 'slds-hide'); 25 | return; 26 | } 27 | 28 | // Show the lookuplist 29 | $A.util.removeClass(lookupList, 'slds-hide'); 30 | 31 | // Get the API Name 32 | var sObjectAPIName = cmp.get('v.sObjectAPIName'); 33 | 34 | // Create an Apex action 35 | var action = cmp.get('c.lookup'); 36 | 37 | // Mark the action as abortable, this is to prevent multiple events from the keyup executing 38 | action.setAbortable(); 39 | 40 | // Set the parameters 41 | action.setParams({ "searchString" : searchString, "sObjectAPIName" : sObjectAPIName}); 42 | 43 | // Define the callback 44 | action.setCallback(this, function(response) { 45 | var state = response.getState(); 46 | 47 | // Callback succeeded 48 | if (cmp.isValid() && state === "SUCCESS") 49 | { 50 | // Get the search matches 51 | var matches = response.getReturnValue(); 52 | 53 | // If we have no matches, return nothing 54 | if (matches.length == 0) 55 | { 56 | cmp.set('v.matches', null); 57 | return; 58 | } 59 | 60 | // Store the results 61 | cmp.set('v.matches', matches); 62 | } 63 | else if (state === "ERROR") // Handle any error by reporting it 64 | { 65 | var errors = response.getError(); 66 | 67 | if (errors) 68 | { 69 | if (errors[0] && errors[0].message) 70 | { 71 | this.displayToast('Error', errors[0].message); 72 | } 73 | } 74 | else 75 | { 76 | this.displayToast('Error', 'Unknown error.'); 77 | } 78 | } 79 | }); 80 | 81 | // Enqueue the action 82 | $A.enqueueAction(action); 83 | }, 84 | 85 | /** 86 | * Handle the Selection of an Item 87 | */ 88 | handleSelection : function(cmp, event) { 89 | // Resolve the Object Id from the events Element Id (this will be the tag) 90 | var objectId = this.resolveId(event.currentTarget.id); 91 | 92 | // The Object label is the inner text) 93 | var objectLabel = event.currentTarget.innerText; 94 | 95 | // Log the Object Id and Label to the console 96 | console.log('objectId=' + objectId); 97 | console.log('objectLabel=' + objectLabel); 98 | 99 | // Create the UpdateLookupId event 100 | var updateEvent = cmp.getEvent("updateLookupIdEvent"); 101 | 102 | // Get the Instance Id of the Component 103 | var instanceId = cmp.get('v.instanceId'); 104 | 105 | // Populate the event with the selected Object Id and Instance Id 106 | updateEvent.setParams({ 107 | "sObjectId" : objectId, "instanceId" : instanceId 108 | }); 109 | 110 | // Fire the event 111 | updateEvent.fire(); 112 | 113 | // Update the Searchstring with the Label 114 | cmp.set("v.searchString", objectLabel); 115 | 116 | // Hide the Lookup List 117 | var lookupList = cmp.find("lookuplist"); 118 | $A.util.addClass(lookupList, 'slds-hide'); 119 | 120 | // Hide the Input Element 121 | var inputElement = cmp.find('lookup'); 122 | $A.util.addClass(inputElement, 'slds-hide'); 123 | 124 | // Show the Lookup pill 125 | var lookupPill = cmp.find("lookup-pill"); 126 | $A.util.removeClass(lookupPill, 'slds-hide'); 127 | 128 | // Lookup Div has selection 129 | var inputElement = cmp.find('lookup-div'); 130 | $A.util.addClass(inputElement, 'slds-has-selection'); 131 | 132 | }, 133 | 134 | /** 135 | * Clear the Selection 136 | */ 137 | clearSelection : function(cmp) { 138 | // Create the ClearLookupId event 139 | var clearEvent = cmp.getEvent("clearLookupIdEvent"); 140 | 141 | // Get the Instance Id of the Component 142 | var instanceId = cmp.get('v.instanceId'); 143 | 144 | // Populate the event with the Instance Id 145 | clearEvent.setParams({ 146 | "instanceId" : instanceId 147 | }); 148 | 149 | // Fire the event 150 | clearEvent.fire(); 151 | 152 | // Clear the Searchstring 153 | cmp.set("v.searchString", ''); 154 | 155 | // Hide the Lookup pill 156 | var lookupPill = cmp.find("lookup-pill"); 157 | $A.util.addClass(lookupPill, 'slds-hide'); 158 | 159 | // Show the Input Element 160 | var inputElement = cmp.find('lookup'); 161 | $A.util.removeClass(inputElement, 'slds-hide'); 162 | 163 | // Lookup Div has no selection 164 | var inputElement = cmp.find('lookup-div'); 165 | $A.util.removeClass(inputElement, 'slds-has-selection'); 166 | }, 167 | 168 | /** 169 | * Resolve the Object Id from the Element Id by splitting the id at the _ 170 | */ 171 | resolveId : function(elmId) 172 | { 173 | var i = elmId.lastIndexOf('_'); 174 | return elmId.substr(i+1); 175 | }, 176 | 177 | /** 178 | * Display a message 179 | */ 180 | displayToast : function (title, message) 181 | { 182 | var toast = $A.get("e.force:showToast"); 183 | 184 | // For lightning1 show the toast 185 | if (toast) 186 | { 187 | //fire the toast event in Salesforce1 188 | toast.setParams({ 189 | "title": title, 190 | "message": message 191 | }); 192 | 193 | toast.fire(); 194 | } 195 | else // otherwise throw an alert 196 | { 197 | alert(title + ': ' + message); 198 | } 199 | } 200 | }) -------------------------------------------------------------------------------- /metadata/aura/UpdateLookupId/UpdateLookupId.evt: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /metadata/aura/svg/svg.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /metadata/aura/svg/svgRenderer.js: -------------------------------------------------------------------------------- 1 | ({ 2 | render: function(component, helper) { 3 | //grab attributes from the component markup 4 | var classname = component.get("v.class"); 5 | var xlinkhref = component.get("v.xlinkHref"); 6 | var ariaHidden = component.get("v.ariaHidden"); 7 | 8 | //return an svg element w/ the attributes 9 | var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 10 | svg.setAttribute('class', classname); 11 | svg.setAttribute('aria-hidden', ariaHidden); 12 | svg.innerHTML = ''; 13 | return svg; 14 | } 15 | }) -------------------------------------------------------------------------------- /src/classes/LookupSObjectController.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * (c) Tony Scott. This code is provided as is and without warranty of any kind. 3 | * 4 | * This work by Tony Scott is licensed under a Creative Commons Attribution 3.0 Unported License. 5 | * http://creativecommons.org/licenses/by/3.0/deed.en_US 6 | */ 7 | 8 | /** 9 | * Apex Controller for looking up an SObject via SOSL 10 | */ 11 | public with sharing class LookupSObjectController 12 | { 13 | /** 14 | * Aura enabled method to search a specified SObject for a specific string 15 | */ 16 | @AuraEnabled 17 | public static Result[] lookup(String searchString, String sObjectAPIName) 18 | { 19 | // Sanitze the input 20 | String sanitizedSearchString = String.escapeSingleQuotes(searchString); 21 | String sanitizedSObjectAPIName = String.escapeSingleQuotes(sObjectAPIName); 22 | 23 | List results = new List(); 24 | 25 | // Build our SOSL query 26 | String searchQuery = 'FIND \'' + sanitizedSearchString + '*\' IN ALL FIELDS RETURNING ' + sanitizedSObjectAPIName + '(id,name) Limit 50'; 27 | 28 | // Execute the Query 29 | List> searchList = search.query(searchQuery); 30 | 31 | // Create a list of matches to return 32 | for (SObject so : searchList[0]) 33 | { 34 | results.add(new Result((String)so.get('Name'), so.Id)); 35 | } 36 | 37 | return results; 38 | } 39 | 40 | /** 41 | * Inner class to wrap up an SObject Label and its Id 42 | */ 43 | public class Result 44 | { 45 | @AuraEnabled public String SObjectLabel {get; set;} 46 | @AuraEnabled public Id SObjectId {get; set;} 47 | 48 | public Result(String sObjectLabel, Id sObjectId) 49 | { 50 | this.SObjectLabel = sObjectLabel; 51 | this.SObjectId = sObjectId; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/classes/LookupSObjectControllerTest.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * (c) Tony Scott. This code is provided as is and without warranty of any kind. 3 | * 4 | * This work by Tony Scott is licensed under a Creative Commons Attribution 3.0 Unported License. 5 | * http://creativecommons.org/licenses/by/3.0/deed.en_US 6 | */ 7 | @isTest 8 | private class LookupSObjectControllerTest 9 | { 10 | @isTest static void testSearch() { 11 | // Create some accounts 12 | Account abc = new Account(Name = 'ABC Account'); 13 | Account xyz = new Account(Name = 'XYZ Account'); 14 | 15 | List accounts = new List { abc, xyz }; 16 | 17 | insert accounts; 18 | 19 | Id[] fixedSearchResults = new Id[] { xyz.Id }; 20 | Test.setFixedSearchResults(fixedSearchResults); 21 | 22 | List results = LookupSObjectController.lookup('xy', 'Account'); 23 | 24 | System.assertEquals(1, results.size()); 25 | System.assertEquals(xyz.Name, results[0].SObjectLabel); 26 | System.assertEquals(xyz.Id, results[0].SObjectId); 27 | } 28 | } --------------------------------------------------------------------------------