@record-evolution/widget-form 1.0.10 → 1.0.12
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/dist/widget-form.d.ts +1 -0
- package/dist/widget-form.js +230 -174
- package/dist/widget-form.js.map +1 -1
- package/package.json +2 -1
- package/src/definition-schema.d.ts +45 -13
- package/src/definition-schema.json +45 -21
- package/src/widget-form.ts +86 -10
|
@@ -1,37 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "InputData",
|
|
3
|
+
"description": "A form widget for collecting user input and storing it in database tables. Use this widget to create data entry interfaces for manual input, configuration settings, user feedback, or any scenario requiring structured data collection. Supports various field types including text, numbers, dropdowns, checkboxes, textareas, and datetime pickers. Each field maps to a database column, enabling direct data persistence. Can display as a button-triggered modal or inline form.",
|
|
3
4
|
"type": "object",
|
|
4
5
|
"properties": {
|
|
5
6
|
"title": {
|
|
6
7
|
"title": "Title",
|
|
8
|
+
"description": "The main heading displayed at the top of the form. Use to describe the form's purpose (e.g., 'Add New Device', 'Submit Feedback', 'Configuration Settings').",
|
|
7
9
|
"order": 1,
|
|
8
10
|
"dataDrivenDisabled": true,
|
|
9
11
|
"type": "string"
|
|
10
12
|
},
|
|
11
13
|
"subTitle": {
|
|
12
14
|
"title": "Subtitle",
|
|
15
|
+
"description": "Secondary text displayed below the title. Use for instructions, context, or additional information about the form.",
|
|
13
16
|
"order": 2,
|
|
14
17
|
"dataDrivenDisabled": true,
|
|
15
18
|
"type": "string"
|
|
16
19
|
},
|
|
20
|
+
"deleteFlagColumn": {
|
|
21
|
+
"title": "Delete-Flag Column",
|
|
22
|
+
"description": "The target table column used to mark records as deleted when the delete button is clicked. This MUST be a boolean column. If the target table has such a deleted_flag column, it should be specified here regardless of the delete button setting.",
|
|
23
|
+
"dataDrivenDisabled": true,
|
|
24
|
+
"type": "targetColumn",
|
|
25
|
+
"order": 3
|
|
26
|
+
},
|
|
27
|
+
"deleteButton": {
|
|
28
|
+
"title": "Show Delete Button",
|
|
29
|
+
"description": "When enabled, shows a delete button that allows users to remove existing entries.",
|
|
30
|
+
"dataDrivenDisabled": true,
|
|
31
|
+
"type": "boolean",
|
|
32
|
+
"order": 4
|
|
33
|
+
},
|
|
17
34
|
"formButton": {
|
|
18
35
|
"title": "Form Open Button",
|
|
19
|
-
"description": "
|
|
36
|
+
"description": "When enabled, shows a button that opens the form in a modal dialog when clicked. When disabled, the form fields are displayed directly in the widget area. Use button mode for space-constrained layouts or optional data entry.",
|
|
20
37
|
"dataDrivenDisabled": true,
|
|
21
38
|
"type": "boolean",
|
|
22
|
-
"order":
|
|
39
|
+
"order": 5
|
|
23
40
|
},
|
|
24
41
|
"formFields": {
|
|
25
42
|
"title": "Form Fields",
|
|
26
|
-
"description": "
|
|
43
|
+
"description": "Array of input fields that make up the form. Each field defines its type, validation rules, and target database column for storage. Fields are rendered in the order specified.",
|
|
27
44
|
"type": "array",
|
|
28
|
-
"order":
|
|
45
|
+
"order": 6,
|
|
29
46
|
"dataDrivenDisabled": true,
|
|
30
47
|
"items": {
|
|
31
48
|
"type": "object",
|
|
32
49
|
"properties": {
|
|
33
50
|
"label": {
|
|
34
51
|
"title": "Label",
|
|
52
|
+
"description": "The text label displayed next to this form field. Should clearly describe what data the user should enter.",
|
|
35
53
|
"type": "string",
|
|
36
54
|
"dataDrivenDisabled": true,
|
|
37
55
|
"required": true,
|
|
@@ -39,6 +57,7 @@
|
|
|
39
57
|
},
|
|
40
58
|
"type": {
|
|
41
59
|
"title": "Field Type",
|
|
60
|
+
"description": "The input control type: 'dropdown' for selection from predefined options, 'textfield' for single-line text, 'numberfield' for numeric values, 'checkbox' for boolean yes/no, 'textarea' for multi-line text, 'datetime' for date and time selection.",
|
|
42
61
|
"enum": ["dropdown", "textfield", "numberfield", "checkbox", "textarea", "datetime"],
|
|
43
62
|
"type": "string",
|
|
44
63
|
"dataDrivenDisabled": true,
|
|
@@ -48,76 +67,81 @@
|
|
|
48
67
|
"hiddenField": {
|
|
49
68
|
"title": "Hidden Field",
|
|
50
69
|
"type": "boolean",
|
|
51
|
-
"description": "
|
|
70
|
+
"description": "When enabled, this field is hidden from users but its value (typically a default or data-bound value) is still submitted with the form. Useful for including metadata, timestamps, or user IDs automatically.",
|
|
52
71
|
"dataDrivenDisabled": true,
|
|
53
72
|
"order": 3
|
|
54
73
|
},
|
|
55
74
|
"required": {
|
|
56
75
|
"title": "Required",
|
|
57
76
|
"type": "boolean",
|
|
58
|
-
"description": "
|
|
77
|
+
"description": "When enabled, users must fill out this field before the form can be submitted. The form will show a validation error if left empty. Ignored when a default value is provided.",
|
|
59
78
|
"dataDrivenDisabled": true,
|
|
60
79
|
"order": 4
|
|
61
80
|
},
|
|
62
81
|
"description": {
|
|
63
82
|
"title": "Hint Text",
|
|
64
83
|
"type": "string",
|
|
65
|
-
"description": "
|
|
84
|
+
"description": "Helper text displayed below the field to guide users on what to enter. Use for format hints, examples, or clarifying instructions.",
|
|
66
85
|
"dataDrivenDisabled": true,
|
|
67
86
|
"order": 5
|
|
68
87
|
},
|
|
69
88
|
"targetColumn": {
|
|
70
89
|
"title": "Target Column",
|
|
71
90
|
"type": "targetColumn",
|
|
72
|
-
"description": "The
|
|
91
|
+
"description": "The database table and column where this field's value will be stored on form submission. Select from available tables and columns. All fields targeting the same table will be combined into a single row insert.",
|
|
73
92
|
"dataDrivenDisabled": true,
|
|
74
93
|
"required": true,
|
|
75
94
|
"order": 6
|
|
76
95
|
},
|
|
96
|
+
"preFilledValue": {
|
|
97
|
+
"title": "Pre-filled Value",
|
|
98
|
+
"description": "Pre-filled value for this field when the form loads. Can be a static value or bound to a data source.",
|
|
99
|
+
"type": "string",
|
|
100
|
+
"order": 7
|
|
101
|
+
},
|
|
77
102
|
"defaultValue": {
|
|
78
103
|
"title": "Default Value",
|
|
79
|
-
"description": "
|
|
104
|
+
"description": "Default value for this field when the form loads. If the user does not provide a value, this default will be used. Can be a static value or bound to a data source.",
|
|
80
105
|
"type": "string",
|
|
81
|
-
"order":
|
|
82
|
-
"dataDrivenDisabled": true
|
|
106
|
+
"order": 8
|
|
83
107
|
},
|
|
84
108
|
"min": {
|
|
85
109
|
"title": "Minimum Value",
|
|
86
110
|
"type": "number",
|
|
87
|
-
"description": "
|
|
111
|
+
"description": "The minimum allowed numeric value for numberfield types. Values below this will fail validation.",
|
|
88
112
|
"dataDrivenDisabled": true,
|
|
89
113
|
"condition": {
|
|
90
114
|
"relativePath": "../type",
|
|
91
115
|
"showIfValueIn": ["numberfield"]
|
|
92
116
|
},
|
|
93
|
-
"order":
|
|
117
|
+
"order": 9
|
|
94
118
|
},
|
|
95
119
|
"max": {
|
|
96
120
|
"title": "Maximum Value",
|
|
97
121
|
"type": "number",
|
|
98
|
-
"description": "
|
|
122
|
+
"description": "The maximum allowed numeric value for numberfield types. Values above this will fail validation.",
|
|
99
123
|
"dataDrivenDisabled": true,
|
|
100
124
|
"condition": {
|
|
101
125
|
"relativePath": "../type",
|
|
102
126
|
"showIfValueIn": ["numberfield"]
|
|
103
127
|
},
|
|
104
|
-
"order":
|
|
128
|
+
"order": 10
|
|
105
129
|
},
|
|
106
130
|
"validation": {
|
|
107
131
|
"title": "Validation Regex",
|
|
108
132
|
"type": "string",
|
|
109
|
-
"description": "
|
|
133
|
+
"description": "A regular expression pattern to validate text input (e.g., '^[A-Z]{2}[0-9]{4}$' for format like 'AB1234'). Leave empty to accept any text.",
|
|
110
134
|
"dataDrivenDisabled": true,
|
|
111
135
|
"condition": {
|
|
112
136
|
"relativePath": "../type",
|
|
113
137
|
"showIfValueIn": ["textfield"]
|
|
114
138
|
},
|
|
115
|
-
"order":
|
|
139
|
+
"order": 11
|
|
116
140
|
},
|
|
117
141
|
"values": {
|
|
118
142
|
"title": "Dropdown Values",
|
|
119
143
|
"type": "array",
|
|
120
|
-
"description": "
|
|
144
|
+
"description": "The list of selectable options for dropdown field types. Each option has a display label and a stored value.",
|
|
121
145
|
"condition": {
|
|
122
146
|
"relativePath": "../type",
|
|
123
147
|
"showIfValueIn": ["dropdown"]
|
|
@@ -128,20 +152,20 @@
|
|
|
128
152
|
"displayLabel": {
|
|
129
153
|
"title": "Display Label",
|
|
130
154
|
"type": "string",
|
|
131
|
-
"description": "
|
|
155
|
+
"description": "The text shown to users in the dropdown list.",
|
|
132
156
|
"required": true,
|
|
133
157
|
"order": 1
|
|
134
158
|
},
|
|
135
159
|
"value": {
|
|
136
160
|
"title": "Value",
|
|
137
161
|
"type": "string",
|
|
138
|
-
"description": "
|
|
162
|
+
"description": "The actual value stored in the database when this option is selected. May differ from the display label.",
|
|
139
163
|
"required": true,
|
|
140
164
|
"order": 2
|
|
141
165
|
}
|
|
142
166
|
}
|
|
143
167
|
},
|
|
144
|
-
"order":
|
|
168
|
+
"order": 12
|
|
145
169
|
}
|
|
146
170
|
}
|
|
147
171
|
}
|
package/src/widget-form.ts
CHANGED
|
@@ -67,6 +67,9 @@ export class WidgetForm extends LitElement {
|
|
|
67
67
|
|
|
68
68
|
handleFormSubmit(event: Event) {
|
|
69
69
|
event.preventDefault()
|
|
70
|
+
const submitter = (event as SubmitEvent).submitter as HTMLElement
|
|
71
|
+
const action = submitter?.getAttribute('value') ?? 'submit'
|
|
72
|
+
|
|
70
73
|
const form = event.target as HTMLFormElement
|
|
71
74
|
const formData = new FormData(form)
|
|
72
75
|
const data = Object.fromEntries((formData as any).entries())
|
|
@@ -82,6 +85,15 @@ export class WidgetForm extends LitElement {
|
|
|
82
85
|
)
|
|
83
86
|
}
|
|
84
87
|
})
|
|
88
|
+
|
|
89
|
+
if (this.inputData?.deleteFlagColumn)
|
|
90
|
+
submitData?.push({
|
|
91
|
+
swarm_app_databackend_key: this.inputData?.deleteFlagColumn?.swarm_app_databackend_key,
|
|
92
|
+
table_name: this.inputData?.deleteFlagColumn?.tablename,
|
|
93
|
+
column_name: this.inputData?.deleteFlagColumn?.column,
|
|
94
|
+
value: action === 'delete'
|
|
95
|
+
})
|
|
96
|
+
|
|
85
97
|
this.dispatchEvent(
|
|
86
98
|
new CustomEvent('data-submit', {
|
|
87
99
|
detail: submitData,
|
|
@@ -110,6 +122,7 @@ export class WidgetForm extends LitElement {
|
|
|
110
122
|
.name="column-${i}"
|
|
111
123
|
.label="${field.label ?? ''}"
|
|
112
124
|
.type="${field.type === 'numberfield' ? 'number' : 'text'}"
|
|
125
|
+
.value="${field.preFilledValue ?? ''}"
|
|
113
126
|
.placeholder="${field.defaultValue ?? ''}"
|
|
114
127
|
.pattern="${field.validation ?? ''}"
|
|
115
128
|
supporting-text=${field.description ?? ''}
|
|
@@ -126,6 +139,7 @@ export class WidgetForm extends LitElement {
|
|
|
126
139
|
.label="${field.label ?? ''}"
|
|
127
140
|
style="width: 200px;"
|
|
128
141
|
type="number"
|
|
142
|
+
.value="${field.preFilledValue ?? ''}"
|
|
129
143
|
.placeholder="${field.defaultValue ?? ''}"
|
|
130
144
|
step="any"
|
|
131
145
|
min=${field.min ?? ''}
|
|
@@ -142,7 +156,7 @@ export class WidgetForm extends LitElement {
|
|
|
142
156
|
<md-checkbox
|
|
143
157
|
name="column-${i}"
|
|
144
158
|
aria-label=${field.label ?? ''}
|
|
145
|
-
?checked=${field.defaultValue === 'true'}
|
|
159
|
+
?checked=${(String(field.preFilledValue) ?? field.defaultValue) === 'true'}
|
|
146
160
|
supporting-text=${field.description ?? ''}
|
|
147
161
|
?required=${field.required && !field.defaultValue}
|
|
148
162
|
></md-checkbox>
|
|
@@ -157,6 +171,7 @@ export class WidgetForm extends LitElement {
|
|
|
157
171
|
.name="column-${i}"
|
|
158
172
|
.label="${field.label ?? ''}"
|
|
159
173
|
type="textarea"
|
|
174
|
+
.value="${field.preFilledValue ?? ''}"
|
|
160
175
|
.placeholder="${field.defaultValue ?? ''}"
|
|
161
176
|
rows="3"
|
|
162
177
|
?required=${field.required && !field.defaultValue}
|
|
@@ -181,7 +196,7 @@ export class WidgetForm extends LitElement {
|
|
|
181
196
|
return html`
|
|
182
197
|
<md-select-option
|
|
183
198
|
.value="${val.value ?? ''}"
|
|
184
|
-
?selected="${val.value === field.defaultValue}"
|
|
199
|
+
?selected="${val.value === (field.preFilledValue ?? field.defaultValue)}"
|
|
185
200
|
>
|
|
186
201
|
${val.displayLabel}
|
|
187
202
|
</md-select-option>
|
|
@@ -200,14 +215,33 @@ export class WidgetForm extends LitElement {
|
|
|
200
215
|
style="width: 200px;"
|
|
201
216
|
.label="${field.label ?? ''}"
|
|
202
217
|
type="datetime-local"
|
|
203
|
-
.value="${field.defaultValue ?? ''}"
|
|
218
|
+
.value="${field.preFilledValue ?? field.defaultValue ?? ''}"
|
|
204
219
|
supporting-text=${field.description ?? ''}
|
|
205
220
|
?required=${field.required && !field.defaultValue}
|
|
206
221
|
></md-outlined-text-field>
|
|
207
222
|
`
|
|
208
223
|
}
|
|
209
224
|
|
|
225
|
+
resetForm() {
|
|
226
|
+
const form = this.shadowRoot?.querySelector('#form') as HTMLFormElement
|
|
227
|
+
if (!form) return
|
|
228
|
+
this.inputData?.formFields?.forEach((field, i) => {
|
|
229
|
+
const name = `column-${i}`
|
|
230
|
+
if (field.type === 'checkbox') {
|
|
231
|
+
const cb = form.querySelector(`md-checkbox[name="${name}"]`) as any
|
|
232
|
+
if (cb) cb.checked = field.preFilledValue === 'true'
|
|
233
|
+
} else if (field.type === 'dropdown') {
|
|
234
|
+
const select = form.querySelector(`md-outlined-select[name="${name}"]`) as any
|
|
235
|
+
if (select) select.value = field.preFilledValue ?? ''
|
|
236
|
+
} else {
|
|
237
|
+
const input = form.querySelector(`md-outlined-text-field[name="${name}"]`) as any
|
|
238
|
+
if (input) input.value = field.preFilledValue ?? ''
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
|
|
210
243
|
cancelEdit(event: Event) {
|
|
244
|
+
this.resetForm()
|
|
211
245
|
this.dialogOpen = false
|
|
212
246
|
}
|
|
213
247
|
|
|
@@ -325,10 +359,37 @@ export class WidgetForm extends LitElement {
|
|
|
325
359
|
--md-fab-container-color: #007bff;
|
|
326
360
|
--md-fab-label-text-color: white;
|
|
327
361
|
}
|
|
362
|
+
|
|
363
|
+
.delete-btn {
|
|
364
|
+
--md-filled-button-container-color: #d32f2f;
|
|
365
|
+
--md-filled-button-label-text-color: #fff;
|
|
366
|
+
--md-filled-button-hover-label-text-color: #fff;
|
|
367
|
+
--md-filled-button-focus-label-text-color: #fff;
|
|
368
|
+
--md-filled-button-pressed-label-text-color: #fff;
|
|
369
|
+
}
|
|
328
370
|
`
|
|
329
371
|
|
|
330
372
|
render() {
|
|
373
|
+
const fontColor = this.themeTitleColor
|
|
374
|
+
const bgColor = this.themeBgColor
|
|
375
|
+
const bgColorOpaque = bgColor?.startsWith('rgba')
|
|
376
|
+
? bgColor.replace(/rgba\(([^)]+),\s*[\d.]+\)/, 'rgb($1)')
|
|
377
|
+
: bgColor?.startsWith('#') && bgColor.length === 9
|
|
378
|
+
? bgColor.substring(0, 7)
|
|
379
|
+
: bgColor
|
|
331
380
|
return html`
|
|
381
|
+
<style>
|
|
382
|
+
:host {
|
|
383
|
+
--md-sys-color-on-surface: ${fontColor};
|
|
384
|
+
--md-sys-color-on-surface-variant: ${fontColor};
|
|
385
|
+
--md-sys-color-outline: ${fontColor};
|
|
386
|
+
--md-sys-color-surface-container: ${bgColorOpaque};
|
|
387
|
+
--md-menu-container-color: ${bgColorOpaque};
|
|
388
|
+
--md-menu-item-selected-container-color: ${bgColorOpaque};
|
|
389
|
+
--md-menu-item-selected-label-text-color: ${fontColor};
|
|
390
|
+
color: ${fontColor};
|
|
391
|
+
}
|
|
392
|
+
</style>
|
|
332
393
|
<div class="header">
|
|
333
394
|
${this.inputData?.formButton
|
|
334
395
|
? html`
|
|
@@ -352,9 +413,17 @@ export class WidgetForm extends LitElement {
|
|
|
352
413
|
<div class="wrapper">
|
|
353
414
|
${this.renderForm()}
|
|
354
415
|
<div class="form-actions">
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
416
|
+
${this.inputData?.deleteButton
|
|
417
|
+
? html`<md-filled-button
|
|
418
|
+
class="delete-btn"
|
|
419
|
+
form="form"
|
|
420
|
+
value="delete"
|
|
421
|
+
type="submit"
|
|
422
|
+
autofocus
|
|
423
|
+
>Delete</md-filled-button
|
|
424
|
+
>`
|
|
425
|
+
: nothing}
|
|
426
|
+
<md-outlined-button @click=${this.resetForm}>Reset</md-outlined-button>
|
|
358
427
|
<md-filled-button form="form" value="submit" type="submit" autofocus
|
|
359
428
|
>Submit</md-filled-button
|
|
360
429
|
>
|
|
@@ -378,9 +447,17 @@ export class WidgetForm extends LitElement {
|
|
|
378
447
|
<div slot="headline">${this.inputData?.title ?? 'Data Entry'}</div>
|
|
379
448
|
${this.renderForm()}
|
|
380
449
|
<div slot="actions">
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
450
|
+
${this.inputData?.deleteButton
|
|
451
|
+
? html`<md-filled-button
|
|
452
|
+
class="delete-btn"
|
|
453
|
+
form="form"
|
|
454
|
+
value="delete"
|
|
455
|
+
type="submit"
|
|
456
|
+
autofocus
|
|
457
|
+
>Delete</md-filled-button
|
|
458
|
+
>`
|
|
459
|
+
: nothing}
|
|
460
|
+
<md-outlined-button @click=${this.cancelEdit}>Cancel</md-outlined-button>
|
|
384
461
|
<md-filled-button form="form" value="submit" type="submit" autofocus
|
|
385
462
|
>Submit</md-filled-button
|
|
386
463
|
>
|
|
@@ -398,7 +475,6 @@ export class WidgetForm extends LitElement {
|
|
|
398
475
|
method="dialog"
|
|
399
476
|
class="form-content"
|
|
400
477
|
@submit=${this.handleFormSubmit}
|
|
401
|
-
@reset=${this.cancelEdit}
|
|
402
478
|
>
|
|
403
479
|
${repeat(
|
|
404
480
|
this.inputData?.formFields?.filter((field) => !field.hiddenField) ?? [],
|