Custom functions
Overview
The mobile extension standard flow allows custom forms to fetch and save data using GraphQL. This works for most use cases. However, it does not support advanced requirements, such as fetching data from multiple data sources (external systems) using REST APIs or transforming data before or after fetching and saving.
To address these limitations, the mobile extension custom functions are built to provide a solution to support businesses’ needs. They allow users to do the following things:
- Customize how their custom form fetches and saves data.
- Use both GraphQL and REST APIs to fetch or save data.
- Apply custom business logic using TypeScript.
Limitations
- Data size: The response data must be smaller than 10MB. If it exceeds this limit, the custom function will throw an exception.
- Execution time: The custom function must be executed within 30 seconds. If it takes longer, the custom function will throw an exception.
Exposed function
Mobile extension custom functions provide two types of outputs:
- Frontend custom functions: Customize the UI of the custom form.
- Backend custom functions: Customize the business logic of mobile extension backend functions, such as processing data before saving, modifying API responses, or integrating with external systems.
Mobile extension custom functions expose backend functions similar to those provided by the mobile extension service. They include the following functions:
-
Static fetch functions: Fetch shared data for a form. Since this data is scoped to the form, multiple jobs or resources can access the same data. It is typically used for retrieving common or reference data. Refer to Fetch static data.
-
Instance fetch functions: Fetch data for each instance, for example, a job or resource. Each job or resource has its own instance-specific fetch data per form.
-
Save functions: Save changes made in a custom form to the data source.
-
Online mode functions: Allow the custom form to search for data in real-time when online.
Create a custom function
To create a new custom function template, refer to the guide in the mex-custom-function-template repository. This repository is a Cookiecutter template that includes detailed steps to generate a basic mobile extension custom function project.
Make sure that Cookiecutter is installed before proceeding.
You can generate a new custom function automatically using the following command:
cookiecutter -s --no-input git@github.com:Skedulo/mex-custom-function-template.git project_name="$NAME" project_slug="$SLUG"
Once you’ve successfully created a custom function, it has the following structure:
├── node_modules
├── package.json
├── src
│ ├── __tests__
│ │ └── Sample.spec.ts
│ ├── buildscripts
│ │ ├── esbuild.backend.ts
│ │ └── esbuild.frontend.ts
│ ├── functions
│ │ ├── fetcher.ts
│ │ └── saver.t
│ ├── graphql
│ │ └── queries
│ │ └── queries.graphql
│ ├── index.backend.ts
│ ├── index.frontend.ts
│ ├── localDevIndex.ts
│ ├── params.ts
│ ├── tools
│ │ ├── config.json
│ │ ├── generate-graphql-ast.ts
│ │ ├── generate-graphql-types.ts
│ │ └── generate-info.ts
│ └── tracing.ts
└── tsconfig.json
Focus on the following files:
-
src/functions/*.ts
: Implements the execution code for the custom function. -
src/graphql/queries/queries.graphql
: Defines GraphQL queries to fetch data. -
src/params.ts
: Configures the exposed functions of the custom function.
Input/output of a custom function
The input and output of a custom function may vary depending on the version of mex-service-libs
it uses.
Note
Our services support backward compatibility.Backend custom function
Static fetch
-
Input:
export type CustomFetchInput = { contextObject: string; contextObjectId: string; variables?: Variables; }; export type CustomStaticFetchInput = CustomFetchInput;
-
Output:
export type ObjectDataResult = { __typename: string; UID?: string; [key: string]: any; }; export type CustomStaticFetchResult = { data: { [key: string]: ObjectDataResult | ObjectDataResult[]; }; };
Instance fetch
-
Input:
export type CustomFetchInput = { contextObject: string; contextObjectId: string; variables?: Variables; };
-
Output:
export type CustomStaticFetchResult = { data: { [key: string]: ObjectDataResult | ObjectDataResult[]; }; };
Save
-
Input:
export type CustomSaveInput0_0_19 = { metadata: { [key: string]: any; }; instanceData: { [key: string]: any; }; newInstanceData: { [key: string]: any; }; staticData?: { [key: string]: any; }; objectMapping?: { [key: string]: string; }; variables?: Variables; };
-
Output:
export type CustomSaveResult = { objectMapping: { [key: string]: string } }
Online mode
-
Input:
export type CustomSearchInput = { label: string variables?: Variables }
-
Output:
export type ObjectDataResult = { __typename: string UID?: string [key: string]: any } export type CustomSearchResult = { data: ObjectDataResult[] }
Frontend custom function
A frontend custom function is different from a backend custom function. It is converted into pure JavaScript and executed on the mobile frontend.
Caution
On Android, avoid using optional chaining (?.
) and nullish coalescing (??
) in frontend custom functions.
A typical frontend custom function follows this format:
function FunctionName(customArg1, customArg2, ..., extras: Extras)
customArg1, customArg2 |
Arguments passed when building the UI Definition. There is no limit on the number of arguments. |
extras |
The extras object is automatically provided by the engine when executing a custom function. It acts as a helper, offering built-in methods to handle data, UI interactions, and other utilities. |
Important
Inindex.frontend.js
, imports (import statements) are not allowed. The code must be written in pure TypeScript or JavaScript.
To export the method, add it to the end of the file:
let exportFns = {
FunctionName: FunctionName
}
The result of the custom function depends on how you use it.
For example:
-
If you use the custom functions as an expression that requires a return of a boolean, then it returns a boolean.
-
If you use the custom function as a method that results in a certain behavior when users interact with a custom button, then no result needs to be returned.
-
If you use the custom functions in a localized key, then it returns a string.
Examples
The following example shows how to use the custom function in a localized key:
Setup for index.frontend.ts
function renderElapsedTime(pageData:any, {extHelpers, libraries}: Extras): string {
let now = libraries.moment.utc(extHelpers.date.getNowDateTime())
let lastCheckInDate = libraries.moment.utc(pageData.CheckInTime)
let diff = libraries.moment.duration(now.diff(lastCheckInDate))
let result = ""
if (diff.days() > 0) {
result += " " + extHelpers.data.translate('ElapsedTimeDays', diff.days())
}
if (diff.hours() > 0) {
result += " " + extHelpers.data.translate('ElapsedTimeHours', diff.hours())
}
if (diff.minutes() > 0) {
result += " " + extHelpers.data.translate('ElapsedTimeMinutes', diff.minutes())
}
if (result == "") {
result = extHelpers.data.translate('CheckInJustMomentAgo')
}
return result
}
let exportFns = {
renderElapsedTime: renderElapsedTime
}
How to use custom function inside en.json (localized key)
"ElapsedTimeValue": "${cf.renderElapsedTime(pageData)}"
Important
All custom functions must be called using thecf
prefix.
For further information, refer to the following topic: Complex computation using custom functions.
Extras object
The Extras
object provides various helper methods to boost development:
interface Extras {
extHelpers: ExtHelper,
libraries: Libraries
}
export interface ExtHelper {
data: ExtHelperData
date: ExtHelperDate
ui: ExtHelperUI
}
interface ExtHelperData {
changeData(fn: () => void):any /* Change data in data context */
submit(options?: ExtHelperSubmitOptions): Promise<boolean> /* Submit/Save data from current page, and automatically close page */
translate(key: string, args?: any[]): string /* translate key from localization keys */
}
type ExtHelperSubmitOptions = {
stopWhenInvalid?: Boolean
}
interface ExtHelperDate {
getNowDateTime(): string /* get current date time */
}
interface ExtHelperUI {
alert(message: string): void /* make an alert UI */
}
interface Libraries {
moment: any
}
Feedback
Was this page helpful?