This page introduces the Groovy API used to implement Forms 2.0. It assumes you are familiar with basic form concepts (Init / ValueOptions / Action phases) and focuses on the runtime API exposed via the injected form helper and related objects.
Remember to https://knowledge.pricefx.com/configuration-engineer-knowledge-base/tools/pricefx-studio/how-to-studio/upgrade-studio-project-libraries after upgrade to Paloma 17.0.0 to enable auto-completion for forms API in Studio.
Runtime Context and Helper APIs
When a Form logic runs, the engine injects a form helper into the Groovy context. This object is the main entry point for:
-
Inspecting the current object (Quote line, header, dashboard, etc.).
-
Identifying which input triggered the current event.
-
Creating, updating, removing and re-laying-out inputs.
-
Handling Init, ValueOptions, and Action phases.
Key helpers:
-
form.sourceInputName()
Returns the name of the input that triggered the current ValueOptions or Action phase. Use this to branch behavior by source input. -
api.getLocale()
Returns the first part of the end user’s locale (for example"en"). Typical usage is to localize value options based on the current user’s language. -
api.currentItem()
Returns the domain object (e.g., Quote line item, Agreement & Promotion line item) the form is currently bound to.
Depending on the phase (Init, ValueOptions, Action), different capabilities of form are relevant, as described below.
Phase: Init – Creating the Initial Input Set
The Init context is executed when:
-
A user creates a new object (Quote, Rebate Agreement, etc.).
-
A new line item is added.
-
A dashboard using a Form is opened.
This replaces the legacy “input generation” / “syntax check” mode of calculation logics.
Guidelines:
-
Use Init to declare the initial set of inputs.
-
Avoid or strictly minimize database queries to keep form rendering fast.
-
Do not populate value options here; Unity will lazy-load them later through ValueOptions.
Example: Basic Init – Text + Option Input
// Init element in a Form logic
// Simple free-text input
form.createStringUserEntry("Comment")
.setLabel("Comment")
// Single-select input (value options will be loaded lazily in ValueOptions phase)
form.createOptionEntry("Country")
.setLabel("Country")
This Init element defines two inputs:
-
Comment– a string entry. -
Country– anOPTIONinput whose value options will later be provided by a<inputName>_ValueOptionselement.
Phase: ValueOptions – Supplying OPTION / OPTIONS Data
The ValueOptions context is called by Unity on demand to provide values for OPTION and OPTIONS inputs already defined in Init.
For an input named Country, create an element named:
-
ValueOptions
with calculation context ValueOptions.
You typically use Query API to load value options from master data or company parameters, and return either:
-
A map of
value → label, or -
A list of values.
Example: Returning value → label Map
// Country_ValueOptions (context: ValueOptions)
def qapi = api.queryApi()
def t1 = qapi.tables().companyParameterRows("Country")
return qapi.source(t1)
.stream { rows ->
rows.collectEntries { row ->
[(row.CountryCode): row.CountryName]
}
}
Response payload shape:
{
"response": {
"data": {
"CZ": "Czech Republic",
"FR": "France",
"DE": "Germany",
"UK": "United Kingdom",
"US": "USA"
}
}
}
Unity interprets keys as stored values, values as rendered labels in the dropdown.
Example: Returning a Flat List of Values
// Colors_ValueOptions (context: ValueOptions)
def qapi = api.queryApi()
def t1 = qapi.tables().companyParameterRows("Colors")
return qapi.source(t1)
.stream { rows ->
rows.collect { row -> row.Color }
}
Response payload shape:
{
"response": {
"data": [
"Red",
"Green",
"Blue"
]
}
}
In this variant, each entry is both the stored value and the displayed text.
Localization of Labels
The ValueOptions phase can also tailor labels to the user’s language by using:
-
api.getLocale()to detect the user’s locale.
Phase: Action – Reacting to Frontend Events
The Action context is triggered when a frontend event fires on an input that has dynamic behavior attached—for example, onClick (buttons) or onValueChange (other inputs).
In Action, you can:
-
Refresh value options.
-
Add or remove inputs on the fly.
-
Modify input properties (value, required flag, labels, etc.).
-
Perform custom validation and show inline messages.
The entry point is again the form helper, often combined with switch (form.sourceInputName()) to branch based on the triggering input.
Reloading Value Options
To force a dropdown to reload its options (which causes Unity to re-run the corresponding ValueOption elements):
// Example: triggered in Action context when some input changes
form.updateInput("Country")
.invalidateValueOptions()
Unity will subsequently request fresh options for Country from ValueOptions.
Removing an Input
// Remove "Comment" input from the current form
form.updateInput("Comment")
.remove()
This removes the input from the form’s definition; the user will no longer see it in the UI.
Adding an Input at a Specific Position
Inputs can be inserted at various positions using:
-
insertAfter(inputName, inputDefinition)
Typical pattern:
-
Build an input definition using the builder.
-
Insert the built definition relative to an existing input.
// Create a new text input definition for "Comment"
def commentInputDef = form.createTextUserEntry("Comment")
.setLabel("Comment")
.buildContextParameter()
// Insert it after an existing input named "Hide"
form.insertAfter("Hide", commentInputDef)
This dynamically extends the form when the Action phase runs.
Adjusting Input Properties
To update properties (value, required flag, etc.) of an existing input:
// Set default value and mark "Comment" as required
form.updateInput("Comment")
.setValue("Please add a justification")
.setRequired(true)
These changes are pushed to the client as part of the Action response, without full form reload.
Custom Validation and Messages
You can implement custom validation logic and return inline messages bound to specific inputs:
-
setErrorMessage(...)– red, blocking submission. -
setWarningMessage(...)– yellow, non-blocking. -
setSuccessMessage(...)– green, non-blocking.
Example:
// Action context: validate inputs based on which one triggered the event
switch (form.sourceInputName()) {
case "Quantity":
// Example: Quantity must be even
def isInvalid = (input.Quantity % 2) != 0
form.updateInput("Quantity")
.setErrorMessage(isInvalid ? "Quantity must be a multiple of 2." : null)
break
// Add more cases for other inputs as needed
}
This pattern allows you to centralize validation rules per input and provide immediate feedback to the end user.
Layout API – Controlling Visual Structure
By default, inputs are rendered in a simple vertical list (one below another, top to bottom). For more complex UIs, Forms 2.0 exposes a layout builder via form.layout() that lets you:
-
Arrange inputs into rows (horizontal layout).
-
Group inputs in collapsible containers.
-
Define modal dialogs for advanced or secondary inputs.
Inputs or layout items are registered using:
-
form.layout().addChild(inputOrContainer)– Add an element (row, container, modal) to the root layout.
All layout configuration is typically done in the Init phase, so the user sees the desired structure immediately.
Horizontal Layout Example (Row)
// Init: lay out Width and Height next to each other
def layout = form.layout()
def row = layout.createRow("Size")
form.createUserEntry("Width")
.setFrom(1)
.setTo(1000)
.addToFormLayout(row)
form.createUserEntry("Height")
.setFrom(1)
.setTo(1000)
.addToFormLayout(row)
// Attach row to the layout
layout.addChild(row)
This renders Width and Height side by side in a single row.
Collapsible Container Example
// Init: group additional inputs into a collapsible section
def layout = form.layout()
def container = layout.createContainer("Additional")
.setCollapsible(true)
.setCollapsed(true)
.setHeading("Additional")
form.createDateUserEntry("ReviewDate")
.addToFormLayout(container)
form.createStringUserEntry("Comment")
.setLabel("Additional Comment")
.addToFormLayout(container)
// Attach container to the root layout
layout.addChild(container)
This creates a section titled “Additional” that is initially collapsed and can be expanded by the user.
Modal Dialog Example
// Init: define a modal dialog with two inputs
def layout = form.layout()
def modal = layout.createModal("MyModal")
form.createStringUserEntry("Input5")
.addToFormLayout(modal)
form.createStringUserEntry("Input6")
.addToFormLayout(modal)
// Attach modal to the root layout
layout.addChild(modal)
Unity renders a modal dialog named “My Modal” that can be opened from the form UI; the two inputs are shown inside the dialog.
What Forms 2.0 API Enables
By combining:
-
Init for initial input set and layout,
-
ValueOptions for lazy-loaded options, and
-
Action for dynamic behavior and validation,
the Forms 2.0 API:
-
Isolates form definition from pricing/business logic in a dedicated Form logic.
-
Allows forms and inputs to be shared across objects (dashboards, quote types, agreements, etc.).
-
Reduces database load and recalculation scope compared to configurators.
-
Avoids persisting value options directly in documents.
-
Eliminates the legacy “syntax check” mode in favor of a clearer, phase-based model.
Use this API as the foundation when designing new Forms 2.0 implementations or refactoring existing static-input / configurator-based UIs into a single, maintainable form layer.