@record-evolution/widget-form 1.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.
@@ -0,0 +1,150 @@
1
+ {
2
+ "title": "InputData",
3
+ "type": "object",
4
+ "properties": {
5
+ "title": {
6
+ "title": "Title",
7
+ "order": 1,
8
+ "dataDrivenDisabled": true,
9
+ "type": "string"
10
+ },
11
+ "subTitle": {
12
+ "title": "Subtitle",
13
+ "order": 2,
14
+ "dataDrivenDisabled": true,
15
+ "type": "string"
16
+ },
17
+ "formButton": {
18
+ "title": "Form Open Button",
19
+ "description": "If checked, a button will be shown to open the form. If unchecked, the form will be shown directly.",
20
+ "dataDrivenDisabled": true,
21
+ "type": "boolean",
22
+ "order": 3
23
+ },
24
+ "formFields": {
25
+ "title": "Form Fields",
26
+ "description": "Add fields and define how they should be stored.",
27
+ "type": "array",
28
+ "order": 4,
29
+ "dataDrivenDisabled": true,
30
+ "items": {
31
+ "type": "object",
32
+ "properties": {
33
+ "label": {
34
+ "title": "Label",
35
+ "type": "string",
36
+ "dataDrivenDisabled": true,
37
+ "required": true,
38
+ "order": 1
39
+ },
40
+ "type": {
41
+ "title": "Field Type",
42
+ "enum": ["dropdown", "textfield", "numberfield", "checkbox", "textarea", "datetime"],
43
+ "type": "string",
44
+ "dataDrivenDisabled": true,
45
+ "required": true,
46
+ "order": 2
47
+ },
48
+ "hiddenField": {
49
+ "title": "Hidden Field",
50
+ "type": "boolean",
51
+ "description": "If false, this field will be hidden in the form but still saved on submit.",
52
+ "dataDrivenDisabled": true,
53
+ "order": 3
54
+ },
55
+ "required": {
56
+ "title": "Required",
57
+ "type": "boolean",
58
+ "description": "This field must be filled out before the form can be submitted. Ignored when a default value is provided.",
59
+ "dataDrivenDisabled": true,
60
+ "order": 4
61
+ },
62
+ "description": {
63
+ "title": "Hint Text",
64
+ "type": "string",
65
+ "description": "This text will be shown as a description at the field.",
66
+ "dataDrivenDisabled": true,
67
+ "order": 5
68
+ },
69
+ "targetColumn": {
70
+ "title": "Target Column",
71
+ "type": "targetColumn",
72
+ "description": "The column in the target table where this field's data will be stored. This will be combined with all other fields in this form connected with the same target table.",
73
+ "dataDrivenDisabled": true,
74
+ "required": true,
75
+ "order": 6
76
+ },
77
+ "defaultValue": {
78
+ "title": "Default Value",
79
+ "description": "This value will be used if the user does not provide a value.",
80
+ "type": "string",
81
+ "order": 7,
82
+ "dataDrivenDisabled": true
83
+ },
84
+ "min": {
85
+ "title": "Minimum Value",
86
+ "type": "number",
87
+ "description": "Minimum value for number fields.",
88
+ "dataDrivenDisabled": true,
89
+ "condition": {
90
+ "relativePath": "../type",
91
+ "showIfValueIn": ["numberfield"]
92
+ },
93
+ "order": 8
94
+ },
95
+ "max": {
96
+ "title": "Maximum Value",
97
+ "type": "number",
98
+ "description": "Maximum value for number fields.",
99
+ "dataDrivenDisabled": true,
100
+ "condition": {
101
+ "relativePath": "../type",
102
+ "showIfValueIn": ["numberfield"]
103
+ },
104
+ "order": 9
105
+ },
106
+ "validation": {
107
+ "title": "Validation Regex",
108
+ "type": "string",
109
+ "description": "Regular expression for validating text fields.",
110
+ "dataDrivenDisabled": true,
111
+ "condition": {
112
+ "relativePath": "../type",
113
+ "showIfValueIn": ["textfield"]
114
+ },
115
+ "order": 10
116
+ },
117
+ "values": {
118
+ "title": "Dropdown Values",
119
+ "type": "array",
120
+ "description": "List of values for the dropdown field.",
121
+ "condition": {
122
+ "relativePath": "../type",
123
+ "showIfValueIn": ["dropdown"]
124
+ },
125
+ "items": {
126
+ "type": "object",
127
+ "properties": {
128
+ "displayLabel": {
129
+ "title": "Display Label",
130
+ "type": "string",
131
+ "description": "Label shown in the dropdown.",
132
+ "required": true,
133
+ "order": 1
134
+ },
135
+ "value": {
136
+ "title": "Value",
137
+ "type": "string",
138
+ "description": "Value stored in the database.",
139
+ "required": true,
140
+ "order": 2
141
+ }
142
+ }
143
+ },
144
+ "order": 11
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,439 @@
1
+ import { html, css, LitElement, PropertyValues, nothing } from 'lit'
2
+ import { repeat } from 'lit/directives/repeat.js'
3
+ import { property, state, customElement, query } from 'lit/decorators.js'
4
+ import { InputData, FormFields } from './definition-schema.js'
5
+
6
+ import '@material/web/fab/fab.js'
7
+ import '@material/web/icon/icon.js'
8
+ import '@material/web/dialog/dialog.js'
9
+
10
+ import '@material/web/button/text-button.js'
11
+ import '@material/web/button/outlined-button.js'
12
+ import '@material/web/button/filled-button.js'
13
+ import '@material/web/textfield/filled-text-field.js'
14
+ import '@material/web/checkbox/checkbox.js'
15
+ import '@material/web/select/filled-select.js'
16
+ import '@material/web/select/select-option.js'
17
+ // import 'lit-flatpickr'
18
+
19
+ import type { MdDialog } from '@material/web/dialog/dialog.js'
20
+
21
+ type Column = Exclude<InputData['formFields'], undefined>[number]
22
+ type Theme = {
23
+ theme_name: string
24
+ theme_object: any
25
+ }
26
+ @customElement('widget-form-versionplaceholder')
27
+ export class WidgetTableEdit extends LitElement {
28
+ @property({ type: Object })
29
+ inputData?: InputData
30
+
31
+ @property({ type: Object })
32
+ theme?: Theme
33
+
34
+ @state() private themeBgColor?: string
35
+ @state() private themeTitleColor?: string
36
+ @state() private themeSubtitleColor?: string
37
+
38
+ @state() dialogOpen: boolean = false
39
+
40
+ @query('md-dialog') dialog!: MdDialog
41
+
42
+ version: string = 'versionplaceholder'
43
+
44
+ update(changedProperties: Map<string, any>) {
45
+ if (changedProperties.has('theme')) {
46
+ this.registerTheme(this.theme)
47
+ }
48
+
49
+ super.update(changedProperties)
50
+ }
51
+
52
+ protected firstUpdated(_changedProperties: PropertyValues): void {
53
+ this.registerTheme(this.theme)
54
+ }
55
+
56
+ registerTheme(theme?: Theme) {
57
+ const cssTextColor = getComputedStyle(this).getPropertyValue('--re-text-color').trim()
58
+ const cssBgColor = getComputedStyle(this).getPropertyValue('--re-tile-background-color').trim()
59
+ this.themeBgColor = cssBgColor || this.theme?.theme_object?.backgroundColor
60
+ this.themeTitleColor = cssTextColor || this.theme?.theme_object?.title?.textStyle?.color
61
+ this.themeSubtitleColor =
62
+ cssTextColor || this.theme?.theme_object?.title?.subtextStyle?.color || this.themeTitleColor
63
+ }
64
+
65
+ openFormDialog() {
66
+ this.dialogOpen = true
67
+ }
68
+
69
+ handleFormSubmit(event: Event) {
70
+ event.preventDefault()
71
+ const form = event.target as HTMLFormElement
72
+ const formData = new FormData(form)
73
+ const data = Object.fromEntries((formData as any).entries())
74
+
75
+ for (const field of this.inputData?.formFields ?? []) {
76
+ }
77
+
78
+ const submitData = this.inputData?.formFields?.map((field, i) => {
79
+ return {
80
+ swarm_app_databackend_key: field.targetColumn?.swarm_app_databackend_key,
81
+ table_name: field.targetColumn?.tablename,
82
+ column_name: field.targetColumn?.column,
83
+ value: this.formatValue(
84
+ data[`column-${i}`] || field.defaultValue || '',
85
+ field.type ?? 'textfield'
86
+ )
87
+ }
88
+ })
89
+ this.dispatchEvent(
90
+ new CustomEvent('action-submit', {
91
+ detail: submitData,
92
+ bubbles: false,
93
+ composed: false
94
+ })
95
+ )
96
+ form.reset()
97
+ this.dialogOpen = false
98
+ }
99
+
100
+ formatValue(value: string, type: string): any {
101
+ switch (type) {
102
+ case 'numberfield':
103
+ return parseFloat(value)
104
+ case 'checkbox':
105
+ return value === 'on' ? true : false
106
+ default:
107
+ return value
108
+ }
109
+ }
110
+
111
+ renderTextField(field: Column, i: number) {
112
+ return html`
113
+ <md-filled-text-field
114
+ .name="column-${i}"
115
+ .label="${field.label ?? ''}"
116
+ .type="${field.type === 'numberfield' ? 'number' : 'text'}"
117
+ .placeholder="${field.defaultValue ?? ''}"
118
+ .pattern="${field.validation ?? ''}"
119
+ supporting-text=${field.description ?? ''}
120
+ validation-message="${field.validationMessage ?? 'Invalid input'}"
121
+ ?required=${field.required && !field.defaultValue}
122
+ ></md-filled-text-field>
123
+ `
124
+ }
125
+
126
+ renderNumberField(field: Column, i: number) {
127
+ return html`
128
+ <md-filled-text-field
129
+ .name="column-${i}"
130
+ .label="${field.label ?? ''}"
131
+ style="width: 200px;"
132
+ type="number"
133
+ .placeholder="${field.defaultValue ?? ''}"
134
+ step="any"
135
+ min=${field.min ?? ''}
136
+ max=${field.max ?? ''}
137
+ supporting-text=${field.description ?? ''}
138
+ ?required=${field.required && !field.defaultValue}
139
+ ></md-filled-text-field>
140
+ `
141
+ }
142
+
143
+ renderCheckbox(field: Column, i: number) {
144
+ return html`
145
+ <div class="checkbox-container">
146
+ <md-checkbox
147
+ name="column-${i}"
148
+ aria-label=${field.label ?? ''}
149
+ ?checked=${field.defaultValue === 'true'}
150
+ supporting-text=${field.description ?? ''}
151
+ ?required=${field.required && !field.defaultValue}
152
+ ></md-checkbox>
153
+ <label class="label"> ${field.label} </label>
154
+ </div>
155
+ `
156
+ }
157
+
158
+ renderTextArea(field: Column, i: number) {
159
+ return html`
160
+ <md-filled-text-field
161
+ .name="column-${i}"
162
+ .label="${field.label ?? ''}"
163
+ type="textarea"
164
+ .placeholder="${field.defaultValue ?? ''}"
165
+ rows="3"
166
+ ?required=${field.required && !field.defaultValue}
167
+ supporting-text=${field.description ?? ''}
168
+ ></md-filled-text-field>
169
+ `
170
+ }
171
+
172
+ renderDropdown(field: Column, i: number) {
173
+ return html`
174
+ <label class="label">
175
+ ${field.label}
176
+ <md-filled-select
177
+ name="column-${i}"
178
+ supporting-text=${field.description ?? ''}
179
+ ?required=${field.required && !field.defaultValue}
180
+ >
181
+ ${repeat(
182
+ field.values ?? [],
183
+ (val) => val.value,
184
+ (val) => {
185
+ return html`
186
+ <md-select-option
187
+ .value="${val.value ?? ''}"
188
+ ?selected="${val.value === field.defaultValue}"
189
+ >
190
+ ${val.displayLabel}
191
+ </md-select-option>
192
+ `
193
+ }
194
+ )}
195
+ </md-filled-select>
196
+ </label>
197
+ `
198
+ }
199
+
200
+ renderDateTimeField(field: Column, i: number) {
201
+ // return html`
202
+ // <label id="x" class="label"> ${field.label} </label>
203
+ // <lit-flatpickr
204
+ // .name="column-${i}"
205
+ // .label="${field.label ?? ''}"
206
+ // allowInput
207
+ // enableTime
208
+ // mode="single"
209
+ // dateFormat="Y-m-d H:i:S"
210
+ // altFormat="Y-m-d H:i:S"
211
+ // time_24hr
212
+ // .defaultDate="${field.defaultValue}"
213
+ // showMonths="1"
214
+ // weekNumbers
215
+ // >
216
+ // <input />
217
+ // </lit-flatpickr>
218
+ // `
219
+
220
+ return html`
221
+ <md-filled-text-field
222
+ .name="column-${i}"
223
+ style="width: 200px;"
224
+ .label="${field.label ?? ''}"
225
+ type="datetime-local"
226
+ .value="${field.defaultValue ?? ''}"
227
+ supporting-text=${field.description ?? ''}
228
+ ?required=${field.required && !field.defaultValue}
229
+ ></md-filled-text-field>
230
+ `
231
+ }
232
+
233
+ static styles = css`
234
+ :host {
235
+ display: flex;
236
+ flex-direction: column;
237
+ font-family: sans-serif;
238
+ box-sizing: border-box;
239
+ position: relative;
240
+ margin: auto;
241
+ }
242
+
243
+ .edit-fab {
244
+ --md-fab-icon-color: white;
245
+ --md-fab-container-color: #007bff;
246
+ --md-fab-label-text-color: white;
247
+ position: absolute;
248
+ bottom: 24px;
249
+ right: 24px;
250
+ z-index: 10;
251
+ }
252
+
253
+ .paging:not([active]) {
254
+ display: none !important;
255
+ }
256
+
257
+ .wrapper {
258
+ display: flex;
259
+ flex-direction: column;
260
+ padding: 16px;
261
+ box-sizing: border-box;
262
+ overflow: auto;
263
+ }
264
+
265
+ .form-actions {
266
+ display: flex;
267
+ flex-direction: row;
268
+ justify-content: flex-end;
269
+ gap: 8px;
270
+ margin-top: 16px;
271
+ }
272
+
273
+ h3 {
274
+ margin: 0;
275
+ overflow: hidden;
276
+ text-overflow: ellipsis;
277
+ white-space: nowrap;
278
+ padding: 16px 0px 0px 16px;
279
+ box-sizing: border-box;
280
+ }
281
+ p {
282
+ margin: 10px 0 16px 0;
283
+ font-size: 14px;
284
+ overflow: hidden;
285
+ text-overflow: ellipsis;
286
+ white-space: nowrap;
287
+ padding-left: 16px;
288
+ box-sizing: border-box;
289
+ }
290
+
291
+ /* The dialog classes */
292
+ .form {
293
+ min-width: 80%;
294
+ }
295
+
296
+ .form [slot='header'] {
297
+ display: flex;
298
+ flex-direction: row-reverse;
299
+ align-items: center;
300
+ }
301
+
302
+ .form .headline {
303
+ flex: 1;
304
+ }
305
+
306
+ .form-content,
307
+ .form-row {
308
+ display: flex;
309
+ gap: 8px;
310
+ }
311
+
312
+ .form-content {
313
+ flex-direction: column;
314
+ gap: 24px;
315
+ }
316
+
317
+ .form-row > * {
318
+ flex: 1;
319
+ }
320
+
321
+ .checkbox-container {
322
+ display: flex;
323
+ align-items: center;
324
+ gap: 12px;
325
+ }
326
+
327
+ .label {
328
+ display: flex;
329
+ flex-direction: column;
330
+ }
331
+
332
+ md-filled-select {
333
+ flex: 1;
334
+ }
335
+
336
+ md-dialog {
337
+ overflow: visible;
338
+ }
339
+
340
+ lit-flatpickr {
341
+ z-index: 1000;
342
+ }
343
+ `
344
+
345
+ render() {
346
+ return html`
347
+ <header>
348
+ <h3 class="paging" ?active=${this.inputData?.title}>${this.inputData?.title}</h3>
349
+ <p class="paging" ?active=${this.inputData?.subTitle}>${this.inputData?.subTitle}</p>
350
+ </header>
351
+ ${!this.inputData?.formButton
352
+ ? html`
353
+ <div class="wrapper">
354
+ ${this.renderForm()}
355
+ <div class="form-actions">
356
+ <md-outlined-button form="form" value="cancel" type="button"
357
+ >Cancel</md-outlined-button
358
+ >
359
+ <md-filled-button form="form" value="submit" type="submit" autofocus
360
+ >Submit</md-filled-button
361
+ >
362
+ </div>
363
+ </div>
364
+ `
365
+ : html`
366
+ <md-fab
367
+ aria-label="Add"
368
+ class="edit-fab"
369
+ style="--md-fab-container-color: ${this.theme?.theme_object?.color[0] ?? '#9064f7'}"
370
+ @click=${this.openFormDialog}
371
+ >
372
+ <md-icon slot="icon">add</md-icon>
373
+ </md-fab>
374
+
375
+ <md-dialog
376
+ aria-label="${this.inputData?.title ?? 'Data Entry'}"
377
+ class="form"
378
+ quick
379
+ ?open=${this.dialogOpen}
380
+ @cancel=${(event: any) => {
381
+ event.preventDefault()
382
+ }}
383
+ @keydown=${(event: any) => {
384
+ if (event.key === 'Escape') event.preventDefault()
385
+ }}
386
+ @closed=${() => (this.dialogOpen = false)}
387
+ >
388
+ <div slot="headline">${this.inputData?.title ?? 'Data Entry'}</div>
389
+ ${this.renderForm()}
390
+ <div slot="actions">
391
+ <md-outlined-button
392
+ form="form"
393
+ value="cancel"
394
+ type="button"
395
+ @click=${() => this.dialog?.close()}
396
+ >Cancel</md-outlined-button
397
+ >
398
+ <md-filled-button form="form" value="submit" type="submit" autofocus
399
+ >Submit</md-filled-button
400
+ >
401
+ </div>
402
+ </md-dialog>
403
+ `}
404
+ `
405
+ }
406
+
407
+ renderForm() {
408
+ return html`
409
+ <form
410
+ id="form"
411
+ slot="content"
412
+ method="dialog"
413
+ class="form-content"
414
+ @submit=${this.handleFormSubmit}
415
+ >
416
+ ${repeat(
417
+ this.inputData?.formFields?.filter((field) => !field.hiddenField) ?? [],
418
+ (field, i) => i,
419
+ (field, i) => {
420
+ switch (field.type) {
421
+ case 'textfield':
422
+ return this.renderTextField(field, i)
423
+ case 'numberfield':
424
+ return this.renderNumberField(field, i)
425
+ case 'datetime':
426
+ return this.renderDateTimeField(field, i)
427
+ case 'textarea':
428
+ return this.renderTextArea(field, i)
429
+ case 'dropdown':
430
+ return this.renderDropdown(field, i)
431
+ case 'checkbox':
432
+ return this.renderCheckbox(field, i)
433
+ }
434
+ }
435
+ )}
436
+ </form>
437
+ `
438
+ }
439
+ }