
Hello, and welcome to Another Salesforce Blog! Here I will be posting solutions to problems that I couldn’t find an answer to in hopes of helping those who find themselves stuck when using the Salesforce platform.
Introduction
Today I’ll be expanding on my previous post, Capturing Geolocation Using Lightning Components. If you don’t need to capture your device’s geolocation for a lead, that’s okay! This tutorial can be used on any object that has a geolocation field.
User Story
We are selling cookies door to door, and we want to keep track of the houses we’ve hit! We already captured the geolocation of each house on a lead object using our phones, now we want to be able to see where the lead is on a map.
Background
I found a nifty tutorial by Dreamforce speaker Luciano Straga that serves as a jumping off point for this follow up. The base code for this project can be found in a GitHub repo here. I’ll highlight the code that I’ve borrowed below, and break everything down line by line. The component file and controller file are mostly borrowed, but I have made some important updates to the helper code for our purposes.
Solution
Step one – Write the Lightning Component.
Here’s what our code will look like:
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,force:hasRecordId,force:hasSObjectName">
<!-- attributes -->
<aura:attribute name="recordId" type="String" />
<aura:attribute name="latitudeField" type="String" />
<aura:attribute name="longitudeField" type="String" />
<aura:attribute name="mapMarkers" type="Object" />
<aura:attribute name="zoomLevel" type="Integer" />
<aura:attribute name="simpleRecord" type="Object" />
<aura:attribute name="recordError" type="String" />
<aura:attribute name="mapReady" type="Boolean" default="false"/>
<force:recordData aura:id="record"
layoutType="FULL"
mode="VIEW"
recordId="{!v.recordId}"
fields="Name, House_Location__Latitude__s, House_Location__Longitude__s"
targetFields="{!v.simpleRecord}"
recordUpdated="{!c.handleRecordUpdated}"
/>
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<!-- the map component -->
<lightning:card iconName="action:map" title="House Map">
<p class="slds-p-horizontal_small">
<aura:if isTrue="{!and(!empty(v.latitudeField),!empty(v.longitudeField))}">
<aura:if isTrue="{!v.mapReady}">
<lightning:map mapMarkers="{!v.mapMarkers}" zoomLevel="{!v.zoomLevel}" />
<aura:set attribute="else">
<ui:message title="Error" severity="error" closable="false">
Coordinates are not defined for this {!v.sObjectName}. <br/>
Complete <b>{!v.latitudeField}</b> and <b>{!v.longitudeField}</b> fields for displaying the map.
</ui:message>
</aura:set>
</aura:if>
<aura:set attribute="else">
<ui:message title="Error" severity="error" closable="false">
Latitude and Longitude API Field Names are already set.
</ui:message>
</aura:set>
</aura:if>
</p>
</lightning:card>
</aura:component>
Let’s break it down line by line.
We want this component to be able to be used in Lightning Tabs, so we implement force:appHostable
, we want to be able to use it on Record Pages and other page types, so we implement flexipage:availableForAllPageTypes
, and we want to pull the ID from the Lead record that we are looking at, so we implement force:hasRecordId
. We also want to eventually be able to use this for multiple objects, so let’s pull in force:hasSObjectName
as well.
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,force:hasRecordId,force:hasSObjectName">
...
For our attributes, we need:
recordId
so we know which record we’re looking atlatitudeField
and alongitudeField
to keep track of where we’re putting our map markermapMarkers
andzoomLevel
attributes to display the content of our map markers and to specify how zoomed in on the map we’ll besimpleRecord
to keep track of our record detailsrecordError
to keep track of any errors we may encounter loading the record data
andmapReady
to keep track of whether or not our data is loaded yet – we’ll get to why in a second!
...
<!-- attributes -->
<aura:attribute name="recordId" type="String" />
<aura:attribute name="latitudeField" type="String" />
<aura:attribute name="longitudeField" type="String" />
<aura:attribute name="mapMarkers" type="Object" />
<aura:attribute name="zoomLevel" type="Integer" />
<aura:attribute name="simpleRecord" type="Object" />
<aura:attribute name="recordError" type="String" />
<aura:attribute name="mapReady" type="Boolean" default="false"/>
...
Next, we need a force:recordData
component. This is what we’ll use to pull information from the record we’re looking at. Details on this component can be found in this Trailhead. This part is a little bit tricky, because force:recordData
runs asynchronously. Basically, we can’t access the information on our record the second the page loads (ie, in our initialization controller), we have to wait a minute until our simplified record updates. For this reason, we specify that when recordUpdated
, we call !c.handleRecordUpdated
. This is also why we have a boolean
to verify if our map is ready to load or not, which we’ll check in our next step!
...
<force:recordData aura:id="record"
layoutType="FULL"
mode="VIEW"
recordId="{!v.recordId}"
fields="Name, House_Location__Latitude__s, House_Location__Longitude__s"
targetFields="{!v.simpleRecord}"
recordUpdated="{!c.handleRecordUpdated}"
/>
...
Lastly, we need our map component! We’ll start by creating a lightning:card
component with a title of “House Map.” We’ll check to make sure that our latitudeField
and longitudeField
values are not empty using an aura:if
component, and then we’ll check and make sure that our map is ready to load. If we pass those tests, we’ll load our lightning:map
component, if not, we’ll display an error.
...
<!-- the map component -->
<lightning:card iconName="action:map" title="House Map">
<p class="slds-p-horizontal_small">
<aura:if isTrue="{!and(!empty(v.latitudeField),!empty(v.longitudeField))}">
<aura:if isTrue="{!v.mapReady}">
<lightning:map mapMarkers="{!v.mapMarkers}" zoomLevel="{!v.zoomLevel}" />
<aura:set attribute="else">
<ui:message title="Error" severity="error" closable="false">
Coordinates are not defined for this {!v.sObjectName}. <br/>
Complete <b>{!v.latitudeField}</b> and <b>{!v.longitudeField}</b> fields for displaying the map.
</ui:message>
</aura:set>
</aura:if>
<aura:set attribute="else">
<ui:message title="Error" severity="error" closable="false">
Latitude and Longitude API Field Names are already set.
</ui:message>
</aura:set>
</aura:if>
</p>
</lightning:card>
</aura:component>
Step two – Write the Controller.
After our lightning component is complete, we’ll write our controller.
({
doInit : function (component, event, helper) {
component.set('v.zoomLevel', 12);
},
handleRecordUpdated : function(component, event, helper) {
var eventParams = event.getParams();
if(eventParams.changeType === "LOADED") {
try{
helper.setMapCoordinates(component);
}
catch(err) {
console.log("ERROR")
}
} else if(eventParams.changeType === "ERROR") {
console.log("ERROR")
// there’s an error while loading, saving, or deleting the record
}
}
})
All we want to do in our initialization function is set the zoomLevel
attribute, because, again, force:recordData
is called asynchronously.
({
doInit : function (component, event, helper) {
component.set('v.zoomLevel', 12);
},
...
Once our asynchronous call is made and our simpleObject
updates, we’ll call the handleRecordUpdated
function. We’ll use the JavaScript event.getParams()
and .changeType
to determine whether or not the record was loaded successfully, and throw an error if not. If the record was loaded successfully, we call our helper function, helper.setMapCoordinates(component)
.
handleRecordUpdated : function(component, event, helper) {
var eventParams = event.getParams();
if(eventParams.changeType === "LOADED") {
try{
helper.setMapCoordinates(component);
}
catch(err) {
console.log("ERROR")
}
} else if(eventParams.changeType === "ERROR") {
console.log("ERROR")
// there’s an error while loading, saving, or deleting the record
}
}
})
Step three – Write the Helper.
Lastly, we have to write the helper. This is where there’s some slight tweaks to the code in Luciano’s tutorial, although these changes are crucial to the function of the code.
({
setMapCoordinates : function(component, event) {
var latitude = parseFloat(component.get('v.simpleRecord').House_Location__Latitude__s);
var longitude = parseFloat(component.get('v.simpleRecord').House_Location__Longitude__s);
component.set('v.latitudeField',latitude);
component.set('v.longitudeField',longitude);
var name = component.get('v.simpleRecord').Name;
if(latitude == null || longitude == null){
component.set('v.mapReady', false);
}else{
component.set('v.mapMarkers', [
{
location: {
Latitude : latitude,
Longitude : longitude
},
title: name
}
]);
component.set('v.mapReady', true);
}
},
})
First, we set variables latitude
and longitude
to be the values of House_Location__Latitude__s
and Longitude__s
, respectively, using component.get('v.simpleRecord').House_Location__Lat/Longitude__s
. We must parse this variable as a Float
because this number must be passed into the Google Maps API as a decimal.
({
setMapCoordinates : function(component, event) {
var latitude = parseFloat(component.get('v.simpleRecord').House_Location__Latitude__s);
var longitude = parseFloat(component.get('v.simpleRecord').House_Location__Longitude__s);
...
Next, we set the values of latitudeField
and longitudeField
in our Lightning Component to the values of the variables latitude
and longitude
. We also want to get the name of our record, so we’ll assign the value to a variable name
using component.get('v.simpleRecord').Name
.
...
component.set('v.latitudeField',latitude);
component.set('v.longitudeField',longitude);
var name = component.get('v.simpleRecord').Name;
...
Lastly, we want to check and make sure our map is ready to load by making sure our latitude
and longitude
values aren’t empty. If they are, we set the value of mapReady
to false, and if not, we set the values for our mapMarkers
by passing the information pulled from the record page we are on and set mapReady
to true.
...
if(latitude == null || longitude == null){
component.set('v.mapReady', false);
}else{
component.set('v.mapMarkers', [
{
location: {
Latitude : latitude,
Longitude : longitude
},
title: name
}
]);
component.set('v.mapReady', true);
}
},
})
It’s a little change, but it makes all the difference in the world.
Step four – Add Lightning Component to Lightning Record Page.
Once everything is saved, all we have to do is add it to our record page.
Open up a lead and select “Edit Page” under the Setup gear.

Once on the Lightning App Builder page, on the left hand side, search for our custom Lightning Component.

Drag this component anywhere on the page that you would like to place it.
Once the component is placed, on the right hand side, there will be an option to add a filter for our component’s visibility. We don’t want to waste resources by showing a map if we don’t have coordinates for a lead, so let’s hide the map if our House_Location__c field is empty.

We’ll add a filter to hide the map if House_Location__Latitude__s is equal to zero…

…and another one to filter for if our House_Location__Longitude__s is equal to zero.

We’ll set the component to show when both filters are true.

Once we save our Lightning Record Page, we’ll be able to see the map once the Geolocation is populated on our record!

Once populated, either manually or via our Capture House Location button from our previous tutorial, a map will display.

Thanks for reading, let me know if you have any comments or questions!
-Evelyn, Another Salesforce Blog

Make a one-time donation
Make a monthly donation
Make a yearly donation
Choose an amount
Or enter a custom amount
Help keep Another Salesforce Blog on the internet by donating today!
Your contribution is appreciated.
Your contribution is appreciated.
DonateDonate monthlyDonate yearly