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
data
object 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 thedata
object 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
data
object asynchronously or at multiple points during execution.Calling this function sends the
newData
back 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.registerAction
to set up interactive elements, you should call this function.This signals to the parent
ScriptManager
that the iframe needs to remain active after the initial script execution finishes to handle potential user interactions triggered by the registered actions.(Note: The
ScriptManager
also checks an internal flag (_didRegisterAction
) which is set whenregisterAction
is called. CallingnotifyInteractiveReady
explicitly 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.
reason
is 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
ScriptManager
in the parent application receives thescriptRegisterAction
message. 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
ScriptManager
then sends anexecuteUserAction
message back to the iframe where the script is running. This message includes theactionName
("handleMyButtonClick"
) and anactionPayload
containing details about the event that occurred (like target ID, tag name, input value, coordinates, key presses, etc.).Script Handles Action: The iframe receives the
executeUserAction
message. 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 actionName
s 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
data
object 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
data
state 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 finaldata
result.Using [
context.setData(newData)
]: You can callcontext.setData()
one or more times during the script's execution. Each call updates thedata
state within the iframe and sends thenewData
back to the parent.
If a script uses
context.setData
, the last state of thedata
object (either from a direct return or the lastsetData
call) 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 youruserScriptActions
handlers 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...catch
blocks 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 thecontext
object which allows them to trigger actions in the parent. Design your scripts and the availablecontext
functions carefully. Avoid executing arbitrary, untrusted code.Asynchronous Operations: Scripts can perform asynchronous operations (e.g.,
fetch
calls,setTimeout
). Ensure you handle promises correctly. If your script isasync
, the value it resolves with will be used as the result if nocontext.setData
calls 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