Salesforce Flow Toolbar and Modal Windows

How can we execute Modal Flows from a button in the "object action bar"?



Flow Button Toolbar Component in action
Flow Button Toolbar Component in action

In this article I share a component that shows as a toolbar in the Record Page and allow to execute a Flow from a Modal Window.

Objective

We will create a component that will allow us to insert several buttons in a line, each button will execute a selected Flow in a modal window. Because we are inserting the toolbar in a record page, each Flow will get only as an input value, the recordId.

Flow Button Toolbar Component Desing
Flow Button Toolbar Component Desing

I made this component because I couldn't find a way to add a Flow action button to the object's record page when the Chatter Feed Tracking is enable for that object.

The problems I tried to solve were:
  1. Execute a Flow in a modal window with a button to "cancel and close" the flow screen (by default, there is no button to "cancel" a Flow). 
  2. Start the execution of the Flow from a button in the object's page (not the Chatter tab).
  3. Be able to add more than one button.
I believe the reason why the action button will not show in the action bar, is because the Chatter is enabled and the Feed Tracking is enabled too, according to Actions With and Without Chatter I understand it is not technically possible - or probably I'm too lazy to find the standard technical way to do it 😃. 

Anyway, and according to Salesforce docs, I think the reason is the red area marked below:

Actions With and Without Chatter
Salesforce Help - Actions With and Without Chatter
If there is a way, or I misunderstood, please do comment.

Specification


Name Label Type Required Default Description
flowsMetadata Flows Metadata String true DATA#;#DATA#;#DATA,... where DATA is: name:=Flow_API_Name,label:=value,description:=value

The name (API) of the Flow is required, the label will show in the button and the desciption will show as the button tooltip.

The Flow should have an input variable "recordId".

 

Flow Button Toolbar Component

<aura:component implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <aura:attribute name="showModal" type="Boolean" default="false"/>
    <aura:attribute name="arrayItems" type="Array"/>
    <aura:attribute name="flowAuraId" type="String"/>
    <aura:attribute name="flowsMetadata" type="String" required="true"/>

    <div class="{! v.showModal ? 'slds-show' : 'slds-hide' }">
            <!--###### MODAL BOX Start######--> 
            <section role="dialog" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true" aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open slds-modal_large">
                <div class="slds-modal__container">
                    <!-- ###### MODAL BOX HEADER Start ######-->
                    <header class="slds-modal__header">
                        <lightning:buttonIcon iconName="utility:close" onclick="{! c.hideModal }" alternativeText="Close" variant="bare-inverse" class="slds-modal__close"/>
                    </header>
                    <!--###### MODAL BOX BODY Part Start######-->
                    <div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
                     {! v.body }
                    </div>
                    <!--###### MODAL BOX FOOTER Part Start ######-->
                    <footer class="slds-modal__footer">
                        <lightning:button variant="neutral"  label="Cancel" title="Cancel" onclick="{! c.hideModal }"/>
                    </footer>
                </div>
            </section>
            <div class="slds-backdrop slds-backdrop_open"></div>
            <!--###### MODAL BOX Part END Here ######-->
        <div class="slds-backdrop slds-backdrop_open"></div>
    </div>
    <div class="slds-size_3-of-3 slds-box slds-box_x-small" style="background:#ecebea;margin-bottom: -20px;">
        <div class="slds-clearfix">
            <div class="slds-float_right slds-p-right_xx-small">
                <lightning:buttonGroup>
                    <lightning:buttonIcon name="Settings" iconName="utility:settings" variant="brand" alternativeText="Operations" />
                    <aura:iteration items="{!v.arrayItems}" var="item">
                     <lightning:button name="{!item.name}" label="{!item.label}" title="{!item.description}"
                                          iconName="utility:new_window" iconPosition="left" onclick="{!c.runFlow}"/>
                    </aura:iteration>
                </lightning:buttonGroup>
            </div>
        </div>
    </div> 
</aura:component>

 

Flow Button Toolbar Design

<design:component label="Flow Button Toolbar">
    <design:attribute name="flowsMetadata" label="Flows Metadata" description="DATA#;#DATA#;#DATA,... where DATA is: name:=Flow_API_Name,label:=value,description:=value" required="true"/>
</design:component>

 

Flow Button Toolbar Controller

({
    doInit : function(component, event, helper) 
    {
        // lets get the data that defines the properties of each button 
        let dataItems = component.get("v.flowsMetadata").split('#;#'); // this separator is REQUIERED
        let arrayItems = new Array(dataItems.length);

        for (var i=0; i<dataItems.length; i++)
        {
            // Validate the commas in the item ...
            if ((dataItems[i].match(/,/g) || []).length != 2)
            {
                throw new Error("Flow Button Toolbar ERROR: wrong number of properties in an element. HINT: check the commas.");
            }

            // ... and the property assignment symbol :=
            if ((dataItems[i].match(/:=/g) || []).length != 3)
            {
                throw new Error("Flow Button Toolbar ERROR: wrong number of properties in an element. HINT: check the ':=' chars.");
            }
            
            // now we have something like name:=<Flow_API_Name>,label:=<value>,description:=<value>
            // and we will create a map with all the preperties, because the order they were entered is not important
            let properties = dataItems[i].split(',');
            var buttonMap = new Map(); 
            for (var j=0; j<properties.length; j++)
            {
                buttonMap.set(properties[j].split(':=')[0], properties[j].split(':=')[1]);
            }
            
            // the Button will go to the array as an object, in order to be able to reference the properties in the aura:iteration
            var button = { name:buttonMap.get('name'), label:buttonMap.get('label'), description:buttonMap.get('description')};
            arrayItems[i] = button;
        }

        component.set("v.arrayItems", arrayItems);
    },

    runFlow : function( component, event, helper ) 
    {
        let flowName = event.getSource().get( "v.name" );
        component.set("v.flowAuraId", flowName + "Id");
        component.set( "v.showModal", true );

        $A.createComponent(
            "lightning:flow",
            {
                "aura:id": component.get("v.flowAuraId"),
                "onstatuschange": component.getReference( "c.hideModal" )
            },
            ( flow, status, errorMessage ) => {
                if ( status === "SUCCESS" ) {
                    component.set( "v.body", flow );
                    component.set( "v.flow", flow );
                    var inputVariables = 
                    [
                        { name : "recordId", type : "String", value: component.get("v.recordId") }, 
                    ];
                    flow.startFlow( flowName, inputVariables );
                }
            }
        );
    },

    hideModal : function( component, event, helper ) 
    {
        let params = event.getParam( "status" );
        
        if (params == null) // When the modal window is closed
        {
            component.set( "v.showModal", false );
            //let cmp = component.find(component.get("v.flowAuraId"));
            //component.get("v.flowAuraId").destroy();
            component.find(component.get("v.flowAuraId")).destroy();

            helper.showWarningToast(component, event, helper);
        }
        else if ( event.getParam( "status" ).indexOf( "FINISHED" ) !== -1 ) // when the flow ends
        {
            component.set( "v.showModal", false );
            component.find(component.get("v.flowAuraId")).destroy();
            
            helper.showSuccessToast(component, event, helper);
        }
    },
})

 

Flow Button Toolbar Helper

({
    showSuccessToast : function(component, event, helper) 
    {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({ "title": "Mensaje", "message": "El registro se actualizó correctamente.", "type":"success" });
        toastEvent.fire();
    },

    showWarningToast : function(component, event, helper) 
    {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({ "title": "Advertencia", "message": "El proceso fué cancelado.", "type":"warning" });
        toastEvent.fire();
    }
})

Is there any known problem with this Component?

Not as far as I know.

The number of buttons will depend in the length of each DATA element, you know, the Flow API name, the label and the description or tooltip. There is no requirement about if the Flow should be a Screen flow or Autolaunched.

I didn't test it for scheduled Flows, and there is no code to control the execution of a scheduled Flow, like for instance "validate that the user don't execute the scheduled Flow twice in a day", or something of the sort.

 

Wrap-up

The toolbar will show in the section you insert it. I made the bottom margin in such way that it will appear very close to the component below it (my objective was to show it near the Highlights Panel, above the action buttons).

Again, one more reason that shows that with little code and the power of the flows, much can be achieved 😉. You develop to keep the application on the Point&Click side.

 

Resources and Acknowledgement

Salesforce Developers and Ligthning Design System where very useful to do this component.

Part of the code is thanks to the selected answer to How do I launch a flow from a modal window?

 

What Next?

I'm still looking to develop a component with a Drop Zone that can be the container for other component while you are in the Lightning Record Page Editor. I know it is not simple, and as far as I know, Drag & Drop is not supported in design mode (from the Visual Editor Palette Items).




"... simplicity is a great virtue but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better ..."
Edsger Wybe Dijkstra, Dutch pioneer in computing science 

Comments