Month: September 2018

Apex Inherited Sharing

Introduction

Salesforce apex with sharing or without sharing keywords on a class to specify whether sharing rules must be enforced. Use the inherited sharing keyword on an Apex class to run the class in the sharing mode of the class that called it. 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. Such a technique 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.

Using inherited sharing enables you to pass AppExchange 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 Lightning component controller, a Visualforce controller, an Apex REST service, or any other entry point to an Apex transaction.

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 as without sharing only when explicitly called from an already established without sharing context.

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>

 

 

Apex Cacheable methods

Introduction  

With salesforce winter 19 release, Salesforce introduces another way to improve the performance of the lightning component cache by mark an Apex method as storable (cacheable) instead of using setStorable() on every JavaScript action. With the apex storable methods, you can centralize your caching notation for a method in the Apex class instead of doing at client side javascript. Marking a method as storable improves your component’s performance by quickly showing cached data from client-side storage without waiting for a server trip. If the cached data is stale, the framework retrieves the latest data from the server.

How to Use?

To cache data returned from an Apex method for any component with an API version of 44.0 or later, annotate the Apex method with
@AuraEnabled(cacheable=true). For example, the following method is made as cacheable

 @AuraEnabled(cacheable=true)
    public static List<Account> getAccount() {
        return [Select Id, Name,Type, Industry, BillingAddress,
                BillingCity, BillingCountry, BillingPostalCode,
                BillingState,Phone from Account  where Industry!=NULL ] ;
    }

Here is the complete apex class.

public class ApexCacheableController {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccount() {
        return [Select Id, Name,Type, Industry, BillingAddress,
                BillingCity, BillingCountry, BillingPostalCode,
                BillingState,Phone from Account  where Industry!=NULL ] ;
    }
}

Lightning Component 

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" 
                controller="ApexCacheableController">
    <aura:attribute name="accounts" type="Account[]"/>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    
    <table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_col-bordered">
        <thead>
            <tr class="slds-line-height_reset">
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title=" Name">Name</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="Type">Type</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="Industry">Industry</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="BillingCity">BillingCity</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="BillingCountry">BillingCountry</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="BillingPostalCode">BillingPostalCode</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="BillingState">BillingState</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="Phone">Phone</div>
                </th>
            </tr>
        </thead>
        <tbody>
            <aura:iteration items="{!v.accounts}" var="obj">
                <tr class="slds-hint-parent">
                    <td data-label="Account Name">
                        <div class="slds-truncate" title="Name">{!obj.Name}</div>
                    </td>
                    <td data-label="Type">
                        <div class="slds-truncate" title="Type">{!obj.Type}</div>
                    </td>
                    <td data-label="Industry">
                        <div class="slds-truncate" title="Industry">{!obj.Industry}</div>
                    </td>
                    <td data-label="BillingCity">
                        <div class="slds-truncate" title="BillingCity">{!obj.BillingCity}</div>
                    </td>
                    <td data-label="BillingCountry">
                        <div class="slds-truncate" title="BillingCountry">{!obj.BillingCountry}</div>
                    </td>
                    
                    <td data-label="BillingPostalCode">
                        <div class="slds-truncate" title="BillingPostalCode">{!obj.BillingPostalCode}</div>
                    </td>
                    
                    <td data-label="BillingState">
                        <div class="slds-truncate" title="BillingState">{!obj.BillingState}</div>
                    </td>
                    <td data-label="Phone">
                        <div class="slds-truncate" title="Phone">{!obj.Phone}</div>
                    </td>
                </tr>
            </aura:iteration>
            
        </tbody>
    </table>
    
    
</aura:component>

JavaScript controller

({
    doInit : function(component, event) {
        console.time("concatenation");
        var action = component.get("c.getAccount");
        action.setCallback(this, function(a) {
            component.set("v.accounts", a.getReturnValue());
        });
        $A.enqueueAction(action);
        console.timeEnd("concatenation");
    }
})

Now if the javascript code we no need to use setStorable() action instead we are using cachable apex methods.

Can I use javascript storable along with Cachable methods?

Yes ..You can able to use both setStorable and apex cacheable methods together. But recommended will update an existing component to API version 44.0, remove setStorable() calls in JavaScript code along with Apex Cachable methods.

({
    doInit : function(component, event) {
        console.time("concatenation");
        var action = component.get("c.getAccount");
        action.setStorable();
        action.setCallback(this, function(a) {
            component.set("v.accounts", a.getReturnValue());
        });
        $A.enqueueAction(action);
        console.timeEnd("concatenation");
    }
})

Can I use the Cacheable methods in visualforce?

You can able to use the cacheable methods in visualforce controller also. Here is the simple page that calls the apex controller with cachable actions.

<apex:page controller="ApexCacheableController" id="thePage" lightningStylesheets="true">
    
    <apex:pageBlock >
        <apex:pageblockTable value="{!account}" var="acc">
            <apex:column value="{!acc.Name}"/>    
            <apex:column value="{!acc.Type}"/>    
            <apex:column value="{!acc.Industry}"/> 
            <apex:column value="{!acc.BillingCity}"/>    
            <apex:column value="{!acc.BillingCountry}"/>    
            <apex:column value="{!acc.Phone}"/> 
            
        </apex:pageblockTable>  
    </apex:pageBlock>
    
</apex:page>

Multiple Sections with lightning:accordion

In this blog, I am going to show how to open Multiple Sections with lightning: accordion with winter 19 enhancement. An earlier version of the accordion doesn’t allow more than one content panel to be open at the same time, and it takes a lot of effort to do that to do that by using jquery or other javascript libraries. Now with allowMultipleSectionsOpen attribute on the accordion, you can able to open multiple sections of the accordion at the same time. By default, only one accordion section is expanded at a time.

Apex Class

The below simple class will fetch all the accounts and its contacts. In this example, we will be displaying account and its contacts as an accordion section.

public class AccountsWithContact {
    @AuraEnabled
    public static List<Account> getBooksByAllCategories(){
        List<Account> accCons = [Select  Name,(Select FirstName, LastName from Contacts) from Account];
        return accCons;
    } 
    
}

Lightning Component

The below component will be displayed in the accordion with multiple sections capability.

<aura:component controller="AccountsWithContact" implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
    <aura:attribute name="accList" type="Account[]" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <lightning:accordion aura:id="accordion" allowMultipleSectionsOpen="true">
        <aura:iteration items="{!v.accList}" var="acc">
            <lightning:accordionSection name="A" label="{!acc.Name}" >
                <aura:set attribute="body">
                    <table class="slds-table slds-table--bordered slds-table--cell-buffer slds-table--striped slds-max-medium-table--stacked-horizontal"
                           role="grid">
                        <thead>
                            <tr>
                                <th class="slds-is-sortable slds-cell-wrap" scope="col">
                                    Name
                                </th>
                            </tr>
                        </thead>
                        <tbody>
                            <aura:iteration items="{!acc.Contacts}" var="con">
                                <tr class="slds-hint-parent">
                                    <td role="gridcell" class="slds-cell-wrap">
                                        <div class="">{!con.LastName}</div>
                                    </td>
                                    <td role="gridcell" class="slds-cell-wrap">
                                        <div class="" data-label="Role">{!con.FirstName}</div>
                                    </td>
                                </tr>
                            </aura:iteration>
                        </tbody>
                    </table>
                </aura:set>
            </lightning:accordionSection>
        </aura:iteration>
        
    </lightning:accordion>
    
    
</aura:component>

Controller 

({
    doInit : function(component, event, helper) {
        var action = component.get("c.getBooksByAllCategories");
        action.setCallback(this, function(a) {
            component.set("v.accList", a.getReturnValue());
        });
        $A.enqueueAction(action);
    }
})

Code Highlights  

  1. The below attribute is to hold the account object and its contacts
<aura:attribute name="accList" type="Account[]" />

2. The below Aura init handler to get the data from the server and set to the account list attribute

    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

3. The below line shows the accordion to open multiple sections of the accordion at the same time by setting an allowMultipleSectionsOpen attribute to true.

 <lightning:accordion aura:id="accordion" allowMultipleSectionsOpen="true">

4. Iteration all the collection to show the accordion sections.

   <aura:iteration items="{!v.accList}" var="acc">

5 . the below line is to create an accordion section.

 <lightning:accordionSection name="A" label="{!acc.Name}" >

The below image shows the multiple sections of the accordion at the same time

EmpJs Streaming API

Introduction 

Salesforce introduced a new component lightning:empApi  with winter 19 to support streaming API. Prior to winter 19 in order to use the streaming API, we need to upload the CometD javascript libraries to the static resource and we need to use cometD by using ltng:require along with afterscriptsloaded . But with the new EmpJs Streaming API library which subscribes to a streaming channel and listens to event messages using a shared CometD connection without using the CometD static resource. This component is supported only in desktop browsers. The lightning:empApi component provides access to methods for subscribing to a streaming channel         and listening to event messages. All streaming channels are supported, including channels for platform events, PushTopic events, generic events, and Change Data Capture events. The lightning:empApicomponent uses a shared CometD connection.

To call the component’s methods, add the lightning:empApi component inside your custom component and assign an aura:id attribute to it.

<lightning:empApi aura:id="empApi"/>

 

This example subscribes to a channel when you click the Subscribe button. The channel is specified in an input component. The default value provided is an example platform event channel. Replace the value with the desired channel name. A callback function on the subscribe() call prints the payload of each received event to the console. To view the event messages, enable your browser console view. The Unsubscribe button lets you stop the subscription and stop receiving event notifications.Add the following client-side controller functions. They are called by the Subscribe and Unsubscribe buttons. Each controller function calls the corresponding subscribe or unsubscribe method on the empApi component.

Step 1: Create a Pust topic

create a simple push topic as shown below the workbench.

Step 2: Use Emp Api in your code

Here is the simple component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" 
                access="global" >
    <aura:attribute name="channel" type="String" />
    <aura:attribute name="payload" type="Object" />
    <lightning:notificationsLibrary aura:id="notifLib"/>
    <lightning:empApi aura:id="empApi" />
    <lightning:input aura:id="channel" label="channel" name="channel" type="text" value="/topic/Case_Notification"/>
    <lightning:button label="Subscribe" onclick="{! c.subscribe }" />
    <lightning:button label="Unsubscribe" onclick="{! c.unsubscribe }" />
    Channel  :{!v.channel}
    
</aura:component>
({
    // Client-side function that invokes the subscribe method on the
    // empApi component.
    subscribe : function(component, event, helper) {
        // Get the empApi component.
        var empApi = component.find("empApi");
        // Get the channel from the input box.
        var channel = component.find("channel").get("v.value");
        var replayId = -1;
        // var data= [];
        
        // Callback function to be passed in the subscribe call.
        // After an event is received, this callback prints the event
        // payload to the console.
        var callback = function (message) {
            component.find('notifLib').showToast({
                "title": "Message Received!",
                "message":'Case is create with '+message.data.sobject.Status+' . Origin :'+message.data.sobject.Origin+'. Priority '+message.data.sobject.Priority,
                "variant": "pester"
            }); 
            
            
            component.set("v.payload" ,message.data.sobject);
        }.bind(this);
        
        // Error handler function that prints the error to the console.
        var errorHandler = function (message) {
            console.log("Received error ", message);
        }.bind(this);
        
        // Register error listener and pass in the error handler function.
        empApi.onError(errorHandler);
        
        var sub;
        // Subscribe to the channel and save the returned subscription object.
        empApi.subscribe(channel, replayId, callback).then(function(value) {
            console.log("Subscribed to channel " + channel);
            sub = channel;
            component.set("v.channel", sub);
        });
    },
    
    // Client-side function that invokes the unsubscribe method on the
    // empApi component.
    unsubscribe : function(component, event, helper) {
        // Get the empApi component.
        var empApi = component.find("empApi");
        // Get the channel from the input box.
        var channel = component.find("channel").get("v.value");
        
        // Callback function to be passed in the subscribe call.
        var callback = function (message) {
            console.log("Unsubscribed from channel " + channel);
        }.bind(this);
        
        // Error handler function that prints the error to the console.
        var errorHandler = function (message) {
            console.log("Received error ", message);
        }.bind(this);
        
        // Object that contains subscription attributes used to
        // unsubscribe.
        var sub = {"id": component.get("v.sub")["id"],
                   "channel": component.get("v.sub")["channel"]};
        
        // Register error listener and pass in the error handler function.
        empApi.onError(errorHandler);
        
        // Unsubscribe from the channel using the sub object.
        empApi.unsubscribe(sub, callback);
    }
})

After saving the component you can enter the streaming channel name and click on subscribe.

After creating a case record you will see the notifications on the lightning page.

Google Maps with lightning:map component

with salesforce winter 19 release, Salesforce introduced the lightning:map component that will display the google maps on lightning experience. Prior to winter 19 release, we used to do a hack to show the google maps on the lightning experience by using visualforce page. 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. You need to pass the locationmapMarkers property to display the map.

For example:

<lightning:map
    mapMarkers="{!v.mapMarkers}">
</lightning:map>

map-markers is an array of markers that indicate location. A marker contains

  • Location Information: This can be a coordinate pair of latitude and longitude or an address composed of address elements.
  • Descriptive Information: This is information like title, description and an icon which is information relevant to the marker but not specifically related to location.

The location information supports the following address elements: CityCountryPostalCodeState, and Street. Note that to support reliable geocoding of addresses, if Street is specified then at least one of CityCountryPostalCode or State must be specified.

Here’s an example of a marker that uses address elements.

[{
    location: {
        'City': 'San Francisco',
        'Country': 'USA',
        'PostalCode': '94105',
        'State': 'CA',
        'Street': 'The Landmark @ One Market, Suite 300'
    },
    title: 'The Landmark Building',
    description: 'The Landmark is considered to be one of the city's most architecturally distinct and historic properties',
    icon: 'standard:account'
}]

 

Apex Class : 

the below apex class will get the billing  address information from the account will display on the maps

public class AccountLocation {
    @AuraEnabled
    public static List<Location> getAccount() {
        List< Account> accs =  [Select Id, Name,Type, Industry, BillingAddress,BillingStreet,
                                BillingCity, BillingCountry, BillingPostalCode,
                                BillingState,Phone from Account  where BillingStreet!=NULL AND
                                BillingCity!=NULL AND BillingCountry!=NULL AND  BillingPostalCode!=NULL AND
                                BillingState!=NULL] ;
        
        List<Location> loc = new List<Location>();
        for(Account acc :accs){
            GeoLocation geoInfo = new GeoLocation();
            geoInfo.Street = acc.BillingStreet;
            geoInfo.PostalCode = acc.BillingPostalCode;
            geoInfo.City = acc.BillingCity;
            geoInfo.State = acc.BillingState;
            geoInfo.Country = acc.BillingCountry;
            Location locDetail = new Location();
            locDetail.icon = 'action:map'; 
            locDetail.title = acc.Name;
            locDetail.description = acc.Name;
            locDetail.location = geoInfo;
            
            loc.add(locDetail);
        }
        return loc ;
    }
    public class Location{
        @AuraEnabled 
        public String icon{get;set;} 
        @AuraEnabled 
        public String title{get;set;} 
        @AuraEnabled
        public String description{get;set;} 
        @AuraEnabled 
        public GeoLocation location{get;set;} 
    }
    public class GeoLocation{
        @AuraEnabled 
        public String Street{get;set;}
        @AuraEnabled 
        public String PostalCode{get;set;}
        @AuraEnabled 
        public String City{get;set;}
        @AuraEnabled 
        public String State{get;set;}
        @AuraEnabled 
        public String Country{get;set;}
    }
}

 

Here is the component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" 
                access="global" controller="AccountLocation" >
    <!-- attributes -->
    <aura:attribute name="mapMarkers" type="Object" />
    <aura:attribute name="center" type="Object" />
    <aura:attribute name="zoomLevel" type="Integer" default="7" />
    <aura:attribute name="markersTitle" type="String"  default="Google Maps"/>
    <aura:attribute name="showFooter" type="Boolean" default="true" />
    <aura:attribute name="title" type="String" default="Google Maps"/>
    <!-- handlers-->
    <aura:handler name="init" value="{! this }" action="{! c.init }"/>
    
    <!-- the map component -->
    <aura:if isTrue="{!v.mapMarkers.length > 0}" >
        
        <lightning:map
                       mapMarkers="{!v.mapMarkers}"
                       center="{! v.center }"
                       zoomLevel="{! v.zoomLevel }"
                       markersTitle="{! v.markersTitle }"
                       showFooter="{ !v.showFooter }" >
        </lightning:map> 
    </aura:if>
    
</aura:component>

javascript controller

({
    init: function (component, event, helper) {
        var action = component.get("c.getAccount");
        action.setCallback(this, function(response) {
            console.log('response'+response);
            var state = response.getState();
            console.log(state);
            if (state == "SUCCESS") {
                var obj =response.getReturnValue() ;
                console.log(obj);
                component.set('v.center', {
                    location: {
                        City: 'Denver'
                    }
                });
                component.set('v.mapMarkers',obj);
                component.set('v.zoomLevel', 4);
                component.set('v.markersTitle', 'Salesforce locations');
                component.set('v.showFooter', true);
            }
            
        });
        $A.enqueueAction(action); 
    }
})

After creating the component you can able to the see the map with markers as shown below