Salesforce Mass Quick Actions in Lightning Experience

In Spring’18 release, Salesforce introduced Quick Actions in Lightning Experience on ListView. Using Mass quick action users can do mass action on the selected records (Up to 100 records) in a list view, except for recently viewed lists. We can use mass quick action with cases, leads, accounts, campaigns, contacts, opportunities, work orders, and custom objects in Lightning Experience.

Here is an example of Mass Quick Action. In below example I’ve craeted a Mass Quick Action on Account object to update mass records Account Type field from List View.

1. Create New Quick Action Button

Setup | Object Manager Tab | Navigate to Account object | Click Buttons, Links and Actions menu | Click New Action button

Create New Quick Action with following values:

2. Add Quick Action Button to Layout

Go to Account Object | Click Search Layouts menu | Select Edit under ListView

Got to “List View Actions in Lightning Experience” section, and add “Update Customer Type” Quick Action and save it.

3. Open Account Tab

Click the Account tab | Select a view except Recently Viewed | You can see “Update Customer Type” button.

4. Select Account Rcords to Update

Select multiple Account records, up to 100 records from the list view. Click “Update Customer Type” button, and select a Type from picklist values, and click Save button, and then OK button to confirm.

Now, all selected account records Type field will be updated with new value.

Get Picklist Values Dynamically In Lightning Radio Group Component

In below example I’m loading Account object Industry picklist field values in Lightning Radio Group.

Apex Controller:

public class SampleController {
    
    @AuraEnabled //Save Account Data
    Public static void createAccount(Account objacc){
        try{
            //Insert Account Record
            insert objacc; 
            
        }catch(Exception e){
            //throw exception message
            throw new AuraHandledException(e.getMessage());
        }
        finally {
        }
    }
    
    @AuraEnabled //get Account Industry Picklist Values
    public static Map<String, String> getIndustry(){
        Map<String, String> options = new Map<String, String>();
        //get Account Industry Field Describe
        Schema.DescribeFieldResult fieldResult = Account.Industry.getDescribe();
        //get Account Industry Picklist Values
        List<Schema.PicklistEntry> pList = fieldResult.getPicklistValues();
        for (Schema.PicklistEntry p: pList) {
            //Put Picklist Value & Label in Map
            options.put(p.getValue(), p.getLabel());
        }
        return options;
    }
}

Lightning Component:

<!--Sample.cmp-->
<aura:component controller="SampleController" implements="flexipage:availableForAllPageTypes,force:appHostable">
    
    <!--Declare Attributes-->
    <aura:attribute name="industryMap" type="Map"/>
    <aura:attribute name="acc" type="Account" default="{'sobjectType':'Account', 
                                                       'Name': '',
                                                       'AccountNumber': '',
                                                       'Email': '',
                                                       'Phone': '', 
                                                       'Industry': ''}"/>
    
    <!--Declare Handler-->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>  
    
    <!--Component Start-->
    <div class="slds-m-around--xx-large">
        <div class="container-fluid">
            <div class="form-group">
                <lightning:input name="accName" type="text" required="true" maxlength="50" label="Account Name" value="{!v.acc.Name}" />
            </div>
            <div class="form-group">
                <lightning:input name="accNumber" type="text" required="true" maxlength="10" label="Account Number" value="{!v.acc.AccountNumber}" />
            </div>
            <div class="form-group">
                <lightning:input name="accEmail" type="email" required="true" maxlength="100" label="Email" value="{!v.acc.Email}" />
            </div>
            <div class="form-group">
                <lightning:input name="accPhone" type="phone" required="true" maxlength="10" label="Phone" value="{!v.acc.Phone}" />
            </div>
            <div class="form-group">
                <!--Lightning radio group component-->
                <lightning:radioGroup name="radioGroup"
                                      label="Industry"
                                      required="true"
                                      options="{!v.industryMap}"
                                      value="{!v.acc.Industry}"
                                      type="radio"/>
            </div>
        </div>
        <br/>
        <lightning:button variant="brand" label="Submit" onclick="{!c.handleAccountSave}" />              
    </div>
    <!--Component End-->
</aura:component>

Lightning Component Controller:

({
    //Load Account Industry Picklist
    doInit: function(component, event, helper) {        
        helper.getIndustryPicklist(component, event);
    },
    
    //handle Account Save
    handleAccountSave : function(component, event, helper) {
        helper.saveAccount(component, event);
    },
    
    //handle Industry Picklist Selection
    handleCompanyOnChange : function(component, event, helper) {
        var indutry = component.get("v.acc.Industry");
        alert(indutry);
    }
})

Lightning Component Helper:

({
    //get Industry Picklist Value
    getIndustryPicklist: function(component, event) {
        var action = component.get("c.getIndustry");
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                var result = response.getReturnValue();
                var industryMap = [];
                for(var key in result){
                    industryMap.push({label: result[key], value: key});
                }
                component.set("v.industryMap", industryMap);
            }
        });
        $A.enqueueAction(action);
    },
    
    //handle Account Save
    saveAccount : function(component, event) {
        var acc = component.get("v.acc");
        var action = component.get("c.createAccount");
        action.setParams({
            objacc : acc
        });
        action.setCallback(this,function(response){
            var state = response.getState();
            if(state === "SUCCESS"){
                alert('Record is Created Successfully');
            } else if(state === "ERROR"){
                var errors = action.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        alert(errors[0].message);
                    }
                }
            }else if (status === "INCOMPLETE") {
                alert('No response from server or client is offline.');
            }
        });       
        $A.enqueueAction(action);
    }
})

Output:

Platform Cache In Salesforce

What is Cache?
Cache is a temporary data storing technique. We use Cache to store static data, that is being used frequently in our application for faster performance and better reliability.

What is Platform Cache?
Platform Cache is a memory layer that stores Salesforce session and org data for later access. When we use Platform Cache, the application can run faster because they store reusable data in memory. Application can quickly access this data; they don’t need to duplicate calculations and requests to the database on subsequent transactions. The Platform Cache is like the RAM for the cloud application.

There are two types of Platform Cache:

Session Cache: It stores data for individual user sessions and lives alongside a user session. The maximum life of a session is 8 hours. Session cache expires when its specified time-to-live is reached or when the session expires after eight hours, whichever comes first.

Org Cache: It stores data that any user in an org reuses. Unlike session cache, org cache is accessible across sessions, requests, and org users and profiles. Org cache expires when its specified time-to-live is reached.

When can we use Platform Cache?
We can use Platform Cache, when we want to store temporary data to use in code, but do not want to save it in database. Using cached data improves the performance of the application and is faster than performing SOQL queries repetitively, making multiple API calls, or computing complex calculations.

Platform Cache Considerations:

  • Cache isn’t persisted. There’s no guarantee against data loss.
  • Some or all cache is invalidated when you modify an Apex class in your org.
  • Data in the cache isn’t encrypted.
  • Org cache supports concurrent reads and writes across multiple simultaneous Apex transactions.
  • Session cache doesn’t support asynchronous Apex. Like we can’t use future methods or batch Apex with session cache.
  • The session cache can store values up to eight hours and the org cache can store values up to 48 hours.

Platform Cache Limits:

Cache Allocations by Edition:

  • Developer Edition (0 MB by Default, but can get 10 MB as a trial).
  • Enterprise Edition (10 MB by default)
  • Unlimited Edition (30 MB by default)
  • Performance Edition (30 MB by default)

Session Cache Limits:

  • Maximum size of a single cached item for put() methods (100 KB)
  • Maximum local cache size for a partition, per-request (500 KB)
  • Minimum developer-assigned time-to-live 300 seconds (5 minutes)
  • Maximum developer-assigned time-to-live 28,800 seconds (8 hours)
  • Maximum session cache time-to-live 28,800 seconds (8 hours)

Org Cache Limits:

  • Maximum size of a single cached item for put() methods (100 KB)
  • Maximum local cache size for a partition, per-request (1,000 KB)
  • Minimum developer-assigned time-to-live 300 seconds (5 minutes)
  • Maximum developer-assigned time-to-live 172,800 seconds (48 hours)
  • Default org cache time-to-live 86,400 seconds (24 hours)

Create Partition of Platform Cache:
Go to Setup | Search “Platform Cache” in Quick Find Box | Click Platform Cache

In developer edition, it is 0 MB space default. But we can get 10 MB on click of “Request Trial Capacity” button.

Now Click on “New Platform Cache Partition” to create Partition.

Each partition has one session cache and one org cache segment and you can allocate separate capacity to each segment. Session cache can be used to store data for individual user sessions, and org cache is for data that any users in an org can access.


Note: We cannot delete default Partition. If you want to delete partition, you have to uncheck the default partition option.

Put and retrieve data using Platform cache:
To call Partition in apex code we use namespace.partitionName. local as namespace can be used in both scenario if you have namespace enabled or not enable in your org. In below example I’m using “myPartition” partition.

Store and Retrieve Values from the Session Cache:

Session Cache in Apex Class:

//Add a value to the cache
Integer cartValue = 22;
Cache.Session.put('local.myPartition.CartValue', cartValue);
if (Cache.Session.contains('local.myPartition.CartValue')) {
    Integer cachedCartValue = (Integer)Cache.Session.get('local.myPartition.CartValue');
}

To refer to the default partition and the namespace of the invoking class, we can omit the namespace.partition prefix and specify the key name.

//Use of default partition without namespace and partition
Cache.Session.put('CartValue', 22);
if (Cache.Session.contains('CartValue')) {
    Integer cachedCartValue = (DateTime)Cache.Session.get('CartValue');
}

Session Cache in Visualforce Page:

<apex:outputText value="{!$Cache.Session.local.myPartition.CartValue}"/>

Store and Retrieve Values from the Org Cache:

Org Cache in Apex Class:

//Add a value to the cache
Integer cartValue = 22;
Cache.Org.put('local.myPartition.CartValue', cartValue);
if (Cache.Org.contains('local.myPartition.CartValue')) {
    Integer cachedCartValue = (Integer)Cache.Org.get('local.myPartition.CartValue');
}
//Use of default partition without namespace and partition
Cache.Org.put('CartValue', 22);
if (Cache.Org.contains('CartValue')) {
    Integer cachedCartValue = (DateTime)Cache.Org.get('CartValue');
}

Org Cache in Visualforce Page:

<apex:outputText value="{!$Cache.Org.local.myPartition.CartValue}"/>

Salesforce Lightning Custom Path for Picklist Field

Now you can create your custom lightning path for picklist field of any standard and custom objects. In Winter’18 release, Salesforce introduced lightning:picklistPath component, it displays the progress of a process, which is based on the picklist field specified by the picklistFieldApiName attribute. The path is rendered as a horizontal bar with one chevron for each picklist item. Paths created by this component do not have key fields or guidance and do not display the Mark Complete button.

Here is an example to create a path for Application Custom Object records that’s based on the record Id and the Status__c picklist field.

Component:

<aura:component implements="flexipage:availableForAllPageTypes,force:hasRecordId" access="global">
    <lightning:notificationsLibrary aura:id="notifLib"/>
    <!--Picklist Field Attribute-->
    <aura:attribute name="picklistField" type="object"/>
    
    <!--Force Record Data to update picklist value-->
    <force:recordData aura:id="record"
                      layoutType="FULL"
                      recordId="{!v.recordId}"
                      targetFields="{!v.picklistField}"
                      mode="EDIT"/>
    
    <!--For changing from left-to-right use dir="ltr" and from to right-to-left use dir="rtl"-->
    <div dir="ltr">
        <!--Lightning Picklist Path For Applicatin Status-->
        <lightning:picklistPath recordId="{!v.recordId}"
                                variant="linear"
                                picklistFieldApiName="Status__c"
                                onselect="{!c.handleSelect}" />
    </div>
</aura:component>

Component JS Controller:

({
    handleSelect : function (component, event, helper) {
        //get selected Status value
        var selectStatus = event.getParam("detail").value;
        //set selected Status value
        component.set("v.picklistField.Status__c", selectStatus);
        
        component.find("record").saveRecord($A.getCallback(function(response) {
            if (response.state === "SUCCESS") {
                $A.get('e.force:refreshView').fire();
                component.find('notifLib').showToast({
                    "variant": "success",
                    "message": "Record was updated sucessfully",
                    "mode" : "sticky"
                });
            } else {
                component.find('notifLib').showToast({
                    "variant": "error",
                    "message": "There was a problem updating the record.",
                    "mode" : "sticky"
                });
            }
        }));
    }
})

Output:

Handle Trigger and Validation Rule Error in Lightning Component

Sometimes we can have a requirement to display validation rule error or apex trigger error messages, on save of record in Lightning Component. We can handle those errors from apex controller and can throw messages to Lightning Component. In lightning response object though the state value comes as ERROR but the message in the error object always says ‘Internal Server Error”. Here is an example to handle the Trigger and Validation Rule Error in apex controller to show error messages in Lightning Component.

In below example I’ve used Lead object FirstName, LastName, Email & Company Field in lightning component to create record. And I’ve created a validation rule and a trigger validation on Lead object.

1. Validation rule for Company field (Company cannot be Test Company).

2. In below Trigger I’m checking if FirstName field value is “Test” then it will throw error.

trigger LeadTrigger on Lead (before insert, before update) {
    
    for(Lead obj :Trigger.new){
        if(obj.FirstName == 'Test'){
            obj.FirstName.addError('First name cannot be test');
        }
    }
}

Apex Controller:

public class SampleAuraController {
    
    @AuraEnabled
    Public static void createLead(Lead objLead){
        String msg = '';
        try{
            //Insert Lead Record
            insert objLead; 
            
        }catch(DmlException e){
            //Any type of Validation Rule error message, Required field missing error message, Trigger error message etc..
            //we can get from DmlException
            
            //Get All DML Messages
            for (Integer i = 0; i < e.getNumDml(); i++) {
                //Get Validation Rule & Trigger Error Messages
                msg =+ e.getDmlMessage(i) +  '\n' ;
            }
            //throw DML exception message
            throw new AuraHandledException(msg);
            
        }catch(Exception e){
            //throw all other exception message
            throw new AuraHandledException(e.getMessage());
        }
        finally {
        }
    } 
}

Lightning Component:

<!--Sample.cmp--> 
<aura:component controller="SampleAuraController" implements="flexipage:availableForAllPageTypes,force:appHostable">
    
    <!--Declare Attributes-->
    <aura:attribute name="isSpinner" type="boolean" default="false"/>
    <aura:attribute name="objLead" type="Lead" default="{'sobjectType':'Lead', 
                                                        'FirstName': '',
                                                        'LastName': '',
                                                        'Email': '', 
                                                        'Company': ''}"/>
    
    
    <!--Component Start--> 
    <div class="slds-m-around--xx-large">
        <div class="container-fluid">
            <div class="form-group">
                <lightning:input name="fname" type="text" maxlength="50" required="true" label="First Name" value="{!v.objLead.FirstName}" />
            </div>
            <div class="form-group">
                <lightning:input name="lname" type="text" maxlength="50" required="true" label="Last Name" value="{!v.objLead.LastName}" />
            </div>
            <div class="form-group">
                <lightning:input name="emailId" type="email" maxlength="100" required="true" label="Email" value="{!v.objLead.Email}" />
            </div>
            <div class="form-group">
                <lightning:input name="company" type="text" maxlength="50" required="true" label="Company" value="{!v.objLead.Company}" />
            </div>
        </div>
        <br/>
        <lightning:button variant="brand" label="Submit" onclick="{!c.handleLeadSave}" />        
        <lightning:button label="Cancel" onclick="{!c.handleCancel}"/>        
    </div>
    <!--Component End-->
</aura:component>

Lightning JS Controller:

({    
    //Handle Lead Save
    handleLeadSave : function(component, event, helper) {
        var objLead = component.get("v.objLead");
        var action = component.get("c.createLead");
        action.setParams({
            objLead : objLead
        });
        action.setCallback(this,function(a){
            var state = a.getState();
            if(state === "SUCCESS"){
                alert('Record is Created Successfully');
            } else if(state === "ERROR"){
                var errors = action.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        alert(errors[0].message);
                    }
                }
            }else if (status === "INCOMPLETE") {
                alert('No response from server or client is offline.');
            }
        });       
        $A.enqueueAction(action);
    }
})

Output:

Trigger Error Message:

Validation Rule Error Message: