Custom Script
Custom scripts provide a powerful way to extend the application's functionality for specific entities and events. They allow developers to inject custom logic that can interact with the application's data and user interface.
Execution Environment
Custom scripts are executed within a sandboxed <iframe> environment. This provides a layer of isolation, preventing scripts from directly interfering with the main application's global scope or sensitive data, while still allowing controlled interaction through a defined API.
Script Execution Context
When a script is executed, it is provided with two main arguments: data and context.
The data Object
The data object is a copy of the primary data related to the entity and event that triggered the script (e.g., the data of a selected item, a purchase order, etc.).
Scripts receive a deep copy of the initial data.
Modifications made directly to this
dataobject within the script will be captured.If the script function returns a value (that is not
undefined), this returned value will be used as the new state of thedataobject in the parent application, potentially replacing the original data.
The context Object
The context object provides a set of functions that allow the script to interact with the parent application in a controlled manner.
[
context.setData(newData)]:Allows the script to update the
dataobject asynchronously or at multiple points during execution.Calling this function sends the
newDataback to the parent application, which can then update its state.This is useful for scripts that perform asynchronous operations or need to provide incremental updates to the data.
[
context.notify(message, type)]:Sends a notification from the script to the main application.
The parent application will display this message to the user.
message: The text content of the notification.type: The type of notification (e.g.,'info','success','warning','error').
[
context.registerAction(selector, eventType, actionName)]:Crucial for interactive scripts. This function allows the script to register event listeners on specific DOM elements within the parent application's user interface.
selector: A CSS selector string targeting the element(s) in the parent DOM you want to attach a listener to.eventType: The type of DOM event to listen for (e.g.,'click','change','input').actionName: A unique string name for this specific action. This name is used to identify which part of your script should handle the event when it occurs.
[
context.notifyInteractiveReady()]:If your script uses
context.registerActionto set up interactive elements, you should call this function.This signals to the parent
ScriptManagerthat the iframe needs to remain active after the initial script execution finishes to handle potential user interactions triggered by the registered actions.(Note: The
ScriptManageralso checks an internal flag (_didRegisterAction) which is set whenregisterActionis called. CallingnotifyInteractiveReadyexplicitly reinforces this intent, though the flag is the primary mechanism for keeping the iframe alive.)
[
context.abort(reason)]:If you want to stop proceeding the parent DOM, use
context.abort().This function is useful for custom data validation before save.
reasonis optional.
Interacting with the Parent DOM (Action Registration)
The [context.registerAction()] function is the gateway for scripts to make elements in the main application interactive. This enables scenarios where a script might display information and then wait for user input or interaction with specific UI elements.
Here's the lifecycle:
Script Registers Action: Your script calls [
context.registerAction("button#my-button", "click", "handleMyButtonClick")].Parent Attaches Listener: The
ScriptManagerin the parent application receives thescriptRegisterActionmessage. It uses the providedselector("button#my-button") to find the element in the parent DOM and attaches an event listener for the specifiedeventType("click").User Triggers Event: The user interacts with the element in the parent application (e.g., clicks the button).
Parent Notifies Iframe: The event listener in the parent is triggered. The
ScriptManagerthen sends anexecuteUserActionmessage back to the iframe where the script is running. This message includes theactionName("handleMyButtonClick") and anactionPayloadcontaining details about the event that occurred (like target ID, tag name, input value, coordinates, key presses, etc.).Script Handles Action: The iframe receives the
executeUserActionmessage. It looks for a corresponding handler function within a global object defined by your script, typically namedglobalThis.userScriptActions.
Handling Registered Actions in the Script
To handle the actions registered via [context.registerAction()], your script must define a global object, conventionally named globalThis.userScriptActions. This object should contain functions whose names match the actionNames you used when calling registerAction.
JavaScript
If a script registers actions using [context.registerAction()], the initial script execution will signal completion (e.g., by posting scriptResult), but the iframe will not be immediately cleaned up by the ScriptManager. It remains active, waiting for executeUserAction messages triggered by the registered parent DOM events. The iframe will persist until explicitly cleaned up (e.g., when the entity view is closed or the cleanupActiveIframes method is called in the parent).
Data Flow and Return Values
Understanding how data flows is key to writing effective scripts:
The
dataobject passed to your script initially is a copy of the relevant entity data from the parent application.You have two primary ways to influence the final
datastate that the parent application will receive:Direct Return: If the main script function (the code within the IIFE) returns a value (anything other than
undefined), this value will be used as the finaldataresult.Using [
context.setData(newData)]: You can callcontext.setData()one or more times during the script's execution. Each call updates thedatastate within the iframe and sends thenewDataback to the parent.
If a script uses
context.setData, the last state of thedataobject (either from a direct return or the lastsetDatacall) when the script signals completion (by postingscriptResult) will be considered the final result for the initial execution phase.If a script registers actions, the initial execution might finish, but subsequent calls to
context.setData()from within youruserScriptActionshandlers will continue to update the data state in the parent application.
Simple Examples
Here are a few examples demonstrating common script patterns:
Example 1: Modifying Data Directly
This script receives item data and adds a new property to it.
Example 2: Using context.setData and context.notify
This script validates a field in a Purchase Order and notifies the user.
Example 3: Registering an Action
This script assumes a button with the ID customItemActionBtn exists in the parent application's DOM. It registers a click listener on this button and defines a handler for the action.
Best Practices and Notes
Idempotency: Where possible, design your scripts to be idempotent, meaning executing them multiple times with the same input has the same effect as executing it once. This can help prevent unexpected behavior if scripts are triggered more than once.
Performance: Be mindful of the code you run in scripts. Complex calculations, long loops, or excessive DOM manipulations (if you were somehow able to achieve them, though the sandbox limits this) can impact the user experience.
Error Handling: Use
try...catchblocks within your script code to gracefully handle errors. Use [context.notify(errorMessage, 'error')] to inform the user of issues. Errors are also caught by the iframe executor and reported to the parentScriptManager.Security: While the iframe sandbox (
allow-scripts allow-same-origin) provides isolation, be aware that scripts have access to thecontextobject which allows them to trigger actions in the parent. Design your scripts and the availablecontextfunctions carefully. Avoid executing arbitrary, untrusted code.Asynchronous Operations: Scripts can perform asynchronous operations (e.g.,
fetchcalls,setTimeout). Ensure you handle promises correctly. If your script isasync, the value it resolves with will be used as the result if nocontext.setDatacalls were made after the finalawait.Interactive Scripts Cleanup: Remember that if a script registers actions, the iframe remains active. Ensure you have a mechanism (like closing the view associated with the entity) that eventually triggers the cleanup of these active iframes via the
ScriptManager.
Join our Community Forum
Any other questions? Get in touch