@ticatec/uniface-flexi-module 0.0.2
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/FLEXICRITERIASET_GUIDE.md +1559 -0
- package/FLEXICRITERIASET_GUIDE_CN.md +1133 -0
- package/FLEXIDATATABLE_GUIDE.md +1650 -0
- package/FLEXIDATATABLE_GUIDE_CN.md +1650 -0
- package/FLEXIFORM_GUIDE.md +1068 -0
- package/FLEXIFORM_GUIDE_CN.md +1068 -0
- package/FLEXI_CONTEXT_GUIDE_CN.md +172 -0
- package/MODULE_LOADER_CN.md +228 -0
- package/README.md +307 -0
- package/README_CN.md +51 -0
- package/SANDBOX_CN.md +201 -0
- package/dist/FlexiContext.d.ts +28 -0
- package/dist/FlexiContext.js +45 -0
- package/dist/ModuleLoader.d.ts +41 -0
- package/dist/ModuleLoader.js +55 -0
- package/dist/Sandbox.d.ts +33 -0
- package/dist/Sandbox.js +101 -0
- package/dist/criteria-panel/CriteriaFieldsPanel.svelte +26 -0
- package/dist/criteria-panel/CriteriaFieldsPanel.svelte.d.ts +22 -0
- package/dist/criteria-panel/components/CascadeSelectSearchField.svelte +10 -0
- package/dist/criteria-panel/components/CascadeSelectSearchField.svelte.d.ts +25 -0
- package/dist/criteria-panel/components/DateRangeField.svelte +11 -0
- package/dist/criteria-panel/components/DateRangeField.svelte.d.ts +25 -0
- package/dist/criteria-panel/components/DateSearchField.svelte +10 -0
- package/dist/criteria-panel/components/DateSearchField.svelte.d.ts +24 -0
- package/dist/criteria-panel/components/DateTimeSearchField.svelte +10 -0
- package/dist/criteria-panel/components/DateTimeSearchField.svelte.d.ts +24 -0
- package/dist/criteria-panel/components/InputOptionSelectSearchField.svelte +9 -0
- package/dist/criteria-panel/components/InputOptionSelectSearchField.svelte.d.ts +24 -0
- package/dist/criteria-panel/components/NumberRangeField.svelte +11 -0
- package/dist/criteria-panel/components/NumberRangeField.svelte.d.ts +25 -0
- package/dist/criteria-panel/components/NumberSearchField.svelte +9 -0
- package/dist/criteria-panel/components/NumberSearchField.svelte.d.ts +24 -0
- package/dist/criteria-panel/components/OptionMultiSelectSearchField.svelte +9 -0
- package/dist/criteria-panel/components/OptionMultiSelectSearchField.svelte.d.ts +24 -0
- package/dist/criteria-panel/components/OptionSelectSearchField.svelte +9 -0
- package/dist/criteria-panel/components/OptionSelectSearchField.svelte.d.ts +24 -0
- package/dist/criteria-panel/components/SearchField.svelte +14 -0
- package/dist/criteria-panel/components/SearchField.svelte.d.ts +33 -0
- package/dist/criteria-panel/components/TextSearchField.svelte +9 -0
- package/dist/criteria-panel/components/TextSearchField.svelte.d.ts +24 -0
- package/dist/criteria-panel/components/UnknownCriteriaField.svelte +9 -0
- package/dist/criteria-panel/components/UnknownCriteriaField.svelte.d.ts +24 -0
- package/dist/criteria-panel/index.d.ts +6 -0
- package/dist/criteria-panel/index.js +6 -0
- package/dist/criteria-panel/lib/CriteriaComponentBuilder.d.ts +19 -0
- package/dist/criteria-panel/lib/CriteriaComponentBuilder.js +31 -0
- package/dist/criteria-panel/lib/CriteriaFieldBuilder.d.ts +1 -0
- package/dist/criteria-panel/lib/CriteriaFieldBuilder.js +127 -0
- package/dist/criteria-panel/lib/FlexiCriteriaField.d.ts +38 -0
- package/dist/criteria-panel/lib/FlexiCriteriaField.js +31 -0
- package/dist/criteria-panel/lib/FlexiCriteriaSet.d.ts +24 -0
- package/dist/criteria-panel/lib/FlexiCriteriaSet.js +48 -0
- package/dist/flexi-datatable/FlexiDataTable.d.ts +111 -0
- package/dist/flexi-datatable/FlexiDataTable.js +90 -0
- package/dist/flexi-datatable/index.d.ts +2 -0
- package/dist/flexi-datatable/index.js +2 -0
- package/dist/flexi-form/FlexiCompound.d.ts +34 -0
- package/dist/flexi-form/FlexiCompound.js +84 -0
- package/dist/flexi-form/FlexiFormDialog.svelte +24 -0
- package/dist/flexi-form/FlexiFormDialog.svelte.d.ts +21 -0
- package/dist/flexi-form/FlexiFormPage.svelte +26 -0
- package/dist/flexi-form/FlexiFormPage.svelte.d.ts +25 -0
- package/dist/flexi-form/Schema.d.ts +6 -0
- package/dist/flexi-form/Schema.js +1 -0
- package/dist/flexi-form/components/BreakLine.svelte +1 -0
- package/dist/flexi-form/components/BreakLine.svelte.d.ts +26 -0
- package/dist/flexi-form/components/CardTitleBar.svelte +18 -0
- package/dist/flexi-form/components/CardTitleBar.svelte.d.ts +22 -0
- package/dist/flexi-form/components/CascadeOptionSelectField.svelte +13 -0
- package/dist/flexi-form/components/CascadeOptionSelectField.svelte.d.ts +24 -0
- package/dist/flexi-form/components/CellFieldBuilder.d.ts +1 -0
- package/dist/flexi-form/components/CellFieldBuilder.js +178 -0
- package/dist/flexi-form/components/DateField.svelte +12 -0
- package/dist/flexi-form/components/DateField.svelte.d.ts +24 -0
- package/dist/flexi-form/components/DateTimeField.svelte +13 -0
- package/dist/flexi-form/components/DateTimeField.svelte.d.ts +24 -0
- package/dist/flexi-form/components/InputOptionSelectField.svelte +13 -0
- package/dist/flexi-form/components/InputOptionSelectField.svelte.d.ts +24 -0
- package/dist/flexi-form/components/MemoField.svelte +12 -0
- package/dist/flexi-form/components/MemoField.svelte.d.ts +24 -0
- package/dist/flexi-form/components/NumberField.svelte +12 -0
- package/dist/flexi-form/components/NumberField.svelte.d.ts +24 -0
- package/dist/flexi-form/components/OptionsMultiSelectField.svelte +13 -0
- package/dist/flexi-form/components/OptionsMultiSelectField.svelte.d.ts +24 -0
- package/dist/flexi-form/components/OptionsSelectField.svelte +13 -0
- package/dist/flexi-form/components/OptionsSelectField.svelte.d.ts +24 -0
- package/dist/flexi-form/components/TextField.svelte +12 -0
- package/dist/flexi-form/components/TextField.svelte.d.ts +24 -0
- package/dist/flexi-form/components/UnitNumberField.svelte +12 -0
- package/dist/flexi-form/components/UnitNumberField.svelte.d.ts +24 -0
- package/dist/flexi-form/components/UnknownTypeField.svelte +5 -0
- package/dist/flexi-form/components/UnknownTypeField.svelte.d.ts +18 -0
- package/dist/flexi-form/containers/FlexiPanel.svelte +13 -0
- package/dist/flexi-form/containers/FlexiPanel.svelte.d.ts +33 -0
- package/dist/flexi-form/flexi_card/FlexiCard.d.ts +64 -0
- package/dist/flexi-form/flexi_card/FlexiCard.js +66 -0
- package/dist/flexi-form/flexi_card/FlexiCardPanel.svelte +57 -0
- package/dist/flexi-form/flexi_card/FlexiCardPanel.svelte.d.ts +22 -0
- package/dist/flexi-form/flexi_composite/FlexiComposite.d.ts +50 -0
- package/dist/flexi-form/flexi_composite/FlexiComposite.js +26 -0
- package/dist/flexi-form/flexi_composite/FlexiCompositePanel.svelte +42 -0
- package/dist/flexi-form/flexi_composite/FlexiCompositePanel.svelte.d.ts +25 -0
- package/dist/flexi-form/flexi_composite/README.md +50 -0
- package/dist/flexi-form/flexi_datasheet/FlexiDataSheet.d.ts +4 -0
- package/dist/flexi-form/flexi_datasheet/FlexiDataSheet.js +2 -0
- package/dist/flexi-form/flexi_field/FlexiField.d.ts +76 -0
- package/dist/flexi-form/flexi_field/FlexiField.js +128 -0
- package/dist/flexi-form/flexi_field/FlexiFieldCell.svelte +35 -0
- package/dist/flexi-form/flexi_field/FlexiFieldCell.svelte.d.ts +25 -0
- package/dist/flexi-form/flexi_field/UnknownField.d.ts +3 -0
- package/dist/flexi-form/flexi_field/UnknownField.js +3 -0
- package/dist/flexi-form/flexi_form/FlexiForm.d.ts +127 -0
- package/dist/flexi-form/flexi_form/FlexiForm.js +160 -0
- package/dist/flexi-form/flexi_form/FlexiFormPanel.svelte +57 -0
- package/dist/flexi-form/flexi_form/FlexiFormPanel.svelte.d.ts +25 -0
- package/dist/flexi-form/index.d.ts +11 -0
- package/dist/flexi-form/index.js +11 -0
- package/dist/flexi-form/lib/ComponentBuilder.d.ts +15 -0
- package/dist/flexi-form/lib/ComponentBuilder.js +31 -0
- package/dist/flexi-form/lib/index.d.ts +5 -0
- package/dist/flexi-form/lib/index.js +2 -0
- package/dist/flexi-form/lib/types.d.ts +7 -0
- package/dist/flexi-form/lib/types.js +6 -0
- package/dist/flexi-form/lib/utils.d.ts +10 -0
- package/dist/flexi-form/lib/utils.js +48 -0
- package/dist/i18n-res/i18nRes.d.ts +2 -0
- package/dist/i18n-res/i18nRes.js +8 -0
- package/dist/i18n-res/index.d.ts +2 -0
- package/dist/i18n-res/index.js +2 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/uniface-flexi-module.css +46 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +8 -0
- package/package.json +135 -0
|
@@ -0,0 +1,1068 @@
|
|
|
1
|
+
# FlexiForm Complete Usage Guide
|
|
2
|
+
|
|
3
|
+
A comprehensive guide to using FlexiForm for building dynamic, flexible forms in Svelte applications.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Overview](#overview)
|
|
8
|
+
2. [Basic Usage](#basic-usage)
|
|
9
|
+
3. [Schema Structure](#schema-structure)
|
|
10
|
+
4. [Field Types](#field-types)
|
|
11
|
+
5. [Layout and Arrangement](#layout-and-arrangement)
|
|
12
|
+
6. [Dynamic Form Generation](#dynamic-form-generation)
|
|
13
|
+
7. [Events and Interactions](#events-and-interactions)
|
|
14
|
+
8. [Extending FlexiForm](#extending-flexiform)
|
|
15
|
+
9. [Advanced Patterns](#advanced-patterns)
|
|
16
|
+
10. [Best Practices](#best-practices)
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
FlexiForm is a powerful form framework that generates dynamic forms from JSON schemas. It supports multiple layout modes, various field types, and provides extensible architecture for custom components.
|
|
21
|
+
|
|
22
|
+
### Key Features
|
|
23
|
+
|
|
24
|
+
- **Dynamic Schema-Based**: Generate forms from JSON configurations
|
|
25
|
+
- **Flexible Layouts**: Support for flex and grid layouts
|
|
26
|
+
- **Extensible Field Types**: Built-in fields + custom field support
|
|
27
|
+
- **Event System**: Rich event handling for form interactions
|
|
28
|
+
- **Type Safety**: Full TypeScript support
|
|
29
|
+
- **Modular Architecture**: Plug-and-play components
|
|
30
|
+
|
|
31
|
+
## Basic Usage
|
|
32
|
+
|
|
33
|
+
### 1. Installation and Setup
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { FlexiFormPage, registerFormFieldBuilder } from '@ticatec/uniface-flexi-form/flexi-form';
|
|
37
|
+
import '@ticatec/uniface-flexi-form/uniface-flexi-form.css';
|
|
38
|
+
|
|
39
|
+
// Register all field builders (required)
|
|
40
|
+
registerFormFieldBuilder();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Simple Form Example
|
|
44
|
+
|
|
45
|
+
```svelte
|
|
46
|
+
<!-- SimpleForm.svelte -->
|
|
47
|
+
<script lang="ts">
|
|
48
|
+
import { FlexiFormPage } from '@ticatec/uniface-flexi-form/flexi-form';
|
|
49
|
+
|
|
50
|
+
// Form data
|
|
51
|
+
let userData = {
|
|
52
|
+
name: '',
|
|
53
|
+
email: '',
|
|
54
|
+
age: null,
|
|
55
|
+
birthDate: null
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Form schema
|
|
59
|
+
const userFormSchema = {
|
|
60
|
+
mode: 'flex',
|
|
61
|
+
arrangement: 'vertical',
|
|
62
|
+
variant: 'outlined',
|
|
63
|
+
elements: {
|
|
64
|
+
'user-info': {
|
|
65
|
+
title: 'User Information',
|
|
66
|
+
fields: [
|
|
67
|
+
{
|
|
68
|
+
type: 'text-editor',
|
|
69
|
+
keyField: 'name',
|
|
70
|
+
name: 'name',
|
|
71
|
+
label: 'Full Name',
|
|
72
|
+
required: true,
|
|
73
|
+
props: {
|
|
74
|
+
placeholder: 'Enter your full name'
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: 'text-editor',
|
|
79
|
+
keyField: 'email',
|
|
80
|
+
name: 'email',
|
|
81
|
+
label: 'Email Address',
|
|
82
|
+
required: true,
|
|
83
|
+
props: {
|
|
84
|
+
type: 'email',
|
|
85
|
+
placeholder: 'Enter your email'
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'number-editor',
|
|
90
|
+
keyField: 'age',
|
|
91
|
+
name: 'age',
|
|
92
|
+
label: 'Age',
|
|
93
|
+
props: {
|
|
94
|
+
min: 0,
|
|
95
|
+
max: 120
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
type: 'date-picker',
|
|
100
|
+
keyField: 'birthDate',
|
|
101
|
+
name: 'birthDate',
|
|
102
|
+
label: 'Birth Date'
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
function handleFormChange(event) {
|
|
110
|
+
console.log('Form data changed:', event.detail);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function handleSubmit() {
|
|
114
|
+
console.log('Submitting form:', userData);
|
|
115
|
+
}
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<FlexiFormPage
|
|
119
|
+
schema={userFormSchema}
|
|
120
|
+
bind:data={userData}
|
|
121
|
+
on:change={handleFormChange}
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
<button on:click={handleSubmit}>Submit</button>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Schema Structure
|
|
128
|
+
|
|
129
|
+
### FlexiFormSchema
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
interface FlexiFormSchema {
|
|
133
|
+
mode?: 'flex' | 'grid'; // Layout mode
|
|
134
|
+
arrangement?: 'vertical' | 'horizontal'; // Layout direction
|
|
135
|
+
props?: any; // Layout-specific properties
|
|
136
|
+
elements: FormElements; // Form cards/sections
|
|
137
|
+
label$style?: string; // Label styling
|
|
138
|
+
variant?: 'filled' | 'outlined' | ''; // Field variant
|
|
139
|
+
actions?: Array<FormActionSchema>; // Form actions
|
|
140
|
+
style?: string; // Custom CSS
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
type FormElements = {
|
|
144
|
+
[cardKey: string]: FlexiCardSchema | FlexiBlockSchema;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### FlexiCardSchema
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
interface FlexiCardSchema {
|
|
152
|
+
title?: string; // Card title
|
|
153
|
+
mode?: 'flex' | 'grid'; // Card layout mode
|
|
154
|
+
props?: any; // Layout properties
|
|
155
|
+
arrangement?: 'vertical' | 'horizontal'; // Layout direction
|
|
156
|
+
fields: Array<FlexiFieldSchema>; // Fields in this card
|
|
157
|
+
readonly?: boolean; // Make card readonly
|
|
158
|
+
disabled?: boolean; // Disable card
|
|
159
|
+
variant?: 'filled' | 'outlined' | ''; // Field variant
|
|
160
|
+
foldable?: boolean; // Allow collapsing
|
|
161
|
+
actions?: Array<ActionSchema>; // Card actions
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### FlexiCompositeSchema
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
interface FlexiCompositeSchema {
|
|
169
|
+
type?: 'block'; // Type identifier for blocks
|
|
170
|
+
title?: string; // Block title
|
|
171
|
+
fields: Array<FlexiFieldSchema>; // Fields in this composite block
|
|
172
|
+
readonly?: boolean; // Make block readonly
|
|
173
|
+
disabled?: boolean; // Disable block
|
|
174
|
+
label$style?: string; // Label styling
|
|
175
|
+
variant?: string; // Field variant
|
|
176
|
+
mode?: LayoutMode; // Layout mode
|
|
177
|
+
props?: any; // Layout properties
|
|
178
|
+
arrangement?: Arrangement; // Layout direction
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### FlexiCompositeFieldSchema
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
interface FlexiCompositeFieldSchema {
|
|
186
|
+
type: "composite"; // Type identifier
|
|
187
|
+
keyField?: string; // Data binding path for nested object
|
|
188
|
+
name: string; // Field name
|
|
189
|
+
composite: string; // Reference to composite schema name
|
|
190
|
+
cell?: any; // Cell properties
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### FlexiFieldSchema
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
interface FlexiFieldSchema {
|
|
198
|
+
type: string; // Field type identifier
|
|
199
|
+
keyField: string; // Data binding path
|
|
200
|
+
name: string; // Field name
|
|
201
|
+
label: string; // Display label
|
|
202
|
+
dictName?: string; // Dictionary for options
|
|
203
|
+
variant?: 'filled' | 'outlined' | ''; // Field variant
|
|
204
|
+
readonly?: boolean; // Field readonly state
|
|
205
|
+
disabled?: boolean; // Field disabled state
|
|
206
|
+
required?: boolean; // Field required state
|
|
207
|
+
events?: Record<string, string>; // Event handlers
|
|
208
|
+
cell?: any; // Cell properties
|
|
209
|
+
props?: any; // Field-specific properties
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Field Types
|
|
214
|
+
|
|
215
|
+
### Built-in Field Types
|
|
216
|
+
|
|
217
|
+
| Type | Description | Common Props |
|
|
218
|
+
|------|-------------|--------------|
|
|
219
|
+
| `text-editor` | Single-line text input | `placeholder`, `maxLength`, `pattern` |
|
|
220
|
+
| `memo-editor` | Multi-line text area | `rows`, `placeholder`, `maxLength` |
|
|
221
|
+
| `number-editor` | Numeric input | `min`, `max`, `step`, `precision` |
|
|
222
|
+
| `unit-number-editor` | Number input with units | `min`, `max`, `step`, `unit`, `precision` |
|
|
223
|
+
| `date-picker` | Date selection | `min`, `max`, `format` |
|
|
224
|
+
| `datetime-picker` | Date and time selection | `min`, `max`, `format` |
|
|
225
|
+
| `options-selector` | Single select dropdown | `dictName`, `placeholder` |
|
|
226
|
+
| `options-multi-selector` | Multi-select dropdown | `dictName`, `maxSelections` |
|
|
227
|
+
| `cascade-options-selector` | Cascading select | `dictName`, `levels` |
|
|
228
|
+
| `input-options-selector` | Searchable select | `getOptions`, `minLength` |
|
|
229
|
+
| `-` | Break line/separator | Visual separator |
|
|
230
|
+
|
|
231
|
+
### Field Examples
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// Text field with validation
|
|
235
|
+
{
|
|
236
|
+
type: 'text-editor',
|
|
237
|
+
keyField: 'username',
|
|
238
|
+
name: 'username',
|
|
239
|
+
label: 'Username',
|
|
240
|
+
required: true,
|
|
241
|
+
props: {
|
|
242
|
+
placeholder: 'Enter username',
|
|
243
|
+
maxLength: 50,
|
|
244
|
+
pattern: '^[a-zA-Z0-9_]+$'
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Number field with range
|
|
249
|
+
{
|
|
250
|
+
type: 'number-editor',
|
|
251
|
+
keyField: 'price',
|
|
252
|
+
name: 'price',
|
|
253
|
+
label: 'Price',
|
|
254
|
+
props: {
|
|
255
|
+
min: 0,
|
|
256
|
+
max: 9999.99,
|
|
257
|
+
step: 0.01,
|
|
258
|
+
prefix: '$'
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Date field with constraints
|
|
263
|
+
{
|
|
264
|
+
type: 'date-picker',
|
|
265
|
+
keyField: 'startDate',
|
|
266
|
+
name: 'startDate',
|
|
267
|
+
label: 'Start Date',
|
|
268
|
+
props: {
|
|
269
|
+
min: '2024-01-01',
|
|
270
|
+
max: '2024-12-31',
|
|
271
|
+
format: 'YYYY-MM-DD'
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Select field with dictionary
|
|
276
|
+
{
|
|
277
|
+
type: 'options-selector',
|
|
278
|
+
keyField: 'category',
|
|
279
|
+
name: 'category',
|
|
280
|
+
label: 'Category',
|
|
281
|
+
dictName: 'product-categories',
|
|
282
|
+
required: true,
|
|
283
|
+
props: {
|
|
284
|
+
placeholder: 'Select a category'
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Layout and Arrangement
|
|
290
|
+
|
|
291
|
+
### Flex Layout
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
const flexFormSchema = {
|
|
295
|
+
mode: 'flex',
|
|
296
|
+
arrangement: 'vertical',
|
|
297
|
+
elements: {
|
|
298
|
+
'main-card': {
|
|
299
|
+
mode: 'flex',
|
|
300
|
+
arrangement: 'horizontal', // Fields side by side
|
|
301
|
+
fields: [
|
|
302
|
+
{
|
|
303
|
+
type: 'text-editor',
|
|
304
|
+
keyField: 'firstName',
|
|
305
|
+
name: 'firstName',
|
|
306
|
+
label: 'First Name',
|
|
307
|
+
cell: { flex: 1 } // Equal width
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
type: 'text-editor',
|
|
311
|
+
keyField: 'lastName',
|
|
312
|
+
name: 'lastName',
|
|
313
|
+
label: 'Last Name',
|
|
314
|
+
cell: { flex: 1 } // Equal width
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Grid Layout
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const gridFormSchema = {
|
|
326
|
+
mode: 'grid',
|
|
327
|
+
props: {
|
|
328
|
+
columns: 3, // 3-column grid
|
|
329
|
+
gap: '16px'
|
|
330
|
+
},
|
|
331
|
+
elements: {
|
|
332
|
+
'contact-info': {
|
|
333
|
+
mode: 'grid',
|
|
334
|
+
props: {
|
|
335
|
+
columns: 2 // 2-column grid for this card
|
|
336
|
+
},
|
|
337
|
+
fields: [
|
|
338
|
+
{
|
|
339
|
+
type: 'text-editor',
|
|
340
|
+
keyField: 'email',
|
|
341
|
+
name: 'email',
|
|
342
|
+
label: 'Email',
|
|
343
|
+
cell: { colspan: 2 } // Span 2 columns
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
type: 'text-editor',
|
|
347
|
+
keyField: 'phone',
|
|
348
|
+
name: 'phone',
|
|
349
|
+
label: 'Phone'
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
type: 'text-editor',
|
|
353
|
+
keyField: 'mobile',
|
|
354
|
+
name: 'mobile',
|
|
355
|
+
label: 'Mobile'
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Dynamic Form Generation
|
|
364
|
+
|
|
365
|
+
### Loading Schema from API
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
class DynamicFormLoader {
|
|
369
|
+
async loadFormSchema(formId: string): Promise<FlexiFormSchema> {
|
|
370
|
+
const response = await fetch(`/api/forms/${formId}/schema`);
|
|
371
|
+
return await response.json();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async loadFormData(formId: string, recordId?: string): Promise<any> {
|
|
375
|
+
const url = recordId
|
|
376
|
+
? `/api/forms/${formId}/data/${recordId}`
|
|
377
|
+
: `/api/forms/${formId}/data/new`;
|
|
378
|
+
const response = await fetch(url);
|
|
379
|
+
return await response.json();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Usage in component
|
|
384
|
+
const loader = new DynamicFormLoader();
|
|
385
|
+
let formSchema = null;
|
|
386
|
+
let formData = {};
|
|
387
|
+
|
|
388
|
+
onMount(async () => {
|
|
389
|
+
formSchema = await loader.loadFormSchema('user-profile');
|
|
390
|
+
formData = await loader.loadFormData('user-profile', userId);
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Schema Builder Pattern
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
class FormSchemaBuilder {
|
|
398
|
+
private schema: FlexiFormSchema;
|
|
399
|
+
|
|
400
|
+
constructor() {
|
|
401
|
+
this.schema = {
|
|
402
|
+
mode: 'flex',
|
|
403
|
+
arrangement: 'vertical',
|
|
404
|
+
variant: 'outlined',
|
|
405
|
+
elements: {}
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
addCard(key: string, title?: string): CardBuilder {
|
|
410
|
+
const cardBuilder = new CardBuilder(title);
|
|
411
|
+
this.schema.elements[key] = cardBuilder.getSchema();
|
|
412
|
+
return cardBuilder;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
setLayout(mode: 'flex' | 'grid', arrangement?: 'vertical' | 'horizontal') {
|
|
416
|
+
this.schema.mode = mode;
|
|
417
|
+
this.schema.arrangement = arrangement;
|
|
418
|
+
return this;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
build(): FlexiFormSchema {
|
|
422
|
+
return this.schema;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
class CardBuilder {
|
|
427
|
+
private card: FlexiCardSchema;
|
|
428
|
+
|
|
429
|
+
constructor(title?: string) {
|
|
430
|
+
this.card = {
|
|
431
|
+
title,
|
|
432
|
+
fields: []
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
addTextField(keyField: string, label: string, options: any = {}) {
|
|
437
|
+
this.card.fields.push({
|
|
438
|
+
type: 'text-editor',
|
|
439
|
+
keyField,
|
|
440
|
+
name: keyField,
|
|
441
|
+
label,
|
|
442
|
+
...options
|
|
443
|
+
});
|
|
444
|
+
return this;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
addSelectField(keyField: string, label: string, dictName: string, options: any = {}) {
|
|
448
|
+
this.card.fields.push({
|
|
449
|
+
type: 'options-selector',
|
|
450
|
+
keyField,
|
|
451
|
+
name: keyField,
|
|
452
|
+
label,
|
|
453
|
+
dictName,
|
|
454
|
+
...options
|
|
455
|
+
});
|
|
456
|
+
return this;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
getSchema(): FlexiCardSchema {
|
|
460
|
+
return this.card;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Usage
|
|
465
|
+
const schema = new FormSchemaBuilder()
|
|
466
|
+
.setLayout('flex', 'vertical')
|
|
467
|
+
.addCard('personal', 'Personal Information')
|
|
468
|
+
.addTextField('name', 'Full Name', { required: true })
|
|
469
|
+
.addTextField('email', 'Email', { required: true })
|
|
470
|
+
.addSelectField('country', 'Country', 'countries')
|
|
471
|
+
.build();
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
## Events and Interactions
|
|
475
|
+
|
|
476
|
+
### Form-Level Events
|
|
477
|
+
|
|
478
|
+
```svelte
|
|
479
|
+
<script>
|
|
480
|
+
function handleFormChange(event) {
|
|
481
|
+
const { data, field, value } = event.detail;
|
|
482
|
+
console.log(`Field ${field} changed to:`, value);
|
|
483
|
+
console.log('Full form data:', data);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function handleFormSubmit(event) {
|
|
487
|
+
const { data, isValid } = event.detail;
|
|
488
|
+
if (isValid) {
|
|
489
|
+
submitForm(data);
|
|
490
|
+
} else {
|
|
491
|
+
console.error('Form validation failed');
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function handleFormReset(event) {
|
|
496
|
+
console.log('Form was reset');
|
|
497
|
+
}
|
|
498
|
+
</script>
|
|
499
|
+
|
|
500
|
+
<FlexiFormPage
|
|
501
|
+
{schema}
|
|
502
|
+
bind:data={formData}
|
|
503
|
+
on:change={handleFormChange}
|
|
504
|
+
on:submit={handleFormSubmit}
|
|
505
|
+
on:reset={handleFormReset}
|
|
506
|
+
/>
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Field-Level Events
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
// In schema definition
|
|
513
|
+
{
|
|
514
|
+
type: 'text-editor',
|
|
515
|
+
keyField: 'email',
|
|
516
|
+
name: 'email',
|
|
517
|
+
label: 'Email',
|
|
518
|
+
events: {
|
|
519
|
+
change: 'onEmailChange',
|
|
520
|
+
blur: 'onEmailBlur',
|
|
521
|
+
focus: 'onEmailFocus'
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// In form implementation class
|
|
526
|
+
class UserForm extends FlexiForm {
|
|
527
|
+
onEmailChange(value: string) {
|
|
528
|
+
console.log('Email changed:', value);
|
|
529
|
+
// Trigger email validation
|
|
530
|
+
this.validateEmail(value);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
onEmailBlur(event: FocusEvent) {
|
|
534
|
+
console.log('Email field lost focus');
|
|
535
|
+
// Check for duplicate email
|
|
536
|
+
this.checkEmailUniqueness(event.target.value);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
onEmailFocus(event: FocusEvent) {
|
|
540
|
+
console.log('Email field gained focus');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
## Extending FlexiForm
|
|
546
|
+
|
|
547
|
+
### Creating Custom Field Types
|
|
548
|
+
|
|
549
|
+
#### 1. Create Custom Field Component
|
|
550
|
+
|
|
551
|
+
```svelte
|
|
552
|
+
<!-- CustomRatingField.svelte -->
|
|
553
|
+
<script lang="ts">
|
|
554
|
+
import type FlexiField from "$lib/flexi-form/flexi_field/FlexiField";
|
|
555
|
+
|
|
556
|
+
export let field: FlexiField;
|
|
557
|
+
export let readonly: boolean;
|
|
558
|
+
export let disabled: boolean;
|
|
559
|
+
export let variant: "" | "outlined" | "filled";
|
|
560
|
+
|
|
561
|
+
$: props = field.props;
|
|
562
|
+
$: data = field.data;
|
|
563
|
+
$: events = field.events;
|
|
564
|
+
|
|
565
|
+
const maxRating = props.maxRating || 5;
|
|
566
|
+
let currentRating = data[field.keyField] || 0;
|
|
567
|
+
|
|
568
|
+
function setRating(rating: number) {
|
|
569
|
+
if (readonly || disabled) return;
|
|
570
|
+
currentRating = rating;
|
|
571
|
+
data[field.keyField] = rating;
|
|
572
|
+
events.change?.(rating);
|
|
573
|
+
}
|
|
574
|
+
</script>
|
|
575
|
+
|
|
576
|
+
<div class="rating-field" class:readonly class:disabled>
|
|
577
|
+
<label>{field.label}</label>
|
|
578
|
+
<div class="stars">
|
|
579
|
+
{#each Array(maxRating) as _, i}
|
|
580
|
+
<button
|
|
581
|
+
type="button"
|
|
582
|
+
class="star"
|
|
583
|
+
class:filled={i < currentRating}
|
|
584
|
+
{disabled}
|
|
585
|
+
on:click={() => setRating(i + 1)}
|
|
586
|
+
>
|
|
587
|
+
⭐
|
|
588
|
+
</button>
|
|
589
|
+
{/each}
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
<style>
|
|
594
|
+
.rating-field {
|
|
595
|
+
display: flex;
|
|
596
|
+
flex-direction: column;
|
|
597
|
+
gap: 8px;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.stars {
|
|
601
|
+
display: flex;
|
|
602
|
+
gap: 4px;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.star {
|
|
606
|
+
background: none;
|
|
607
|
+
border: none;
|
|
608
|
+
font-size: 24px;
|
|
609
|
+
cursor: pointer;
|
|
610
|
+
opacity: 0.3;
|
|
611
|
+
transition: opacity 0.2s;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.star.filled {
|
|
615
|
+
opacity: 1;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.star:hover:not(:disabled) {
|
|
619
|
+
opacity: 0.7;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.readonly .star, .disabled .star {
|
|
623
|
+
cursor: default;
|
|
624
|
+
}
|
|
625
|
+
</style>
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
#### 2. Register Custom Field Type
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
import { ComponentBuilder } from '@ticatec/uniface-flexi-form/flexi-form/lib/ComponentBuilder';
|
|
632
|
+
import CustomRatingField from './CustomRatingField.svelte';
|
|
633
|
+
|
|
634
|
+
// Create builder function
|
|
635
|
+
const buildRatingField = (schema: FlexiFieldSchema, dictLoader: DictionaryLoader) => {
|
|
636
|
+
return {
|
|
637
|
+
component: CustomRatingField,
|
|
638
|
+
props: schema.props
|
|
639
|
+
};
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// Register the custom field type
|
|
643
|
+
const componentBuilder = ComponentBuilder.getInstance();
|
|
644
|
+
componentBuilder.register('rating-field', buildRatingField);
|
|
645
|
+
|
|
646
|
+
// Use in schema
|
|
647
|
+
const schemaWithRating = {
|
|
648
|
+
mode: 'flex',
|
|
649
|
+
elements: {
|
|
650
|
+
'review-card': {
|
|
651
|
+
title: 'Product Review',
|
|
652
|
+
fields: [
|
|
653
|
+
{
|
|
654
|
+
type: 'rating-field',
|
|
655
|
+
keyField: 'rating',
|
|
656
|
+
name: 'rating',
|
|
657
|
+
label: 'Product Rating',
|
|
658
|
+
props: {
|
|
659
|
+
maxRating: 5
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
type: 'memo-editor',
|
|
664
|
+
keyField: 'comment',
|
|
665
|
+
name: 'comment',
|
|
666
|
+
label: 'Review Comment'
|
|
667
|
+
}
|
|
668
|
+
]
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### Creating Custom Form Classes
|
|
675
|
+
|
|
676
|
+
```typescript
|
|
677
|
+
import FlexiForm, { type FlexiFormSchema } from '@ticatec/uniface-flexi-form/flexi-form';
|
|
678
|
+
|
|
679
|
+
class UserProfileForm extends FlexiForm {
|
|
680
|
+
private userId: string;
|
|
681
|
+
|
|
682
|
+
constructor(userId: string, data: any, schema: FlexiFormSchema) {
|
|
683
|
+
super(data, schema);
|
|
684
|
+
this.userId = userId;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
protected buildCards(): void {
|
|
688
|
+
// Custom card building logic
|
|
689
|
+
for (const [key, element] of Object.entries(this.elements)) {
|
|
690
|
+
if (element.type === 'block') {
|
|
691
|
+
// Handle block elements
|
|
692
|
+
} else {
|
|
693
|
+
// Create card with custom logic
|
|
694
|
+
const card = new CustomUserCard(this, element as FlexiCardSchema);
|
|
695
|
+
this.cards.push(card);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
getTitle(): string {
|
|
701
|
+
return `User Profile - ${this.data.name || 'New User'}`;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Custom methods
|
|
705
|
+
async saveProfile(): Promise<void> {
|
|
706
|
+
try {
|
|
707
|
+
await fetch(`/api/users/${this.userId}`, {
|
|
708
|
+
method: 'PUT',
|
|
709
|
+
headers: { 'Content-Type': 'application/json' },
|
|
710
|
+
body: JSON.stringify(this.data)
|
|
711
|
+
});
|
|
712
|
+
console.log('Profile saved successfully');
|
|
713
|
+
} catch (error) {
|
|
714
|
+
console.error('Failed to save profile:', error);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
validateEmail(email: string): boolean {
|
|
719
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
720
|
+
return emailRegex.test(email);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Event handlers (called from field events)
|
|
724
|
+
onEmailChange(value: string): void {
|
|
725
|
+
if (!this.validateEmail(value)) {
|
|
726
|
+
console.warn('Invalid email format');
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
onPhoneChange(value: string): void {
|
|
731
|
+
// Format phone number
|
|
732
|
+
const formatted = this.formatPhoneNumber(value);
|
|
733
|
+
this.data.phone = formatted;
|
|
734
|
+
this.invalidate(); // Trigger UI update
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
private formatPhoneNumber(phone: string): string {
|
|
738
|
+
const cleaned = phone.replace(/\D/g, '');
|
|
739
|
+
if (cleaned.length === 10) {
|
|
740
|
+
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6)}`;
|
|
741
|
+
}
|
|
742
|
+
return phone;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Usage
|
|
747
|
+
const userForm = new UserProfileForm(userId, userData, formSchema);
|
|
748
|
+
await userForm.initialize();
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
## Advanced Patterns
|
|
752
|
+
|
|
753
|
+
### Conditional Field Display
|
|
754
|
+
|
|
755
|
+
```typescript
|
|
756
|
+
class ConditionalFormCard extends FlexiCard {
|
|
757
|
+
protected createFromSchema(): void {
|
|
758
|
+
super.createFromSchema();
|
|
759
|
+
|
|
760
|
+
// Add conditional logic
|
|
761
|
+
this.setupConditionalFields();
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
private setupConditionalFields(): void {
|
|
765
|
+
const typeField = this.field['userType'];
|
|
766
|
+
const companyField = this.field['company'];
|
|
767
|
+
|
|
768
|
+
if (typeField && companyField) {
|
|
769
|
+
typeField.events.change = (value: string) => {
|
|
770
|
+
companyField.visibility = value === 'business'
|
|
771
|
+
? Visibility.View
|
|
772
|
+
: Visibility.Hidden;
|
|
773
|
+
this.invalidate();
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
### Dynamic Field Dependencies
|
|
781
|
+
|
|
782
|
+
```typescript
|
|
783
|
+
class DependentFieldsHandler {
|
|
784
|
+
private form: FlexiForm;
|
|
785
|
+
private dependencies: Map<string, string[]> = new Map();
|
|
786
|
+
|
|
787
|
+
constructor(form: FlexiForm) {
|
|
788
|
+
this.form = form;
|
|
789
|
+
this.setupDependencies();
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
setupDependencies(): void {
|
|
793
|
+
// Define field dependencies
|
|
794
|
+
this.dependencies.set('country', ['state', 'city']);
|
|
795
|
+
this.dependencies.set('state', ['city']);
|
|
796
|
+
|
|
797
|
+
// Setup change handlers
|
|
798
|
+
for (const [parentField, dependentFields] of this.dependencies) {
|
|
799
|
+
this.setupFieldHandler(parentField, dependentFields);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
private setupFieldHandler(parentField: string, dependentFields: string[]): void {
|
|
804
|
+
const field = this.findField(parentField);
|
|
805
|
+
if (field) {
|
|
806
|
+
const originalHandler = field.events.change;
|
|
807
|
+
field.events.change = async (value: any) => {
|
|
808
|
+
// Call original handler
|
|
809
|
+
originalHandler?.(value);
|
|
810
|
+
|
|
811
|
+
// Update dependent fields
|
|
812
|
+
await this.updateDependentFields(parentField, value, dependentFields);
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
private async updateDependentFields(
|
|
818
|
+
parentField: string,
|
|
819
|
+
value: any,
|
|
820
|
+
dependentFields: string[]
|
|
821
|
+
): Promise<void> {
|
|
822
|
+
for (const fieldName of dependentFields) {
|
|
823
|
+
const field = this.findField(fieldName);
|
|
824
|
+
if (field) {
|
|
825
|
+
// Clear current value
|
|
826
|
+
this.form.data[fieldName] = null;
|
|
827
|
+
|
|
828
|
+
// Load new options
|
|
829
|
+
const options = await this.loadOptions(fieldName, { [parentField]: value });
|
|
830
|
+
field.props.options = options;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
this.form.invalidate();
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
private async loadOptions(fieldName: string, context: any): Promise<any[]> {
|
|
838
|
+
const response = await fetch(`/api/options/${fieldName}`, {
|
|
839
|
+
method: 'POST',
|
|
840
|
+
headers: { 'Content-Type': 'application/json' },
|
|
841
|
+
body: JSON.stringify(context)
|
|
842
|
+
});
|
|
843
|
+
return await response.json();
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
private findField(fieldName: string): FlexiField | null {
|
|
847
|
+
for (const card of this.form.cards) {
|
|
848
|
+
if (card.field[fieldName]) {
|
|
849
|
+
return card.field[fieldName];
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### Form Wizard Pattern
|
|
858
|
+
|
|
859
|
+
```typescript
|
|
860
|
+
class WizardFormManager {
|
|
861
|
+
private steps: FlexiFormSchema[] = [];
|
|
862
|
+
private currentStep = 0;
|
|
863
|
+
private stepData: any[] = [];
|
|
864
|
+
|
|
865
|
+
constructor(steps: FlexiFormSchema[]) {
|
|
866
|
+
this.steps = steps;
|
|
867
|
+
this.stepData = new Array(steps.length).fill({});
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
getCurrentSchema(): FlexiFormSchema {
|
|
871
|
+
return this.steps[this.currentStep];
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
getCurrentData(): any {
|
|
875
|
+
return this.stepData[this.currentStep];
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
nextStep(): boolean {
|
|
879
|
+
if (this.currentStep < this.steps.length - 1) {
|
|
880
|
+
this.currentStep++;
|
|
881
|
+
return true;
|
|
882
|
+
}
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
previousStep(): boolean {
|
|
887
|
+
if (this.currentStep > 0) {
|
|
888
|
+
this.currentStep--;
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
getFinalData(): any {
|
|
895
|
+
return this.stepData.reduce((acc, stepData) => ({
|
|
896
|
+
...acc,
|
|
897
|
+
...stepData
|
|
898
|
+
}), {});
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
isFirstStep(): boolean {
|
|
902
|
+
return this.currentStep === 0;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
isLastStep(): boolean {
|
|
906
|
+
return this.currentStep === this.steps.length - 1;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
getProgress(): number {
|
|
910
|
+
return ((this.currentStep + 1) / this.steps.length) * 100;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Usage in Svelte component
|
|
915
|
+
let wizardManager = new WizardFormManager([
|
|
916
|
+
personalInfoSchema,
|
|
917
|
+
contactInfoSchema,
|
|
918
|
+
preferencesSchema
|
|
919
|
+
]);
|
|
920
|
+
|
|
921
|
+
function handleNext() {
|
|
922
|
+
if (wizardManager.nextStep()) {
|
|
923
|
+
currentSchema = wizardManager.getCurrentSchema();
|
|
924
|
+
currentData = wizardManager.getCurrentData();
|
|
925
|
+
} else {
|
|
926
|
+
// Submit final form
|
|
927
|
+
submitWizardData(wizardManager.getFinalData());
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
## Best Practices
|
|
933
|
+
|
|
934
|
+
### 1. Schema Organization
|
|
935
|
+
|
|
936
|
+
```typescript
|
|
937
|
+
// Good: Organized schema structure
|
|
938
|
+
const FORM_SCHEMAS = {
|
|
939
|
+
userProfile: {
|
|
940
|
+
mode: 'flex',
|
|
941
|
+
arrangement: 'vertical',
|
|
942
|
+
elements: {
|
|
943
|
+
personal: USER_PERSONAL_CARD,
|
|
944
|
+
contact: USER_CONTACT_CARD,
|
|
945
|
+
preferences: USER_PREFERENCES_CARD
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
const USER_PERSONAL_CARD: FlexiCardSchema = {
|
|
951
|
+
title: 'Personal Information',
|
|
952
|
+
fields: [
|
|
953
|
+
FIELDS.firstName,
|
|
954
|
+
FIELDS.lastName,
|
|
955
|
+
FIELDS.birthDate
|
|
956
|
+
]
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
const FIELDS = {
|
|
960
|
+
firstName: {
|
|
961
|
+
type: 'text-editor',
|
|
962
|
+
keyField: 'firstName',
|
|
963
|
+
name: 'firstName',
|
|
964
|
+
label: 'First Name',
|
|
965
|
+
required: true,
|
|
966
|
+
props: { maxLength: 50 }
|
|
967
|
+
},
|
|
968
|
+
// ... more field definitions
|
|
969
|
+
};
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
### 2. Type Safety
|
|
973
|
+
|
|
974
|
+
```typescript
|
|
975
|
+
// Define strict types for your schemas
|
|
976
|
+
interface UserFormData {
|
|
977
|
+
firstName: string;
|
|
978
|
+
lastName: string;
|
|
979
|
+
email: string;
|
|
980
|
+
birthDate: Date;
|
|
981
|
+
preferences: UserPreferences;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
interface UserPreferences {
|
|
985
|
+
newsletter: boolean;
|
|
986
|
+
language: string;
|
|
987
|
+
timezone: string;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Use typed form creation
|
|
991
|
+
class TypedUserForm extends FlexiForm {
|
|
992
|
+
protected data: UserFormData;
|
|
993
|
+
|
|
994
|
+
constructor(data: UserFormData, schema: FlexiFormSchema) {
|
|
995
|
+
super(data, schema);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
getData(): UserFormData {
|
|
999
|
+
return this.data;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
### 3. Error Handling
|
|
1005
|
+
|
|
1006
|
+
```typescript
|
|
1007
|
+
class FormErrorHandler {
|
|
1008
|
+
static handleFormError(error: any, form: FlexiForm): void {
|
|
1009
|
+
if (error.validationErrors) {
|
|
1010
|
+
this.displayValidationErrors(error.validationErrors, form);
|
|
1011
|
+
} else if (error.networkError) {
|
|
1012
|
+
this.displayNetworkError(error.networkError);
|
|
1013
|
+
} else {
|
|
1014
|
+
this.displayGenericError(error);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
private static displayValidationErrors(
|
|
1019
|
+
errors: Record<string, string>,
|
|
1020
|
+
form: FlexiForm
|
|
1021
|
+
): void {
|
|
1022
|
+
for (const [fieldName, errorMessage] of Object.entries(errors)) {
|
|
1023
|
+
const field = this.findFieldInForm(fieldName, form);
|
|
1024
|
+
if (field) {
|
|
1025
|
+
field.error = errorMessage;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
form.invalidate();
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
### 4. Performance Optimization
|
|
1034
|
+
|
|
1035
|
+
```typescript
|
|
1036
|
+
// Lazy load large forms
|
|
1037
|
+
class LazyFormLoader {
|
|
1038
|
+
private formCache = new Map<string, FlexiFormSchema>();
|
|
1039
|
+
|
|
1040
|
+
async loadForm(formId: string): Promise<FlexiFormSchema> {
|
|
1041
|
+
if (this.formCache.has(formId)) {
|
|
1042
|
+
return this.formCache.get(formId)!;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const schema = await fetch(`/api/forms/${formId}`).then(r => r.json());
|
|
1046
|
+
this.formCache.set(formId, schema);
|
|
1047
|
+
return schema;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
preloadForms(formIds: string[]): void {
|
|
1051
|
+
formIds.forEach(id => this.loadForm(id));
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Debounce form changes
|
|
1056
|
+
function createDebouncedFormHandler(delay = 300) {
|
|
1057
|
+
let timeoutId: number;
|
|
1058
|
+
|
|
1059
|
+
return function(handler: Function) {
|
|
1060
|
+
return function(...args: any[]) {
|
|
1061
|
+
clearTimeout(timeoutId);
|
|
1062
|
+
timeoutId = setTimeout(() => handler(...args), delay);
|
|
1063
|
+
};
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
This comprehensive guide provides everything needed to effectively use and extend FlexiForm in your applications. The examples progress from basic usage to advanced patterns, showing how to build sophisticated form systems with FlexiForm's flexible architecture.
|