Skip to main content

Form Architecture in tSM

This section describes the internal architecture of forms in tSM — how they are structured, processed, and rendered at both design-time and runtime. A deep understanding of these mechanisms is crucial for building maintainable, high-performance, and flexible forms.

Design-Time vs Runtime Architecture

tSM separates form behavior into two distinct phases:

PhaseContextPurpose
Design-TimeInside the Form DesignerAllows creation and editing of the form schema
RuntimeIn live UI (e.g. Ticket detail view, Dashboard)Interprets schema and renders it with data

Key differences:

  • In design-time, $context variables are not available.
  • JEXL expressions are not evaluated live — only statically visible.
  • Runtime enables full logic: dynamic values, API data, visibility conditions.

This duality must be considered when implementing or debugging forms. Some configuration appears valid in the Designer but only behaves correctly once rendered in a live context.

JSON Schema Core Model

All forms are built using a JSON Schema–based model, extended with layout, widget, and config metadata.

At the top level:

{
  "type": "object",
  "properties": {
    "fieldName": { "type": "string", "widget": { "type": "text" } }
  },
  "required": ["fieldName"],
  "layout": [ ... ]
}

Key fields:

KeyPurpose
typeAlways "object" for root
propertiesDefines all fields used in the form
requiredList of mandatory fields
layoutDefines how fields are arranged (cards, tabs, etc.)
widgetAttached to each property — determines the UI component
configAttached to widgets/layouts — controls visibility, conditions, styles

Each widget is mapped at runtime to an Angular component and is bound to its respective data field.

Form Rendering Lifecycle

  1. Form JSON Schema is fetched from the backend using selector
  2. Default values are inserted where defined
  3. JEXL expressions are parsed, injected, and cached
  4. Context bindings (e.g., form.chars, entity.*, related.*) are resolved
  5. Widgets are lazily rendered — i.e., instantiated only when needed
  6. LOVs and Listings trigger async data loads
  7. Events and reactive behavior (JEXL, NGRX selectors, etc.) are executed live

Understanding this lifecycle helps isolate and debug rendering or behavior issues, especially in complex, conditional forms.

Dynamic Behavior & Conditions

Runtime behavior is controlled primarily using JEXL expressions, placed within:

  • config.hidden
  • readonly
  • default
  • validationMessages
  • layout conditions (e.g., show/hide tabs or cards)

These are evaluated based on the $context, which exposes the live runtime state of:

VariableDescription
$contextRoot context object containing all contextual data
$context.formThe current form being edited or created
$context.entityThe entity associated with the form (e.g., customer, account)
$context.elasticIndexed (flattened) version of the entity
$context.viewModeThe UI view mode (e.g., baseView, tabView)
$valueValues of form characteristics (custom fields)
$configUiConfiguration of the frontend environment (e.g., image compression settings)
$runtimeInfoLogged-in user information (name, ID, roles, etc.)

Nested expressions are supported — allowing highly dynamic behavior.

Example:

"readonly": "${$context.user.role !== 'admin'}"

This will disable editing unless the user is an admin.

Lazy Loading and Performance

tSM form engine is lazy by design — widgets are only instantiated once:

  • They are visible in viewport (tab/card opened)
  • They are not explicitly hidden (config.hidden false)
  • Their dependencies have resolved (e.g., LOV filters)

Benefits:

  • Faster initial render
  • Lower memory use in large forms
  • Allows partial rendering (e.g., tabs not visible on first load are skipped)

Separation of Layout vs Data Binding

While widgets define how a field looks and behaves, layout components (cards, columns, sections) define where it appears and how groups are structured. Layout blocks themselves can have visibility logic, styling, and structural settings (collapsible, tabs, column width, etc.).

Each layout item refers to:

  • A property field by ID (fieldName)
  • Or another nested layout object

Layout examples:

{
  "type": "layout",
  "widget": { "type": "dtl-fluent-columns" },
  "config": { "columns": [...] }
}
{
  "type": "layout",
  "widget": { "type": "dtl-fluent-card" },
  "config": { "header": "Advanced Settings", "collapsible": true },
  "items": ["slaWindow", "priority"]
}

Integration with Backend Systems

Forms are deeply integrated with backend logic, such as:

  • SpEL expressions for server-side evaluation
  • BPMN processes triggered via save or submit
  • API integrations for dynamic LOVs, listings, lookups
  • Validation gateways and rule engines

This allows the form to function not just as a UI artifact, but as a full control plane interface — orchestrating business processes, user input, and automated decisions.

Summary

Form architecture in tSM is a layered system of:

  • JSON Schema definitions
  • Metadata-driven layout and widget configuration
  • Declarative logic via JEXL
  • Live context-based rendering and validation
  • Integration points with NGRX, APIs, and backend systems

It’s designed for dynamic flexibility, with minimal hardcoded logic — enabling domain experts to modify UI and data models without frontend development.