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:
Phase | Context | Purpose |
---|---|---|
Design-Time | Inside the Form Designer | Allows creation and editing of the form schema |
Runtime | In 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:
Key | Purpose |
---|---|
type | Always "object" for root |
properties | Defines all fields used in the form |
required | List of mandatory fields |
layout | Defines how fields are arranged (cards, tabs, etc.) |
widget | Attached to each property — determines the UI component |
config | Attached 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
- Form JSON Schema is fetched from the backend using selector
- Default values are inserted where defined
- JEXL expressions are parsed, injected, and cached
- Context bindings (e.g.,
form.chars
,entity.*
,related.*
) are resolved - Widgets are lazily rendered — i.e., instantiated only when needed
- LOVs and Listings trigger async data loads
- 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:
Variable | Description |
---|---|
$context | Root context object containing all contextual data |
$context.form | The current form being edited or created |
$context.entity | The entity associated with the form (e.g., customer, account) |
$context.elastic | Indexed (flattened) version of the entity |
$context.viewMode | The UI view mode (e.g., baseView , tabView ) |
$value | Values of form characteristics (custom fields) |
$configUi | Configuration of the frontend environment (e.g., image compression settings) |
$runtimeInfo | Logged-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.