Start tracking your progress
Trailhead Home
Trailhead Home

Work with the Lightning Map Component and Apex Inherited Sharing

Learning Objectives

After completing this unit, you’ll be able to:

  • Use inherited sharing to secure your Apex code.
  • Understand what’s new with Lightning components.
  • Use the lightning:map component.
  • Create a class that applies inherited sharing.

Introduction

In this unit, you get to take two brand-new Winter ’19 features for a test-drive: Apex inherited sharing and the lightning:map component.

 

Use Inherited Sharing to Secure Your Apex Code

You can now specify the inherited sharing keyword on an Apex class, which allows the class to run in the sharing mode of the class that called it. Using inherited sharing enables you to pass security review and ensure that your privileged Apex code is not used in unexpected or insecure ways. An Apex class with inherited sharing runs as with sharing when used as a Visualforce page controller, Apex REST service, or an entry point to an Apex transaction.

Apex without a sharing declaration is insecure by default. Designing Apex classes that can run in either with sharing or without sharing mode at runtime is an advanced technique and can be difficult to distinguish from one where a specific sharing declaration is accidentally omitted. An explicit inherited sharing declaration makes the intent clear, avoiding ambiguity arising from an omitted declaration or false positives from security analysis tooling.

There is a distinct difference between an Apex class that is marked with inherited sharing and one with an omitted sharing declaration. If the class is used as the entry point to an Apex transaction, an omitted sharing declaration runs as without sharing. However, inherited sharing ensures that the default is to run as with sharing. A class declared as inherited sharing runs only as without sharing when explicitly called from an already established without sharing context.

Example

This example declares an Apex class with inherited sharing and a Visualforce invocation of that Apex code. Because of the inherited sharing declaration, only contacts for which the running user has sharing access are displayed. If the declaration is omitted, even contacts that the user has no rights to view are displayed due to the insecure default behavior of omitting the declaration.

public inherited sharing class InheritedSharingClass{
   public List<Contact> getAllTheSecrets(){
       return [SELECT Name FROM Contact];
   }
}
<apex:page controller="InheritedSharingClass">
   <apex:repeat value="{!allTheSecrets}" var="record">
       {!record.Name}
   </apex:repeat>
</apex:page>
Note

Note

This change applies to Lightning Experience and Salesforce Classic in Enterprise, Performance, Unlimited, and Developer editions.

New Lightning Components

Build user interfaces quickly with a bevy of new components. These components require API version 44.0 and later.

lightning:empApi

Embed the lightning:empApi component in your custom Lightning component to subscribe to a streaming event channel and receive event notifications. You can subscribe to any type of event channel on the Lightning Platform, including channels for platform events, PushTopic and generic events, and Change Data Capture (Developer Preview) events. The lightning:empApi component uses a shared CometD-based Streaming API connection, enabling you to run multiple streaming apps in the browser.

lightning:map

The lightning:map component securely displays a map of one or more locations using Google Maps. You can pass markers to the component to define the locations to map. A marker can be a coordinate pair of latitude and longitude, or a set of address elements: City, Country, PostalCode, State, and Street.

Here’s the map component with one address.

Screenshot of the lightning:map component image with one address

When you specify multiple locations, the map component creates clickable tiles for each location. You can specify a title to display above the list of addresses. You can also show a footer with an “Open in Google Maps” link and specify an initial zoom level for the map.

See https://lightningdesignsystem.com/components/map/ for more map images.

lightning:menuDivider

This component is used as a child component of lightning:buttonMenu. Use lightning:menuDivider to create a dividing line after a menu item. By default, space is added above and below the divider. Use variant="compact" with lightning:menuDivider to reduce the space.

Screenshot of a button menu with a divider

lightning:menuSubheader

This component is used as a child component of lightning:buttonMenu. Use lightning:menuSubheader to create subheadings in the list of menu items. Specify the text of the heading using the label attribute.

Screenshot of menu with subheadings

Resources

Get Ready for the Hands-on Challenge

In the hands-on challenge below, you’ll try out the lightning:map component and implement Apex inherited sharing. To make the mapping results interesting, a bit of setup is required. 

Out and About Communications installs towers to enhance cell phone coverage in remote locations. They recently installed new towers in some account locations in the western United States.

Launch the org you’ll use for the hands-on challenge, then create the following custom object and two custom fields.

  • Create a custom object that will be used to store information about the various towers in the western States that are owned by Out and About Communications.
    • Label: Tower
    • Plural Label: Towers
  • Create a new custom field to establish a Master-Detail relationship between Tower and Account. Add the Towers related list to the Account page layout.
    • Field Label: State
    • Type: Master-Detail
    • Field Name: State
    • Child Relationship Name: Towers
  • Create a new custom field to enter the latitude and longitude for the location of each Tower.
    • Field Label: Location
    • Field Name: Location
    • Type: Geolocation
    • Latitude and Longitude Display Notation: Decimal
    • Decimal Places: 6

Now add some data.

  • Create two new Account records to represent the regions (only the Name field is required).
    • Utah
    • Idaho
  • Create four new Tower records
    • Name: Lightning Ridge
      • State: Utah
      • Latitude: 40.490684
      • Longitude: -110.908727
    • Name: Craters
      • State: Idaho
      • Latitude: 43.555375
      • Longitude: -113.70069
    • Name: Nuckols
      • State: Idaho
      • Latitude: 47.516694
      • Longitude: -115.939163
    • Name: Rainbow
      • State: Utah
      • Latitude: 37.060663
      • Longitude: -110.975708

That’s it for object setup. You use the code blocks below to complete the challenge.

  • UtilityClass code block:
public class UtilityClass {
     public static List<sObject> queryObjects(String theObject, List<String> theFields, List<String> theFilters, String sortField, String sortOrder) {
          String theQuery = 'SELECT ' + string.join(theFields, ',');
          theQuery += ' FROM ' + theObject;
          boolean firstFilter = true;
          for (String filter : theFilters) { //loop through the filters
               String clauseToUse = (firstFilter) ? ' WHERE ' : ' AND '; //get the right clause
               filter = filter.trim();
               filter = filter.replaceAll('(\\s+)', ' ');  //remove white spaces
               theQuery += clauseToUse + filter;  //add the filter to the query
               firstFilter = false;  //changes the filter clause
          }
          if(!String.isEmpty(sortField)) {
               theQuery += ' ORDER BY ' + sortField;
               if(!String.isEmpty(sortOrder)) {
                    theQuery += ' ' + sortOrder;
               }
          }
          String theQueryResult = string.escapeSingleQuotes(theQuery);  //escapes the string
          return database.query(theQueryResult);
     }
}
  • TowerMapController code block:
public class TowerMapController {
     @AuraEnabled
     public static List<Tower__c> getAllTowers() {
          String theObject = 'Tower__c';
          List<String> theFields = new List<String>{'Id', 'Name', 'State__r.Name', 'Location__Latitude__s', 'Location__Longitude__s'};
          List<String> theFilters = new List<String>();  //empty filter list
          String sortField = 'Name';
          String sortOrder = 'ASC';
          List<Tower__c> allTowers = UtilityClass.queryObjects(theObject, theFields, theFilters, sortField, sortOrder);
          return allTowers;
     }
}
  • Towermap Lightning component code block:
<aura:component implements="flexipage:availableForAllPageTypes" controller="TowerMapController" access="global" >
     <aura:attribute name="mapMarkers" type="Object" access="PRIVATE" />
     <aura:attribute name="markersTitle" type="String" access="PRIVATE" />
     <aura:handler name="init" value="{!this}" action="{!c.handleInit}"/>
     <aura:if isTrue="{!!empty(v.mapMarkers)}" >
          <!-- Create lightning:map here -->
     </aura:if>
</aura:component>
  • Controller code block:
({
     handleInit: function (component, event, helper) {
          helper.initHelper(component, event, helper);
     }
})
  • Helper code block:
({
     initHelper : function(component, event, helper) {
          helper.utilSetMarkers(component, event, helper);
     },
     utilSetMarkers : function(component, event, helper) {
          let action = component.get("c.getAllTowers");
          action.setCallback(this, function(response) {
               const data = response.getReturnValue();
               const dataSize = data.length;
               let markers = [];
               for(let i=0; i < dataSize; i += 1) {
                    const Tower = data[i];
                    markers.push({
                        'location': {
                             'Latitude' : Tower.Location__Latitude__s,
                             'Longitude' : Tower.Location__Longitude__s
                        },
                        'icon': 'utility:Tower',
                        'title' : Tower.Name,
                        'description' : Tower.Name + ' Tower Location at ' + Tower.State__r.Name
                   });
               }
               component.set('v.markersTitle', 'Out and About Communications Tower Locations');
               component.set('v.mapMarkers', markers);
          });
          $A.enqueueAction(action);
     }
})

retargeting