tSM-Specific Schema Extensions
In addition to standard JSON Schema capabilities, tSM introduces a number of powerful metadata extensions that allow forms to be dynamic, context-aware, and tightly integrated with the backend platform.
This document explores those extensions in depth.
Overview of Extensions
Field | Purpose |
---|---|
widget | Declares the Angular component to render the field or layout block |
config | Hosts all behavior: UI labels, conditions, defaults, integrations |
layout | Defines visual grouping and composition of form elements |
readonly , hidden , default | Control runtime behavior via JEXL |
validationMessages | Provide static or dynamic error feedback |
dispatchOnChange | Trigger NGRX actions on field updates |
selectFromStore | Bind widget values to NGRX selectors |
dataSource , filters , selectProperty | Configure LOVs, listings, and data-driven widgets |
Detailed Examples
1. widget
Each field (or layout block) must define its widget.type
, which maps to a registered Angular component.
Examples:
"widget": { "type": "tsm-text" }
"widget": { "type": "dtl-fluent-tab" }
2. config
A deeply extensible object that controls:
- Static settings:
label
,tooltip
,legend
,columns
, etc. - Dynamic logic:
hidden
,readonly
,validationMessages
(JEXL-based) - Data & UX behaviors:
lazyLoad
,selectFirstValue
,cssClasses
,collapsible
, etc. - Event-driven behavior: Hooks such as
dispatchOnChange
,selectFromStore
Example:
"config": {
"label": "Priority",
"readonly": "${$context.user.role !== 'admin'}",
"validationMessages": { "required": "Please select a priority." }
}
3. layout
A layout
block organizes how widgets are presented on screen:
- Can use widgets like:
dtl-fluent-card
,dtl-fluent-tab
,dtl-fluent-columns
- Supports nested content blocks
- Allows dynamic conditional rendering
Example:
"layout": [
{
"type": "layout",
"widget": { "type": "dtl-fluent-card" },
"config": {
"header": "Customer Info",
"collapsible": true
},
"items": ["customerName", "segment"]
}
]
4. validationMessages
with Dynamic Rules
You can provide conditionally evaluated validations using JEXL inside a nested custom
block.
"validationMessages": {
"custom": {
"expression": "${$context.form.customerType == 'VIP' && $value > 50}",
"message": "VIP customers cannot select a discount above 50%."
}
}
5. Computed Properties: readonly
, hidden
, default
These expressions can be pure booleans or JEXL strings:
"readonly": "${$context.user.role !== 'admin'}"
"default": "addTime(now(), 1, 'days')"
"config": {
"hidden": "${isNullOrEmpty($context.form.customerId)}"
}
6. Event Bindings: dispatchOnChange
, selectFromStore
tSM widgets can be bound to state actions and selectors using:
dispatchOnChange
: Fires an NGRX action on value changeselectFromStore
: Subscribes to state values and reflects them into widget state
Example:
"config": {
"dispatchOnChange": {
"type": "UpdatePriority",
"payload": { "id": "$context.entity.id", "priority": "$value" }
},
"selectFromStore": {
"storeSelector": "getCurrentPriority",
"target": "default"
}
}
7. Data Integration Fields for LOVs and Listings
"config": {
"dataSource": "ticketStatuses",
"filters": {
"statusGroup": "OPEN"
},
"selectProperty": "code",
"lazyLoad": true,
"selectFirstValue": true
}
These are essential for rendering value lists and inline embedded listings.
8. Advanced Layout Features
tabs
: Named tab groupscolumns
: Horizontal layoutscollapsible
: Expand/collapse any card or sectionvisibilityLogic
: Controlled viaconfig.hidden
readonlyBased
storage modes
Summary
tSM's extensions to JSON Schema are designed to provide a fully metadata-driven UI engine that supports:
- Live logic and scripting (via JEXL)
- Deep backend integration (via selectors, dispatchers)
- Layered layout and dynamic control
- Fine-grained access, visibility, and interaction rules
These fields go beyond simple configuration — they represent the architecture of a low-code platform’s UI layer.