Widget Placement
A Widget Placement is an instance of a Widget Definition in a specific Pane, with app-specific configuration, layout position, actions, and visibility rules.
Widget Placement Structure
// TypeScript — WidgetPlacement interface
interface WidgetPlacement {
// Identity
widgetId: string; // Unique within the app (e.g., "leads-grid")
widgetTypeId: string; // References WidgetDefinition (e.g., "DataGrid")
// Configuration — overrides the WidgetDefinition defaults
config: Record<string, unknown>; // Supports {{ token }} expressions in values
// Layout position within the parent Pane's grid
layout: {
colStart: number; // 1-12
colSpan: number; // 1-12
rowStart?: number; // auto if omitted
rowSpan?: number; // default 1
};
// Per-breakpoint layout overrides
layoutBreakpoints?: {
tablet?: Partial<WidgetLayout>;
mobile?: Partial<WidgetLayout>;
};
// Actions — event to action bindings
actions: Record<string, ActionConfig | ActionConfig[]>;
// e.g., { onClick: { type: "navigate", target: "/leads/{{ row.id }}" } }
// Visibility
visibleTo?: string[]; // Role list, empty = all
visibilityExpression?: string; // Token expression: "{{ context.roles.includes('admin') }}"
// Custom JS (optional)
customJs?: string; // Sandboxed JS for this widget
}
Placement vs. Definition Config
The config in a Widget Placement is a partial override of the Widget Definition's defaultConfig. Properties not specified in the placement config fall back to the definition's defaults via the configuration cascade.
// Widget Definition defaultConfig for DataGrid:
{
"pageSize": 20,
"selectable": false,
"sortable": true
}
// Widget Placement config (placement overrides only):
{
"dataSource": "GetLeads",
"columns": [ ... ],
"pageSize": 50 // overrides the default of 20
// selectable and sortable inherit from definition defaults
}
// Effective config at runtime (merged):
{
"dataSource": "GetLeads",
"columns": [ ... ],
"pageSize": 50, // from placement
"selectable": false, // from definition default
"sortable": true // from definition default
}
Actions on a Placement
Actions are defined per-placement and per-event. The same widget type in different apps can have completely different actions configured — a DataGrid's onRowSelect in the Leads app navigates to a lead detail page, while in the Contacts app it opens a modal.
// Widget Placement actions example
"actions": {
"onRowSelect": {
"type": "navigate",
"target": "/leads/{{ row.id }}"
},
"onMount": [
{
"type": "set-variable",
"variable": "currentPage",
"value": "leads-list"
}
]
}
Visibility Rules
Two complementary visibility mechanisms are available on every placement:
visibleTo: a list of role names. The widget renders only if the current user has at least one of these roles. Checked at render time — evaluated client-side.visibilityExpression: a token expression that must evaluate totruefor the widget to render. More powerful — can incorporate variables, route params, or service results.
// Simple role list
"visibleTo": ["admin", "manager"]
// Token expression — visibility based on a variable
"visibilityExpression": "{{ variables.showAdvancedControls }}"
// Combined — both must be satisfied (AND logic)
"visibleTo": ["admin"],
"visibilityExpression": "{{ route.id !== undefined }}"
Widget visibility rules are enforced client-side — a user with the wrong role simply does not see the widget rendered. However, the underlying data API calls are always protected server-side by AIExtension.Service. A hidden widget cannot be "un-hidden" to reveal unauthorized data.