Creating Child Records from Related List Using Custom Button in Salesforce A Generic Solution

Creating Child Records from Related List Using Custom Button in Salesforce: A Generic Solution

, ,

Welcome to our new blog post, in this post we will showcase Creating Child Records from Related List Using Custom Button. This in continuation to our previous blog post, where in we showcased a aura component to redirect to record from a flow, now we will be showing the same using LWC also.

Requirement

Many times, we have requirement, where we need to create records quickly from related list. As per the below ideas, salesforce has a limitation, where we can’t call the LWC directly from the related list and to call it from url, we need to wrap it inside a aura and flow.

Lets suppose we have a below use case:

  1. Create child records from a related list button.
  2. Redirect users to the newly created child record or keep them in the current context based on the app context.
  3. Support both standard apps and console apps with appropriate navigation logic.
  4. Make the solution reusable for any parent-child relationship, not just Accounts and Opportunities.

This blog post will demonstrates a generic approach for Creating Child Records from Related List Using Custom Button, and the user is redirected to record based on their context—whether in a standard or console app.

Solution

To meet the requirement, we implemented a generic framework using URL Button, Aura Components, Screen Flow and LWC.

As an example, we will be showing how we can create an opportunity record from Account related list using this solution.

Demo

Let’s see the solution in details –

1. URL Button

The URL button is configured to pass parameters to a Screen Flow. These parameters include:

  • Flow Name: The specific flow to invoke.
  • Parent ID: The record ID of the parent (e.g., Account).

This makes the solution adaptable to different parent-child relationships.

/lightning/cmp/c__GenericRelatedListFlowLauncher?c__flowName=FlowName&c__parentId=ParentId
2. Aura Component for URL Button

The Aura Component GenericRelatedListFlowLauncher allows calling the Screen Flow from a related list button and passing the necessary parameters:

Below is the high level functionality of this aura component –

  • Displays a Salesforce Flow in a modal when navigated to via a URL with specific parameters.
  • Extracts input variables like parentId and flowName from the URL and passes them to the Flow.
  • Closes the modal when the Flow finishes or is manually dismissed using the close button.
  • Redirects users to the related parent record if the necessary parameters are missing in the URL.
  • If opened via a console app. uses the Lightning Workspace API to close the focused tab in the Salesforce Console App.

GenericRelatedListFlowLauncher.cmp (Aura Component)

<aura:component implements="lightning:isUrlAddressable" access="global" >
    <!-- Attributes -->
    <!-- Public attribute to receive the parent record ID -->
    <aura:attribute name="parentId" type="String" access="public"/>
    <!-- Public attribute to receive the Flow Name to start -->
    <aura:attribute name="flowName" type="String" access="public"/>
    <!-- Private attribute to control the modal visibility -->
    <aura:attribute name="isOpen" type="Boolean" default="false" access="private"/>
    <!-- Workspace API for managing Lightning Console tabs -->
    <lightning:workspaceAPI aura:id="workspace"/>
    <!-- Initialization handler to set up the component -->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <!-- Handle changes to pageReference (URL updates) -->
    <aura:handler name="change" value="{!v.pageReference}" action="{!c.doInit}"/>
    <!-- Conditional rendering of the modal -->
    <aura:if isTrue="{!v.isOpen}">
        <div style="height: 640px;">
            <!-- SLDS Modal to display the flow -->
            <section role="dialog" tabindex="-1" class="slds-modal slds-fade-in-open">
                <div class="slds-modal__container">
                    <!-- Modal header with close button -->
                    <header class="slds-modal__header slds-modal__header_empty">
                        <!-- Close button for the modal -->
                        <lightning:buttonIcon iconName="utility:close" class="slds-modal__close" onclick="{!c.closeFlowModal}"/>
                    </header>
                    <!-- Modal content rendering the flow -->
                    <div class="slds-modal__content slds-p-around_medium">
                        <!-- Lightning Flow component to display the flow -->
                        <lightning:flow aura:id="flowId" onstatuschange="{!c.closeModalOnFinish}" />
                    </div>
                </div>
            </section>
            <!-- Backdrop for modal -->
            <div class="slds-backdrop slds-backdrop_open"></div>
        </div> 
    </aura:if>
</aura:component>


GenericRelatedListFlowLauncherController.js (Aura Controller JS)

({
    // Initialization logic to handle flow invocation and modal display
    doInit : function(component, event, helper) {
        // Open the modal
        component.set('v.isOpen', true);
        // Retrieve page reference and parameters
        var myPageRef = component.get("v.pageReference");
        if (myPageRef && myPageRef.state) {
            // Extract Flow Name and Parent ID from URL
            var flowName = myPageRef.state.c__flowName;
            var parentId = myPageRef.state.c__parentId;
            // Find the flow component to interact with
            var flow = component.find('flowId');
            // Define input variables for the flow
            var inputVariables = [
                {
                    name : 'ParentId',
                    type : 'String',
                    value : parentId
                }
            ];
            // Start the flow with input variables
            flow.startFlow(flowName, inputVariables);
        } else {
            // Redirect to record if parameters are missing
            helper.redirectToRecord(component, event, helper);
        }
    },
    
    // Close the modal
    closeFlowModal : function(component, event, helper) {
        console.log('closeFlowModal');
        // Set modal visibility to false
        component.set("v.isOpen", false);
        // Close the current tab and redirect to record
        helper.closeTab(component, event, helper);
        helper.redirectToRecord(component, event, helper);
    },
    
    // Close the modal when the flow finishes
    closeModalOnFinish : function(component, event, helper) {
        console.log('closeModalOnFinish');
        // Check the flow status, if status is finished close the modal
        if(event.getParam('status') === "FINISHED") {
            // Close the modal
            component.set("v.isOpen", false);
        }
    },
})

GenericRelatedListFlowLauncherHelper.js((Aura Helper JS))

({
    // Redirect the user to the record page
    redirectToRecord : function(component, event, helper) {
        // Retrieve parent record ID from page reference
        var myPageRef = component.get("v.pageReference");        	
        var parentId = myPageRef.state.c__parentId;         
        // Use force:navigateToSObject to redirect to record page
        var redirect = $A.get("e.force:navigateToSObject");            
        redirect.setParams({
            "recordId": parentId
        });            
        redirect.fire();
    },
    
    // Close the current tab in Lightning Console
    closeTab : function(component, event, helper) {
        // Access the Lightning Workspace API
        var workspaceAPI = component.find("workspace");
        if(workspaceAPI !== undefined){
            // Retrieve focused tab info and close the tab
            workspaceAPI.getFocusedTabInfo().then(function(response) {
                // Get the ID of the focused tab
                var focusedTabId = response.tabId;
                // Close the tab using its ID
                workspaceAPI.closeTab({tabId: focusedTabId});
            })
            .catch(function(error) {
                console.log(error);
            });
        }        
    },
})
3. Screen Flow

Below is the high level functionality of this flow –

Parameters: Accepts the ParentId as an input variable, making the flow reusable for creating Opportunities related to any Account.

Starts with a Screen Input: The flow begins with a screen where the user provides Opportunity details like Name, Stage, Amount, and Close Date.

Assigns Values to Variables: User-provided inputs are assigned to the OppDetails SObject variable, including Amount and AccountId, which is mapped to the ParentId input variable.

Creates an Opportunity Record: The flow creates an Opportunity record using the OppDetails variable populated with user inputs.

Handles Errors Gracefully: If an error occurs during the record creation step, the flow invokes a subflow for error handling (SUB_Screen_Flow_Error_Handler), passing the fault message for further processing.

Displays Success Message: On successful record creation, the user sees a confirmation message styled with custom text and formatting.

Redirects User: The flow redirects the user to the newly created Opportunity record or the parent Account record using a custom Lightning Web Component (c:redirectToRecordLWC).

4. LWC Component to Redirect

The LWC facilitates navigation to Salesforce record pages and manages tabs/subtabs in Console and Standard Navigation apps. It is designed for use within Salesforce Flows to handle redirection and workspace tab behavior seamlessly.

Below is the high level functionality of this LWC–

redirecttoRecordLWC

Parameters:

  • redirectToParent: Boolean to decide whether to redirect to the parent record.
  • parentRecordId: ID of the parent record.
  • recordId: ID of the newly created record (child record).

Methods:

  • handleInvoke: This method has the core logic that manages navigation and workspace tab behavior based on the app’s navigation mode. If the app is in Console Mode, it first closes the current tab and then either opens the parent record tab (if redirectToParent is true) or opens both the parent record in a new tab and the child record as a subtab under it. If in Standard Mode, it redirects directly to the record page using redirectToRecord(). The method uses lightning/platformWorkspaceApi to perform tab operations and ensures smooth navigation within the Salesforce console while dispatching a FlowNavigationFinishEvent to indicate the flow action’s completion.
  • redirectToRecord: Redirects to the record page in standard navigation mode using NavigationMixin.
  • showToast: Displays toast messages to users with customizable title, message, and type.

redirectToRecordLWC.html

<template>

</template>

redirectToRecordLWC.js

// Import necessary LWC modules and decorators
import { LightningElement, wire, api } from 'lwc';
// Import platformWorkspaceApi methods for handling workspace tabs
import {
    IsConsoleNavigation,
    EnclosingTabId,
    openTab,
    openSubtab,
    closeTab
} from 'lightning/platformWorkspaceApi';
// Import NavigationMixin for navigation functionality
import { NavigationMixin } from 'lightning/navigation';
// Import ShowToastEvent to show toast notifications
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
// Import FlowNavigationFinishEvent to notify flow completion in flows
import { FlowNavigationFinishEvent } from 'lightning/flowSupport';

// Export the class and extend LightningElement with NavigationMixin for navigation capabilities
export default class RedirectToRecordLWC extends NavigationMixin(LightningElement) {
    // Public property to store the parent record ID
    @api parentRecordId;
    // Public property to store the record ID to navigate
    @api recordId;
    // Public property to store the redirectToParent value from flow
    @api redirectToParent;
    // Wire to check if the app is in console navigation mode
    @wire(IsConsoleNavigation) isConsoleNavigation;
    // Wire to retrieve the enclosing tab ID
    @wire(EnclosingTabId) enclosingTabId;

    /**
     * connectedCallback is a lifecycle hook called when the component is inserted into the DOM.
     * It automatically triggers the `handleInvoke` method to perform navigation logic.
     */
    connectedCallback() {
        // Call the main handler method when the component is inserted into the DOM
        this.handleInvoke();
    }

    /**
     * handleInvoke method is responsible for managing the navigation and tab behavior
     * based on the app's navigation mode (console or standard). 
     * - In console mode, it handles the opening of parent and subtab records.
     * - In standard mode, it redirects to the record page directly.
     * 
     * If the `redirectToParent` flag is true, it opens the parent record tab.
     * Otherwise, it opens the parent record tab and then opens the child record in a subtab.
     * 
     * @param {void} - No parameters are accepted by this method.
     * @return {void} - This method does not return any value.
     */
    async handleInvoke() {
        debugger;
        try {
            // Check if the app is not in console navigation mode
            if (!this.isConsoleNavigation) {
                // Redirect to the record page in standard mode
                this.redirectToRecord();
                return;
            }

            // Close the current enclosing tab
            await closeTab(this.enclosingTabId);

            // Construct the URL for the parent record page
            const parentrecordUrl = `/lightning/r/${this.parentRecordId}/view`;

            // If redirectToParent is true, open the parent record in a new tab
            if (this.redirectToParent) {
                await openTab({
                    url: parentrecordUrl,
                    focus: true,
                });
            } else {
                // Otherwise, open the parent record and then open the child record in a subtab
                const parentTabResponse = await openTab({
                    url: parentrecordUrl,
                    focus: true,
                });

                // Store the response (parent tab ID) in a variable
                const parentTabId = parentTabResponse;

                // Construct the URL for the record page
                const recordUrl = `/lightning/r/${this.recordId}/view`;

                // Open a subtab for the record under the parent tab and focus on it
                await openSubtab(parentTabId, {
                    url: recordUrl,
                    focus: true,
                });
            }

            // Dispatch an event to notify that the flow navigation has finished
            this.dispatchEvent(new FlowNavigationFinishEvent());
        } catch (error) {
            // Log any error that occurs during execution
            console.error('Error in handleInvoke:', JSON.stringify(error));
        }
    }


    /**
     * redirectToRecord method handles navigation to the record page in standard mode.
     * It uses NavigationMixin to navigate to the record detail view.
     *  @param {void} - No parameters are accepted by this method.
     * *@return {void} - This method does not return any value.
     */
    redirectToRecord() {
        // Use NavigationMixin to navigate to the record detail page
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: this.recordId,
                actionName: 'view',
            },
        });
        // Dispatch an event to notify that the flow navigation has finished
        this.dispatchEvent(new FlowNavigationFinishEvent());
    }

    /**
     * showToast method displays a toast notification with the specified details.
     * @param {string} title - The title of the toast
     * @param {string} message - The message content of the toast
     * @param {string} variant - The variant type of the toast (e.g., 'success', 'error')
     * @return - N.A.
     */
    showToast(title, message, variant) {
        // Create a ShowToastEvent with the provided details
        const event = new ShowToastEvent({
            title,
            message,
            variant,
        });
        // Dispatch the toast event to display the notification
        this.dispatchEvent(event);
    }
}

redirectToRecordLWC.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__FlowScreen</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__FlowScreen">
            <property name="redirectToParent" label="1.Redirect To Parent" 
            type="Boolean" role="inputOnly" 
            description="Add true, if you want to redirect to parent record." 
            default="false"/>
            <property name="parentRecordId" label="2.Parent Record ID" 
            type="String" role="inputOnly" 
            description="The Parent Id of the record, if you want to redirect to parent record." />
            <property name="recordId" label="3.Record ID" 
            type="String" role="inputOnly" 
            description="The Id of the newly created record, if you want to redirect to new record."/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Leave a comment