Since I was checking Force.com documentation in order to pass Platform Developer Transition exam I had the opportunity to learn more about some topics that I don’t usually use on my daily job.
One of those are Maps, and I’m not talking about Collections (Lists, Sets and Maps). I would like to talk about locations on this post.
Till some releases ago, the only way to add a location into your visualforce page, was via JavaScript. Let’s see an example. (Bellow use case is based on this Salesforce Trailhead)
I’m working on a healthcare app, and we would like to show a map on every doctor’s record, so that, the customer would be able to find the location easily.
In order to get it, I will create a visualforce page with the map and include it in the standard layout.
JavaScript Map Version
First thing is to add the listener:
google.maps.event.addDomListener(window, 'load', initialize);
that call an initialize function
var map; function initialize() { var mapOptions = { center: new google.maps.LatLng(43.2616465, -2.9393368), zoom: 15 }; map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions); loadDoctor(); }
Where we define map properties, like latitude, longitude and zoom.
But this function call another, loadDoctor
function loadDoctor() { Visualforce.remoting.Manager.invokeAction( '{!$RemoteAction.DoctorsController.findTheDoctor}', '{!Id}', function(result, event) { if (event.status) { var name = result.SurgeryName__c; var lat = result.Location__Latitude__s; var lng = result.Location__Longitude__s; addMarker(name, lat, lng); } else { alert(event.message); } }, {escape: true} ); }
Above function uses a RemoteAction method, so we know that a Controller is included on this page. This communication with the controller just returns information about the Doctor’s records like his place name and the Location. Maybe you are wondering what below line means.
Location__Latitude__s and Location__Longitude__s
Location is a Geolocation field, and, in the end, it is a Compound field, so in order to see the information, we need to decouple into its “elements”, Latitude and Longitude.
Finally, on above method, we have a call to another function that will add the marker on the google map
function addMarker(name, lat, lng) { var marker = new google.maps.Marker ({ position: new google.maps.LatLng(lat, lng), map: map, title: name }); }
With this code, the result is this one:
Apex Map Version
Is there a different way to get the same result? Yes !! Since Spring ’15 we have a new tag available on visualforce pages: apex:map and with this piece of code:
<apex:map width="100%" height="100%" mapType="roadmap" zoomLevel="15" center="43.2616465, -2.9393368"> <apex:mapMarker title="{!Doctor__c.SurgeryName__c}" position="{!Doctor__c.Location__Latitude__s}, {!Doctor__c.Location__Longitude__s}"> </apex:mapMarker> </apex:map>
we can get exactly what we had on the javascript code as we can see on below image that compares both maps. Left side uses apex:maps whereas on the right side the map is created with javascripts.
More about apex:map
Attributes
- Height and Width could be defined as pixels or percentage
- Center could be used with Location data, Address and also with JSON format. From my point of view, it would be easier to create a method in the controller with the data
public Map<String,Double> getDoctorsCenter() { Map<String,Double> mapCenter = new Map<String,Double> { 'latitude' => 43.2616465, 'longitude' => -2.9393368 }; return mapCenter; }
and use the method in the visualforce page.
<apex:map width="100%" height="100%" mapType="roadmap" zoomLevel="15" center="{!doctorsCenter}">
Above example set geolocation with hardcode but if we have this information on data base, the code could be safer and don’t fail if there is any change on this data.
MapMarker
- Similar as before, position attribute could be defined based on Address, JSON or using the Geolocation field. On above example, we directly used the compound sub-fields, but it would be also fine to get this information in the controller, create the map and call the method in the page as we show on center attribute.
- If your idea is to show all doctors around your city, instead of adding an mapMarker per record, remember to use apex:repeat tag
<apex:map width="100%" height="100%" mapType="roadmap" zoomLevel="15" center="43.2616465, -2.9393368"> <apex:repeat value="{!locations}" var="pos"> <apex:mapMarker position="{!pos}"/> </apex:repeat> </apex:map>
- Another mapMarker attribute is icon that help us to show a different image than the red bubble that google offer to us. A good way to show it is using a static resource and make a call to it.
<apex:mapMarker title="{!Doctor__c.SurgeryName__c}" position="{!Doctor__c.Location__Latitude__s}, {!Doctor__c.Location__Longitude__s}" icon="{!URLFOR($Resource.MapMarkers, 'myIcon.png')"> </apex:mapMarker>
MapInfoWindows
- This is another tag to show even more information. So for instance, below code show the doctor’s name and phone if we click on the marker icon
<apex:map width="100%" height="100%" mapType="roadmap" zoomLevel="15" center="43.2616465, -2.9393368"> <apex:mapMarker title="{!Doctor__c.SurgeryName__c}" position="{!Doctor__c.Location__Latitude__s}, {!Doctor__c.Location__Longitude__s}"> <apex:mapInfoWindow > <apex:outputPanel layout="block" style="font-weight: bold;"> <apex:outputText >{!Doctor__c.SurgeryName__c} </apex:outputText> </apex:outputPanel> <apex:outputPanel> <apex:outputText >{!Doctor__c.Phone__c} </apex:outputText> </apex:outputPanel> </apex:mapInfoWindow> </apex:mapMarker> </apex:map>
JavaScript vs apex:map
- Unfortunately, we cannot create a page with map tag in a Developer Edition, however JavaScript is available on Developer editions. But if you want to give it a try, create your own Trial edition.
- As JavaScript is executed on the client side, the map is loaded quicker than the one that use apex:map.
- On the other side, Salesforce takes care of any apex tag so any change on code versions, or browser, etc. that could make the code fail, Salesforce will deal with it.
- JavaScripts needs a method in the controller in order to get Location information dynamically and if we want to show the map inside a Standard layout, we must define the RemoteAction method as global that makes you define the class as global as well. This is not important till you add the code inside a package. As a reminder, global classes are available to everybody that install your package and maybe this is not your desire.
- RemoteAction is not able to get parameters from url. So I cannot do something like
Id currentDoctorId = controller.getId()
and then use it on the RemoteAction method. However, in our case, we can use directly the Id of the record on the visualforce page as a workaround:
Visualforce.remoting.Manager.invokeAction( '{!$RemoteAction.DoctorsController.findTheDoctor}', '{!Id}', function(result, event){....});
Resources
- Apex:map Salesforce documentation
- Github Repository with code used on above examples