@acorex/platform 20.3.0-next.20 → 20.3.0-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/index.d.ts +14 -1
- package/fesm2022/acorex-platform-auth.mjs +19 -19
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/acorex-platform-common.mjs +126 -105
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +55 -42
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-domain.mjs +16 -16
- package/fesm2022/acorex-platform-domain.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +182 -159
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +542 -2387
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +72 -72
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/{acorex-platform-layout-entity-create-entity.command-BXExgI3W.mjs → acorex-platform-layout-entity-create-entity.command-DyXF9zAh.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-entity-create-entity.command-BXExgI3W.mjs.map → acorex-platform-layout-entity-create-entity.command-DyXF9zAh.mjs.map} +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +156 -156
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +15 -15
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +73 -73
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-button-widget-designer.component-BzsfTNs2.mjs → acorex-platform-layout-widgets-button-widget-designer.component-C_3IWNkj.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-button-widget-designer.component-BzsfTNs2.mjs.map → acorex-platform-layout-widgets-button-widget-designer.component-C_3IWNkj.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-Dvk76-2W.mjs → acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-CJltEgut.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-Dvk76-2W.mjs.map → acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-CJltEgut.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-BYLaipWi.mjs → acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-pM-TIuk0.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-BYLaipWi.mjs.map → acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-pM-TIuk0.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-DcSllNik.mjs → acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-BqI96-fU.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-DcSllNik.mjs.map → acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-BqI96-fU.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-values-widget-view.component-BT-U4BiA.mjs → acorex-platform-layout-widgets-extra-properties-values-widget-view.component-C-AhenaM.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-values-widget-view.component-BT-U4BiA.mjs.map → acorex-platform-layout-widgets-extra-properties-values-widget-view.component-C-AhenaM.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-widget-edit.component-Il7jnRBg.mjs → acorex-platform-layout-widgets-extra-properties-widget-edit.component-DCAya5ne.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-widget-edit.component-Il7jnRBg.mjs.map → acorex-platform-layout-widgets-extra-properties-widget-edit.component-DCAya5ne.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-widget-view.component-CBEPu7Fl.mjs → acorex-platform-layout-widgets-extra-properties-widget-view.component-D-PnBqLb.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-widget-view.component-CBEPu7Fl.mjs.map → acorex-platform-layout-widgets-extra-properties-widget-view.component-D-PnBqLb.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-file-list-popup.component-BPzn8lr3.mjs → acorex-platform-layout-widgets-file-list-popup.component-DuuFHWvB.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-file-list-popup.component-BPzn8lr3.mjs.map → acorex-platform-layout-widgets-file-list-popup.component-DuuFHWvB.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-page-widget-designer.component-C_JrGoXy.mjs → acorex-platform-layout-widgets-page-widget-designer.component-Bss0xUcu.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-page-widget-designer.component-C_JrGoXy.mjs.map → acorex-platform-layout-widgets-page-widget-designer.component-Bss0xUcu.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-C6DaBt_N.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-Cy9mHnNP.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-C6DaBt_N.mjs.map → acorex-platform-layout-widgets-tabular-data-edit-popup.component-Cy9mHnNP.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-Bth3jI9T.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-DznLtuer.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-Bth3jI9T.mjs.map → acorex-platform-layout-widgets-tabular-data-view-popup.component-DznLtuer.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-text-block-widget-designer.component-CUHptbP4.mjs → acorex-platform-layout-widgets-text-block-widget-designer.component-ndOUSFi9.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-text-block-widget-designer.component-CUHptbP4.mjs.map → acorex-platform-layout-widgets-text-block-widget-designer.component-ndOUSFi9.mjs.map} +1 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs +742 -892
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-native.mjs +7 -7
- package/fesm2022/acorex-platform-native.mjs.map +1 -1
- package/fesm2022/acorex-platform-runtime.mjs +40 -40
- package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-create-view.component-eGzN6g2Y.mjs → acorex-platform-themes-default-entity-master-create-view.component-Fnj54AxV.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-entity-master-create-view.component-eGzN6g2Y.mjs.map → acorex-platform-themes-default-entity-master-create-view.component-Fnj54AxV.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-nDHfQQ3O.mjs → acorex-platform-themes-default-entity-master-list-view.component-C60W6UnN.mjs} +60 -13
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-C60W6UnN.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-default-entity-master-modify-view.component-HJyalvcu.mjs → acorex-platform-themes-default-entity-master-modify-view.component-B3NyKGIG.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-entity-master-modify-view.component-HJyalvcu.mjs.map → acorex-platform-themes-default-entity-master-modify-view.component-B3NyKGIG.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-e7m70Wls.mjs → acorex-platform-themes-default-entity-master-single-view.component-B8gx5cG7.mjs} +7 -7
- package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-e7m70Wls.mjs.map → acorex-platform-themes-default-entity-master-single-view.component-B8gx5cG7.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-error-401.component-CoBaQFTn.mjs → acorex-platform-themes-default-error-401.component-CcvGfdhu.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-error-401.component-CoBaQFTn.mjs.map → acorex-platform-themes-default-error-401.component-CcvGfdhu.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-error-404.component-BLlVOsS2.mjs → acorex-platform-themes-default-error-404.component-4-CaEsnV.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-error-404.component-BLlVOsS2.mjs.map → acorex-platform-themes-default-error-404.component-4-CaEsnV.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-error-offline.component-CybYQI9F.mjs → acorex-platform-themes-default-error-offline.component-BNecbFEj.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-error-offline.component-CybYQI9F.mjs.map → acorex-platform-themes-default-error-offline.component-BNecbFEj.mjs.map} +1 -1
- package/fesm2022/acorex-platform-themes-default.mjs +39 -39
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-ReKSoVeN.mjs → acorex-platform-themes-shared-icon-chooser-view.component-Dc_Txe32.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-ReKSoVeN.mjs.map → acorex-platform-themes-shared-icon-chooser-view.component-Dc_Txe32.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-column.component-B2HDyY2z.mjs → acorex-platform-themes-shared-theme-color-chooser-column.component-hgWLhhle.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-column.component-B2HDyY2z.mjs.map → acorex-platform-themes-shared-theme-color-chooser-column.component-hgWLhhle.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-view.component-CeZxa49U.mjs → acorex-platform-themes-shared-theme-color-chooser-view.component-CY3JZK_W.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-view.component-CeZxa49U.mjs.map → acorex-platform-themes-shared-theme-color-chooser-view.component-CY3JZK_W.mjs.map} +1 -1
- package/fesm2022/acorex-platform-themes-shared.mjs +41 -41
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +25 -25
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/layout/builder/README.md +1577 -2
- package/layout/builder/index.d.ts +182 -125
- package/layout/components/index.d.ts +8 -805
- package/layout/entity/index.d.ts +2 -2
- package/layout/widgets/index.d.ts +11 -36
- package/package.json +9 -9
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-nDHfQQ3O.mjs.map +0 -1
package/layout/builder/README.md
CHANGED
|
@@ -1,3 +1,1578 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ACoreX Layout Builder System
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The **Layout Builder** is a powerful, fluent API system for creating dynamic layouts, pages, forms, and dialogs in the ACoreX Platform. It replaces the older Dynamic Form Builder with a more flexible, type-safe, and comprehensive solution.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Key Features](#key-features)
|
|
9
|
+
- [Architecture](#architecture)
|
|
10
|
+
- [Getting Started](#getting-started)
|
|
11
|
+
- [Core Concepts](#core-concepts)
|
|
12
|
+
- [Container Types](#container-types)
|
|
13
|
+
- [Form Fields](#form-fields)
|
|
14
|
+
- [Widget Types](#widget-types)
|
|
15
|
+
- [Dialog Builder](#dialog-builder)
|
|
16
|
+
- [Layout Inheritance](#layout-inheritance)
|
|
17
|
+
- [Complete Examples](#complete-examples)
|
|
18
|
+
- [Migration Guide](#migration-guide)
|
|
19
|
+
- [Best Practices](#best-practices)
|
|
20
|
+
- [API Reference](#api-reference)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Overview
|
|
25
|
+
|
|
26
|
+
The Layout Builder provides a fluent, delegate-based API for creating complex layouts without manually constructing configuration objects. It supports:
|
|
27
|
+
|
|
28
|
+
- **🎨 Multiple Container Types**: Flex, Grid, Panel, Page, Tabset, Fieldset
|
|
29
|
+
- **📝 Rich Form Capabilities**: Form fields with automatic path generation
|
|
30
|
+
- **🔄 Property Inheritance**: Automatic propagation of mode, readonly, disabled, and visibility
|
|
31
|
+
- **💬 Dialog Support**: Built-in dialog builder with action management
|
|
32
|
+
- **✅ Type Safety**: Full TypeScript support with IntelliSense
|
|
33
|
+
- **🎯 Fluent API**: Readable, chainable method calls with delegate pattern
|
|
34
|
+
- **🔧 Widget Support**: 15+ built-in widgets plus custom widget support
|
|
35
|
+
|
|
36
|
+
### Comparison: Layout Builder vs Dynamic Form Builder
|
|
37
|
+
|
|
38
|
+
| Feature | Dynamic Form Builder | Layout Builder |
|
|
39
|
+
|----------------------------------|----------------------|----------------|
|
|
40
|
+
| **Purpose** | Forms only | Full layouts + forms |
|
|
41
|
+
| **Container Types** | Groups only | Flex, Grid, Panel, Page, Tabset, Fieldset |
|
|
42
|
+
| **Nesting** | Limited | Unlimited nesting |
|
|
43
|
+
| **Layout Control** | Basic | Advanced (Flexbox, Grid) |
|
|
44
|
+
| **Property Inheritance** | No | Yes (mode, readonly, disabled) |
|
|
45
|
+
| **Dialog Support** | Separate service | Built-in |
|
|
46
|
+
| **Widget Path Management** | Manual | Automatic |
|
|
47
|
+
| **Custom Widgets** | Limited | Full support |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Key Features
|
|
52
|
+
|
|
53
|
+
### 1. Fluent API with Delegate Pattern
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
builder.flex(flexContainer => {
|
|
57
|
+
flexContainer
|
|
58
|
+
.setDirection('column')
|
|
59
|
+
.setGap('16px')
|
|
60
|
+
.formField('First Name', field => {
|
|
61
|
+
field.path('firstName');
|
|
62
|
+
field.textBox({ placeholder: 'Enter name' });
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Automatic Property Inheritance
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Parent container sets mode to 'view'
|
|
71
|
+
builder.flex(flexContainer => {
|
|
72
|
+
flexContainer.mode('view'); // All children inherit 'view' mode
|
|
73
|
+
|
|
74
|
+
flexContainer.formField('Name', field => {
|
|
75
|
+
field.textBox(); // Automatically in 'view' mode
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Automatic Path Generation
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// Path is automatically generated from field name
|
|
84
|
+
flexContainer.formField('First Name', field => {
|
|
85
|
+
field.textBox(); // path = 'first_name' (auto-generated)
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Or explicitly set path
|
|
89
|
+
flexContainer.formField('First Name', field => {
|
|
90
|
+
field.path('user.firstName');
|
|
91
|
+
field.textBox();
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Nested Containers
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
builder.flex(mainContainer => {
|
|
99
|
+
mainContainer
|
|
100
|
+
.setDirection('column')
|
|
101
|
+
.panel(panel => {
|
|
102
|
+
panel
|
|
103
|
+
.setCaption('Personal Info')
|
|
104
|
+
.flex(innerFlex => {
|
|
105
|
+
innerFlex.setDirection('row');
|
|
106
|
+
// Add widgets...
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Architecture
|
|
115
|
+
|
|
116
|
+
### Service Structure
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
AXPLayoutBuilderService
|
|
120
|
+
└── create() → ILayoutBuilder
|
|
121
|
+
├── flex()
|
|
122
|
+
├── grid()
|
|
123
|
+
├── panel()
|
|
124
|
+
├── page()
|
|
125
|
+
├── tabset()
|
|
126
|
+
├── fieldset()
|
|
127
|
+
├── dialog()
|
|
128
|
+
└── build() → AXPWidgetNode
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Builder Hierarchy
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
BaseContainerBuilder (Abstract)
|
|
135
|
+
├── LayoutContainerMixin
|
|
136
|
+
│ ├── ChildContainerMixin
|
|
137
|
+
│ │ └── WidgetContainerMixin
|
|
138
|
+
│ │ ├── FlexContainerBuilder
|
|
139
|
+
│ │ ├── GridContainerBuilder
|
|
140
|
+
│ │ ├── PanelContainerBuilder
|
|
141
|
+
│ │ ├── PageContainerBuilder
|
|
142
|
+
│ │ ├── TabsetContainerBuilder
|
|
143
|
+
│ │ └── ListWidgetBuilder
|
|
144
|
+
│ └── FieldsetContainerBuilder
|
|
145
|
+
│ └── FormFieldBuilder
|
|
146
|
+
└── DialogContainerBuilder
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Getting Started
|
|
152
|
+
|
|
153
|
+
### Installation
|
|
154
|
+
|
|
155
|
+
The Layout Builder is part of the `@acorex/platform/layout/builder` package:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { AXPLayoutBuilderService, AXPLayoutRendererComponent } from '@acorex/platform/layout/builder';
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Basic Setup
|
|
162
|
+
|
|
163
|
+
**Component:**
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { Component, OnInit, inject, signal } from '@angular/core';
|
|
167
|
+
import { AXPLayoutBuilderService, AXPLayoutRendererComponent } from '@acorex/platform/layout/builder';
|
|
168
|
+
import { AXPWidgetNode } from '@acorex/platform/layout/widget-core';
|
|
169
|
+
|
|
170
|
+
@Component({
|
|
171
|
+
selector: 'app-my-page',
|
|
172
|
+
standalone: true,
|
|
173
|
+
imports: [AXPLayoutRendererComponent],
|
|
174
|
+
template: `
|
|
175
|
+
<axp-layout-renderer
|
|
176
|
+
[layout]="layoutDefinition()"
|
|
177
|
+
[(context)]="formContext">
|
|
178
|
+
</axp-layout-renderer>
|
|
179
|
+
`
|
|
180
|
+
})
|
|
181
|
+
export class MyPageComponent implements OnInit {
|
|
182
|
+
private readonly layoutBuilder = inject(AXPLayoutBuilderService);
|
|
183
|
+
|
|
184
|
+
layoutDefinition = signal<AXPWidgetNode | undefined>(undefined);
|
|
185
|
+
formContext = signal<any>({
|
|
186
|
+
firstName: '',
|
|
187
|
+
lastName: '',
|
|
188
|
+
email: ''
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
ngOnInit() {
|
|
192
|
+
this.buildLayout();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
buildLayout() {
|
|
196
|
+
const builder = this.layoutBuilder.create();
|
|
197
|
+
|
|
198
|
+
builder.flex(flexContainer => {
|
|
199
|
+
flexContainer
|
|
200
|
+
.setDirection('column')
|
|
201
|
+
.setGap('16px')
|
|
202
|
+
.formField('First Name', field => {
|
|
203
|
+
field.path('firstName');
|
|
204
|
+
field.textBox({ placeholder: 'Enter first name' });
|
|
205
|
+
})
|
|
206
|
+
.formField('Last Name', field => {
|
|
207
|
+
field.path('lastName');
|
|
208
|
+
field.textBox({ placeholder: 'Enter last name' });
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
this.layoutDefinition.set(builder.build());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Core Concepts
|
|
220
|
+
|
|
221
|
+
### 1. Containers
|
|
222
|
+
|
|
223
|
+
Containers hold other containers or widgets. They provide layout structure.
|
|
224
|
+
|
|
225
|
+
**Available Containers:**
|
|
226
|
+
- **Flex**: Flexbox layout (row/column)
|
|
227
|
+
- **Grid**: CSS Grid layout
|
|
228
|
+
- **Panel**: Card-like container with header
|
|
229
|
+
- **Page**: Full page layout
|
|
230
|
+
- **Tabset**: Tabbed container
|
|
231
|
+
- **Fieldset**: Form section with title/description
|
|
232
|
+
|
|
233
|
+
### 2. Form Fields
|
|
234
|
+
|
|
235
|
+
Form fields are special containers that wrap a single widget and provide a label.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
.formField('Label', field => {
|
|
239
|
+
field.path('fieldPath'); // Data binding path
|
|
240
|
+
field.textBox({ ... }); // Widget type
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 3. Widgets
|
|
245
|
+
|
|
246
|
+
Widgets are the actual input/display components. They are always contained within form fields or containers.
|
|
247
|
+
|
|
248
|
+
### 4. Inheritance Context
|
|
249
|
+
|
|
250
|
+
Properties like `mode`, `readonly`, `disabled`, and `visible` are automatically inherited from parent containers.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
builder.flex(flexContainer => {
|
|
254
|
+
flexContainer.mode('view'); // Set parent mode
|
|
255
|
+
flexContainer.readonly(true); // All children readonly
|
|
256
|
+
|
|
257
|
+
flexContainer.formField('Name', field => {
|
|
258
|
+
field.textBox(); // Automatically: mode='view', readonly=true
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Container Types
|
|
266
|
+
|
|
267
|
+
### 1. Flex Container
|
|
268
|
+
|
|
269
|
+
**Purpose**: Flexbox layout for responsive row/column arrangements.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
builder.flex(flexContainer => {
|
|
273
|
+
flexContainer
|
|
274
|
+
.setDirection('column') // row | column | row-reverse | column-reverse
|
|
275
|
+
.setJustifyContent('center') // flex-start | flex-end | center | space-between | space-around
|
|
276
|
+
.setAlignItems('stretch') // flex-start | flex-end | center | baseline | stretch
|
|
277
|
+
.setGap('16px') // Gap between items
|
|
278
|
+
.setWrap('wrap') // nowrap | wrap | wrap-reverse
|
|
279
|
+
.setPadding('20px')
|
|
280
|
+
.setMargin('10px')
|
|
281
|
+
.setBackgroundColor('#f5f5f5');
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### 2. Grid Container
|
|
286
|
+
|
|
287
|
+
**Purpose**: CSS Grid layout for complex 2D layouts.
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
builder.grid(gridContainer => {
|
|
291
|
+
gridContainer
|
|
292
|
+
.setColumns(3) // Number of columns
|
|
293
|
+
.setRows(2) // Number of rows
|
|
294
|
+
.setGap('16px') // Gap between cells
|
|
295
|
+
.setJustifyItems('center') // start | end | center | stretch
|
|
296
|
+
.setAlignItems('center') // start | end | center | stretch
|
|
297
|
+
.setAutoFlow('row') // row | column | row dense | column dense
|
|
298
|
+
.setPadding('20px')
|
|
299
|
+
.setBackgroundColor('#ffffff');
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### 3. Panel Container
|
|
304
|
+
|
|
305
|
+
**Purpose**: Card-like container with optional header, icon, and styling.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
builder.panel(panel => {
|
|
309
|
+
panel
|
|
310
|
+
.setCaption('User Information') // Header text
|
|
311
|
+
.setIcon('fa-user') // Header icon
|
|
312
|
+
.setLook('outline') // solid | fill | outline | flat | none
|
|
313
|
+
.setShowHeader(true) // Show/hide header
|
|
314
|
+
.setCollapsed(false) // Collapsed state
|
|
315
|
+
.formField('Name', field => {
|
|
316
|
+
field.textBox();
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 4. Page Container
|
|
322
|
+
|
|
323
|
+
**Purpose**: Full page layout with theme support.
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
builder.page(page => {
|
|
327
|
+
page
|
|
328
|
+
.setBackgroundColor('#f9f9f9')
|
|
329
|
+
.setTheme({ id: 'light-theme' })
|
|
330
|
+
.setHasHeader(true)
|
|
331
|
+
.setHasFooter(true)
|
|
332
|
+
.setDirection('ltr')
|
|
333
|
+
.flex(content => {
|
|
334
|
+
// Page content
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### 5. Tabset Container
|
|
340
|
+
|
|
341
|
+
**Purpose**: Tabbed interface for organizing content.
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
builder.tabset(tabset => {
|
|
345
|
+
tabset
|
|
346
|
+
.setLook('with-line') // with-line | with-line-color | pills | pills-color
|
|
347
|
+
.setOrientation('horizontal') // vertical | horizontal
|
|
348
|
+
.setActiveIndex(0) // Initial active tab
|
|
349
|
+
.panel(tab1 => {
|
|
350
|
+
tab1.setCaption('Tab 1');
|
|
351
|
+
// Tab 1 content
|
|
352
|
+
})
|
|
353
|
+
.panel(tab2 => {
|
|
354
|
+
tab2.setCaption('Tab 2');
|
|
355
|
+
// Tab 2 content
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### 6. Fieldset Container
|
|
361
|
+
|
|
362
|
+
**Purpose**: Form section with title, description, and multi-column support.
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
builder.fieldset(fieldset => {
|
|
366
|
+
fieldset
|
|
367
|
+
.setTitle('Personal Information')
|
|
368
|
+
.setDescription('Please provide your personal details')
|
|
369
|
+
.setIcon('fa-user')
|
|
370
|
+
.setCols(2) // Number of columns for fields
|
|
371
|
+
.setCollapsible(true)
|
|
372
|
+
.setIsOpen(true)
|
|
373
|
+
.setLook('fieldset') // fieldset | card | group
|
|
374
|
+
.setShowHeader(true)
|
|
375
|
+
.formField('First Name', field => {
|
|
376
|
+
field.textBox();
|
|
377
|
+
})
|
|
378
|
+
.formField('Last Name', field => {
|
|
379
|
+
field.textBox();
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Form Fields
|
|
387
|
+
|
|
388
|
+
Form fields wrap widgets and provide labels, descriptions, and path management.
|
|
389
|
+
|
|
390
|
+
### Basic Form Field
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
.formField('Field Label', field => {
|
|
394
|
+
field.path('dataPath');
|
|
395
|
+
field.textBox({ placeholder: 'Enter value' });
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Form Field Methods
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
.formField('Email', field => {
|
|
403
|
+
field.path('user.email'); // Data binding path
|
|
404
|
+
field.setLabel('Email Address'); // Custom label
|
|
405
|
+
field.setShowLabel(true); // Show/hide label
|
|
406
|
+
field.mode('edit'); // edit | view
|
|
407
|
+
field.visible(true); // boolean | expression string
|
|
408
|
+
field.readonly(false); // boolean | expression string
|
|
409
|
+
field.disabled(false); // boolean | expression string
|
|
410
|
+
field.textBox({
|
|
411
|
+
placeholder: 'email@example.com',
|
|
412
|
+
prefix: '📧'
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Auto-Generated Paths
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// Path from field name
|
|
421
|
+
.formField('First Name', field => {
|
|
422
|
+
field.textBox(); // path = 'first_name'
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Path from explicit name
|
|
426
|
+
.formField('Full Name', field => {
|
|
427
|
+
field.name('fullName');
|
|
428
|
+
field.textBox(); // path = 'fullName'
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Path from explicit path
|
|
432
|
+
.formField('Name', field => {
|
|
433
|
+
field.path('user.profile.fullName');
|
|
434
|
+
field.textBox(); // path = 'user.profile.fullName'
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Widget Types
|
|
441
|
+
|
|
442
|
+
### Text Input Widgets
|
|
443
|
+
|
|
444
|
+
#### 1. Text Box
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
field.textBox({
|
|
448
|
+
placeholder: 'Enter text',
|
|
449
|
+
maxLength: 100,
|
|
450
|
+
minLength: 3,
|
|
451
|
+
prefix: '🔍',
|
|
452
|
+
suffix: '.com',
|
|
453
|
+
clearButton: true
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### 2. Large Text Box
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
field.largeTextBox({
|
|
461
|
+
placeholder: 'Enter description',
|
|
462
|
+
rows: 4,
|
|
463
|
+
maxLength: 1000,
|
|
464
|
+
resizable: true
|
|
465
|
+
});
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
#### 3. Rich Text Editor
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
field.richText({
|
|
472
|
+
placeholder: 'Enter formatted text',
|
|
473
|
+
toolbar: ['bold', 'italic', 'underline', 'link']
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
#### 4. Password Box
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
field.passwordBox({
|
|
481
|
+
placeholder: 'Enter password',
|
|
482
|
+
revealToggle: true,
|
|
483
|
+
maxLength: 50
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Selection Widgets
|
|
488
|
+
|
|
489
|
+
#### 5. Select Box
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
field.selectBox({
|
|
493
|
+
dataSource: ['Option 1', 'Option 2', 'Option 3'],
|
|
494
|
+
placeholder: 'Select option',
|
|
495
|
+
multiple: false,
|
|
496
|
+
searchable: true,
|
|
497
|
+
clearButton: true
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// With objects
|
|
501
|
+
field.selectBox({
|
|
502
|
+
dataSource: [
|
|
503
|
+
{ id: 1, name: 'Option 1' },
|
|
504
|
+
{ id: 2, name: 'Option 2' }
|
|
505
|
+
],
|
|
506
|
+
valueField: 'id',
|
|
507
|
+
textField: 'name',
|
|
508
|
+
placeholder: 'Select option'
|
|
509
|
+
});
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
#### 6. Lookup Box
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
field.lookupBox({
|
|
516
|
+
entity: 'human-capital-management.employee',
|
|
517
|
+
multiple: true,
|
|
518
|
+
expose: [
|
|
519
|
+
{ source: 'id', target: 'employeeIds.{id}' },
|
|
520
|
+
{ source: 'title', target: 'employeeNames.{title}' }
|
|
521
|
+
],
|
|
522
|
+
searchable: true,
|
|
523
|
+
allowClear: true
|
|
524
|
+
});
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### 7. Selection List
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
field.selectionList({
|
|
531
|
+
dataSource: ['Option A', 'Option B', 'Option C'],
|
|
532
|
+
multiple: true,
|
|
533
|
+
searchable: false
|
|
534
|
+
});
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Numeric Widgets
|
|
538
|
+
|
|
539
|
+
#### 8. Number Box
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
field.numberBox({
|
|
543
|
+
min: 0,
|
|
544
|
+
max: 100,
|
|
545
|
+
step: 1,
|
|
546
|
+
format: '#,##0.00',
|
|
547
|
+
placeholder: 'Enter number'
|
|
548
|
+
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Date/Time Widgets
|
|
552
|
+
|
|
553
|
+
#### 9. Date Time Box
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
field.dateTimeBox({
|
|
557
|
+
type: 'date', // date | time | datetime
|
|
558
|
+
format: 'YYYY-MM-DD',
|
|
559
|
+
min: '2024-01-01',
|
|
560
|
+
max: '2024-12-31',
|
|
561
|
+
clearButton: true,
|
|
562
|
+
placeholder: 'Select date'
|
|
563
|
+
});
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Boolean Widgets
|
|
567
|
+
|
|
568
|
+
#### 10. Toggle Switch
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
field.toggleSwitch({
|
|
572
|
+
label: 'Enable notifications',
|
|
573
|
+
trueText: 'On',
|
|
574
|
+
falseText: 'Off'
|
|
575
|
+
});
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Color Widgets
|
|
579
|
+
|
|
580
|
+
#### 11. Color Box
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
field.colorBox({
|
|
584
|
+
format: 'hex', // hex | rgb | hsl
|
|
585
|
+
showAlpha: true,
|
|
586
|
+
showPalette: true
|
|
587
|
+
});
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Custom Widgets
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
field.customWidget('signature-pad', {
|
|
594
|
+
width: 400,
|
|
595
|
+
height: 200,
|
|
596
|
+
backgroundColor: '#ffffff'
|
|
597
|
+
});
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## Dialog Builder
|
|
603
|
+
|
|
604
|
+
The Layout Builder includes built-in dialog support with automatic form rendering and action management.
|
|
605
|
+
|
|
606
|
+
### Basic Dialog
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
async showDialog() {
|
|
610
|
+
const dialogRef = await this.layoutBuilder
|
|
611
|
+
.create()
|
|
612
|
+
.dialog(dialog => {
|
|
613
|
+
dialog
|
|
614
|
+
.setTitle('User Information')
|
|
615
|
+
.setSize('md') // sm | md | lg | xl
|
|
616
|
+
.setCloseButton(false)
|
|
617
|
+
.setContext({ firstName: '', lastName: '' })
|
|
618
|
+
.content(layoutBuilder => {
|
|
619
|
+
layoutBuilder.flex(flex => {
|
|
620
|
+
flex
|
|
621
|
+
.setDirection('column')
|
|
622
|
+
.setGap('16px')
|
|
623
|
+
.formField('First Name', field => {
|
|
624
|
+
field.path('firstName');
|
|
625
|
+
field.textBox({ placeholder: 'Enter first name' });
|
|
626
|
+
})
|
|
627
|
+
.formField('Last Name', field => {
|
|
628
|
+
field.path('lastName');
|
|
629
|
+
field.textBox({ placeholder: 'Enter last name' });
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
})
|
|
633
|
+
.setActions(actions => {
|
|
634
|
+
actions
|
|
635
|
+
.cancel('@general:actions.cancel.title')
|
|
636
|
+
.submit('@general:actions.submit.title');
|
|
637
|
+
});
|
|
638
|
+
})
|
|
639
|
+
.show();
|
|
640
|
+
|
|
641
|
+
// Wait for user interaction
|
|
642
|
+
const formData = dialogRef.context();
|
|
643
|
+
const action = dialogRef.action();
|
|
644
|
+
|
|
645
|
+
if (action === 'submit') {
|
|
646
|
+
console.log('Form data:', formData);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
dialogRef.close();
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### Dialog with Custom Actions
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
.setActions(actions => {
|
|
657
|
+
actions
|
|
658
|
+
.cancel('@general:actions.cancel.title')
|
|
659
|
+
.submit('@general:actions.save.title')
|
|
660
|
+
.custom({
|
|
661
|
+
title: 'Save Draft',
|
|
662
|
+
icon: 'fa-save',
|
|
663
|
+
color: 'secondary',
|
|
664
|
+
command: { name: 'save-draft' }
|
|
665
|
+
});
|
|
666
|
+
})
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### Dialog with Nested Containers
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
.content(layoutBuilder => {
|
|
673
|
+
layoutBuilder.flex(mainFlex => {
|
|
674
|
+
mainFlex
|
|
675
|
+
.setDirection('column')
|
|
676
|
+
.setGap('20px')
|
|
677
|
+
.fieldset(fieldset => {
|
|
678
|
+
fieldset
|
|
679
|
+
.setTitle('Personal Info')
|
|
680
|
+
.setCols(2)
|
|
681
|
+
.formField('First Name', field => {
|
|
682
|
+
field.textBox();
|
|
683
|
+
})
|
|
684
|
+
.formField('Last Name', field => {
|
|
685
|
+
field.textBox();
|
|
686
|
+
});
|
|
687
|
+
})
|
|
688
|
+
.panel(panel => {
|
|
689
|
+
panel
|
|
690
|
+
.setCaption('Additional Info')
|
|
691
|
+
.formField('Phone', field => {
|
|
692
|
+
field.textBox();
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
})
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Layout Inheritance
|
|
702
|
+
|
|
703
|
+
Properties are automatically inherited from parent containers to children.
|
|
704
|
+
|
|
705
|
+
### Inherited Properties
|
|
706
|
+
|
|
707
|
+
- **mode**: `edit` | `view`
|
|
708
|
+
- **readonly**: `boolean | expression string`
|
|
709
|
+
- **disabled**: `boolean | expression string`
|
|
710
|
+
- **visible**: `boolean | expression string`
|
|
711
|
+
- **direction**: `rtl` | `ltr`
|
|
712
|
+
|
|
713
|
+
### Example: Parent Sets Mode
|
|
714
|
+
|
|
715
|
+
```typescript
|
|
716
|
+
builder.flex(mainContainer => {
|
|
717
|
+
mainContainer.mode('view'); // All children inherit 'view' mode
|
|
718
|
+
|
|
719
|
+
mainContainer.formField('Name', field => {
|
|
720
|
+
field.textBox(); // mode = 'view'
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
mainContainer.formField('Email', field => {
|
|
724
|
+
field.mode('edit'); // Override: mode = 'edit'
|
|
725
|
+
field.textBox();
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Example: Readonly Inheritance
|
|
731
|
+
|
|
732
|
+
```typescript
|
|
733
|
+
builder.fieldset(fieldset => {
|
|
734
|
+
fieldset.readonly(true); // All fields readonly
|
|
735
|
+
|
|
736
|
+
fieldset.formField('First Name', field => {
|
|
737
|
+
field.textBox(); // readonly = true
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
fieldset.formField('Last Name', field => {
|
|
741
|
+
field.readonly(false); // Override: readonly = false
|
|
742
|
+
field.textBox();
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### Example: Conditional Visibility
|
|
748
|
+
|
|
749
|
+
```typescript
|
|
750
|
+
builder.flex(container => {
|
|
751
|
+
container.visible("context.eval('user.role') === 'admin'");
|
|
752
|
+
|
|
753
|
+
container.formField('Admin Settings', field => {
|
|
754
|
+
field.textBox(); // Only visible when user.role === 'admin'
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
---
|
|
760
|
+
|
|
761
|
+
## Complete Examples
|
|
762
|
+
|
|
763
|
+
### Example 1: Registration Form
|
|
764
|
+
|
|
765
|
+
```typescript
|
|
766
|
+
buildLayout() {
|
|
767
|
+
const builder = this.layoutBuilder.create();
|
|
768
|
+
|
|
769
|
+
builder.flex(mainContainer => {
|
|
770
|
+
mainContainer
|
|
771
|
+
.mode('edit')
|
|
772
|
+
.setDirection('column')
|
|
773
|
+
.setGap('20px');
|
|
774
|
+
|
|
775
|
+
// Personal Information
|
|
776
|
+
mainContainer.fieldset(personalFieldset => {
|
|
777
|
+
personalFieldset
|
|
778
|
+
.setTitle('Personal Information')
|
|
779
|
+
.setDescription('Please provide your basic personal details')
|
|
780
|
+
.setIcon('fa-light fa-user')
|
|
781
|
+
.setCols(2)
|
|
782
|
+
.formField('First Name', field => {
|
|
783
|
+
field.path('firstName');
|
|
784
|
+
field.textBox({
|
|
785
|
+
placeholder: 'Enter your first name',
|
|
786
|
+
validations: [{ rule: 'required', message: 'First name is required' }]
|
|
787
|
+
});
|
|
788
|
+
})
|
|
789
|
+
.formField('Last Name', field => {
|
|
790
|
+
field.path('lastName');
|
|
791
|
+
field.textBox({ placeholder: 'Enter your last name' });
|
|
792
|
+
})
|
|
793
|
+
.formField('Email Address', field => {
|
|
794
|
+
field.path('email');
|
|
795
|
+
field.textBox({
|
|
796
|
+
placeholder: 'Enter your email address',
|
|
797
|
+
prefix: '📧'
|
|
798
|
+
});
|
|
799
|
+
})
|
|
800
|
+
.formField('Phone Number', field => {
|
|
801
|
+
field.path('phone');
|
|
802
|
+
field.textBox({
|
|
803
|
+
placeholder: 'Enter your phone number',
|
|
804
|
+
prefix: '📱'
|
|
805
|
+
});
|
|
806
|
+
})
|
|
807
|
+
.formField('Date of Birth', field => {
|
|
808
|
+
field.path('birthDate');
|
|
809
|
+
field.dateTimeBox({
|
|
810
|
+
type: 'date',
|
|
811
|
+
placeholder: 'Select your birth date'
|
|
812
|
+
});
|
|
813
|
+
})
|
|
814
|
+
.formField('Gender', field => {
|
|
815
|
+
field.path('gender');
|
|
816
|
+
field.selectBox({
|
|
817
|
+
placeholder: 'Select your gender',
|
|
818
|
+
dataSource: ['Male', 'Female', 'Other', 'Prefer not to say']
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// Address Information
|
|
824
|
+
mainContainer.fieldset(addressFieldset => {
|
|
825
|
+
addressFieldset
|
|
826
|
+
.setTitle('Address Information')
|
|
827
|
+
.setDescription('Your current residential address')
|
|
828
|
+
.setIcon('fa-light fa-home')
|
|
829
|
+
.setCols(2)
|
|
830
|
+
.formField('Street Address', field => {
|
|
831
|
+
field.path('address.street');
|
|
832
|
+
field.textBox({ placeholder: 'Enter your street address' });
|
|
833
|
+
})
|
|
834
|
+
.formField('City', field => {
|
|
835
|
+
field.path('address.city');
|
|
836
|
+
field.textBox({ placeholder: 'Enter your city' });
|
|
837
|
+
})
|
|
838
|
+
.formField('State/Province', field => {
|
|
839
|
+
field.path('address.state');
|
|
840
|
+
field.selectBox({
|
|
841
|
+
placeholder: 'Select your state/province',
|
|
842
|
+
dataSource: ['California', 'New York', 'Texas', 'Florida']
|
|
843
|
+
});
|
|
844
|
+
})
|
|
845
|
+
.formField('ZIP/Postal Code', field => {
|
|
846
|
+
field.path('address.zip');
|
|
847
|
+
field.textBox({ placeholder: 'Enter ZIP/postal code' });
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
// Account Security
|
|
852
|
+
mainContainer.fieldset(securityFieldset => {
|
|
853
|
+
securityFieldset
|
|
854
|
+
.setTitle('Account Security')
|
|
855
|
+
.setDescription('Set up your account credentials')
|
|
856
|
+
.setIcon('fa-light fa-lock')
|
|
857
|
+
.setCols(2)
|
|
858
|
+
.formField('Password', field => {
|
|
859
|
+
field.path('password');
|
|
860
|
+
field.passwordBox({
|
|
861
|
+
placeholder: 'Enter password (min 8 characters)',
|
|
862
|
+
revealToggle: true
|
|
863
|
+
});
|
|
864
|
+
})
|
|
865
|
+
.formField('Confirm Password', field => {
|
|
866
|
+
field.path('confirmPassword');
|
|
867
|
+
field.passwordBox({
|
|
868
|
+
placeholder: 'Confirm your password',
|
|
869
|
+
revealToggle: true
|
|
870
|
+
});
|
|
871
|
+
})
|
|
872
|
+
.formField('Two-Factor Authentication', field => {
|
|
873
|
+
field.path('twoFactorEnabled');
|
|
874
|
+
field.toggleSwitch({
|
|
875
|
+
label: 'Enable Two-Factor Authentication'
|
|
876
|
+
});
|
|
877
|
+
});
|
|
878
|
+
});
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
return builder.build();
|
|
882
|
+
}
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
### Example 2: Dashboard with Tabs
|
|
886
|
+
|
|
887
|
+
```typescript
|
|
888
|
+
buildDashboard() {
|
|
889
|
+
const builder = this.layoutBuilder.create();
|
|
890
|
+
|
|
891
|
+
builder.page(page => {
|
|
892
|
+
page
|
|
893
|
+
.setBackgroundColor('#f5f5f5')
|
|
894
|
+
.setHasHeader(true)
|
|
895
|
+
.tabset(tabset => {
|
|
896
|
+
tabset
|
|
897
|
+
.setLook('pills')
|
|
898
|
+
.setOrientation('horizontal')
|
|
899
|
+
|
|
900
|
+
// Overview Tab
|
|
901
|
+
.panel(overviewTab => {
|
|
902
|
+
overviewTab
|
|
903
|
+
.setCaption('Overview')
|
|
904
|
+
.setIcon('fa-chart-line')
|
|
905
|
+
.flex(content => {
|
|
906
|
+
content
|
|
907
|
+
.setDirection('row')
|
|
908
|
+
.setGap('20px')
|
|
909
|
+
.panel(statsPanel => {
|
|
910
|
+
statsPanel.setCaption('Statistics');
|
|
911
|
+
// Add stats widgets
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
})
|
|
915
|
+
|
|
916
|
+
// Settings Tab
|
|
917
|
+
.panel(settingsTab => {
|
|
918
|
+
settingsTab
|
|
919
|
+
.setCaption('Settings')
|
|
920
|
+
.setIcon('fa-cog')
|
|
921
|
+
.fieldset(fieldset => {
|
|
922
|
+
fieldset
|
|
923
|
+
.setTitle('User Preferences')
|
|
924
|
+
.setCols(2)
|
|
925
|
+
.formField('Language', field => {
|
|
926
|
+
field.selectBox({
|
|
927
|
+
dataSource: ['English', 'Spanish', 'French']
|
|
928
|
+
});
|
|
929
|
+
})
|
|
930
|
+
.formField('Theme', field => {
|
|
931
|
+
field.selectBox({
|
|
932
|
+
dataSource: ['Light', 'Dark', 'Auto']
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
});
|
|
936
|
+
});
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
return builder.build();
|
|
941
|
+
}
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
### Example 3: Dialog with Nested Panels
|
|
945
|
+
|
|
946
|
+
```typescript
|
|
947
|
+
async showComplexDialog() {
|
|
948
|
+
const dialogRef = await this.layoutBuilder
|
|
949
|
+
.create()
|
|
950
|
+
.dialog(dialog => {
|
|
951
|
+
dialog
|
|
952
|
+
.setTitle('Project Configuration')
|
|
953
|
+
.setSize('xl')
|
|
954
|
+
.setContext({
|
|
955
|
+
projectName: '',
|
|
956
|
+
category: '',
|
|
957
|
+
members: [],
|
|
958
|
+
startDate: null
|
|
959
|
+
})
|
|
960
|
+
.content(layoutBuilder => {
|
|
961
|
+
layoutBuilder.flex(mainFlex => {
|
|
962
|
+
mainFlex
|
|
963
|
+
.setDirection('column')
|
|
964
|
+
.setGap('20px')
|
|
965
|
+
|
|
966
|
+
// Project Details Panel
|
|
967
|
+
.panel(detailsPanel => {
|
|
968
|
+
detailsPanel
|
|
969
|
+
.setCaption('Project Details')
|
|
970
|
+
.setIcon('fa-folder')
|
|
971
|
+
.fieldset(fieldset => {
|
|
972
|
+
fieldset
|
|
973
|
+
.setCols(2)
|
|
974
|
+
.formField('Project Name', field => {
|
|
975
|
+
field.path('projectName');
|
|
976
|
+
field.textBox({
|
|
977
|
+
placeholder: 'Enter project name',
|
|
978
|
+
validations: [{ rule: 'required' }]
|
|
979
|
+
});
|
|
980
|
+
})
|
|
981
|
+
.formField('Category', field => {
|
|
982
|
+
field.path('category');
|
|
983
|
+
field.lookupBox({
|
|
984
|
+
entity: 'project-management.category',
|
|
985
|
+
multiple: false
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
});
|
|
989
|
+
})
|
|
990
|
+
|
|
991
|
+
// Team Panel
|
|
992
|
+
.panel(teamPanel => {
|
|
993
|
+
teamPanel
|
|
994
|
+
.setCaption('Team Members')
|
|
995
|
+
.setIcon('fa-users')
|
|
996
|
+
.formField('Members', field => {
|
|
997
|
+
field.path('members');
|
|
998
|
+
field.lookupBox({
|
|
999
|
+
entity: 'human-capital-management.employee',
|
|
1000
|
+
multiple: true,
|
|
1001
|
+
expose: [
|
|
1002
|
+
{ source: 'id', target: 'memberIds.{id}' },
|
|
1003
|
+
{ source: 'title', target: 'memberNames.{title}' }
|
|
1004
|
+
]
|
|
1005
|
+
});
|
|
1006
|
+
});
|
|
1007
|
+
})
|
|
1008
|
+
|
|
1009
|
+
// Timeline Panel
|
|
1010
|
+
.panel(timelinePanel => {
|
|
1011
|
+
timelinePanel
|
|
1012
|
+
.setCaption('Timeline')
|
|
1013
|
+
.setIcon('fa-calendar')
|
|
1014
|
+
.fieldset(fieldset => {
|
|
1015
|
+
fieldset
|
|
1016
|
+
.setCols(2)
|
|
1017
|
+
.formField('Start Date', field => {
|
|
1018
|
+
field.path('startDate');
|
|
1019
|
+
field.dateTimeBox({
|
|
1020
|
+
type: 'date',
|
|
1021
|
+
format: 'YYYY-MM-DD'
|
|
1022
|
+
});
|
|
1023
|
+
})
|
|
1024
|
+
.formField('End Date', field => {
|
|
1025
|
+
field.path('endDate');
|
|
1026
|
+
field.dateTimeBox({
|
|
1027
|
+
type: 'date',
|
|
1028
|
+
format: 'YYYY-MM-DD'
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
});
|
|
1034
|
+
})
|
|
1035
|
+
.setActions(actions => {
|
|
1036
|
+
actions
|
|
1037
|
+
.cancel('@general:actions.cancel.title')
|
|
1038
|
+
.submit('@general:actions.create.title');
|
|
1039
|
+
});
|
|
1040
|
+
})
|
|
1041
|
+
.show();
|
|
1042
|
+
|
|
1043
|
+
const formData = dialogRef.context();
|
|
1044
|
+
const action = dialogRef.action();
|
|
1045
|
+
|
|
1046
|
+
if (action === 'submit') {
|
|
1047
|
+
console.log('Project data:', formData);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
dialogRef.close();
|
|
1051
|
+
}
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
---
|
|
1055
|
+
|
|
1056
|
+
## Migration Guide
|
|
1057
|
+
|
|
1058
|
+
### From Dynamic Form Builder to Layout Builder
|
|
1059
|
+
|
|
1060
|
+
#### Before (Dynamic Form Builder)
|
|
1061
|
+
|
|
1062
|
+
```typescript
|
|
1063
|
+
import { AXPDynamicFormBuilderService } from '@acorex/platform/layout/components';
|
|
1064
|
+
|
|
1065
|
+
async showDialog() {
|
|
1066
|
+
const dialog = this.formBuilder.dialog();
|
|
1067
|
+
const dialogRef = await dialog
|
|
1068
|
+
.title('@user:edit.title')
|
|
1069
|
+
.size('lg')
|
|
1070
|
+
.group('basic-info', group => {
|
|
1071
|
+
group
|
|
1072
|
+
.field('firstName', field => {
|
|
1073
|
+
field.title('@user:firstName');
|
|
1074
|
+
field.textBox({ required: true, maxLength: 50 });
|
|
1075
|
+
})
|
|
1076
|
+
.field('lastName', field => {
|
|
1077
|
+
field.title('@user:lastName');
|
|
1078
|
+
field.textBox({ required: true, maxLength: 50 });
|
|
1079
|
+
});
|
|
1080
|
+
})
|
|
1081
|
+
.context(user)
|
|
1082
|
+
.actions(actions => {
|
|
1083
|
+
actions
|
|
1084
|
+
.cancel('@general:actions.cancel.title')
|
|
1085
|
+
.submit('@general:actions.save.title');
|
|
1086
|
+
})
|
|
1087
|
+
.show();
|
|
1088
|
+
|
|
1089
|
+
const formData = dialogRef.context();
|
|
1090
|
+
const action = dialogRef.action();
|
|
1091
|
+
}
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
#### After (Layout Builder)
|
|
1095
|
+
|
|
1096
|
+
```typescript
|
|
1097
|
+
import { AXPLayoutBuilderService } from '@acorex/platform/layout/builder';
|
|
1098
|
+
|
|
1099
|
+
async showDialog() {
|
|
1100
|
+
const dialogRef = await this.layoutBuilder
|
|
1101
|
+
.create()
|
|
1102
|
+
.dialog(dialog => {
|
|
1103
|
+
dialog
|
|
1104
|
+
.setTitle('@user:edit.title')
|
|
1105
|
+
.setSize('lg')
|
|
1106
|
+
.setContext(user)
|
|
1107
|
+
.content(layoutBuilder => {
|
|
1108
|
+
layoutBuilder.flex(flex => {
|
|
1109
|
+
flex
|
|
1110
|
+
.setDirection('column')
|
|
1111
|
+
.setGap('16px')
|
|
1112
|
+
.formField('@user:firstName', field => {
|
|
1113
|
+
field.path('firstName');
|
|
1114
|
+
field.textBox({
|
|
1115
|
+
placeholder: 'Enter first name',
|
|
1116
|
+
validations: [{ rule: 'required' }, { rule: 'maxLength', options: { value: 50 } }]
|
|
1117
|
+
});
|
|
1118
|
+
})
|
|
1119
|
+
.formField('@user:lastName', field => {
|
|
1120
|
+
field.path('lastName');
|
|
1121
|
+
field.textBox({
|
|
1122
|
+
placeholder: 'Enter last name',
|
|
1123
|
+
validations: [{ rule: 'required' }, { rule: 'maxLength', options: { value: 50 } }]
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
})
|
|
1128
|
+
.setActions(actions => {
|
|
1129
|
+
actions
|
|
1130
|
+
.cancel('@general:actions.cancel.title')
|
|
1131
|
+
.submit('@general:actions.save.title');
|
|
1132
|
+
});
|
|
1133
|
+
})
|
|
1134
|
+
.show();
|
|
1135
|
+
|
|
1136
|
+
const formData = dialogRef.context();
|
|
1137
|
+
const action = dialogRef.action();
|
|
1138
|
+
}
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
### Key Migration Changes
|
|
1142
|
+
|
|
1143
|
+
| Dynamic Form Builder | Layout Builder |
|
|
1144
|
+
|-----------------------------------|----------------|
|
|
1145
|
+
| `.group(name, delegate)` | `.flex(delegate)` or `.fieldset(delegate)` |
|
|
1146
|
+
| `.field(path, delegate)` | `.formField(label, delegate)` + `field.path(path)` |
|
|
1147
|
+
| `.title(text)` | `.setTitle(text)` or `.setCaption(text)` |
|
|
1148
|
+
| `.mode(mode)` | `.mode(mode)` (same, but inherited) |
|
|
1149
|
+
| Direct widget methods | Same widget methods |
|
|
1150
|
+
| `.actions(delegate)` | `.setActions(delegate)` |
|
|
1151
|
+
| `.show()` | `.show()` (same) |
|
|
1152
|
+
|
|
1153
|
+
### Component Migration Example
|
|
1154
|
+
|
|
1155
|
+
**Before:**
|
|
1156
|
+
|
|
1157
|
+
```typescript
|
|
1158
|
+
export class SignatureComponent {
|
|
1159
|
+
private readonly formBuilder = inject(AXPDynamicFormBuilderService);
|
|
1160
|
+
|
|
1161
|
+
async showPopup() {
|
|
1162
|
+
const ref = await this.formBuilder
|
|
1163
|
+
.dialog()
|
|
1164
|
+
.title('Signature')
|
|
1165
|
+
.size('lg')
|
|
1166
|
+
.group('signature', group => {
|
|
1167
|
+
group
|
|
1168
|
+
.mode('view')
|
|
1169
|
+
.field('preview', field => {
|
|
1170
|
+
field
|
|
1171
|
+
.options({ readonly: true })
|
|
1172
|
+
.widget('image', { src: url, width: '100%' });
|
|
1173
|
+
});
|
|
1174
|
+
})
|
|
1175
|
+
.actions(a => a.cancel('@general:actions.cancel.title'))
|
|
1176
|
+
.show();
|
|
1177
|
+
ref.close();
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
**After:**
|
|
1183
|
+
|
|
1184
|
+
```typescript
|
|
1185
|
+
export class SignatureComponent {
|
|
1186
|
+
private readonly layoutBuilder = inject(AXPLayoutBuilderService);
|
|
1187
|
+
|
|
1188
|
+
async showPopup() {
|
|
1189
|
+
const ref = await this.layoutBuilder
|
|
1190
|
+
.create()
|
|
1191
|
+
.dialog(dialog => {
|
|
1192
|
+
dialog
|
|
1193
|
+
.setTitle('Signature')
|
|
1194
|
+
.setSize('lg')
|
|
1195
|
+
.content(layoutBuilder => {
|
|
1196
|
+
layoutBuilder.flex(flex => {
|
|
1197
|
+
flex
|
|
1198
|
+
.mode('view')
|
|
1199
|
+
.formField('Preview', field => {
|
|
1200
|
+
field
|
|
1201
|
+
.readonly(true)
|
|
1202
|
+
.customWidget('image', {
|
|
1203
|
+
src: url,
|
|
1204
|
+
width: '100%',
|
|
1205
|
+
height: 'auto',
|
|
1206
|
+
objectFit: 'contain'
|
|
1207
|
+
});
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1210
|
+
})
|
|
1211
|
+
.setActions(actions => {
|
|
1212
|
+
actions.cancel('@general:actions.cancel.title');
|
|
1213
|
+
});
|
|
1214
|
+
})
|
|
1215
|
+
.show();
|
|
1216
|
+
ref.close();
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
---
|
|
1222
|
+
|
|
1223
|
+
## Best Practices
|
|
1224
|
+
|
|
1225
|
+
### 1. Use Descriptive Names
|
|
1226
|
+
|
|
1227
|
+
```typescript
|
|
1228
|
+
// ✅ Good
|
|
1229
|
+
.formField('User Email Address', field => {
|
|
1230
|
+
field.path('user.email');
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
// ❌ Bad
|
|
1234
|
+
.formField('Email', field => {
|
|
1235
|
+
field.path('e');
|
|
1236
|
+
});
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
### 2. Group Related Fields
|
|
1240
|
+
|
|
1241
|
+
```typescript
|
|
1242
|
+
// ✅ Good
|
|
1243
|
+
.fieldset(fieldset => {
|
|
1244
|
+
fieldset
|
|
1245
|
+
.setTitle('Contact Information')
|
|
1246
|
+
.setCols(2)
|
|
1247
|
+
.formField('Email', field => { ... })
|
|
1248
|
+
.formField('Phone', field => { ... });
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
// ❌ Bad - scattered fields
|
|
1252
|
+
.formField('Email', field => { ... })
|
|
1253
|
+
.formField('Name', field => { ... })
|
|
1254
|
+
.formField('Phone', field => { ... })
|
|
1255
|
+
.formField('Address', field => { ... });
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
### 3. Use Property Inheritance
|
|
1259
|
+
|
|
1260
|
+
```typescript
|
|
1261
|
+
// ✅ Good
|
|
1262
|
+
.fieldset(fieldset => {
|
|
1263
|
+
fieldset.mode('view'); // All fields inherit view mode
|
|
1264
|
+
fieldset.formField('Name', field => field.textBox());
|
|
1265
|
+
fieldset.formField('Email', field => field.textBox());
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
// ❌ Bad - repetitive
|
|
1269
|
+
.fieldset(fieldset => {
|
|
1270
|
+
fieldset.formField('Name', field => {
|
|
1271
|
+
field.mode('view');
|
|
1272
|
+
field.textBox();
|
|
1273
|
+
});
|
|
1274
|
+
fieldset.formField('Email', field => {
|
|
1275
|
+
field.mode('view');
|
|
1276
|
+
field.textBox();
|
|
1277
|
+
});
|
|
1278
|
+
});
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
### 4. Provide Clear Placeholders
|
|
1282
|
+
|
|
1283
|
+
```typescript
|
|
1284
|
+
// ✅ Good
|
|
1285
|
+
.formField('Email', field => {
|
|
1286
|
+
field.textBox({
|
|
1287
|
+
placeholder: 'Enter your email address (e.g., user@example.com)'
|
|
1288
|
+
});
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
// ❌ Bad
|
|
1292
|
+
.formField('Email', field => {
|
|
1293
|
+
field.textBox();
|
|
1294
|
+
});
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
### 5. Use Appropriate Container Types
|
|
1298
|
+
|
|
1299
|
+
```typescript
|
|
1300
|
+
// ✅ Good - Use flex for simple vertical/horizontal layouts
|
|
1301
|
+
.flex(container => {
|
|
1302
|
+
container.setDirection('column');
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
// ✅ Good - Use grid for complex 2D layouts
|
|
1306
|
+
.grid(container => {
|
|
1307
|
+
container.setColumns(3).setRows(2);
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
// ❌ Bad - Using grid for simple vertical layout
|
|
1311
|
+
.grid(container => {
|
|
1312
|
+
container.setColumns(1); // Just use flex!
|
|
1313
|
+
});
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
### 6. Set Validation Rules
|
|
1317
|
+
|
|
1318
|
+
```typescript
|
|
1319
|
+
// ✅ Good
|
|
1320
|
+
.formField('Email', field => {
|
|
1321
|
+
field.textBox({
|
|
1322
|
+
validations: [
|
|
1323
|
+
{ rule: 'required', message: 'Email is required' },
|
|
1324
|
+
{ rule: 'email', message: 'Invalid email format' }
|
|
1325
|
+
]
|
|
1326
|
+
});
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
// ❌ Bad - No validation
|
|
1330
|
+
.formField('Email', field => {
|
|
1331
|
+
field.textBox();
|
|
1332
|
+
});
|
|
1333
|
+
```
|
|
1334
|
+
|
|
1335
|
+
### 7. Use Translation Keys
|
|
1336
|
+
|
|
1337
|
+
```typescript
|
|
1338
|
+
// ✅ Good
|
|
1339
|
+
.setTitle('@module:section.title')
|
|
1340
|
+
.formField('@module:field.label', field => { ... })
|
|
1341
|
+
|
|
1342
|
+
// ❌ Bad - Hardcoded text
|
|
1343
|
+
.setTitle('User Information')
|
|
1344
|
+
.formField('Name', field => { ... })
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
### 8. Proper Dialog Context Management
|
|
1348
|
+
|
|
1349
|
+
```typescript
|
|
1350
|
+
// ✅ Good
|
|
1351
|
+
async showDialog(user: User) {
|
|
1352
|
+
const dialogRef = await this.layoutBuilder
|
|
1353
|
+
.create()
|
|
1354
|
+
.dialog(dialog => {
|
|
1355
|
+
dialog.setContext({ ...user }); // Clone the object
|
|
1356
|
+
// ... rest of dialog setup
|
|
1357
|
+
})
|
|
1358
|
+
.show();
|
|
1359
|
+
|
|
1360
|
+
const formData = dialogRef.context();
|
|
1361
|
+
const action = dialogRef.action();
|
|
1362
|
+
|
|
1363
|
+
if (action === 'submit') {
|
|
1364
|
+
await this.saveUser(formData);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
dialogRef.close();
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// ❌ Bad - Mutating original object
|
|
1371
|
+
async showDialog(user: User) {
|
|
1372
|
+
const dialogRef = await this.layoutBuilder
|
|
1373
|
+
.create()
|
|
1374
|
+
.dialog(dialog => {
|
|
1375
|
+
dialog.setContext(user); // Direct reference!
|
|
1376
|
+
})
|
|
1377
|
+
.show();
|
|
1378
|
+
}
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
### 9. Organize Complex Forms with Fieldsets
|
|
1382
|
+
|
|
1383
|
+
```typescript
|
|
1384
|
+
// ✅ Good
|
|
1385
|
+
.flex(mainContainer => {
|
|
1386
|
+
mainContainer
|
|
1387
|
+
.fieldset(personalInfo => {
|
|
1388
|
+
personalInfo.setTitle('Personal Information');
|
|
1389
|
+
// Personal fields
|
|
1390
|
+
})
|
|
1391
|
+
.fieldset(contactInfo => {
|
|
1392
|
+
contactInfo.setTitle('Contact Information');
|
|
1393
|
+
// Contact fields
|
|
1394
|
+
})
|
|
1395
|
+
.fieldset(preferences => {
|
|
1396
|
+
preferences.setTitle('Preferences');
|
|
1397
|
+
// Preference fields
|
|
1398
|
+
});
|
|
1399
|
+
});
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
### 10. Handle Dialog Actions Properly
|
|
1403
|
+
|
|
1404
|
+
```typescript
|
|
1405
|
+
// ✅ Good
|
|
1406
|
+
const dialogRef = await dialog.show();
|
|
1407
|
+
const formData = dialogRef.context();
|
|
1408
|
+
const action = dialogRef.action();
|
|
1409
|
+
|
|
1410
|
+
if (action === 'cancel') {
|
|
1411
|
+
return { cancelled: true };
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
if (action === 'submit') {
|
|
1415
|
+
const isValid = await this.validate(formData);
|
|
1416
|
+
if (isValid) {
|
|
1417
|
+
await this.save(formData);
|
|
1418
|
+
return { cancelled: false, data: formData };
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
dialogRef.close();
|
|
1423
|
+
|
|
1424
|
+
// ❌ Bad - No action handling
|
|
1425
|
+
const dialogRef = await dialog.show();
|
|
1426
|
+
await this.save(dialogRef.context());
|
|
1427
|
+
dialogRef.close();
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1430
|
+
---
|
|
1431
|
+
|
|
1432
|
+
## API Reference
|
|
1433
|
+
|
|
1434
|
+
### AXPLayoutBuilderService
|
|
1435
|
+
|
|
1436
|
+
```typescript
|
|
1437
|
+
class AXPLayoutBuilderService {
|
|
1438
|
+
create(): ILayoutBuilder;
|
|
1439
|
+
}
|
|
1440
|
+
```
|
|
1441
|
+
|
|
1442
|
+
### ILayoutBuilder
|
|
1443
|
+
|
|
1444
|
+
```typescript
|
|
1445
|
+
interface ILayoutBuilder {
|
|
1446
|
+
flex(delegate: (container: IFlexContainerBuilder) => void): ILayoutBuilder;
|
|
1447
|
+
grid(delegate: (container: IGridContainerBuilder) => void): ILayoutBuilder;
|
|
1448
|
+
panel(delegate: (container: IPanelContainerBuilder) => void): ILayoutBuilder;
|
|
1449
|
+
page(delegate: (container: IPageContainerBuilder) => void): ILayoutBuilder;
|
|
1450
|
+
tabset(delegate: (container: ITabsetContainerBuilder) => void): ILayoutBuilder;
|
|
1451
|
+
fieldset(delegate: (container: IFieldsetContainerBuilder) => void): ILayoutBuilder;
|
|
1452
|
+
dialog(delegate: (container: IDialogBuilder) => void): IDialogBuilder;
|
|
1453
|
+
formField(label: string, delegate?: (field: IFormFieldBuilder) => void): ILayoutBuilder;
|
|
1454
|
+
build(): AXPWidgetNode;
|
|
1455
|
+
}
|
|
1456
|
+
```
|
|
1457
|
+
|
|
1458
|
+
### Container Builders
|
|
1459
|
+
|
|
1460
|
+
All container builders extend the base interface:
|
|
1461
|
+
|
|
1462
|
+
```typescript
|
|
1463
|
+
interface IBaseContainerBuilder<TContainer> {
|
|
1464
|
+
name(name: string): TContainer;
|
|
1465
|
+
path(path: string): TContainer;
|
|
1466
|
+
mode(mode: 'edit' | 'view'): TContainer;
|
|
1467
|
+
visible(condition: boolean | string): TContainer;
|
|
1468
|
+
disabled(condition: boolean | string): TContainer;
|
|
1469
|
+
readonly(condition: boolean | string): TContainer;
|
|
1470
|
+
direction(direction: 'rtl' | 'ltr'): TContainer;
|
|
1471
|
+
build(): AXPWidgetNode;
|
|
1472
|
+
}
|
|
1473
|
+
```
|
|
1474
|
+
|
|
1475
|
+
### Form Field Builder
|
|
1476
|
+
|
|
1477
|
+
```typescript
|
|
1478
|
+
interface IFormFieldBuilder extends IBaseContainerBuilder<IFormFieldBuilder> {
|
|
1479
|
+
setLabel(label: string): IFormFieldBuilder;
|
|
1480
|
+
setShowLabel(showLabel: boolean): IFormFieldBuilder;
|
|
1481
|
+
|
|
1482
|
+
// Widget methods
|
|
1483
|
+
textBox(options?: TextBoxOptions): IFormFieldBuilder;
|
|
1484
|
+
largeTextBox(options?: LargeTextBoxOptions): IFormFieldBuilder;
|
|
1485
|
+
richText(options?: RichTextOptions): IFormFieldBuilder;
|
|
1486
|
+
passwordBox(options?: PasswordBoxOptions): IFormFieldBuilder;
|
|
1487
|
+
numberBox(options?: NumberBoxOptions): IFormFieldBuilder;
|
|
1488
|
+
selectBox(options: SelectBoxOptions): IFormFieldBuilder;
|
|
1489
|
+
lookupBox(options: LookupBoxOptions): IFormFieldBuilder;
|
|
1490
|
+
selectionList(options: SelectionListOptions): IFormFieldBuilder;
|
|
1491
|
+
dateTimeBox(options?: DateTimeBoxOptions): IFormFieldBuilder;
|
|
1492
|
+
toggleSwitch(options?: ToggleSwitchOptions): IFormFieldBuilder;
|
|
1493
|
+
colorBox(options?: ColorBoxOptions): IFormFieldBuilder;
|
|
1494
|
+
customWidget<T>(type: string, options?: T): IFormFieldBuilder;
|
|
1495
|
+
}
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
### Dialog Builder
|
|
1499
|
+
|
|
1500
|
+
```typescript
|
|
1501
|
+
interface IDialogBuilder {
|
|
1502
|
+
setTitle(title: string): IDialogBuilder;
|
|
1503
|
+
setMessage(message?: string): IDialogBuilder;
|
|
1504
|
+
setSize(size: 'sm' | 'md' | 'lg' | 'xl'): IDialogBuilder;
|
|
1505
|
+
setCloseButton(closeButton: boolean): IDialogBuilder;
|
|
1506
|
+
setContext(context: any): IDialogBuilder;
|
|
1507
|
+
content(delegate: (layoutBuilder: IFlexContainerBuilder) => void): IDialogBuilder;
|
|
1508
|
+
setActions(delegate?: (actions: IActionBuilder) => void): IDialogBuilder;
|
|
1509
|
+
show(): Promise<AXPDialogRef>;
|
|
1510
|
+
}
|
|
1511
|
+
```
|
|
1512
|
+
|
|
1513
|
+
### Action Builder
|
|
1514
|
+
|
|
1515
|
+
```typescript
|
|
1516
|
+
interface IActionBuilder {
|
|
1517
|
+
cancel(text?: string): IActionBuilder;
|
|
1518
|
+
submit(text?: string): IActionBuilder;
|
|
1519
|
+
custom(action: AXPActionMenuItem): IActionBuilder;
|
|
1520
|
+
}
|
|
1521
|
+
```
|
|
1522
|
+
|
|
1523
|
+
### Dialog Reference
|
|
1524
|
+
|
|
1525
|
+
```typescript
|
|
1526
|
+
interface AXPDialogRef {
|
|
1527
|
+
context(): any;
|
|
1528
|
+
action(): string;
|
|
1529
|
+
setLoading(loading: boolean): void;
|
|
1530
|
+
close(): void;
|
|
1531
|
+
}
|
|
1532
|
+
```
|
|
1533
|
+
|
|
1534
|
+
### Layout Renderer Component
|
|
1535
|
+
|
|
1536
|
+
```typescript
|
|
1537
|
+
@Component({
|
|
1538
|
+
selector: 'axp-layout-renderer'
|
|
1539
|
+
})
|
|
1540
|
+
class AXPLayoutRendererComponent {
|
|
1541
|
+
@Input() layout: AXPWidgetNode | AXPDynamicFormDefinition;
|
|
1542
|
+
@Input() context: any;
|
|
1543
|
+
@Input() look: 'fieldset' | 'card' | 'group';
|
|
1544
|
+
@Input() mode: 'edit' | 'view';
|
|
1545
|
+
|
|
1546
|
+
@Output() contextInitiated: EventEmitter<any>;
|
|
1547
|
+
@Output() validityChange: EventEmitter<boolean>;
|
|
1548
|
+
|
|
1549
|
+
getContext(): any;
|
|
1550
|
+
updateContext(context: any): void;
|
|
1551
|
+
getWidgetTree(): AXPWidgetNode | null;
|
|
1552
|
+
validate(): Promise<AXValidationSummary>;
|
|
1553
|
+
clear(): void;
|
|
1554
|
+
reset(): void;
|
|
1555
|
+
}
|
|
1556
|
+
```
|
|
1557
|
+
|
|
1558
|
+
---
|
|
1559
|
+
|
|
1560
|
+
## Summary
|
|
1561
|
+
|
|
1562
|
+
The **ACoreX Layout Builder** is a comprehensive solution for building dynamic, type-safe layouts in the ACoreX Platform. It provides:
|
|
1563
|
+
|
|
1564
|
+
✅ **Fluent API** with delegate pattern for readable code
|
|
1565
|
+
✅ **Multiple container types** for flexible layouts
|
|
1566
|
+
✅ **Automatic property inheritance** for consistent behavior
|
|
1567
|
+
✅ **Built-in dialog support** with action management
|
|
1568
|
+
✅ **15+ widgets** with custom widget support
|
|
1569
|
+
✅ **Type safety** with full TypeScript support
|
|
1570
|
+
✅ **Automatic path generation** for form fields
|
|
1571
|
+
|
|
1572
|
+
For more examples and advanced usage, refer to the [examples directory](./examples/) or contact the platform team.
|
|
1573
|
+
|
|
1574
|
+
---
|
|
1575
|
+
|
|
1576
|
+
**Last Updated**: October 2025
|
|
1577
|
+
**Version**: 1.0.0
|
|
1578
|
+
**Maintainer**: ACoreX Platform Team
|