Salesforce Flow Actions

 Introduction

In this blog post, I am going to explain how to create a Salesforce  1 and lightning action using the flows. With the new support on flows with Action on your Lightning pages and Salesforce 1, you can invoke flow directly from action rather than using URL to invoke   To create a flow action, select Flow (Beta) from the Action Type dropdown when you create an object-specific action. You can add flow actions to an object’s page layout using the page layout editor.

Considerations

  • Flow actions support only flows that include screens. Make sure the flow that you want to use has a type “Flow”.
  • Flow Status The flow must be active.
  • Flow actions are available only as object-specific actions.
  • Flow actions send the record ID to your flow automatically if the flow includes a Text input variable called recordId
  • ActionsPackaging and other deployment options aren’t supported for flow actions.

Here what we are going to do now is set up an action that is going to create a beta customer and its deployment from the action. Here is the data model for the same.

Data model Setup

 

Create a new child Object Beta Customer from the account with the following details

Beta Customer with Auto Number (Plugin__c) and list of fields are here below.

API Name Type
Product_Name__c Text
Beta_Status__c Picklist Active, Closed, On-Hold, Closed
Beta_Start_Date__c Date
Beta_End_Date__c Date
Version__c Text

Beta Deployment with Auto Number and list of fields are here below.

API Name Type
Site_Status__c Picklist Closed, On-Hold, Planned, Active
Beta_Site_Update_Date__c Date
 Plugin Master-Detail(Beta Product)  Master Details
Internal_Comments__c Text Area
Customer_Feedback__c Text Area
Beta_Site_Update_Date__c Date

Designing Flow

Now we are going to design the flow to invoke from the action . Go to Flow designer  from the setup and create a new flow from there . From the Palette drag  and drop the screen to designer as show below .Enter the name of the screen name as “Beta Customer Setup”

Go to Add Fields sections click on the Textbox to add it the screen. 

Double click the new text box from Field Settings and add the Label and Unique name with Product Id as shown below. Repeat the same steps and add two new fields to screen namely start date and end date of type date.

Now Add the new Dropdown list from the add fields and name it as Beta Status.

from the choice, set select the picklist choice and complete it as shown below.

Click on the OK now to complete the screen setup.
From the flow designer palette drag and drop the record create the element to the screen as shown below and name it as “Insert Beta”. From the assignment, section choose the “Plugins__c” from the custom object list as shown below.

Map all the files to the object from the screen input fields and screen input choice type . after mapping all the fields screen should look like as below.

Create a new variable name “betaId” to store the inserted object id as shown below.

Click OK to save.

Drag and Drop the screen element from the Palette and name it as beta deployment as shown below.

Add the new filed “Drop down list type to screen and name it as “Site_Status” from the choice setting create a new pick list choice from the Object and Custom field  as status shown below and click OK

And add the Beta_Site_Update_Date filed of type date  and Internal_Comments, Customer_Feedback of type Long Text Area to the screen.

Click on OK  as shown below.

Now connect all the element as shown below and make Beta Customer Setup as the starting element to the flow.

Save the flow as shown below.

Creating Object Actions

now go to Setup–>Customize-> Account-> Buttons links and actions ->create a new action as shown below and add it to the page layout

After adding it to the page layout, you can see the action on the account record page which allows you to save the two records beta customer and beta deployment with the same action.

With the flows with the quick action, you can easily achieve the more actions on the lightning experience and salesforce 1 without writing any sort of code.

Salesforce Canvas LifeCycle Handler

Introduction

In this blog, I am going to explain how to use canvas lifecycle handler to send the context information to canvas app and add custom behavior when your app when it is rendered.To handler the canvas lifecycle you need to implement the     CanvasLifecycleHandler interface. With lifecycle handler, you can

  • Control what sections of the CanvasRequest Context data get sent to your apps such as Organization, User or record details.
  • Retrieve application context data when the app is rendered and alter the behavior of your app accordingly.
  • Modify some of the context data, such as the canvas app URL, custom parameters, or the list of object fields that are returned in the Record data when the app is rendered.
  • Display proper error message back to Salesforce

Creating a CanvasLifecycleHandler

                   Crate an apex class by implementing Canvas.CanvasLifecycleHandler interface provides methods and callbacks for customizing app lifecycle behavior as shown below.

public class CanvasLifeCycle  implements Canvas.CanvasLifecycleHandler {
    
    public Set<Canvas.ContextTypeEnum> excludeContextTypes(){
        Set<Canvas.ContextTypeEnum> excluded = new Set<Canvas.ContextTypeEnum>();
        excluded.add(Canvas.ContextTypeEnum.ORGANIZATION);
        excluded.add(Canvas.ContextTypeEnum.USER);
        excluded.add(Canvas.ContextTypeEnum.RECORD_DETAIL);
        return excluded;
    }
    
    public void onRender(Canvas.RenderContext renderContext) {
        Canvas.ApplicationContext app = renderContext.getApplicationContext();
        Canvas.EnvironmentContext env = renderContext.getEnvironmentContext();
        Double currentVersion = Double.valueOf(app.getVersion());
        if (currentVersion <= 5){
            throw new Canvas.CanvasRenderException('Error: Versions earlier than 5 are no longer supported.');
        }
        app.setCanvasUrlPath('/alternatePath');
        env.addEntityFields(new Set<String>{'Name','BillingAddress','YearStarted'});
        Map<String, Object> previousParams = 
            (Map<String, Object>) JSON.deserializeUntyped(env.getParametersAsJSON());
        previousParams.put('newCustomParam','newValue');
        env.setParametersAsJSON(JSON.serialize(previousParams));
        
    }
}

Let’s understand the code now. The first method is used to filter CanvasRequest Context data that gets sent to your canvas app as shown below .with excludeContextTypes() types method you can specify what data you wanted to exclude from the canvas application.

    public Set<Canvas.ContextTypeEnum> excludeContextTypes(){
        Set<Canvas.ContextTypeEnum> excluded = new Set<Canvas.ContextTypeEnum>();
        excluded.add(Canvas.ContextTypeEnum.ORGANIZATION);
        excluded.add(Canvas.ContextTypeEnum.USER);
        excluded.add(Canvas.ContextTypeEnum.RECORD_DETAIL);
        
        
        return excluded;
    }
  • ORGANIZATION: Exclude context information about the organization in which the canvas app is running.
  • RECORD_DETAIL: Exclude context information about the object record on which the canvas app appears.
  • USER: Exclude context information about the current user.
To modify the default behavior of the signed request, you need to provide an Apex class that implements onRender() and associate this class with your canvas app. In onRender() implementation, you can control app behavior with custom code.
In your onRender() implementation, you can retrieve the following context information.
 public void onRender(Canvas.RenderContext renderContext) {
        Canvas.ApplicationContext app = renderContext.getApplicationContext();
        Canvas.EnvironmentContext env = renderContext.getEnvironmentContext();
        Double currentVersion = Double.valueOf(app.getVersion());
        if (currentVersion <= 5){
            throw new Canvas.CanvasRenderException('Error: Versions earlier than 5 are no longer supported.');
        }
        app.setCanvasUrlPath('/alternatePath');
        env.addEntityFields(new Set<String>{'Name','BillingAddress','YearStarted'});
        Map<String, Object> previousParams = 
            (Map<String, Object>) JSON.deserializeUntyped(env.getParametersAsJSON());
        previousParams.put('newCustomParam','newValue');
        env.setParametersAsJSON(JSON.serialize(previousParams));
        
    }

The ApplicationContext interface provides methods to retrieve application information about the canvas app that’s being rendered. The EnvironmentContext interface provides methods to retrieve environment information about the current canvas app. The OnRender method performs the following in the above code.

 1.Checks the app version information and, if the version is unsupported, throws a CanvasRenderException.
2.Overrides the current canvas app URL, appending ‘/alternatePath’ to the domain portion of the original URL.
3.Sets the list of object fields
4.Overrides the set of custom parameters by adding a new ‘newCustomParam’ parameter

Lightning Component Quick Actions

Introductions

In this blog, I am going to explain how to custom actions to customize lightning and  Salesforce1 experiences with both global actions and quick actions. Global actions are not associated with any object means you can insert data that is not going to establish any relations other record and Object-specific actions are associated with the object so it allows creating records that have automatic relationships to other records, make updates to specific records. To available Lightning component as actions, you need to implement either the force: LightningQuickAction or force: LightningQuickActionWithoutHeader interfaces.Add the force:hasRecordId interface to a Lightning component to enable the component to be assigned the ID of the current record. When we implement the force:lightningQuickAction interface display in a panel with standard action controls, such as a Cancel button. If instead, you want complete control over the user interface, use the force: lightningQuickActionWithoutHeader interface. Components that implement the force:lightningQuickActionWithoutHeader display in a panel without additional controls and are expected to provide a complete user interface for the action.

Object-Specific Actions
What we are going to do it here, we will create a quick action that is going to create a quote from the opportunity. Create an apex class as shown below and the apex class is used to communicate to server-side actions to save the Quote.

public class QuickQuote {
    @AuraEnabled
    public static boolean saveQuote(Quote newQuote, Id oppId) {
        system.debug('newQuote'+newQuote);
        newQuote.OpportunityId = oppId;
        try{
            insert newQuote ; 
            return true ;
        }catch(Exception e){
            throw new AuraHandledException(e.getMessage());
            return false ;
        }
        
    }
    
}

Go to developer console and create a new lightning component name ” Quick Quote” with below code

<aura:component controller="QuickQuote"
                implements="force:lightningQuickActionWithoutHeader">
    <aura:attribute name="recordId" type="String" />
    <aura:attribute name="newQuote" type="Quote"
                    default="{ 'sobjectType': 'Quote','Name':'Default Quote' , 
                             'ExpirationDate':'2017-10-30','Email':'test@email.com' ,'Phone':'' ,'Discount':'19' , 'BillingName':'Teest' }" /> 
    <div class="slds-page-header" role="banner">
        <h1 class="slds-page-header__title slds-m-right--small
                   slds-truncate slds-align-left">Create New Quote</h1>
    </div>
    <lightning:input aura:id="quoteName" 
                     name="Quote Name" 
                     label="Quote Name" 
                     value="{!v.newQuote.QuoteToName}"
                     required="true"/>
    <ui:inputDate aura:id="quoteDate" displayDatePicker="true"
                  label="Expiration Date" 
                  value="{!v.newQuote.ExpirationDate}"
                  />
    <lightning:input aura:id="quoteEmail"
                     name="Email Date" 
                     label="Email"
                     value="{!v.newQuote.Email}" 
                     Type="email"/>
    <lightning:input aura:id="quotePhone" 
                     name="Phone Date" 
                     label="Phone"
                     value="{!v.newQuote.Phone}"
                     Type="tel"/>
    
    <lightning:input aura:id="quoteDiscount" 
                     name="Discount" 
                     label="Discount"
                     value="{!v.newQuote.Discount}"
                     Type="number"/>
    
    <lightning:button label="Create Quote" onclick="{!c.createQuote}"
                      class="slds-m-top--medium"/>
    
    <lightning:button label="Cancel" onclick="{!c.handleCancel}" class="slds-m-top--medium" />
    
    
</aura:component>

Here is Lightning component controller code.

({
    createQuote: function(component, event, helper) {
        var saveAction = component.get("c.saveQuote");
        console.log('saveAction');
        saveAction.setParams({
            newQuote: component.get("v.newQuote"),
            oppId: component.get("v.recordId")  
             
        });
        saveAction.setCallback(this, function(response) {
            var state = response.getState();
            console.log('state'+state);
            if(state === "SUCCESS") {
                var resultsToast = $A.get("e.force:showToast");
                resultsToast.setParams({
                    "title": "Quote",
                    "message": "Quote Creation is Success."
                });
                $A.get("e.force:closeQuickAction").fire();
                resultsToast.fire();
                $A.get("e.force:refreshView").fire();
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " + 
                                    errors[0].message);
                    }
                } else {
                    console.log("Unknown error");
                }
            } 
        });
        console.log('En Quier action ');
        $A.enqueueAction(saveAction);
        
        
    },
    
    handleCancel: function(component, event, helper) {
        $A.get("e.force:closeQuickAction").fire();
    }
})

The createQuote action handler method will do the following action

  • Invoke server action to save the quote by calling component.get(“c.saveQuote”) from the apex class .
  • Defines a callback function to handle the server response.
  • Enqueues the server action by calling” $A.enqueueAction(saveAction)
  • handleCancel Closes the action panel by firing the force:closeQuickAction event.
  • Displays a “toast” message that the Quote was created by firing the force:showToast event.

Go to Opportunity and create an action as shown below.

Add it to the opportunity  Salesforce1 and Lightning Experience Actions section on the page layouts to create quote by using actions.

 

Global Actions

Now we are going to create global actions that allow users to create the new user from the global action.
create a new apex class “CreateNewUser” with the below code.

public class CreateNewUser {
    @AuraEnabled
    public static void insertNewUser(User userInsert ){
        try{
            
            User temp = userInsert ; 
            Database.DMLOptions dmo = new Database.DMLOptions();
            dmo.EmailHeader.triggerUserEmail = false;
            String profileId =[Select id from Profile where name = 'Chatter Free User' Limit 1] .Id;
            temp.ProfileId = ProfileId;
            temp.TimeZoneSidKey = 'America/Los_Angeles';
            temp.LanguageLocaleKey = 'en_US';
            temp.EmailEncodingKey ='UTF-8';
            temp.LocaleSidKey = 'en_US';
            temp.setOptions(dmo);
        
            insert temp ;  
        }catch(Exception e){
            throw new AuraHandledException(e.getMessage());
          }
        
    }
    
}

Go to developer console and create a new lightning component “CreateNewUser” with below code.

<aura:component controller="CreateNewUser" implements="force:lightningQuickActionWithoutHeader">
    
    
    <aura:attribute name="newUser" type="User"  default="{ 'sobjectType': 'User',
                                                         'FirstName':'' , 
                                                         'LastName':'',
                                                         'Email':'' ,
                                                         'Username':'' ,
                                                         'Alias':''
                                                         }" />
    
    
    <ui:inputtext label="First Name" value="{!v.newUser.FirstName}"/>
    <ui:inputtext label="Last Name" value="{!v.newUser.LastName}"/>
    <ui:inputemail label="Email" value="{!v.newUser.Email}"/>
    <ui:inputemail label="User Name" value="{!v.newUser.Username}"/>
    <ui:inputemail label="Alias" value="{!v.newUser.Alias}"/>
    
    <lightning:button label="Save " onclick="{!c.handleSave}"
                      variant="brand" class="slds-button slds-button_brand slds-col_bump-left"/>
    
    
       
</aura:component>

Here is the component controller.

({
    handleSave: function(component, event, helper) {
        
        var newUserInsert = component.get("c.insertNewUser");
        //console.log(newUserInsert)
        console.log(component.get("v.newUser"));
        newUserInsert.setParams({
            "userInsert": component.get("v.newUser")
        });
        
        console.log(newUserInsert);
        newUserInsert.setCallback(this, function(response) {
            console.log(response);
            var state = response.getState();
            if(state === "SUCCESS") {
                // Prepare a toast UI message
                var resultsToast = $A.get("e.force:showToast");
                resultsToast.setParams({
                    "title": "Record is  Created  !",
                    "message": response
                });
                // Update the UI: close panel, show toast, refresh account page
                $A.get("e.force:closeQuickAction").fire();
                resultsToast.fire();
                $A.get("e.force:refreshView").fire();
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " + 
                                    errors[0].message);
                    }
                } else {
                    console.log("Unknown error");
                }
            } 
                else {
                    console.log('Unknown problem, response state: ' + state);
                }
        });
        
        // Send the request to create the new contact
        $A.enqueueAction(newUserInsert);
    }
    
    
},
 
 })

Go to Global action from the setup menu and create a new global action as shown below.

Go to publisher layout from the setup menu and add the lightning component create to the layout as shown below.

You can access global action from salesforce global header.

 

 

 

 

 

 

 

Visualforce Page Actions

Introduction

In this blog post, I am going to explain how to set up global actions and object-specific actions by using visualforce page.The global action which we are going to see in this blog is to send the message to slack channel and object specific action is to take the case ownership from the assigned queues.

Global Actions:-

As we are going to going to post the message to slack by using the global action, go and create a Slack app and get the slack token. Please refer this link

visualforce page is here below

<apex:page controller="GlobalSlackActions" lightningStylesheets="true" sidebar="false" showQuickActionVfHeader="false" docType="html-5.0">
    <apex:slds />
    /canvas/sdk/js/publisher.js
    
    <div class="slds-scope">
        <apex:form >
            <apex:actionFunction action="{!postMessagetoSlack}" name="postSlackMessage" rerender="out"
                                 oncomplete="refreshFeed();"/>
            <apex:outputPanel id="out">
                
                
                <div class="slds-form--compound slds-m-top--xxx-small">
                    <div class="slds-form-element">
                        <div class="slds-form-element__row slds-align_absolute-center">
                            <label class="slds-form-element__label" for="sample1">#Slack Channel</label>
                            <div class="slds-form-element__control slds-picklist">
                                <apex:selectList id="countries" value="{!slackChannel}" size="1" required="true" styleClass="slds-input">
                                    <apex:selectOptions value="{!ListOfSlackChannels}"/>
                                </apex:selectList>  
                            </div>
                        </div>
                    </div>
                    <div class="slds-form-element">
                        
                        <div class="slds-form-element__row slds-align_absolute-center ">
                            <label class="slds-form-element__label " for="sample2"> Name </label>
                            <div class="slds-form-element__control slds-picklist">
                                <apex:inputTextArea value="{!slackTextMessge}"  id="sample1" styleClass="slds-input"/>
                            </div>
                        </div>       
                    </div>
                </div>
                
                
            </apex:outputPanel>
            <div class="slds-form-element slds-align_absolute-center">
                <button  onclick="postSlackMessage();" styleClass="slds-button slds-button_neutral">Post to Slack </button>
                <button  onclick="closeAction();" styleClass="slds-button slds-button_neutral">Close  </button>
                
            </div>
        </apex:form>
    </div>
</apex:page>

 

And the controller is here.

public class GlobalSlackActions {
    public String slackChannel {get;set;}
    public List<SelectOption> channelNameOptions{get;set;}
    public String slackTextMessge {get;set;}
    public String slackToken {get;set;}
    public GlobalSlackActions(){
        slackToken ='xoxp-167092741267-167199659508-179751455458-6fd12a391bd432b735b4efcb1313193f';
    }
    public List<SelectOption> getListOfSlackChannels () {
        String slackEndpoint = 'https://slack.com/api/channels.list?token='+slackToken;
        HttpResponse res = buildHttp(slackEndpoint);
        
        if(res.getStatusCode() ==200){
            JSON2Apex cls =  (JSON2Apex)parse(res.getBody());
            list<Channels> c =cls.channels ; 
            System.debug('c'+c);
            List<SelectOption> options = new List<SelectOption>();
            for(Channels cName :c){
                options.add(new SelectOption(cName.name,cName.name));
            }
            return options ; 
        }else{
            return null ;
        }
    }
    
    
    
    public class JSON2Apex {
        public Boolean ok;
        public List<Channels> channels{get;set;}
    }
    
    
    
    public class Channels {
        public String id;
        public String name{get;set;}
        public Boolean is_channel;
        public Integer created;
        public String creator;
        public Boolean is_archived;
        public Boolean is_general;
        public Integer unlinked;
        public String name_normalized;
        public Boolean is_shared;
        public Boolean is_org_shared;
        public Boolean is_member;
        public Boolean is_private;
        public Boolean is_mpim;
        public List<String> members;
        public Integer num_members;
    }
    
    
    public static JSON2Apex parse(String json) {
        return (JSON2Apex) System.JSON.deserialize(json, JSON2Apex.class);
    }
    
    
    public PageReference postMessagetoSlack () {
        String slackEndpoint = 'https://slack.com/api/chat.postMessage?token='+slackToken+'&channel='+slackChannel+'&text='+slackTextMessge+'&pretty=1';
        buildHttp(slackEndpoint);
        return null ;
    }
    public HttpResponse buildHttp(String url){
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint(url);
        req.setMethod('POST');
        HttpResponse res = new HttpResponse(); 
        res = h.send(req);
        return res ;
    }
    
}

Go to set up, find global actions and create a new one as shown below.

Add to the publisher layout as shown below.

After saving the publishers layout you can able to see the quick actions as shown below.

Object Specific actions

Now I am going to walk thru how to create the object specific action.To associate an action to object you need to user standardController attribute of the object. Here we are going to create a Case Quick actions which are allowing the user to take the ownership of the case from case queue.

Here is the visual force page

<apex:page standardController="Case" extensions="CaseTakeOwnership" showQuickActionVfHeader="false">
    
    /canvas/sdk/js/publisher.js
    
    
    <apex:form id="demo">
        <apex:actionFunction action="{!takeOwnership}" name="takeOwner" reRender="demo" oncomplete="refreshFeed();"/>
        <div style="align:center">
            <br/><br/><br/>
            <button  onclick="takeOwner();" class="sucessBtn">Take Ownership</button> <br/><br/><br/><br/>
            <button onclick="closeAction();" class="closebtn">Cancel</button>
        </div>
    </apex:form>
    
    
</apex:page>

The controller is here below.

public class CaseTakeOwnership {
    public Case careRecord {get; set;}
    public CaseTakeOwnership(ApexPages.StandardController controller) {
        careRecord =(Case)controller.getRecord();
    }
    public PageReference takeOwnership() {
        try {
            careRecord.OwnerId = UserInfo.getUserId();
            update careRecord;
        } catch(Exception ex){
        }
        return null ;
    }
    
    
}

Both in global action and Object actions, I set showQuickActionVfHeader to false. So that we will not able to see the Standard Submit and Cancel Global action header.

Now go to Case Object buttons and links and create a new action as shown below

Add the object to quick action, salesforce 1 and lightning action on page layout based on whether you wanted to show on salesforce class or salesforce 1 as shown below.

 

Publisher Events

Refresh feed (desktop) 

Sfdc.canvas.publisher.publish({ name: 'publisher.refresh',
 payload: {feed:true}
});

Submit hook (Salesforce1)

Sfdc.canvas.publisher.subscribe({name: "publisher.showPanel",
 onData:function(e) {
 Sfdc.canvas.publisher.publish({name:
 "publisher.setValidForSubmit", payload:"true"});
}});
Sfdc.canvas.publisher.subscribe({ name: "publisher.post",
 onData: function(e) {
 alert("call some remote action here");
 Sfdc.canvas.publisher.publish({ name: "publisher.close",
 payload:{ refresh:"true" }});
}});

Close publisher event

        Sfdc.canvas.publisher.publish({name: "publisher.close", payload:{ refresh:"true" , successMessage: 'Cancel!'}});  

Activate publish button 

Sfdc.canvas.publisher.publish({
    name: "publisher.setValidForSubmit", 
    payload:"true"});
Clear Panel State
Sfdc.canvas.publisher.subscribe(
             { name : "publisher.clearPanelState", onData:function(e) { alert('Fire ClearPanelState'); }}
        );

Show Panel State 

   Sfdc.canvas.publisher.subscribe(
             { name : "publisher.showPanel", onData:function(e) { alert('Fire ShowPanel'); }}
        );
Success State 
            Sfdc.canvas.publisher.publish({ name : "publisher.success", payload : { feed:true }});

 

Redirect (Salesforce1)

sforce.one.navigateToSObject(recordId,view)
sforce.one.navigateToURL(url)
sforce.one.navigateToFeed(subjectId, type)
sforce.one.navigateToFeedItemDetail(feedItemID)
sforce.one.navigateToRelatedList(relatedListId, parentRecordId)
sforce.one.navigateToList(listViewId, listViewName, scope)
sforce.one.createRecord(entityName, recordTypeId)
sforce.one.editRecord(recordId)