Common Code Examples
Use the following libraries and common code resources compiled here to help you write flows.
Libraries
App Xchange supports the NodeJS v18.15.0 version of JavaScript.
The App Xchange flow engine supports these JavaScript libraries.
-
Day.js
View the Day.js library documentation.
V1.11.7
The flow engine supports all methods including the following plugins:
-
Numeral.js
View the Numeral.js library documentation.
V2.06
The flow engine supports all methods.
-
Lodash
View the Lodash library documentation.
V4.17.21
All methods are supported. To call lodash functions, use the standard
_.method()
. -
uuid
View the uuid library documentation.
V9.0.0
The most common method for generating GUIDs is
uuid.v4()
.
Flow Helpers
The App Xchange flow engine uses the following helper code snippets to help you access data when writing flows. Helpers are organized by the namespaces they access.
workspace
info()
Access data of the Flow Workspace: workspace.info()
Access the Workspace id: workspace.info().id
Access the Workspace name:
workspace.info().name
appNetwork
fileUrl(fileId:
string)
Generate an App Network file url:
appNetwork.fileUrl('fileGuid')
legacyFileUrl(fileId:
string)
Generate a Legacy App Network file url:
appNetwork.legacyFileUrl('fileGuid')
flow
info()
Access flow info:
flow.info()
Access the flow registration ID:
flow.info().registrationId
Access the flow URL:
flow.info().url
config
Access
the flow's configuration: flow.config
Access
a property on the flow's configuration: flow.config.myProperty
||
flow.config[myProperty]
loopItem()
Access
item data (only available for a For Each in a List step):
flow.loopItem()
mapItem()
Access item
data (only available in the context of mapping data in a Map step or Connector
Action step): flow.mapItem()
trigger
Access the flow's trigger event:flow.trigger.event
Access the flow's trigger data: flow.trigger.data
Check if the flow run was triggered by a cache record create event:
flow.trigger.isCacheCreate()
Check if the flow
run was triggered by a cache record update event:
flow.trigger.isCacheUpdate()
Check if the flow
run was triggered by a cache record delete event:
flow.trigger.isCacheDelete()
Check if the flow
run was triggered by an action closed out as successful:
flow.trigger.isActionSuccess()
Check if the
flow run was triggered by an action closed out as failed:
flow.trigger.isActionFail()
step(stepId:
string)
Access a property on the output data of a previously
executed step: flow.step("step-id").output.JobCode
flow.step("step-id").actionResponse
flow.step("step-id").actionResponse.status
Access the action request of a previously executed step:
flow.step("step-id").actionRequest
Check if a
previously executed step has an action status of Success:
flow.step("step-id").isActionSuccess()
Check if
a previously executed step has an action status of Fail:
flow.step("step-id").isActionFail()
Check if a
previously executed step has an action status of Queue Action Success:
flow.step("step-id").isQueueActionSuccess()
Dates
Return today’s date:
dayjs(); // returns a dayjs object
dayjs().date();
dayjs().toString();
22
'Wed, 22 Feb 2023 22:33:27 GMT'
dayjs().date(1).format('YYYY-MM-DD');
'2023-02-01'
Compare a date to the current day:
dayjs().isSame(dayjs(flow.trigger.data["Transaction_Date"]));
false
Create a date variable, set it equal to an input value, and output it in a specific format:
dayjs(flow.trigger.data.Items[0].TransactionDate).format('YYYY-MM-DD');
'2023-02-28'
Safely convert a value to a date, or default to today’s date:
const dateObj = dayjs(flow.trigger.data.payRateEffectiveDate);
if (dateObj.isValid()) {
return dateObj.format('YYYY-MM-DD');
}
return dayjs().format('YYYY-MM-DD');
'2022-05-18'
const closestFri = dayjs('9/29/2021').day(5);
closestFri.toString();
Sample Output: 'Fri, 01 Oct 2021 07:00:00 GMT'
Calculate the last day in a month:
const endOfFeb = dayjs('2/14/2023').endOf('month');
endOfFeb.date()
endOfFeb.format('MM/DD/YYYY')
28
'02/28/2023'
Calculate the last day in a week:
const endOfWeek = dayjs(flow.trigger.data.postedDate).endOf('week');
endOfWeek.date();
endOfWeek.day();
endOfWeek.format('dddd, DD MMM YYYY')
25
6 // 6 = Saturday
'Saturday, 25 Feb 2023'
Compare two dates:
const syncStart = flow.config.syncStartDate;
if (syncStart) {
// greater than
if (dayjs(syncStart) > dayjs(flow.trigger.data.DateCreated)) {
return true;
}
// isAfter
if (dayjs(syncStart).isAfter(dayjs(flow.trigger.data.DateCreated))) {
return true;
}
};
Strings
const vTest = 'Flow writing, is cool';
Check if a string contains specified text:
vTest.includes('rit');
true
vTest.indexOf('w');
3
vTest.toUpperCase();
vTest.toLowerCase();
'FLOW WRITING, IS COOL'
'flow writing, is cool'
Lodash also gives us the following:
_.camelCase(vTest);
_.startCase(vTest);
_.kebabCase(vTest);
'flowWritingIsCool'
'Flow Writing Is Cool'
'flow-writing-is-cool'
Return the length of the string:
vTest.length;
20
Delete or keep part of the string:
Javascript has a substring()
method, but we recommend
slice(
. For more information, read this explanation from Mastering JS.
slice()
takes a startIndex
and an
optional endIndex
and returns the string between the start and the
end indexes.
If no endIndex
is supplied, slice()
preserves through the end of the string.
This is non-destructive. It returns a new string from the start index to
the end index while leaving the original string intact. vTest
remains unchanged. You will have to assign this to a new variable in order to access
it.
const updatedStr = vTest.slice(0, 4);
const slicedStr = vTest.slice(5);
// original variable is unchanged
// vTest = 'Flow writing, is cool'
vTest.slice(vTest.indexOf('F'), 12)
// Prevents failure when string is less than the end
// index value
vTest.slice(0, Math.min(4, vTest.length))
// or even simpler
vTest.slice(0, 4 || vTest.length)
'Flow'
'writing, is cool'
'Flow writing'
'Flow'
'Flow'
const replaced = vTest.replace('i', '1');
const replacedAll = vTest.replaceAll('i', '1');
// original variable is unchanged
// vTest = 'Flow writing, is cool'
const money = '$3.00';
money.replace('$', '');
'Flow wr1ting, is cool'
'Flow wr1t1ng, 1s cool'
'3.00'
Remove starting and ending white space:
Trim should be one of the most common functions used with strings. Use it to get rid of any leading or trailing whitespace that can cause issues. Whitespace has been known to cause support tickets and delays in onboarding, and is very tough to track down. Use Trim and eliminate this headache!
const stringWithWhitepace = 'Hello World '
vTest.trim()
'Hello World'
if (vTest) {
// true when vTest is non-empty string,
// is also true if only whitespace
}
if (vTest.trim()) {
// same as above but fails if only whitespace
}
Format data as currency:
numeral('4.35615676').format('$0,0.00');
$4.36
-
As with previous JS string methods, these are non-destructive to the original string and must be assigned to new variables.
-
If the string does not contain the character or string you are splitting on, it returns
undefined
.
vTest.split(',')[0]
vTest.split(', ')[1]
if (vTest.includes(',')) {
return vTest.split(',')[0];
}
if (vTest.includes("STINKS")) {
return vTest.Split("STINKS")[1];
}
'Flow writing'
'is cool'
true
false
Add characters to the beginning or ending of a string:
vTest.padStart(25);
vTest.padStart(25, '*');
vTest.padEnd(25, '-');
' Flow writing, is fun'
'*****Flow writing, is fun'
'Flow writing, is fun-----'
Numbers
Parse a number from a string:
numeral('28').value();
28
Format a number into dollars:
numeral('20000.25793').format('$0,0.00');
'$20,000.26'
Arrays
const intArr = [10, 15];
const stringArr = ['Approved', 'Variance Approved'];
Determine if any element in an array contain a value matching the criteria:
const hasOwnerAssignee = flow.trigger.data.Assignees
.some((a) => domains.some(
d => a.ContactEMail.endsWith(d)
),
);
Determine if all elements of an array contain a value matching the criteria:
const hasOwnerAssignee = flow.trigger.data.Assignees
.some((a) => domains.some(
d => a.ContactEMail.endsWith(d)
),
);
Sort an array by a column in descending order and then return the first value (highest value):
flow.trigger.data.Items.sort(
(a, b) => b.Hubometer_Reading - a.Hubometer_Reading
)[0];
sort()
mutates the original array. The default sort order
is ascending. The time and space complexity of the sort cannot be guaranteed as it
depends on the implementation.
Lodash is another option:
// Lodash
const sorted = _.orderBy(flow.trigger.data.Items, 'Hubometer_Reading',
'desc')[0];
Evaluate if a lookup step had any records:
flow.step('lookup-employee').output.length > 0
// Lodash
_.any(flow.step('lookup-employee').output)
Access an element in lookup:
if (flow.step('lookup-employee').output.length > 0) {
return flow.step('lookup-employee').output[0].Job_Number.toString()
} else {
return null;
}
Map a filter step's output array:
flow.step('filter-ineight-employee-list-yx5mr').output
.map(x => x.SourceSystemId);
Add each value in an array to a list or a string:
let results = '';
flow.trigger.data.flags[0].system_flag_details.forEach(flag => {
if (flag.reasons) {
results += `${flag.reasons}:`;
}
})
return results;
Sum all values in a field of an array or list:
-
The
reduce()
method executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value. -
The first time the callback is run there is no return value of the previous calculation. If supplied, an initial value may be used in its place. Otherwise, the array element at index 0 is used as the initial value and iteration starts from the next element (index 1 instead of index 0). For more information, see this explanation from Mozilla Developer Network.
const values = flow.step('lookup-class').output.DednLiabs
values.reduce(
(prev, curr) => numeral(prev).add(curr.NewRate).value(),
0
);
// Lodash
_.sumBy(values, (e) => numeral(e.NewRate).value());
Assign a value based on looking up a value in an array:
const payRates = flow.step('lookup-class').output[0]
.PayRates;
const shiftToMatch = flow.step('lookup-employee-in-vista')
.output.Shift;
// if there can be multiple matches, use filter()
const rates = payRates.filter(e => e.Shift === shiftToMatch);
// if you only want to match on the first successful hit,
// use find()
const rate = payRates.find(e => e.Shift === shiftToMatch);
Do something for each item in an array:
flow.trigger.data.forEach(item => {
if (item.department) {
// do something
}
})
sort()
will mutate the
original array whereas _.orderBy()
will return a new
array.lines.sort((a, b) => {
if (b.Equipment > a.Equipment) return 1;
if (b.Equipment < a.Equipment) return -1;
return 0;
});
// Lodash
const orderedLines = _.orderBy(lines, 'Equipment', 'desc');
Other Helpful Coding Tips
Create two lists and then find the values from one that does not exist in the other:
-
Create the two lookup steps
-
lookup-ineight-employees-ocakj
-
lookup-vista-employees-xy6c9
-
-
Create the filter step
-
List
-
Test Expression
-
flow.step("lookup-ineight-ocakj").output
const inEightEmployeeId = numeral(flow.trigger.data.SourceSystemId)
.value();
if (!inEightEmployeeId) {
return false;
}
return flow.step('lookup-vista-employees-xy6c9'').output
.every(x => numeral(x.KeyID) !== inEightEmployeeId);
const x = flow.trigger.data.firstName ?? ''
const x = flow.trigger.data.firstName !== null ?
flow.trigger.data.firstName :
''
.
before ["Concur_Approver"].
flow.trigger.data["__custom_fields"]?.["Concur_Approver"] ?? ''
Use regex for any file containing KPC and ending in .CSV:
^.*KPC.*.csv
Call a configured variable:
-
If the variable is a single object
-
If the variable is a Multiple Text Items
flow.config.Company
flow.config.exceptionEmails
Reference a configuration property set as multiple text items:
flow.config.exceptionEmails
Check if the Configuration Value is empty:
if (flow.config.companyName)
Create a map step without any input:
-
Set “List or Object” [] or {}
Dynamically determine start row for a Parse Delimited File step:
-
You have to edit the JSON of the flow, but then you can change the property to any value you want. Must resolve to a number.
StartingRowNumber:
numeral(flow.step('count-headers').output[0].CountHeaders).value()
Create a random string:
uuid.v4()
TryParse:
const testValue = '1';
const num = numeral(testValue).value();
if (num) {
return num;
};
flow.trigger.data['__custom_fields'][flow.config.udSyncEmployee]
Everything inside the brackets is just a string value, so this method can be used in a number of ways.
const field = flow.config.udSyncEmployee;
flow.trigger.data['__custom_fields'][field];
You can also create other dynamic situations.
const x = 1;
const y = 2;
let lookupField = '';
if (x + y > 5) {
lookupField = 'something';
} else {
lookupField = 'somethingElse';
}
return flow.trigger.data[lookupField];
Was this helpful? Give us feedback.