Let’s build a Map Application using Leaflet and Lightning Components

Salesforce’s Lightning component system is quite a robust framework where you can build full pledged single-page applications in a heartbeat. I especially like it’s built-in SLDS (styles), so all you have to really think about is the logic of your application. In this walk trough, we’re building a real life map application with Lightning. We’re using Google Places for our city lookup, as well as Leaflet for our map and finally Chatter for our chat component.

The goal is to have pins on the map – which is on the right side of our page. These pins are also shown in a list format on the left hand side of the page. Once a pin is clicked, the list on the left hand side is replaced by the pin details, along with the ability to chat about that specific pin directly below. When the “Close” button is clicked, the list re-appears, as well as the map zooms to its original view.

The same behavior is achieved when the pin on the map is clicked.

Salesforce Map

We also have a custom form, that allows us to add a pin right in the same page. The form has an auto suggest field for the cities.

Note that this is going to be a high level tutorial. You should be well versed in Lightning Components and JavaScript to follow along. A working knowledge of Salesforce is also needed.

Read to get started? Let’s begin.

Setting things Up

Before we can actually start coding, let’s back up and think about what we need. We will need a map software.  We need to store the locations in a custom object in Salesforce. And we need an Apex class to fetch these records for us. Seems simple enough? Let’s continue.

Static Resources

We’re using Leaflet – an open source map software which allows us to build a map on the page. All we need to do is pass it an array of locations (Longitude + Latitude) and Leaflet will populate magically. We also need LeafletMarkerCluster, an add-on to Leaflet, which allows us to bundle our pins together. This prevents the ugly grouping of many pins together.

So add these two zip files as static resources to Salesforce:

Static Resources

Custom Object

Each pin on the map is a record from a Custom Object in Salesforce. Let’s call ours “Pin”. Go ahead and build that custom object with the fields shown below.

custom object

You also might need to enter a few records. Since we’re dealing with coordinates, you will need to Google maps and grab this information. Don’t worry – this is just for the prototype. We are building our own custom form – that will take care of all this information for us.

The Apex Class

An Apex Class can behave sort of like a hash table that can hold our functions. This is how we’re going to interact with data from our Lightning components. Go ahead and create a class called “Pin”. Let’s add a method in there that grabs all of our Pins as well:

Above is a class that has one static method “getPins“. This means that we don’t have to instantiate the class to use the method. The @auraEnabled declaration also make it callable directly from our Lightning components.

Component

Once that’s done, go ahead and create a Lightning component – let’s call ours “Pin“. Open pin.cmp and add the code below:

You will see that all we’re doing is setting our static resources up in our component file. We’re also doing controller=”Pin” in our component declaration. This means that we’re going to create an Apex class called “Pin”. We’ll get to that later.

Also note we have afterScriptsLoaded=”{!c.jsLoaded}” in our lightning scripts tag.  This is because we want the function jsLoaded to run as soon as our scripts are loaded. We don’t have this function yet – so loading the page will cause an error. We’ll get to this function soon.

Let’s continue building the markup.

Still in our .cmp file, after our scripts and styles – add our markup below :

The “aura:attribute” named “pins” is simply a holder for the pins we’re grabbing from the controller. More on this later.

The “aura:attribute” named “singlePin” is also a holder – for a pin that’s in focus. Again, more on this later.

This sets up our page. We’re splitting the page in two, one column is smaller (size_1-of-3) and the other bigger (size_2-of-3). We’re using built in Salesforce classes, with the prefix “slds”, stands for Salesforce Lightning Design System. In case you’re not familiar with it – it’s sort of like Boostrap and React fused into a single framework.

The Map

So we’re ready to load the page and initialize our Leaflet map. We have our temp data in our custom object, we should have our component – along with the JavaScript files in place. Let’s write the jsLoaded() function (this is called when our scripts are done loading):

Okay, a lot going on here. We’re setting some map default options (for more info on Leaflet, see their documentation).  We also have a very important helper function called “.callServer()“. This function is responsible for making the call to our Apex class, and returns our data. Note that I talk about callServer in a previous post. Simply add this function in your PinHelper.js file and that should be good to go.

Inside our callback we’re setting the v.pins in our .cmp file – which is an array of objects that came back from our server.

Right after we have a function called .buildMap() – which is a pretty big function and discussed in detail below.

The Map

So our pins our fetched from the server. I don’t know if you remember, that we are using a Leaflet add-on called LeafletMarkerCluster – which is what does the clustering of the pins – that are in the same proximity. What this does is – it creates a nice visual – the number of pins in a specific grouping, that you can click – and it will zoom in further to show the pins.

It also has a nice “spider” effect – for the clusters – which is really cool:

Cluster

So what we need is a way to group our pins. Notice our custom object earlier – we have a field called “Region”. A region is similar to a “State” in the United States. So every pin that share the same region – will be clustered. Make sure your test data have pins that have the same region, and some that don’t.

Don’t worry – we don’t have to figure out what region our pins need to be – remember we’re building our own form.

The .buildMap() function

Now let’s dig in to the actual code that adds and groups the pins to the map. Open PinHelper.js and add the code below:

First, let’s set some variables in our helper (lines 1-4). These will act as a container as we pass them around from our controller to helper and vice versa.

Then the actual buildMap() function. We have gone through our pins and determined the unique regions and stuffed them into helper.markerGroups. Now starting in line 39, we loop through each of pin and add them to the map – according to their respective groups (region). Passing along the necessary options for each pin.

Your map should now load with the clusters and the pins.

The List of Pins (Left Side)

On the left side of the application – we show all of the pins – sorted by latest entry. Remember that we set our pins as a component attribute in our jsLoaded() function? Now all we need to do is loop through this in our component. But first, we have to check if there’s a singlePin – why you ask? This is to determine if we clicked on a marker on the map OR we clicked on any of the item in our list (which we’re still building). We do this by using the aura:if directive.

The code below goes into the left hand section.

Let’s look at the singlePin scenario first – which is when an item is clicked. We are simply displaying some data from the singlePin – along with a button to close it (this .closeSinglePin() is not built yet). Then notice the use of  forceChatter:publisher and forceChatter:feed. These components are built in to Lightning – all we need to do is pass it a Id – and voila!

This will display our single pin nicely – along with a Chatter component right below it.

Locations with Chatter

For the list, you see how we wrapped each pin as a lightning:button? That’s because we can click each item and something will happen. We have a c.markerClick handler on each item that we haven’t built yet.

The rest is simply markup of the pin details. Let’s create that handler.

Remember, each item in our list – when clicked, will have the same behavior as clicking the pin (marker) on the map. So the above code is simply a trigger (using marker.fire()). But the check before that is to determine – if the marker is inside a cluster. If it is – we zoom to the layer first (using .zoomToShowLayer()), then fire.

Also, remember that we don’t have any behavior yet in when we click our pins (we have it as a TODO in our .buildMap() function above). Let’s go back and create that now.

The handler above reset’s the singlePin to an empty object. Then we get the lat and long from the marker (we get it from “e” – or the event). We set the view on the map according to the coordinates.  Then finally – we loop through our pins – and set the v.singlePin into the one clicked.

Lastly, the .closeSinglePin() function:

The above simply hides our Single Pin and Chatter, resets the singlePin variable, then resets the map.

The Form

Finally, we come to the data entry section – where it will make it easy for us to add pins to our map. This form will have the necessary fields for our custom object – but most importantly, the auto-suggest field – that will auto populate the longitude, latitude, region etc.

City autocomplete

I wrote a tutorial about this Auto-suggest field – using Google Places API, so I’m not going to get into that code. I’m simply going to go through the rest of the logic – that makes our form.

First off, we’re showing our form in a modal. Still in the same component, let’s add this to the lower section of our .cmp file:

The lightning:button in the beginning, is what opens up the modal. It’s up to you how you want to show this button, and who to show it to.

The next three lines you’ll notice that we’re setting some new attributes. Again, these act as a containers for the variables we’re using in our component.

The SLDS dialog box contain a close button, a header, a footer  and a backdrop. The close button triggers a function closenewPin() when clicked. We’ll get to this later. Then we have a validation error list, then the form fields.

The “City” has an onchange handler called getCities() – which grabs the predictions from our Apex class. Again this was in a previous tutorial and find out how it’s done there.  The lightning:input fields that have a class “slds-hide” – are hidden fields. These fields are automatically filled – and will require no user interaction.

Finally, the lightning:button that takes care of saving the record.

Oh, we also have lightning:notificationsLibrary at the very end – which is Salesforce’s toast notification. We use this for messaging when successful.

Saving the Record

First let’s take care of the entering our record. Open the controller and add the method “saveRecord” below

This function gets called as soon as the user clicks the “Save” button. First, we do some validation – and see if our required fields are filled in. If not, we set the validationErrors array up and exits.

Otherwise, we do a .callServer() twice. First to save the pin – and inside it’s callback, we do another .callServer() to get all of the pins. This is so that once we add a new pin – it grabs the new set of pins – so we can see it right away on the map. Remember I discussed about the .callServer() in this post.

This is the part that actually does the saving to the database. Add this to your Apex class – as another method:

Finally, we call .buildMap(), .closeModal() and .showToast(). You get what those functions are doing. Let’s add what’s missing in our helper. Open PinHelper.js and add the code below:

The helper functions above are pretty self-explanatory. For further explanation – especially about the utilities available in the $A object – refer to this question in StackOverflow.

Lastly, the above methods are in our helper, but we need to access them using our controller. Simply add the code below to our controller:

And after all is in place, our form should now be working:

Form

Conclusion

There you have it. A cool map with events, clusters, a custom form – a good start of a real world application you can build inside Salesforce. Keep in mind these are mostly using Lightning – unless we had to build it ourselves. Next up should be filtering the results, adding a better Hover callout on the pins, maybe even having the latest chatter in the hover.

The possibilities are endless.

Leave a Comment.