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,
$contextvariables 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.hiddenreadonlydefaultvalidationMessages- 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.hiddenfalse) - 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.