This blog provides an end-to-end solution to pre-populate address on Salesforce standard layout, custom lwc component for internal org and communities using google API.
- Standard out of the box feature with Map and Location Settings.
- LWC component with show-address-lookup , out of the box feature.
- Complete customization solution with Google API integration in LWC component.
(Since the above two options are not supported in the communities).
Salesforce Standard out of box Address Lookup feature.
To enable the Address Lookup in Salesforce, search for Maps in Setup and enable the following setting:
Address Lookup in Standard Layouts:
On enabling the above setting, the Address Lookup will be available in the Standard layouts for the Address fields.
Address search field:
Address data fed into the respective fields:
2. Address Lookup in Custom Lightning Web Components:
The below solution works only if the Map setting is enabled in your Salesforce org.
The code for the above LWC Component is:
<lightning-input-address
address-label=”Address”
street-label=”Street”
city-label=”City”
country-label=”Country”
province-label=”Province”
postal-code-label=”PostalCode”
street=””
city=””
country=””
province=””
postal-code=””
required
field-level-help=”Help Text for inputAddress field”
show-address-lookup=true
onchange={handleAddressChange} />
</lightning-input-address/>
The attribute ‘show-address-lookup’, when set to true will get the lookup on the component.
The code for the above LWC Component is:
handleAddressChange(event) { this.account.mailingStreet = event.detail.street; this.account.mailingCity = event.detail.city; this.account.mailingCountry = event.detail.country; this.account.mailingPostalCode = event.detail.postcode; }
The ‘show-address-lookup’ attribute does not work in the Salesforce Communities.
References:
https://developer.salesforce.com/docs/component-library/bundle/lightning:inputAddress/documentation
3. Address Lookup in Salesforce Communities:
To implement the Address Lookup in Salesforce Communities, we need to integrate the Google Places API.
Please follow the below link to get a Google Developer Account:
The Billing details are available in the below link:
https://developers.google.com/maps/documentation/places/web-service/usage-and-billing
The rest of this blog details the implementation of LWC component to search and autocomplete addresses, integrating with Google Places API.
Address Search:
This is the first API call, to get the possible suggestions from Google. The address search in Google Places API uses the Place Autocomplete, which gets the possible places, that match with the input text.
https://developers.google.com/maps/documentation/places/web-service/autocomplete
Address Detail:
This is the API call to get the details of a place chosen by the user, using the place ID.
Reference:
https://developers.google.com/maps/documentation/places/web-service/details
LWC Component Integrated with Google Places API:
Custom Setting:
The below snapshot indicates the Custom Setting configuration, used to store the API Key details of the Google Places API:
AddressLookupDetails MockResponse:
{ "html_attributions": [], "result": { "address_components": [ { "long_name": "TF3 4JH", "short_name": "TF3 4JH", "types": [ "postal_code" ] }, { "long_name": "Telford", "short_name": "Telford", "types": [ "postal_town" ] }, { "long_name": "Shropshire", "short_name": "Shropshire", "types": [ "administrative_area_level_2", "political" ] }, { "long_name": "England", "short_name": "England", "types": [ "administrative_area_level_1", "political" ] }, { "long_name": "United Kingdom", "short_name": "GB", "types": [ "country", "political" ] } ], "adr_address": "Telford TF3 4JH, UK", "formatted_address": "Telford TF3 4JH, UK", "geometry": { "location": { "lat": 52.6724488, "lng": -2.4425239 }, "viewport": { "northeast": { "lat": 52.67413585574869, "lng": -2.441274692484295 }, "southwest": { "lat": 52.6714378951657, "lng": -2.444574120171743 } } }, "icon": "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/geocode-71.png", "icon_background_color": "#7B9EB0", "icon_mask_base_uri": "https://maps.gstatic.com/mapfiles/place_api/icons/v2/generic_pinlet", "name": "TF3 4JH", "place_id": "ChIJk7hOBoZ_cEgRUDnpw3KBzUk", "reference": "ChIJk7hOBoZ_cEgRUDnpw3KBzUk", "types": [ "postal_code" ], "url": "https://maps.google.com/?q=TF3+4JH&ftid=0x48707f86064eb893:0x49cd8172c3e93950", "utc_offset": 60 }, "status": "OK" }
AddressLookupDetails MockResponse:
{ "predictions": [ { "description": "Telford TF3 3AT, UK", "matched_substrings": [ { "length": 3, "offset": 8 } ], "place_id": "ChIJKRvpnvh_cEgRppCTUhWrmI8", "reference": "ChIJKRvpnvh_cEgRppCTUhWrmI8", "structured_formatting": { "main_text": "TF3 3AT", "main_text_matched_substrings": [ { "length": 3, "offset": 0 } ], "secondary_text": "Telford, UK" }, "terms": [ { "offset": 0, "value": "Telford" }, { "offset": 8, "value": "TF3 3AT" }, { "offset": 17, "value": "UK" } ], "types": [ "postal_code", "geocode" ] }, { "description": "Telford TF3, UK", "matched_substrings": [ { "length": 3, "offset": 8 } ], "place_id": "ChIJCWNyiQl_cEgRTIROLK4L03o", "reference": "ChIJCWNyiQl_cEgRTIROLK4L03o", "structured_formatting": { "main_text": "TF3", "main_text_matched_substrings": [ { "length": 3, "offset": 0 } ], "secondary_text": "Telford, UK" }, "terms": [ { "offset": 0, "value": "Telford" }, { "offset": 8, "value": "TF3" }, { "offset": 13, "value": "UK" } ], "types": [ "postal_code_prefix", "postal_code", "geocode" ] }, { "description": "Telford TF3 4JH, UK", "matched_substrings": [ { "length": 3, "offset": 8 } ], "place_id": "ChIJk7hOBoZ_cEgRUDnpw3KBzUk", "reference": "ChIJk7hOBoZ_cEgRUDnpw3KBzUk", "structured_formatting": { "main_text": "TF3 4JH", "main_text_matched_substrings": [ { "length": 3, "offset": 0 } ], "secondary_text": "Telford, UK" }, "terms": [ { "offset": 0, "value": "Telford" }, { "offset": 8, "value": "TF3 4JH" }, { "offset": 17, "value": "UK" } ], "types": [ "postal_code", "geocode" ] }, { "description": "Telford TF3 3BA, UK", "matched_substrings": [ { "length": 3, "offset": 8 } ], "place_id": "ChIJ-TQZ_PZ_cEgRO6EYkC-5XXY", "reference": "ChIJ-TQZ_PZ_cEgRO6EYkC-5XXY", "structured_formatting": { "main_text": "TF3 3BA", "main_text_matched_substrings": [ { "length": 3, "offset": 0 } ], "secondary_text": "Telford, UK" }, "terms": [ { "offset": 0, "value": "Telford" }, { "offset": 8, "value": "TF3 3BA" }, { "offset": 17, "value": "UK" } ], "types": [ "postal_code", "geocode" ] }, { "description": "Telford TF3 3BD, UK", "matched_substrings": [ { "length": 3, "offset": 8 } ], "place_id": "ChIJ98h38yCAekgRCmROOqkNxpI", "reference": "ChIJ98h38yCAekgRCmROOqkNxpI", "structured_formatting": { "main_text": "TF3 3BD", "main_text_matched_substrings": [ { "length": 3, "offset": 0 } ], "secondary_text": "Telford, UK" }, "terms": [ { "offset": 0, "value": "Telford" }, { "offset": 8, "value": "TF3 3BD" }, { "offset": 17, "value": "UK" } ], "types": [ "postal_code", "geocode" ] } ], "status": "OK" }
Static Resource:
The above MockResponses are to be added in the Static Resource, for the purpose of testing the API Callouts:
Apex Wrapper Classes:
The below two classes are the API Responses, converted into Apex Classes:
AddressListWrapper:
public class AddressListWrapper { @AuraEnabled public Listpredictions; public String status; public class cls_predictions { @AuraEnabled public String description; @AuraEnabled public String place_id; @AuraEnabled public String reference; } public static AddressListWrapper parse(String json){ return (AddressListWrapper) System.JSON.deserialize(json, AddressListWrapper.class); } }
AddressDetailWrapper:
public class AddressDetailWrapper { @AuraEnabled public Result result; public String status; public class Result { @AuraEnabled public Listaddress_components; public String adr_address; public String business_status; public String formatted_address; public String place_id; } public class AddressComponent { @AuraEnabled public String long_name; @AuraEnabled public String short_name; @AuraEnabled public List types {get;set;} } public static AddressDetailWrapper parse(String json){ return (AddressDetailWrapper) System.JSON.deserialize(json, AddressDetailWrapper.class); } }
Apex Class for API Callout:
public class AddressSearchController { private static final String suggestionURL = 'https://maps.googleapis.com/maps/api/place/autocomplete/json?input='; private static final String placesURL = 'https://maps.googleapis.com/maps/api/place/details/json?placeid='; @AuraEnabled public static ListgetPlaces(String input) { String url = suggestionURL + EncodingUtil.urlEncode(input, 'UTF-8') + getAPIKey(); String response = getResponse(url); AddressListWrapper addressList = AddressListWrapper.parse(response); return addressList.predictions; } @AuraEnabled public static AddressData getPlaceDetails(String placeId) { AddressData addressData = new AddressData(); String url = placesURL + EncodingUtil.urlEncode(placeId, 'UTF-8') + getAPIKey(); String response = getResponse(url); AddressDetailWrapper addressDetail = AddressDetailWrapper.parse(response); addressData = constructAddressData(addressDetail); return addressData; } private static String getResponse(String url) { String responseBody = ''; Http h = new Http(); HttpRequest req = new HttpRequest(); HttpResponse res = new HttpResponse(); req.setMethod('GET'); req.setEndpoint(url); req.setTimeout(120000); res = h.send(req); responseBody = res.getBody(); return responseBody; } @TestVisible private static String getAPIKey() { String apiKey = ''; for(APIKeysForAuthorization__c apiDetails : APIKeysForAuthorization__c.getall().values()) { if(apiDetails.Name == 'GooglePlacesAPI') { apiKey = apiDetails.API_Key__c; } } apiKey = '&key=' + apiKey; return apiKey; } private static AddressData constructAddressData(AddressDetailWrapper addressDetail) { AddressData addressData = new AddressData(); if(addressDetail != null) { for(AddressDetailWrapper.AddressComponent addressComponent: addressDetail.result.address_components) { if(addressComponent.types.contains('postal_code')) { addressData.postcode = addressComponent.long_name; } if(addressComponent.types.contains('postal_town')) { addressData.city = addressComponent.long_name; } if(addressComponent.types.contains('country')) { addressData.country = addressComponent.long_name; } if(addressComponent.types.contains('route')) { addressData.street = addressComponent.long_name; } } } return addressData; } // Class to hold the details of the selected address public class AddressData { @AuraEnabled public String street {get; set;} @AuraEnabled public String city {get; set;} @AuraEnabled public String country {get; set;} @AuraEnabled public String postcode {get; set;} } }
Testing the Address Lookup component Apex:
@isTest public class AddressSearchControllerTest { @isTest static void testGetSuggestions() { Test.setMock(HttpCalloutMock.class, new AddressLookupMock('Suggestions')); Test.startTest(); AddressSearchController.getSuggestions('TF3'); //AddressSearchController.getPlaceDetails('TF3'); Test.stopTest(); } @isTest static void testPlaceDetails() { Test.setMock(HttpCalloutMock.class, new AddressLookupMock('Place')); Test.startTest(); //AddressSearchController.getSuggestions('TF3'); AddressSearchController.getPlaceDetails('TF3'); Test.stopTest(); } @isTest static void testGetAPIKey() { ListsettingsList = new List (); APIKeysForAuthorization__c config = new APIKeysForAuthorization__c(); config.Name = 'GooglePlacesAPI'; config.API_Key__c = 'ABCD12345'; settingsList.add(config); insert settingsList; Test.startTest(); String apiKey = AddressSearchController.getAPIKey(); System.assertEquals(true, apiKey.equals('&key=ABCD12345')); Test.stopTest(); } }
Testing the Address Lookup component Apex:
@isTest public class AddressSearchControllerTest { @isTest static void testGetSuggestions() { Test.setMock(HttpCalloutMock.class, new AddressLookupMock('Suggestions')); Test.startTest(); AddressSearchController.getSuggestions('TF3'); //AddressSearchController.getPlaceDetails('TF3'); Test.stopTest(); } @isTest static void testPlaceDetails() { Test.setMock(HttpCalloutMock.class, new AddressLookupMock('Place')); Test.startTest(); //AddressSearchController.getSuggestions('TF3'); AddressSearchController.getPlaceDetails('TF3'); Test.stopTest(); } @isTest static void testGetAPIKey() { ListsettingsList = new List (); APIKeysForAuthorization__c config = new APIKeysForAuthorization__c(); config.Name = 'GooglePlacesAPI'; config.API_Key__c = 'ABCD12345'; settingsList.add(config); insert settingsList; Test.startTest(); String apiKey = AddressSearchController.getAPIKey(); System.assertEquals(true, apiKey.equals('&key=ABCD12345')); Test.stopTest(); } }
AddressLookupMock:
@isTest global class AddressLookupMock implements HttpCalloutMock { String apiChosen = ''; public AddressLookupMock(String apiName) { apiChosen = apiName; } // Implement this interface method global HTTPResponse respond(HTTPRequest req) { StaticResource sr; if(apiChosen == 'Suggestions') sr = [select id,body from StaticResource Where Name = 'AddressLookupSuggestionsMock']; if(apiChosen == 'Place') sr = [select id,body from StaticResource Where Name = 'AddressLookupDetailsMock']; String respStr = sr.body.toString(); // Create a fake response HttpResponse res = new HttpResponse(); res.setHeader('Content-Type', 'application/json'); res.setBody(respStr); res.setStatusCode(200); return res; } }
LWC Code for AddressLookup component:
<template>
<lightning-input label=”Address Lookup” data-name=”location” onchange={getPlaces} placeholder=”Search by Postcode/Adddress”></lightning-input>
<div style=”background: white;” id=”listbox-id-1″ class=”slds-dropdown_length-with-icon-7 slds-dropdown_fluid” role=”listbox”>
<ul class=”slds-listbox slds-listbox_vertical” role=”presentation”>
<template if:true={places} for:each={places} for:item=”place” for:index=”index”>
<li onclick={handleSelect} role=”presentation” class=”slds-listbox__item” data-record-id={place.place_id} key={place.place_id}>
<div data-id={place.place_id} class=”slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta” role=”option”>
<span class=”slds-media__body”>
<span class=”slds-listbox__option-text slds-listbox__option-text_entity”>
{place.description}
</span>
</span>
</div>
</li>
</template>
</ul>
</div>
</template>
JS Controller:
import { LightningElement } from 'lwc'; import getPlaces from '@salesforce/apex/AddressSearchController.getPlaces'; import getPlaceDetails from '@salesforce/apex/AddressSearchController.getPlaceDetails'; export default class AddressLookup extends LightningElement { places; getPlaces(event) { if(event.detail.value) { getPlaces({input: event.detail.value}) .then(result => { this.places = JSON.parse(JSON.stringify(result)); }).catch(error => { console.log('error ' + JSON.stringify(error)); }); } else { this.places = []; } } handleSelect(event) { getPlaceDetails({placeId: event.currentTarget.dataset.recordId}) .then(result => { this.places = []; const selectedEvent = new CustomEvent("addressselect", {detail: JSON.parse(JSON.stringify(result))}); this.dispatchEvent(selectedEvent); }).catch(error => { console.log('error ' + JSON.stringify(error)); }); } }
Method to be implemented in the Caller’s JS Controller:
handleOnAddressSelect(event) { this.account.mailingStreet = event.detail.street; this.account.mailingCity = event.detail.city; this.account.mailingCountry = event.detail.country; this.account.mailingPostalCode = event.detail.postcode; }
Using the AddressLookup component in another LWC Component:
<c-address-lookup onaddressselect={handleOnAddressSelect}>
</c-address-lookup>
We are Innovalleyworks , We are a passionate team of developers, best thinkers and consultants who can solve anything and everything.
With our highly engaging team, you can easily bring the vision to all your business ventures come true.
We have team, put your problem, get your solution