Forms 2.0 - API Reference

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

Groovy
// 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 – an OPTION input whose value options will later be provided by a <inputName>_ValueOptions element.

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

Groovy
// 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:

Groovy
{
  "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

Groovy
// 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:

Groovy
{
  "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):

Groovy
// 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

Groovy
// 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:

  1. Build an input definition using the builder.

  2. Insert the built definition relative to an existing input.

Groovy
// 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:

Groovy
// 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:

Groovy
// 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)

Groovy
// 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

Groovy
// 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.

Groovy
// 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.