Why are there 2 versions of Flow Launcher?

Flow Launcher and Flow Launcher_SObject both allow you to launch any auto-launched or screen flow from an existing screen flow.  They can show up in-line or as pop-up modals.  The flows can be launched with a button, or you can hide the button and let the flows launch reactively when one of the input attributes changes. This post will review how to configure this component, when to use which version and provide some example use cases.


Configuring Inputs for the Launched Flow

You can pass values into the launched flow a few different ways.  One is to specify the name of the input resource (variable) in the launched flow and identify the value to be passed to it.

NOTE: The Flow Input Variable Name should be a text entry of the name of the attribute in the launched flow. Be sure not to select an existing resource from your own flow in the drop-down with the same name.

If you want to pass in multiple values, they can be specified as a JSON formatted list of variable names, types and values.  The JSON can be created as a plain text Text Template.

Each attribute is composed of three parts.

  1. The name of the input flow attribute in the launched flow
  2. The data type of the attribute (String, Integer, Boolean, etc.)
  3. The value to be passed to the attribute

Note: A future version of Flow Launcher will help with picking the input variables and automating the process of creating the JSON.


Handling Outputs from the Launched Flow

You can also pass values back to the calling flow from the launched flow.  This does require you to give these output attributes in your launched flow special names.  This is why the two versions of Flow Launcher came about.


The regular Flow Launcher recognizes up to two different outputs from the launched flow.  One is the Launched Flow Output Result – Integer and the other is the Launched Flow Output Result – String

To be able to access these values in the calling flow, the launched flow has to have a Text and/or Number variable marked as “Available for output” with the exact names of OUTPUT_Integer and/or OUTPUT_String respectively.


Flow Launcher and Flow Launcher_SObject

This is where the second version of Flow Launcher comes in.  Flow Launcher_SObject adds two additional supported inputs and outputs, a Record or Record Collection can be passed to the launched flow with this version.  You need to specify the object you want to reference when you first configure Flow Launcher_SObject on your screen.  This was put in a separate version to keep you from having to always pick an Object even when you don’t plan on using the Record or Record Collection attributes.

This version of the component will also recognize Launched Flow Output Result – Collection (OUTPUT_Collection) and/or Launched Flow Output Result – Record (OUTPUT_Record).  These allow you to pass a collection of records or a single record of the specified Object back to the calling flow.


Sample Flows with Flow Launcher and Datatable

Let’s take a look at a few demo flows that use the SObject version of the Flow Launcher.  The first two show different ways to use a Datatable row action to edit a record in a separate reactive pop-up screen flow.  The third shows how to use a Flow Launcher button to run a flow to add a new record to an existing Datatable.


Sample Flow #1 – Datatable Row Action Edit Record

This first demo is a screen flow with a datatable where the user selects a record to update via a row action.  The actioned row causes the Flow Launcher to run a pop-up screen flow where the record values are edited and the changed record is passed back to the calling flow to be updated.  By adding the Auto Navigate component to the calling flow, you can force the flow to complete the update by reacting to the returned value from the launched flow instead of making the user click an extra button.

Here’s a breakdown of the two flows used in this demo.

Primary Flow

The primary flow gets some Account records and displays them in a Datatable.  When the user clicks on a Row Action, the Flow Launcher reacts and calls a Screen Flow and passes the actioned record to it.  When the called flow passes back the changed record, the Auto Navigate component’s conditional visibility causes it to navigate to the next element in the flow.  The next element in the flow updates the changed Account record and the flow restarts which gets the newly updated Account records and starts the process over again. 

This method is good for when you want to put this type of screen flow on a Lightning Home or Record page.


On the screen, the Datatable displays the records from the Get Accounts element.

A standard Row Action is added to the Datatable so the user can pick which record they want to make changes to.


The SObject version of the Flow Launcher is configured to:

  1. Work with the Account Object
  2. Show the launched flow in a modal
  3. Disable the user’s ability to close or escape out of the modal before completing the flow
  4. Set the size of the pop-up modal
  5. Pick the flow to launch
  6. Specify the name of the Input variable in the launched flow to pass information into
  7. Pass the Actioned Row output from the Datatable to the launched flow
  8. Make the flow reactive to changes in the Input Record by hiding the Button

The Auto Navigate component is configured with Conditional Visibility to only show (react) when it sees a value come back from the launched flow.


Because the Auto Navigate component is being used to advance to the next element, hide the Screen’s footer so the Next button is not visible.


Finally, the Update Records element is configured to update the Account with the changed record that was passed back as an output attribute from the launched flow.


Launched Flow

The launched flow is a screen flow that takes the Account record passed into it from the primary flow, displays some of the fields for the user to make changes to and assigns the changes to a special output attribute called OUTPUT_Record which gets passed back to the primary flow.


This flow has 2 special resource variables to communicate with the primary flow.  One is an Account record variable (INPUT_Account) that is set as “Available for input” and the other is an Account record variable (OUTPUT_Record) that is set as “Available for output”. 

NOTE: The output variable has to have this exact name in order to be recognized by the Flow Launcher component in the primary flow.  The input variable can have any name as long as the primary flow references the same name in the Flow Launcher configuration.


The flow Screen uses the values from the input record for display and as defaults for the inputs for the field changes.


The Assignment copies the original input record to the output record and then updates the field values changed on the screen.


The changed record is what gets passed back to the primary flow where the actual update is processed.

So combining the two flows, the user selects a record to act on in the primary flow, it gets passed to the launched flow for the user to change field values and the changed record is then referenced back in the primary flow where it reacts to the changes and navigates past the screen and writes the changes back to the database and starts the process all over again.


Sample Flow #2 – Datatable Row Action Edit Records

The previous flow processed one record at a time then restarted the flow to display the updated record in the datatable.  This slightly different version of the primary flow uses a couple of reactive collection actions to allow multiple updates on the same screen with the records displayed in the datatable refreshing each time a change is saved.  This type of flow is useful when you want to handle record updates as part of a larger flow or process.


The CP – Reactive Record Collection acts like the BLANKVALUE function for record collections.  Initially, the component is seeded with the record collection from the Get Accounts element but once there is a value being returned by the CP – Upsert Record By Key component, that collection is used instead. 


The output from the Reactive Record Collection is what gets used as the record collection to display in the Datatable.


The Flow Launcher component is configured exactly the same as it is in the previous example and it calls the same pop-up screen flow.


The CP – Upsert Record By Key component replaces the record in the original collection with the changed record returned by the launched flow.

  1. The input collection is the Output Remaining Rows collection from the datatable.  Note: This output includes all records passed into the datatable while also including any additional inline edits made by the user in the datatable.
  2. The API name of the field used to match the record in the collection can be left blank as it defaults to Id.
  3. The record to upsert is the changed record that was returned by the launched flow.

The four components on the screen work together reactively to keep all of the changes to the records up to date in the datatable.

The Screen footer is shown with the label of the Next button changed to read Save All Changes.  The user will click this when they are done changing records in the datatable and are ready to have those changes saved to the database.


Once the user is done making changes and continues on from the screen, the Update Records component commits the output from the datatable with all of the changes back to the database.


Sample Flow #3 – Datatable with Add New Record Button

The third example demonstrates how to use a Flow Launcher button to add a new record to a Datatable.


Primary Flow

This primary flow is very similar to the flow used in the last example.  It has the same Get Accounts and Update Changed Records elements as well as the same custom components on the Screen.  The difference is how the Datatable and Flow Launcher are configured.


There are no Row Actions in the Datatable and the columns were set up as being editable for inline editing.

The Flow Launcher is configured with these attributes:

  1. Leaving Disable Close unchecked allows the user to cancel out of the Add Record screen
  2. The Add Record flow is selected
  3. Since a record is being added, no inputs are needed for the launched flow
  4. Hide Button stays unchecked so a button will be used to launch the flow instead of reacting to a changed input
  5. The button label, variant and icon are defined
  6. Stretch Button is checked to display the button as a long bar beneath the Datatable

NOTE: To help your future self when going back later to review a flow with a lot of custom screen actions, you can use Display Text components (7) on the screen to add descriptive notes. 

These only appear when editing the flow in the flow builder and are not visible to a user running the flow because conditional visibility is used to hide them when running the flow. 

cHideMe is a Constant resource with an assigned value of True.


This time the Upsert Record By Key will insert the returned record because it does not already exist in the Datatable record collection.


Launched Flow

In this example, the launched flow is designed to collect field values and create a new Account record.


The screen has inputs to collect data needed for the Account record.  It also relabels the Next button in the footer as “Add New Account”.



The Assignment sets the field values in an Account record variable.


The Create Record uses the Account record variable to create a new Account.


Since there could be other automation in the org that acts on a newly created record, a Get Records is used to read the newly committed record back into the flow.  The id of the new record can be accessed here in the Get Records because it was automatically inserted into the Account record variable by the Create Records element.


Finally, the OUTPUT_Record variable is populated with the new Account record values so it can be referenced in the Flow Launcher component in the calling flow.


Hopefully these three examples will give you some templates you can use in your own orgs to take advantage the the Flow Launcher component.

The custom components used in the demonstrations can be found here:

Flow Launcher

Datatable

Auto Navigate

Reactive Collection Processors


Datatable with Row Actions calling a pop-up Screen Flow

Recently I wrote an article on how datatable row actions could be used to execute a Screen Action.  This is a great feature if you need to perform some unattended actions on a record in the datatable.

But what if you need to provide some additional input or interact with the data in the actioned record?  Action Buttons and Screen Actions can only run auto-launched flows, not screen flows.

I was able to work with Josh Dayment to enhance his new Flow Launcher action to launch a screen flow reactively instead of just from a button.  We also added the capability of passing records and record collections in and out of the launched flow.

There will be many different use cases for this new capability, but this article highlights how you could combine Datatable and Flow Launcher to edit fields that aren’t natively supported for inline editing such as Lookup and Multi-Select Picklist fields.

Here’s the flow in action.  Each time the row action button is clicked, a screen flow pops up allowing the user to edit some of the fields in the actioned record.  When they click Save Changes, the datatable gets refreshed with the changed record.


This is a simple flow where you get a collection of Account records and present them in a Datatable on a reactive screen.

The reactive screen displays a Datatable with row actions enabled.  4 additional reactive actions are added to handle the additional processing.  Each of these will be explained in more detail later.

  1. CP – Get First Record
    Extract the single record from the collection of actioned rows provided by the datatable.
  2. Flow Launcher – Sobject
    Launch a screen flow that will allow the user to edit fields in the record provided by the Get First Record action.
  3. CP – Upset Record By Key
    Upsert the updated record from the screen flow into the current record collection.
  4. CP – Reactive Record Collection
    Provide the active record collection to be displayed in the Datatable and used by the Upsert action.  If nothing has been processed yet, seed the flow with the original Get Records collection.  Otherwise, return the collection provided by the Upsert Record By Key action.

When you first build this flow, set the input collection for the Datatable to be the collection of Accounts from the Get Records.  You will come back later and update this once you add the other reactive actions to the screen.

Next, define the Row Action for the Datatable.  Currently, there is only a Remove Row action so select that and restrict the number of actioned rows to 1.


The Remove Row action in the Datatable outputs a collection of records actioned by the user.  In this case, the Datatable is configured to restrict the user to a single action at a time.  The Get First Record component extracts the first record from the collection of Removed (Actioned) Rows from the Datatable so you have the single record you need to pass to the screen flow.


The Flow Launcher will fire by reacting to any changes in the record output by the Get First Record action which fires any time a new record is provided by the Datatable row action.

To configure the Flow Launcher component to launch the flow reactively, set the “Hide Launch Button” attribute to True (1).  To have the flow pop-up in front of the Datatable, set the “Show Flow in Modal” attribute to True (2).  You can select the size of the modal with the “Modal Size” attribute (3).  Identify the flow to be launched by providing the active flow’s API name in the “Flow API Name” attribute (4).  Identify which resource in the launched flow to pass the record to with the name of the input variable in “Subflow Input Variable Name” (5) and the record you are passing to that variable in “Launched Flow Input Attribute – Record” (6).  You can force the user to complete the called flow instead of closing the modal manually, by setting the “Disable Close Icon” attribute to True (7).


The flow being launched takes the values in the input record and displays a screen with inputs for some of the record’s fields.


The field values from the input record are used as defaults for the inputs on the screen.


The Flow Launcher action is looking for an output attribute from the flow with a special name of OUTPUT_Record.  You should create that as an output attribute in your screen flow and assign the values from the screen inputs to the OUTPUT_Record fields.


NOTE: You still want the calling flow to refresh even if the user doesn’t make any changes.  You can force this by having a custom checkbox field on your record.  The called flow will always change this value by setting it using this formula (fToggleChange).


The Upsert Record By Key component takes the modified record from the screen flow and upserts it back into the record collection by replacing the matching record in the collection with the new record values from the screen flow.  The key field API name defaults to Id.  Like you did with Datatable, you first need to configure this by referencing the original Get Records collection.  This will be changed after you add the Reactive Record Collection component to the screen.


Finally, the Reactive Record Collection component acts like a reactive BLANKVALUE function for record collections.  If the Input Record Collection hasn’t been populated yet, it uses the Alternate Record Collection to “seed” the original output from the component.  In this flow, the collection is first seeded with the results of the Get Records flow element.  After that, the record collection is the output from the Upsert Record By Key component. This output is used to provide the reactive record collection to both the Datatable component and the Upsert Record By Key component.


Now that the Reactive Record Collection component has been added to the screen, go back to the Datatable and Upsert Record By Key and change the Get Records collection to the output collection from the Reactive Record Collection component.


NOTE: CP – Get First Record, CP – Upsert Record By Key and CP – Reactive Record Collection are new actions I created and are available in version 1.0.3 or later of Reactive Screen Components.  These also require version 3.2.4 or later of the Collection Processors.


I hope this example gives you some ideas for some other use cases for components like Datatables with Row Actions or Autolaunched Screen Flows or Reactive Collection Processors.  Add your ideas to the comments.

Datatable with Row Actions calling a flow Action Button

This use case for a “hidden” Screen Action button shows how a row action in a custom Datatable can be used to execute a Screen Action subflow to reactively update the actioned record in the Datatable.

The calling flow gets a collection of Account records and presents them on a reactive screen for user interaction.

Here’s the flow in action.  Each time the row action button is clicked, that row gets updated by the Screen Action to set the date field to a month from the current date and change the text field to all uppercase.


The flow screen has 5 components on it.  Only the Datatable is visible to the user.  The Update Record Action Button was hidden from the user and Flow Builder by modifying the flow xml.  (The ability to trigger a hidden Action is coming in a future Salesforce release)


The Datatable component takes the output of the Reactive Record Collection component as its reactive input.


The Remove Row action in the Datatable outputs a collection of records actioned by the user.  In this case, the Datatable is configured to restrict the user to a single action at a time.  The Get First Record component extracts the first record from the collection of Removed (Actioned) Rows from the Datatable.


The now hidden Action Button passes the record from the Get First Record component to the subflow and provides the modified record as an output.


The subflow called by the Action Button takes an Account record as input, modifies two fields and passes back, to the calling flow, the modified record as output.


The Upsert Record By Key component takes the modified record from the Action Button and upserts it back into the Reactive Record Collection.  The key field API name defaults to Id.


The Reactive Record Collection component acts like a reactive BLANKVALUE function for record collections.  If the Input Record Collection hasn’t been populated yet, it uses the Alternate Record Collection to “seed” the original output from the component.  In this flow, the collection is first seeded with the results of the Get Records flow element. After that, the record collection is the output from the Upsert Record By Key component. This output is used to provide the reactive record collection to both the Datatable component and the Upsert Record By Key component.


NOTE: I’ll soon be releasing the three reactive collection processor components used in this example on unofficialsf.com. 

A Better Way to Debug a Screen Flow

Have you ever wished you could debug a screen flow using the same UI that you have when you are debugging a triggered flow?

Record Triggered Flow Debugger

You get to see the path taken through the flow and you can chose to expose or hide the details behind each of the executed nodes in the flow.

This is much easier then what you get with a screen flow, which is an almost endless run-on list of everything that happened in the flow.

Screen Flow Debugger

When debugging a screen flow, you are constantly having to scroll and search trying to find the exact information you need to debug your flow.


Wouldn’t something like this make debugging a screen flow much easier?

Screen Flow Debugging Improved

I’ve created a special subflow you can install in your orgs and use in your screen flows to give you this improved debugging experience. You simply insert this subflow into your screen flow where you want it to stop. You then execute the flow from the system or directly from the flow builder debug button. An error will be generated when reaching the subflow and you will get an email with a link that, when clicked, will take you to the flow builder in debug mode with the path outlined and each individual step available to separately review.


If you want to be able to execute your flow both with or without triggering the debug error, just follow these steps.

  • Create an boolean variable called vDebug and make it available for input

Add the “Debug – Force Flow Error – Subflow” subflow to your flow and pass in your vDebug variable. If the value of the variable is True or you don’t pass in anything (Default=True), the error will be triggered. If you pass in a value of False (vDebug was not checked), the error will be bypassed and your flow will continue normally.

  • When debugging the screen flow from the Flow Builder, check the checkbox for vDebug
  • When you run your flow, you will get an error message when the subflow is executed
  • You will also get an error email with a link you can click to be taken to your flow with the improved debugger UI

TIP: You can Cut/Paste the error node to move it around your flow to try out different debugging scenarios.


Installation

Production or Developer Version 1.2
Sandbox Version 1.2

Post Installation: Make sure the flow “Debug – Force Flow Error – Subflow” is Activated

Package Contents:

  • Custom Object – DebugForceError__c
    • Custom Field – ForceValidationError__c
    • Validation Rule – TriggerFlowError
    • Page Layout
  • Flow – Debug_Force_Flow_Error_Subflow

Notes

  • The screen flow version being debugged must be in Auto-Layout mode
  • The screen flow version being debugged must be Active
  • There is a limit to the size of the Flow Interview that may cause the email link to not be available. (I’m not sure yet how big that is)
  • You must be a user who receives flow error emails (see documentation)

Use Flow to get the running User’s Time Zone offset from GMT

A couple of years ago, I created a component to convert a Date value to a Datetime value in a Flow. Recently, Andy Engin Utkan, figured out a way to use this component to overcome issues he was having when using a Display Text component in a Flow when trying to show Datetime values and have them display in the correct time zone.

You are unable to use a formula in Salesforce to determine a User’s time zone. Admins have created very complex formulas trying to calculate an offset based on the User’s State or Country but then they ran into issues trying to handle Daylight Savings Time adjustments as well.

Here’s an example presented by Eric Praud on Jen Lee’s “How I Solved This” Admin Podcast where he created a new custom object, added 9 custom fields to the User object and came up with this formula to get the hour of the day when converted to the User’s local time:

IF( OR(
ISBLANK( $User.Summertime_Start_Offset__c ),

CreatedDate< DATETIMEVALUE(DATE(YEAR(DATEVALUE(CreatedDate)),MONTH($User.Summertime_Start_Date__c),DAY($User.Summertime_Start_Date__c))
-(WEEKDAY(DATE(YEAR(DATEVALUE(CreatedDate)),MONTH($User.Summertime_Start_Date__c),DAY($User.Summertime_Start_Date__c)))-1)) + $User.Summertime_Start_Offset__c /24,

CreatedDate>=
DATETIMEVALUE(DATE(YEAR(DATEVALUE(CreatedDate)),MONTH($User.Wintertime_start_Date__c),DAY($User.Wintertime_start_Date__c))
-(WEEKDAY(DATE(YEAR(DATEVALUE(CreatedDate)),MONTH($User.Wintertime_start_Date__c),DAY($User.Wintertime_start_Date__c)))-1))+$User.Wintertime_Start_Offset__c/24

),

HOUR(TIMEVALUE(CreatedDate+$User.GMT_Offset__c /24))
+IF( AND($User.Southern_Hemisphere__c, NOT(ISBLANK( $User.Summertime_Start_Offset__c ))),1,0)
-IF(HOUR(TIMEVALUE(CreatedDate+ $User.GMT_Offset__c /24))
+IF( AND($User.Southern_Hemisphere__c, NOT(ISBLANK( $User.Summertime_Start_Offset__c ))),1,0)>23,24,0)

,
HOUR(TIMEVALUE(CreatedDate+(1+ $User.GMT_Offset__c )/24))
-IF( AND($User.Southern_Hemisphere__c, NOT(ISBLANK( $User.Summertime_Start_Offset__c ))),1,0)
-IF(HOUR(TIMEVALUE(CreatedDate+(1+ $User.GMT_Offset__c )/24))
-IF( AND($User.Southern_Hemisphere__c, NOT(ISBLANK( $User.Summertime_Start_Offset__c ))),1,0)>23,24,0)

)

Andy figured out that taking the difference between the Datetime returned by my component (midnight GMT) and the Datetime returned by the Flow formula DATETIMEVALUE(Date) you would get the GMT Offset for that date based on the running User’s time zone.

Here’s a sample sub-flow I created that you could use to get the User’s GMT Offset for any date. You can call this sub-flow anywhere you need to get the offset to use in Datetime value calculations for the User’s time zone.

A Date Variable is created for Input and a Number Variable is created for Output.

A Formula is used to assign a default value of Today if no date value is passed into the flow.

The result of the fDate formula is passed into the Convert Date to Datetime Flow Action

And finally, the difference between the two Datetime values is calculated, converted to hours and passed back to the calling Flow.

Here’s an example of how you could call this sib-flow from your flow.


Depending on the date, the difference between GMT (Greenwich Mean Time) also known as UTC (Coordinated Universal Time) and the User’s time could be different.

For Example:

From the second Sunday of March at 07:00 UTC until the last Sunday of March at 01:00 UTC, London is four hours ahead of New York.

From the last Sunday of March at 01:00 UTC until the last Sunday of October at 01:00 UTC, London is five hours ahead of New York.

From the last Sunday of October at 01:00 UTC until the first Sunday of November at 06:00 UTC, London is four hours ahead of New York.

From the first Sunday of November at 06:00 UTC until the second Sunday of March at 07:00 UTC, London is five hours ahead of New York.

So, for me in the Eastern Time Zone, running the Flow with a Date of 3/1/2022 returns a value of -5 for my offset from GMT.

However, because of Daylight Savings Time, running the Flow with a date of 3/15/2022 returns the correct GMT offset of -4 for that date.


Get the Convert Date to Datetime Flow Action on UnofficialSF.
https://unofficialsf.com/convert-date-to-datetime-flow-action/

Find more from Eric Smith on his blog.
https://ericsplayground.wordpress.com/

Find out more from Andy Engin Utkan on his blog.
https://salesforcebreak.com/

How I use Batch Files to Package my Datatable Component for Release

Narender Singh (ForcePanda) recently wrote a blog post on how he uses SFDX Packaging Commands in VSCode to release component packages. Like Narender, I found it difficult to remember and reuse all of the CLI commands needed to create packaged versions of my components.

When I release a new update for my Datatable component, I need to generate a new unmanaged package with a link so users can install and upgrade the component. To create the new package I first need to create a new version by executing a command line command that looks something like this:

sfdx force:package:version:create -v lexhost -w 10 -x -c -n 3.2.1.0 -d force-app\

Then, to make the new package available, I issue another command similar to this:

sfdx force:package:version:promote -v lexhost --package "[email protected]"

To save all of this typing and to make sure I’m issuing the correct syntax for the commands each time, I created a series of Batch Command files and included them in a packaging directory in my Datatable source code project.

To create a new version, I use my CreateNewVersion.bat command file. This file takes an optional input where I can specify the new version number like this:

>CreateNewVersion 3.2.2

To execute the second command, I would need execute another command file by entering:

>Promote 3.2.2

To avoid any typos with mismatched version numbers, I created another command file to ask for and store the version number that is then used by the other commands if no version number is provided on the command line.

Here are each of the Batch Command Files I created and use to publish updates for Datatable.

SetVersion.bat
--------------
@echo off
if "%1" neq "" goto skipprompt
set /p version="Set Version Number: "
goto exit
:skipprompt
set version=%1
:exit
ShowVersion
@echo on

ShowVersion.bat
---------------
@echo off
echo Version: %version%
@echo on

CreateNewVersion.bat
--------------------
@echo off
if "%1" neq "" set version=%1
@echo on
sfdx force:package:version:create -v lexhost -w 10 -x -c -n %version%.0 -d force-app\

Promote.bat
-----------
@echo off
if "%1" neq "" set version=%1
@echo on
sfdx force:package:version:promote -v lexhost --package "datatable@%version%-0"

Details.bat
-----------
@echo off
if "%1" neq "" set version=%1
@echo on
sfdx force:package:version:report -v lexhost --package "datatable@%version%-0"

Here’s an example of them in actual use when I released a recent Datatable update: (My entries shown in bold)

D:\esmit\Documents\VSCode\LightningFlowComponents\flow_screen_components\datatable>packaging\setversion
Set Version Number: 3.4.5
Version: 3.4.5


D:\esmit\Documents\VSCode\LightningFlowComponents\flow_screen_components\datatable>packaging\createnewversion 

D:\esmit\Documents\VSCode\LightningFlowComponents\flow_screen_components\datatable>sfdx force:package:version:create -v lexhost -w 10 -x -c -n 3.4.5.0 -d force-app\
Request in progress. Sleeping 30 seconds. Will wait a total of 600 more seconds before timing out. Current Status='Queued'
Request in progress. Sleeping 30 seconds. Will wait a total of 570 more seconds before timing out. Current Status='Verifying dependencies'
Request in progress. Sleeping 30 seconds. Will wait a total of 540 more seconds before timing out. Current Status='Verifying dependencies'
Request in progress. Sleeping 30 seconds. Will wait a total of 510 more seconds before timing out. Current Status='Verifying dependencies'
Request in progress. Sleeping 30 seconds. Will wait a total of 480 more seconds before timing out. Current Status='Verifying dependencies'
Request in progress. Sleeping 30 seconds. Will wait a total of 450 more seconds before timing out. Current Status='Verifying dependencies'
Request in progress. Sleeping 30 seconds. Will wait a total of 420 more seconds before timing out. Current Status='Verifying dependencies'
Request in progress. Sleeping 30 seconds. Will wait a total of 390 more seconds before timing out. Current Status='Verifying dependencies'
Request in progress. Sleeping 30 seconds. Will wait a total of 360 more seconds before timing out. Current Status='Verifying metadata'
Request in progress. Sleeping 30 seconds. Will wait a total of 330 more seconds before timing out. Current Status='Verifying metadata'
Request in progress. Sleeping 30 seconds. Will wait a total of 300 more seconds before timing out. Current Status='Verifying metadata'
Request in progress. Sleeping 30 seconds. Will wait a total of 270 more seconds before timing out. Current Status='Verifying metadata'
Request in progress. Sleeping 30 seconds. Will wait a total of 240 more seconds before timing out. Current Status='Verifying metadata'
Request in progress. Sleeping 30 seconds. Will wait a total of 210 more seconds before timing out. Current Status='Finalizing package version'
Request in progress. Sleeping 30 seconds. Will wait a total of 180 more seconds before timing out. Current Status='Finalizing package version'
sfdx-project.json has been updated.
Successfully created the package version [08c5G000000kAvYQAU]. Subscriber Package Version Id: 04t5G000003rUrOQAU
Package Installation URL: https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5G000003rUrOQAU
As an alternative, you can use the "sfdx force:package:install" command.

D:\esmit\Documents\VSCode\LightningFlowComponents\flow_screen_components\datatable>packaging\Promote

D:\esmit\Documents\VSCode\LightningFlowComponents\flow_screen_components\datatable>sfdx force:package:version:promote -v lexhost --package "[email protected]"
Are you sure you want to release package version [email protected]? You can't undo this action. Release package (y/n)?: y
Successfully promoted the package version, ID: 04t5G000003rUrOQAU, to released. Starting in Winter ‘21, only unlocked package versions that have met the minimum 75% code coverage requirement can be promoted. Code coverage minimums aren’t enforced on org-dependent unlocked packages.

D:\esmit\Documents\VSCode\LightningFlowComponents\flow_screen_components\datatable>packaging\Details

D:\esmit\Documents\VSCode\LightningFlowComponents\flow_screen_components\datatable>sfdx force:package:version:report -v lexhost --package "[email protected]"
=== Package Version
Name                            Value
──────────────────────────────  ─────────────────────────────────────────────
Name                            Datatable v3
Subscriber Package Version Id   04t5G000003rUrOQAU
Package Id                      0Ho5G000000XZNaSAO
Version                         3.4.5.0
Description                     Datatable Flow Screen Component by Eric Smith
Branch
Tag
Released                        true
Validation Skipped              false
Ancestor                        N/A
Ancestor Version                N/A
Code Coverage                   92%
Code Coverage Met               true
Org-Dependent Unlocked Package  No
Release Version                 54.0
Build Duration in Seconds       426
Managed Metadata Removed        N/A
Created By                      0055G00000607zRQAQ

Let’s Revisit How to use both the Selected and the Edited records in a Datatable

Starting with v4.3.0 of Datatable, the output attribute Output Remaining Rows will include all records in the Datatable with all saved edits applied. There is no more need to add extra processing to the flow to combine the edited records with the original records.

A year ago, I showed you how you can use a Flow with a Loop and a special Apex action to update the Selected records from the Datatable component with the Edited records from the same Datatable.

Now I’m going to show you how you can get rid of the Loop and use a different action and a simple assignment to produce the same results.

My Datatable Flow Screen Component allows a user to both Select records and make Edits to records.  The component returns two separate collection variables.  One of them includes the original values of just the Selected records.  The other one includes just the Edited records whether they were selected or not.

Sometimes, you may want to process just the selected records in your flow, but include the edited values in those selected records.  This sample flow shows how you can create a selected record collection with edits.  The flow uses the Get Common and Uncommon Records action that is part of the Collection Actions from UnofficialSF.com to compare the collections returned by the Datatable and extract the common and unique records from them.  

Here’s a Datatable displaying all of the Product records from an Opportunity.

I’ll select 3 records and make edits to 2 of them.

My normal outputs from the Datatable include the original 3 selected records and a separate collection of just the edited records.

To create a record collection that combines these two requires just a couple extra steps in your flow.  The magic happens in the Get Common and Uncommon Records Flow Action.  This action takes two separate record collections, Source (Selected) and Target (Edited), along with a field from each (Id) that is used to match them up and returns 4 separate record collections.

  • Source Common – Records from the Source collection that are also in the Target collection
  • Source Unique – Records from the Source collection that are not in the Target collection
  • Target Common – Records from the Target collection that are also in the Source collection
  • Target Unique – Records from the Target collection that are not in the Source collection

First, I created a Record Collection variable for Opportunity Product that will be used to store the combined outputs from the Action.

I pass the outputs from the Datatable into the Get Common and Uncommon Records Action.

We are only going to use two of the output collections and they will be combined into a single collection of Selected records that include any of the Edits made to them.

  • Source Unique – All Selected records that are not also Edited records
  • Target Common – All Edited records that are also Selected records

An Assignment node is used to Add each of the desired collection outputs from the Action into our final record collection of selected and edited records.


As you can see in the Results Screen, the Selected Records are unique from the Edited Records, but the combined Selected Products with Edits includes all Selected with any Edits made to those records.

Validation Checker Flow Action

Validation Checker Flow Action

Created by Eric Smith


See https://unofficialsf.com/validation-checker-flow-action/ for the most up to date information on this component.


Don’t your users just love it when they see this on their screen?

The fun really starts when you get this email in your inbox.

You could certainly take the time and effort to add a bunch of decision elements and recreate all of your validation rules in your flow.  I bet it would be even more exciting trying to keep all of that maintained.  

Wouldn’t it be nice if your Flows could check for and trap validation rule failures, missing required fields and text overruns?

Well now they can with this simple Check Validation flow action.  

Before you attempt a Create Records or Update Records element in your Flow, add this simple Flow Action and pass it a single record or a collection of records.

This action will check your record(s) and let you know if there would be any Validation Rule errors, field size overruns or missing required field errors if you tried to create or update the record(s).

All this is done before you try to do something that would cause the Flow to fail with an unhandled fault.  You get to determine what to do next and what information you want to present to your user.


You can also use this action as part of your Fault Paths. Instead of just handling the fault without knowing exactly what it is, you can get the error(s) that caused the fault and act on them as you see fit.


In this Flow, a Toast Message will be displayed with any errors and the User will be given a choice of whether or not they want to Try Again or Exit.


You can specify which field from your record you want to include in the error messages to help identify which record(s) failed.

The action returns an isError boolean value along with a text message of the individual errors.  

Validation Rule Failures

Missing Required Field Error

Text Field Size Error

You can even specify an optional input attribute to use this action instead of the Create Records or Update Records to actually perform the inserts and updates if no errors are generated.  If you perform a successful insert of new records, the new record ID(s) will be returned by the action.

Take control of your automations and no more worrying about Flows crashing when User provided values won’t pass Validation Rules or other input errors.


Restrictions

  • If there are multiple types of errors, only one type of error will be returned.
  • If any Text fields are over their size limit, only those errors will be returned.
  • If any Validation Rule fails, all Validation Rule failures will be returned.
  • If any Required Fields are missing, only those errors will be returned.
  • Fields over their size limit are handled first, followed by Validation Rules, followed by Required Fields.

Attributes

AttributeTypeNotes
INPUT
Input RecordSObjectAny Standard or Custom SObject Record
Required: Provide either a Record or Record Collection, not both
Input Record CollectionSObject CollectionAny Standard or Custom SObject Record Collection
Required: Provide either a Record or Record Collection, not both
Record Identifier Field API NameString (Field API Name)The record’s value for this field will be included as part of the error message.
NOTE: The field must be included in the Record or Record Collection.
Optional, Default: Id
If no errors, commit inserted & updated records?BooleanSet to True if you want the action to upsert the record(s) if there are no errors
Optional, Default: False
OUTPUT
isErrorBooleanTrue if there were any errors
errorMessagesStringA single string that includes the error message for each failing record
firstInsertedIdStringIf the commit attribute is set to True and there are no errors, this will be the recordId of the first inserted record 
insertedIdCollectionString CollectionIf the commit attribute is set to True and there are no errors, this will be a String collection of the recordIds of all of the inserted records

Installation

Production or Developer Version 1.1

Sandbox Version 1.1


View Source

Source Code

How to Use an Apex-Defined Object with the Datatable Flow Component

How to Use an Apex-Defined Object with the Datatable Flow Component

Updated 1/23/21 to reference the v3 version of the Datatable that utilizes a Custom Property Editor.

I’ve updated my Datatable Lightning Web Component for Flow Screens to support a User Defined (also know as an Apex-Defined) object.

See my Flow and Process Builder List View with Batch Delete App for an example.

To work with an Apex-Defined object in your Flow, you need to create an Apex Descriptor Class for the object.

SampleClassDescriptor.cls

// Apex-Defined Variable Sample Descriptor Class
public with sharing class SampleClassDescriptor {

    // @AuraEnabled annotation exposes the methods to Lightning Components and Flows
    @AuraEnabled
    public String field1;

    @AuraEnabled
    public String field2;

    @AuraEnabled
    public Boolean field3;

    @AuraEnabled
    public Integer field4;    

    // Define the structure of the Apex-Defined Variable
    public SampleClassDescriptor(
            String field1,
            String field2,
            Boolean field3,
            Integer field4
    ) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
    }

    // Required no-argument constructor
    public SampleClassDescriptor() {}
}

In your Flow, you can create and use Apex-Defined record and record collection variables by referencing your Apex Class.

All of the fields in your variable will be available to use in the Flow.

In this sample Flow, I am setting field values in individual records as seen above.

I then add each record to the Apex-Defined record collection variable.

The Datatable component expects a serialized string of the object’s records and fields like the text seen here.

[{"field1":"StringRec1Value1","field2":"StringRec1Value2","field3":false,"field4":10},
{"field1":"StringRec2Value1","field2":"StringRec2Value2","field3":true,"field4":20},
{"field1":"StringRec3Value1","field2":"StringRec3Value2","field3":true,"field4":30}]

Since you can create Apex Flow Actions to work with your Apex-Defined object, I created an action that converts an Apex-Defined record collection to a serialized string that can be passed to the Datatable component. The action will also convert a serialized string back to a record collection. This can be useful in a Flow where you want to act on a collection of selected or edited records that get passed back to the Flow by the Datatable component.

Special Note: Even without an Apex-Defined Class, you can build a String in your Flow formatted as above and use that to populate a datatable.

You can use this code as a template for your own Apex actions designed to work with a Flow.

TranslateApexDefinedRecords.cls

/** 
 * 
 *  Sample Apex Class Template to get data from a Flow, 
 *  Process the data, and Send data back to the Flow
 * 
 *  This example translates an Apex-Defined Variable 
 *  between a Collection of Object Records and a Seraialized String
 * 
 *  Eric Smith - May 2020
 * 
 * 
**/ 

public with sharing class TranslateApexDefinedRecords {         // *** Apex Class Name ***

    // Attributes passed in from the Flow
    public class Requests {
    
        @InvocableVariable(label='Input Record String')
        public String inputString;

        @InvocableVariable(label='Input Record Collection')
        public List<SampleClassDescriptor> inputCollection;     // *** Apex-Defined Class Descriptor Name ***

    }

    // Attributes passed back to the Flow
    public class Results {

        @InvocableVariable
        public String outputString;

        @InvocableVariable
        public List<SampleClassDescriptor> outputCollection;    // *** Apex-Defined Class Descriptor Name ***
    }

    // Expose this Action to the Flow
    @InvocableMethod
    public static List<Results> translateADR(List<Requests> requestList) {

        // Instantiate the record collection
        List<SampleClassDescriptor> tcdList = new List<SampleClassDescriptor>();    // *** Apex-Defined Class Descriptor Name ***

        // Prepare the response to send back to the Flow
        Results response = new Results();
        List<Results> responseWrapper = new List<Results>();

        // Bulkify proccessing of multiple requests
        for (Requests req : requestList) {

            // Get Input Value(s)
            String inputString = req.inputString;
            tcdList = req.inputCollection;


// BEGIN APEX ACTION PROCESSING LOGIC

            // Convert Serialized String to Record Collection
            List<SampleClassDescriptor> collectionOutput = new List<SampleClassDescriptor>();   // *** Apex-Defined Class Descriptor Name ***
            if (inputString != null && inputString.length() > 0) {
                collectionOutput = (List<SampleClassDescriptor>)System.JSON.deserialize(inputString, List<SampleClassDescriptor>.class);    // *** Apex-Defined Class Descriptor Name ***
            }

            // Convert Record Collection to Serialized String
            String stringOutput = JSON.serialize(tcdList);

// END APEX ACTION PROCESSING LOGIC


            // Set Output Values
            response.outputString = stringOutput;
            response.outputCollection = collectionOutput;
            responseWrapper.add(response);

        }
        // Return values back to the Flow
        return responseWrapper;
    }
}

TranslateApexDefinedRecordsTest.cls

@isTest
public with sharing class TranslateApexDefinedRecordsTest {

    static testMethod void test() {

        List<SampleClassDescriptor> inputList = new List<SampleClassDescriptor>();

        TranslateApexDefinedRecords.Requests testRequest = new TranslateApexDefinedRecords.Requests();

        testRequest.inputString = '[{"field1":"value1","field2":"value2"},{"field1":"value31","field2":"value4"}]';
        testRequest.inputCollection = inputList;

        List<TranslateApexDefinedRecords.Requests> testRequestList = new List<TranslateApexDefinedRecords.Requests>();
        testRequestList.add(testRequest);

        List<TranslateApexDefinedRecords.Results> testResponseList = TranslateApexDefinedRecords.translateADR(testRequestList);
        system.debug('RESPONSE - '+testResponseList);
        system.assertEquals(testResponseList[0].outputCollection.size(), 2);
    }

}

When you configure the attributes for the Datatable in your Flow, you need to be aware of these settings:

  1. Start by checking Input data is Apex-Defined in the Advanced section
  2. In the Data Source section, enter your Datatable Record String
  3. Also in the Data Source section, enter a Datatable Record String for any Pre-Selected Rows
  4. There is no Column Wizard so you will have to list your Column Field names and other attributes manually.
  5. For Currency, Number and Percent fields the Column Scales attribute lets you specify the number of places to display after the decimal point. The default is 0.
  6. When you are using an SObject collection, the Datatable component gets information about all of the fields from the system. For a User Defined object, you need to specify the Column Type of data for each field. This can be left blank if all of the columns are text fields.
  7. You are required to provide the name of the datatable’s Key Field. All of the values in this field need to be unique in order for the datatable to function correctly.

There are separate output parameters for Selected and Edited User Defined objects as well.

The (User Defined) outputs will be serialized record strings rather than SObject collections. Be sure to reference the correct ones based on how you assigned the True or False value for the Input data is Apex-Defined attribute.


Get the Sample Flow and Source Code here:

https://github.com/ericrsmith35/Apex-Defined-Example

Datatable Now Includes a Custom Property Editor

See this in action during DreamTX!

Flow Builder Demos 12/17 (1PM2PM3PM Eastern Time)

The Datatable Flow Screen Component has come a long ways from the original Aura component that included separate attributes for 10 different Salesforce objects in a single component. Once Salesforce supported the ability to pick the desired Object in the Flow Builder, at the time of configuration, it was rebuilt from scratch as a Lightning Web Component. Now Datatable has been reimagined again with the addition of a Custom Property Editor that’s used by the Flow Builder whenever a Datatable is added to a Flow Screen.

This image has an empty alt attribute; its file name is image-18-894x1030.png

Custom Property Editors allow a developer to bypass the standard basic list of all component attributes in the Flow Builder and replace it with a Lightning Web Component that can present a logical and formatted interface for the user to configure the component. The CPE developed for the Datatable component takes this even further by including a button that launches a separate Flow that displays a special Datatable the user can interact with to configure their Datatable. I like to refer to this as my Custom Column Configuration Wizard.

Once installed, this component will appear as Datatable in the Flow Builder. DatatableV2 will still work with your existing Flows and can coexist with the new Datatable.

Here are a few examples showing how to build a Flow with a Datatable, how to configure the Datatable using the Custom Property Editor and how a user can interact with a Datatable. For complete documentation, visit the Datatable page.


Build a Flow with a Datatable


Configure a Datatable with the Custom Property Editor


Interact with a Datatable