Time Estimate

Discover Lightning Actions

Learning Objectives

After completing this unit, you’ll be able to:
  • Create a Lightning component and action to populate fields based on user input and provide feedback messages to users.
  • Create a Lightning component and action that integrate with a third-party system.
  • Start migrating your JavaScript custom button functionality to Lightning-friendly alternatives.

Lightning Actions: Smart, Fast, and Mobile

We've covered several solutions that work in both Lightning Experience and Salesforce Classic and that are great replacements for JavaScript buttons. But we recognize that those declarative and programmatic solutions don’t address every use case. The good news is that there’s something that addresses many more. Introducing Lightning actions: quick actions that call Lightning components.

Lightning actions are built on the existing Lightning Component Framework that you may already have adopted. You can easily convert your existing Lightning components to actions and use them in the Salesforce mobile app and Lightning Experience.

You make a Lightning component invokable as an action by adding one of two interfaces to the component: force:lightningQuickAction or force:lightningQuickActionWithoutHeader. The first interface adds the standard header with Save and Cancel buttons to the Lightning action overlay, and the other doesn’t.

Another useful interface for a Lightning action is force:hasRecordId, which provides the record context to the component when it’s invoked from a record page. If you set up your Lightning action as a global quick action, you don’t need the record context. However, if you want to access a record’s data or metadata, you must implement force:hasRecordId.

Note

Note

If you haven’t built Lightning components before, visit the Lightning Development Center, do the Lightning Component Quick Start project, and complete the Lightning Components Basics module.

Let’s dive into Lightning actions. Next, we’ll talk about some JavaScript button functionality that can be addressed with Lightning actions instead.
  • Populating fields based on user input and providing feedback messages during data entry
  • Integrating third-party APIs

Populate Fields Based on User Input and Provide Feedback Messages to Users

Let’s say you use JavaScript buttons to validate or manipulate data and give your users feedback or instructions when they work with records. Here’s an example to show how easily you can validate or manipulate data in a Lightning component before your users create or update a record.

We’ve built a custom object in our sample org called Case Study, which we use to track different research projects. When we add new test users, we capture their names and email addresses. Since the email address is our primary contact method, we want to make sure it’s entered correctly. We also want to create a unique nickname for test users so that they can remain anonymous.

We’re going to create a Lightning action that displays the name and email address fields, validates the email address, and uses the first name and a random number to automatically create the nickname.

Here’s the code for the component called by this Lightning action.
  • CreateUser.cmp—The Lightning component that you see when you open the action. It contains the UI implementation, which includes the text fields, buttons, action title, and so on.
  • CreateUserController.js—The controller that listens to the component and any UI events that take place, such as the initial load, text input, and button clicks. Its function is to delegate these events to the helper class, CreateUserHelper.js.
  • CreateUserHelper.js—The code that deals with all the business logic, such as validating the password and email fields.

CreateUser.cmp

<aura:component implements="force:lightningQuickActionWithoutHeader,force:hasRecordId">
    <aura:attribute name="user" type="Test_User__c" default="{}"/>
    <aura:attribute name="hasErrors" type="Boolean" description="Indicate whether there were failures or not" />
    <aura:attribute name="caseStudy" type="String" />
    <aura:attribute name="recordId" type="String"/>
    <aura:attribute name="errorFromCreate" type="String"/>

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

    <force:recordData aura:id="frd" mode="EDIT" layoutType="FULL"/>
    
    <div class="slds-page-header" role="banner">
    	<p class="slds-text-heading--label">Case Study</p>
        <h1 class="slds-page-header__title slds-m-right--small slds-truncate slds-align-left" title="Case Study Title">{!v.caseStudy}</h1>
    </div>
    <br/>
    
    <aura:if isTrue="{!v.hasErrors}">
        <div class="userCreateError">
            <ui:message title="Error" severity="error" closable="true">
                Please review the error messages.
            </ui:message>
        </div>
    </aura:if>

    <div class="slds-form--stacked">
                
        <div class="slds-form-element">
            <label class="slds-form-element__label" for="firstName">Enter first name: </label>
            <div class="slds-form-element__control">
              <ui:inputText class="slds-input" aura:id="firstName" value="{!v.user.First}" required="true" keydown="{!c.updateNickname}" updateOn="keydown"/>
            </div>
        </div>
        
        <div class="slds-form-element">
            <label class="slds-form-element__label" for="lastName">Enter last name: </label>
            <div class="slds-form-element__control">
              <ui:inputText class="slds-input" aura:id="lastName" value="{!v.user.Last}" required="true" />
            </div>
        </div>
        
        <div class="slds-form-element">
            <label class="slds-form-element__label" for="nickname">Enter nickname: </label>
            <div class="slds-form-element__control">
              <ui:inputText class="slds-input" aura:id="nickname" value="{!v.user.Nickname}" required="false"/>
            </div>
        </div>
        
        <div class="slds-form-element">
        	<label class="slds-form-element__label" for="userEmail">Enter user's email:</label>
            <div class="slds-form-element__control">
        		<ui:inputEmail class="slds-input" aura:id="userEmail" value="{!v.user.Email__c}" required="true"/>
            </div>
        </div>
        
        <div class="slds-form-element">
        	<label class="slds-form-element__label" for="userPassword">Enter user's password:</label>
            <div class="slds-form-element__control">
        		<ui:inputSecret class="slds-input" aura:id="userPassword" value="{!v.user.Password__c}" required="true"/>
            </div>
        </div>
        
        <div class="slds-form-element">
        	<ui:button class="slds-button slds-button--neutral" press="{!c.cancel}" label="Cancel" />
        	<ui:button class="slds-button slds-button--brand" press="{!c.saveUserForm}" label="Save User" />
        </div>
    </div>

</aura:component>

CreateUserController.js

({    
    /**
     * Auto generate the username from firstName on tab
     */
    updateNickname: function(component, event, helper) {
        // Update the nickname field when 'tab' is pressed
        if (event.getParams().keyCode == 9) {
        	var nameInput = component.find("firstName");
        	var nameValue = nameInput.get("v.value");
        	var nickName = component.find("nickname");
            var today = new Date();
        	nickName.set("v.value", nameValue + today.valueOf(today));   
        }
    },
 
    /**
     * Capture the Inputs and invoke the helper.save with the input params 
     */
	saveUserForm : function(component, event, helper) {
        var name = component.get("v.user.First");
        var last = component.get("v.user.Last");
        var password = component.get("v.user.Password__c");
        var email = component.get("v.user.Email__c");
        var nickname = component.get("v.user.Nickname");
        
        var passwordCmp = component.find("userPassword");
        var emailCmp = component.find("userEmail");
        
        helper.validatePassword(component, event, helper);
        helper.validateEmail(component, event, helper);

        if (passwordCmp.get("v.errors") == null && emailCmp.get("v.errors") == null) {
            component.set("v.hasErrors", false);
        	helper.save(component, name + " " + last, password, email, nickname);         
        } else {
            component.set("v.hasErrors", true);
        }
    },
    
    cancel : function(component, event, helper) {
        $A.get("e.force:closeQuickAction").fire();
    }
})

CreateUserHelper.js

({  
    save: function(component, name, password, email, nickname) {
        // Create a user record, save it, and close the panel
        var userRecord = {apiName: 'Test_User__c', fields: {}};
        userRecord.fields.Name = {value: name};
        userRecord.fields.Password__c = {value: password};
        userRecord.fields.Email__c = {value: email};
        userRecord.fields.Nickname__c = {value: nickname};
        userRecord.fields.Case_Study__c = {value: component.get("v.recordId")};
        // get the force:recordData and set the targetRecord
        component.find("frd").set('v.targetRecord', userRecord); 
        // invoke saveRecord of force:recordData
        component.find("frd").saveRecord($A.getCallback(function(response) {
            if (component.isValid() && response.state == "SUCCESS") {
                $A.get("e.force:closeQuickAction").fire();
                var toastEvent = $A.get("e.force:showToast");
                toastEvent.setParams({
                	"title": "Success!",
                    "message": "The test user has been created."
                });
                toastEvent.fire();		
                $A.get('e.force:refreshView').fire();
            } else if (response.state == "ERROR") {
                console.log('There was a problem and the state is: '+ response.state);
            }
        }));
    },
    
    validatePassword : function(component, event, helper) {
        var inputCmp = component.find("userPassword");
        var value = inputCmp.get("v.value");
        
        if (value == undefined) {
           inputCmp.set("v.errors", [{message: "You must enter a password."}]);
        } else if (value.length < 7 || value.length > 15) {
            inputCmp.set("v.errors", [{message: "The password is the wrong length (must be <= 15): " + value.length}]);
        } else if (value.search(/[0-9]+/) == -1) {
            inputCmp.set("v.errors", [{message: "The password must contain at least one number."}]);
        } else if (value.search(/[a-zA-Z]+/) == -1) {
            inputCmp.set("v.errors", [{message: "The password must contain at least one letter."}]);
        } else {
            inputCmp.set("v.errors", null);
        }
	},
    
    validateEmail : function(component, event, helper) {
        var inputCmp = component.find("userEmail");
        var value = inputCmp.get("v.value");
        
        if (value == undefined) {
           inputCmp.set("v.errors", [{message: "You must enter an email."}]);
           return;
        }
        
        var apos = value.indexOf("@");
        var dotpos = value.lastIndexOf(".");

        if (apos<1||dotpos-apos<2){
            inputCmp.set("v.errors", [{message: "Email is not in the correct format: " + value}]);
        } else if (value.substring(apos+1, dotpos) != "gmail") {
            inputCmp.set("v.errors", [{message: "Email must be a gmail account: " + value.substring(apos+1, dotpos)}]);
        } else {
            inputCmp.set("v.errors", null);
        }
	}
})

After creating the Lightning component, we assign it to an action. In the object management settings for Case Study, we go to Buttons, Links, and Actions, click New Action, then configure the action with these parameters.

Field Value
Object Name Case Study
Action Type Lightning Component
Lightning Component c:CreateUser
Height 500px
Label Create Test User
Name CreateUser

Then we add our new Lightning action to the Case Study page layout. When users invoke it from a case study record page, they see the Lightning action that we created.

Create Test User Lightning action overlay

The great thing about this Lightning action is that it also works in the Salesforce mobile app.

Case Study action in the Salesforce app

Integrate Third-Party APIs

Maybe you use JavaScript buttons for integration with third-party systems. Can you use Lightning actions for that? You sure can. The primary difference is that for integration using Lightning actions, you have to use a server-side controller. In return, you get better handling of security credentials and the ability to use Apex for asynchronous and batch API calls.

Let’s look at how to integrate with Twilio for sending SMS messages. In this example, we’re in the luxury travel business, and we handle celebrities and VIPs. We want our customer service agents to be able to communicate with their clients, but we don’t want to expose the clients’ personal contact information to anyone. We create a Lightning action on the Contact object so that agents can send messages without seeing the contact’s phone number.

As in our case study example, we’ll create the Lightning component and helper classes, and then create a quick action to invoke the component.

SMS Lightning action overlay

This Lightning action is composed of the Lightning component, a JavaScript controller, and an Apex controller, which references library classes that handle the Twilio integration.

SendTwilioSMS.cmp

<aura:component controller="TwilioSendSMSController" implements="flexipage:availableForAllPageTypes,force:hasRecordId,force:lightningQuickAction" >
   <aura:attribute name="textMessage" type="String" />
   <aura:attribute name="destinationNumber" type="String" />
   <aura:attribute name="messageError" type="Boolean" />
   <aura:attribute name="recordId" type="String" />

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

   <aura:if isTrue="{!v.messageError}">
      <!-- Load error -->
      <div class="userCreateError">
         <ui:message title="Error" severity="error" closable="true">
            Unable to send message. Please review your data and try again.
         </ui:message>
      </div>
   </aura:if>

   <div class="slds-form--stacked">
      <label class="slds-form-element__label" for="instructMsg">Please enter the message (max 160 char) below: </label>
      <br/>
      <div class="slds-form-element__control">
         <ui:inputText class="slds-input" aura:id="message" label="Text Message" value="{!v.textMessage}" required="true" maxlength="160" size="165" />
      </div>
      <div class="centered">
         <ui:button class="slds-button slds-button--brand" press="{!c.sendMessage}" label="Send Message"/>
      </div>
   </div>
</aura:component>

SendTwilioSmsController.js

({
   init : function(component, event, helper) {
      var action = component.get("c.getPhoneNumber");
      action.setParams({"contactId": component.get("v.recordId")});
      action.setCallback(this, function(response) {
         var state = response.getState();
         if(component.isValid() && state == "SUCCESS"){
            component.set("v.destinationNumber", response.getReturnValue());
         } else {
            component.set("v.messageError", true);
         }
      });
      $A.enqueueAction(action);
   },

      sendMessage : function(component, event, helper) {
      var smsMessage = component.get("v.textMessage");
      var number = component.get("v.destinationNumber");
      var recordId = component.get("v.recordId")

      var action = component.get("c.sendMessages");
      action.setParams({"mobNumber": number, "message": smsMessage, "contactId": component.get("v.recordId")});
      action.setCallback(this, function(response) {
         var state = response.getState();
         if(component.isValid() && state == "SUCCESS"){
            $A.get("e.force:closeQuickAction").fire();
            var toastEvent = $A.get("e.force:showToast");
            toastEvent.setParams({
               "title": "Success!",
               "message": "SMS has been sent woo hoo!"
            });
            toastEvent.fire();
         } else {
            component.set("v.messageError", true);
         }
      });
      $A.enqueueAction(action);
   }
})

SendTwilioSmsController.apxc

/*
* Apex controller that currently contains only one method to send sms message
*/
global class TwilioSendSMSController {

   /*
   * This method uses the Twilio for Salesforce library class and method to
   * send the message using the Twilio api.
   */
   @AuraEnabled
      webService static String sendMessages(String mobNumber, String message, Id contactId) {
         System.debug('the mobNumber is: '+ mobNumber + ' and the message is: '+ message + ' and contactId is: ' + contactId);

         if (mobNumber == null) {
            mobNumber = getPhoneNumber(contactId);
         }

         try {
            TwilioRestClient client = TwilioAPI.getDefaultClient();

            Map<String,String> params = new Map<String,String> {
               'To' => mobNumber,
               'From' => '15555551234',
               'Body' => message
               };
            TwilioSMS sms = client.getAccount().getSMSMessages().create(params);
            return sms.getStatus();
         } catch(exception ex) {
            System.debug('oh no, it failed: '+ex);
            return 'failed';
         }
      }

      @AuraEnabled
      public static String getPhoneNumber(Id contactId) {
         Contact currentRecord = [SELECT Phone FROM Contact WHERE Id = :contactId];
         return currentRecord.Phone.replace(' ', '').replace('-', '').replace(')', '').replace('(', '');
   }
}

After creating the component, we create a Lightning action. This time we add it to the Contact page layout so agents can access it.

Again, the cool thing is that the action is also available in the Salesforce mobile app. If a car service provider is trying to contact the customer at the airport, the driver can use a mobile device to reach the customer easily.

SMS Lightning action in the Salesforce app

Lightning actions are the future of programmatic actions in Lightning Experience. We hope that you start looking at this set of solutions as a better alternative to JavaScript buttons.

And if you depend on partner apps that use JavaScript buttons, you’ll be glad to know that many of our partners have already started migrating and upgrading their apps to Lightning. You can find more apps on the AppExchange that are updated for Lightning Experience.

Beyond the Basics

If you’d like to try Twilio for yourself, you can find an unmanaged package of the Twilio for Salesforce library class used in the third-party integration example in the Salesforce Github library: https://github.com/twilio/twilio-salesforce

Before using Twilio for Salesforce and the Twilio API, create a Twilio account, set up a phone number, and connect your account to Salesforce. See the TwilioApi.cls in the Github library for more detailed instructions.

retargeting