@cyprnet/node-red-contrib-uibuilder-formgen 0.4.11
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/CHANGELOG.md +33 -0
- package/LICENSE +22 -0
- package/README.md +58 -0
- package/docs/user-guide.html +565 -0
- package/examples/formgen-builder/src/index.html +921 -0
- package/examples/formgen-builder/src/index.js +1338 -0
- package/examples/portalsmith-formgen-example.json +531 -0
- package/examples/schema-builder-integration.json +109 -0
- package/examples/schemas/Banking/banking_fraud_report.json +102 -0
- package/examples/schemas/Banking/banking_kyc_update.json +59 -0
- package/examples/schemas/Banking/banking_loan_application.json +113 -0
- package/examples/schemas/Banking/banking_new_account.json +98 -0
- package/examples/schemas/Banking/banking_wire_transfer_request.json +94 -0
- package/examples/schemas/HR/hr_employee_change_form.json +65 -0
- package/examples/schemas/HR/hr_exit_interview.json +105 -0
- package/examples/schemas/HR/hr_job_application.json +166 -0
- package/examples/schemas/HR/hr_onboarding_request.json +140 -0
- package/examples/schemas/HR/hr_time_off_request.json +95 -0
- package/examples/schemas/HR/hr_training_request.json +70 -0
- package/examples/schemas/Healthcare/health_appointment_request.json +103 -0
- package/examples/schemas/Healthcare/health_incident_report.json +82 -0
- package/examples/schemas/Healthcare/health_lab_order_request.json +72 -0
- package/examples/schemas/Healthcare/health_medication_refill.json +72 -0
- package/examples/schemas/Healthcare/health_patient_intake.json +113 -0
- package/examples/schemas/IT/it_access_request.json +145 -0
- package/examples/schemas/IT/it_dhcp_reservation.json +175 -0
- package/examples/schemas/IT/it_dns_domain_external.json +192 -0
- package/examples/schemas/IT/it_dns_domain_internal.json +171 -0
- package/examples/schemas/IT/it_network_change_request.json +126 -0
- package/examples/schemas/IT/it_network_request-form.json +299 -0
- package/examples/schemas/IT/it_new_hardware_request.json +155 -0
- package/examples/schemas/IT/it_password_reset.json +133 -0
- package/examples/schemas/IT/it_software_license_request.json +93 -0
- package/examples/schemas/IT/it_static_ip_request.json +199 -0
- package/examples/schemas/IT/it_subnet_request_form.json +216 -0
- package/examples/schemas/Maintenance/maint_checklist.json +176 -0
- package/examples/schemas/Maintenance/maint_facility_issue_report.json +127 -0
- package/examples/schemas/Maintenance/maint_incident_intake.json +174 -0
- package/examples/schemas/Maintenance/maint_inventory_restock.json +79 -0
- package/examples/schemas/Maintenance/maint_safety_audit.json +92 -0
- package/examples/schemas/Maintenance/maint_vehicle_inspection.json +112 -0
- package/examples/schemas/Maintenance/maint_work_order.json +134 -0
- package/index.js +12 -0
- package/lib/licensing.js +254 -0
- package/nodes/portalsmith-license.html +40 -0
- package/nodes/portalsmith-license.js +23 -0
- package/nodes/uibuilder-formgen.html +261 -0
- package/nodes/uibuilder-formgen.js +598 -0
- package/package.json +47 -0
- package/scripts/normalize_schema_titles.py +77 -0
- package/templates/index.html.mustache +541 -0
- package/templates/index.js.mustache +1135 -0
|
@@ -0,0 +1,921 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>PortalSmith Schema Builder</title>
|
|
7
|
+
|
|
8
|
+
<!-- Bootstrap 4 CSS -->
|
|
9
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
|
|
10
|
+
|
|
11
|
+
<!-- Bootstrap-Vue CSS -->
|
|
12
|
+
<link rel="stylesheet" href="https://unpkg.com/bootstrap-vue@2.23.1/dist/bootstrap-vue.min.css">
|
|
13
|
+
|
|
14
|
+
<!-- Font Awesome -->
|
|
15
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
body {
|
|
19
|
+
background-color: #f5f5f5;
|
|
20
|
+
padding: 20px 0;
|
|
21
|
+
}
|
|
22
|
+
.builder-container {
|
|
23
|
+
max-width: 1400px;
|
|
24
|
+
margin: 0 auto;
|
|
25
|
+
}
|
|
26
|
+
.schema-meta {
|
|
27
|
+
background: white;
|
|
28
|
+
padding: 1.5rem;
|
|
29
|
+
border-radius: 0.25rem;
|
|
30
|
+
margin-bottom: 1.5rem;
|
|
31
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
32
|
+
}
|
|
33
|
+
.section-card {
|
|
34
|
+
background: white;
|
|
35
|
+
border: 1px solid #dee2e6;
|
|
36
|
+
border-radius: 0.25rem;
|
|
37
|
+
margin-bottom: 1rem;
|
|
38
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
39
|
+
}
|
|
40
|
+
.section-header {
|
|
41
|
+
background: #f8f9fa;
|
|
42
|
+
padding: 1rem;
|
|
43
|
+
border-bottom: 1px solid #dee2e6;
|
|
44
|
+
display: flex;
|
|
45
|
+
justify-content: space-between;
|
|
46
|
+
align-items: center;
|
|
47
|
+
}
|
|
48
|
+
.section-body {
|
|
49
|
+
padding: 1rem;
|
|
50
|
+
}
|
|
51
|
+
.field-item {
|
|
52
|
+
background: #f8f9fa;
|
|
53
|
+
padding: 0.75rem;
|
|
54
|
+
margin-bottom: 0.5rem;
|
|
55
|
+
border-radius: 0.25rem;
|
|
56
|
+
border-left: 3px solid #007bff;
|
|
57
|
+
display: flex;
|
|
58
|
+
justify-content: space-between;
|
|
59
|
+
align-items: center;
|
|
60
|
+
}
|
|
61
|
+
.field-info {
|
|
62
|
+
flex: 1;
|
|
63
|
+
}
|
|
64
|
+
.field-badges {
|
|
65
|
+
display: flex;
|
|
66
|
+
gap: 0.5rem;
|
|
67
|
+
align-items: center;
|
|
68
|
+
}
|
|
69
|
+
.badge {
|
|
70
|
+
font-size: 0.75rem;
|
|
71
|
+
}
|
|
72
|
+
.empty-state {
|
|
73
|
+
text-align: center;
|
|
74
|
+
padding: 2rem;
|
|
75
|
+
color: #6c757d;
|
|
76
|
+
}
|
|
77
|
+
.action-bar {
|
|
78
|
+
background: white;
|
|
79
|
+
padding: 1rem;
|
|
80
|
+
border-radius: 0.25rem;
|
|
81
|
+
margin-top: 1.5rem;
|
|
82
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
83
|
+
display: flex;
|
|
84
|
+
gap: 0.5rem;
|
|
85
|
+
flex-wrap: wrap;
|
|
86
|
+
}
|
|
87
|
+
.preview-panel {
|
|
88
|
+
background: white;
|
|
89
|
+
border: 1px solid #dee2e6;
|
|
90
|
+
border-radius: 0.25rem;
|
|
91
|
+
padding: 1rem;
|
|
92
|
+
margin-top: 1.5rem;
|
|
93
|
+
max-height: 600px;
|
|
94
|
+
overflow-y: auto;
|
|
95
|
+
}
|
|
96
|
+
</style>
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<div id="app">
|
|
100
|
+
<div class="container-fluid builder-container">
|
|
101
|
+
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
102
|
+
<h1 class="mb-0">
|
|
103
|
+
<i class="fa fa-wrench"></i> PortalSmith Schema Builder
|
|
104
|
+
</h1>
|
|
105
|
+
<div class="d-flex align-items-center">
|
|
106
|
+
<b-button variant="outline-danger" class="mr-2" @click="newSchema">
|
|
107
|
+
<i class="fa fa-file"></i> New Schema
|
|
108
|
+
</b-button>
|
|
109
|
+
<b-button variant="info" @click="showHelp = true">
|
|
110
|
+
<i class="fa fa-question-circle"></i> Help
|
|
111
|
+
</b-button>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!-- Alert Area -->
|
|
116
|
+
<b-alert
|
|
117
|
+
v-model="showAlert"
|
|
118
|
+
:variant="alertVariant"
|
|
119
|
+
dismissible
|
|
120
|
+
class="mb-3"
|
|
121
|
+
>
|
|
122
|
+
<div v-html="alertMessage"></div>
|
|
123
|
+
</b-alert>
|
|
124
|
+
|
|
125
|
+
<!-- Schema Metadata -->
|
|
126
|
+
<div class="schema-meta">
|
|
127
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
128
|
+
<h3 class="mb-0">Form Metadata</h3>
|
|
129
|
+
<b-button variant="link" size="sm" @click="showFieldHelp('metadata')" class="p-0">
|
|
130
|
+
<i class="fa fa-info-circle text-info"></i>
|
|
131
|
+
</b-button>
|
|
132
|
+
</div>
|
|
133
|
+
<b-form-row>
|
|
134
|
+
<b-col md="6">
|
|
135
|
+
<b-form-group label="Form ID" label-for="formId">
|
|
136
|
+
<b-form-input
|
|
137
|
+
id="formId"
|
|
138
|
+
v-model="schema.formId"
|
|
139
|
+
placeholder="my_form_id"
|
|
140
|
+
:state="formIdState"
|
|
141
|
+
/>
|
|
142
|
+
<small class="form-text text-muted">
|
|
143
|
+
Unique identifier (alphanumeric, underscores, hyphens only).
|
|
144
|
+
Used as the form identifier in Node-RED.
|
|
145
|
+
</small>
|
|
146
|
+
</b-form-group>
|
|
147
|
+
</b-col>
|
|
148
|
+
<b-col md="6">
|
|
149
|
+
<b-form-group label="Form Title" label-for="title">
|
|
150
|
+
<b-form-input
|
|
151
|
+
id="title"
|
|
152
|
+
v-model="schema.title"
|
|
153
|
+
placeholder="My Form Title"
|
|
154
|
+
/>
|
|
155
|
+
<small class="form-text text-muted">Displayed at the top of the generated form</small>
|
|
156
|
+
</b-form-group>
|
|
157
|
+
</b-col>
|
|
158
|
+
</b-form-row>
|
|
159
|
+
<b-form-group label="Description" label-for="description">
|
|
160
|
+
<b-form-textarea
|
|
161
|
+
id="description"
|
|
162
|
+
v-model="schema.description"
|
|
163
|
+
placeholder="Optional form description"
|
|
164
|
+
rows="2"
|
|
165
|
+
/>
|
|
166
|
+
<small class="form-text text-muted">Optional description shown below the form title</small>
|
|
167
|
+
</b-form-group>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<!-- Sections -->
|
|
171
|
+
<div class="mb-3">
|
|
172
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
173
|
+
<h3>Sections</h3>
|
|
174
|
+
<b-button variant="primary" @click="addSection">
|
|
175
|
+
<i class="fa fa-plus"></i> Add Section
|
|
176
|
+
</b-button>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div v-if="schema.sections.length === 0" class="empty-state">
|
|
180
|
+
<i class="fa fa-inbox fa-3x mb-3"></i>
|
|
181
|
+
<p>No sections yet. Add your first section to get started.</p>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<div v-for="(section, sectionIdx) in schema.sections" :key="section.id" class="section-card">
|
|
185
|
+
<div class="section-header">
|
|
186
|
+
<div>
|
|
187
|
+
<strong>{{section.title || 'Untitled Section'}}</strong>
|
|
188
|
+
<small class="text-muted ml-2">({{section.fields.length}} fields)</small>
|
|
189
|
+
</div>
|
|
190
|
+
<div>
|
|
191
|
+
<b-button variant="outline-primary" size="sm" @click="editSection(sectionIdx)" class="mr-2">
|
|
192
|
+
<i class="fa fa-edit"></i> Edit
|
|
193
|
+
</b-button>
|
|
194
|
+
<b-button variant="outline-success" size="sm" @click="addField(sectionIdx)" class="mr-2">
|
|
195
|
+
<i class="fa fa-plus"></i> Add Field
|
|
196
|
+
</b-button>
|
|
197
|
+
<b-button variant="outline-danger" size="sm" @click="deleteSection(sectionIdx)">
|
|
198
|
+
<i class="fa fa-trash"></i> Delete
|
|
199
|
+
</b-button>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="section-body">
|
|
203
|
+
<p v-if="section.description" class="text-muted mb-3">{{section.description}}</p>
|
|
204
|
+
|
|
205
|
+
<div v-if="section.fields.length === 0" class="empty-state">
|
|
206
|
+
<p class="mb-0">No fields in this section. Add a field to get started.</p>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<div v-for="(field, fieldIdx) in section.fields" :key="field.id" class="field-item">
|
|
210
|
+
<div class="field-info">
|
|
211
|
+
<strong>{{field.label || field.id}}</strong>
|
|
212
|
+
<div class="field-badges mt-1">
|
|
213
|
+
<b-badge :variant="field.required ? 'danger' : 'secondary'">
|
|
214
|
+
{{field.type}}
|
|
215
|
+
</b-badge>
|
|
216
|
+
<b-badge v-if="field.required" variant="warning">Required</b-badge>
|
|
217
|
+
<b-badge v-if="field.validate" variant="info">{{field.validate}}</b-badge>
|
|
218
|
+
<small class="text-muted ml-2">{{field.id}}</small>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
<div>
|
|
222
|
+
<b-button variant="outline-primary" size="sm" @click="editField(sectionIdx, fieldIdx)" class="mr-2">
|
|
223
|
+
<i class="fa fa-edit"></i>
|
|
224
|
+
</b-button>
|
|
225
|
+
<b-button variant="outline-danger" size="sm" @click="deleteField(sectionIdx, fieldIdx)">
|
|
226
|
+
<i class="fa fa-trash"></i>
|
|
227
|
+
</b-button>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<!-- Action Bar -->
|
|
235
|
+
<div class="action-bar">
|
|
236
|
+
<b-button variant="success" @click="saveSchema" class="mr-2" :disabled="!canSave">
|
|
237
|
+
<i class="fa fa-save"></i> Save Schema
|
|
238
|
+
</b-button>
|
|
239
|
+
<b-button variant="info" @click="loadSchema" class="mr-2">
|
|
240
|
+
<i class="fa fa-folder-open"></i> Load Schema
|
|
241
|
+
</b-button>
|
|
242
|
+
<b-button variant="success" @click="importJSON" class="mr-2">
|
|
243
|
+
<i class="fa fa-file-import"></i> Import JSON
|
|
244
|
+
</b-button>
|
|
245
|
+
<b-button variant="info" @click="importCSV" class="mr-2">
|
|
246
|
+
<i class="fa fa-file-csv"></i> Import CSV
|
|
247
|
+
</b-button>
|
|
248
|
+
<b-button variant="secondary" @click="exportJSON" class="mr-2">
|
|
249
|
+
<i class="fa fa-file-export"></i> Export JSON
|
|
250
|
+
</b-button>
|
|
251
|
+
<b-button variant="primary" @click="generateForm" :disabled="!canGenerate">
|
|
252
|
+
<i class="fa fa-magic"></i> Generate Form
|
|
253
|
+
</b-button>
|
|
254
|
+
<b-button variant="outline-secondary" @click="showHelp = true" class="ml-auto">
|
|
255
|
+
<i class="fa fa-question-circle"></i> Help
|
|
256
|
+
</b-button>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<!-- JSON Preview (Collapsible) -->
|
|
260
|
+
<b-card class="mt-3">
|
|
261
|
+
<div slot="header" class="d-flex justify-content-between align-items-center">
|
|
262
|
+
<span>JSON Preview</span>
|
|
263
|
+
<b-button variant="outline-secondary" size="sm" @click="showPreview = !showPreview">
|
|
264
|
+
{{showPreview ? 'Hide' : 'Show'}}
|
|
265
|
+
</b-button>
|
|
266
|
+
</div>
|
|
267
|
+
<div v-if="showPreview" class="preview-panel">
|
|
268
|
+
<pre>{{jsonPreview}}</pre>
|
|
269
|
+
</div>
|
|
270
|
+
</b-card>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<!-- Section Editor Modal -->
|
|
274
|
+
<b-modal
|
|
275
|
+
id="section-modal"
|
|
276
|
+
:title="editingSection !== null ? 'Edit Section' : 'Add Section'"
|
|
277
|
+
@ok="saveSection"
|
|
278
|
+
@cancel="cancelEditSection"
|
|
279
|
+
>
|
|
280
|
+
<b-form-group label="Section ID" label-for="section-id">
|
|
281
|
+
<b-form-input
|
|
282
|
+
id="section-id"
|
|
283
|
+
v-model="currentSection.id"
|
|
284
|
+
placeholder="section_id"
|
|
285
|
+
/>
|
|
286
|
+
</b-form-group>
|
|
287
|
+
<b-form-group label="Section Title" label-for="section-title">
|
|
288
|
+
<b-form-input
|
|
289
|
+
id="section-title"
|
|
290
|
+
v-model="currentSection.title"
|
|
291
|
+
placeholder="Section Title"
|
|
292
|
+
/>
|
|
293
|
+
</b-form-group>
|
|
294
|
+
<b-form-group label="Description" label-for="section-desc">
|
|
295
|
+
<b-form-textarea
|
|
296
|
+
id="section-desc"
|
|
297
|
+
v-model="currentSection.description"
|
|
298
|
+
placeholder="Optional description"
|
|
299
|
+
rows="2"
|
|
300
|
+
/>
|
|
301
|
+
</b-form-group>
|
|
302
|
+
</b-modal>
|
|
303
|
+
|
|
304
|
+
<!-- Field Editor Modal -->
|
|
305
|
+
<b-modal
|
|
306
|
+
id="field-modal"
|
|
307
|
+
:title="editingField ? 'Edit Field' : 'Add Field'"
|
|
308
|
+
size="lg"
|
|
309
|
+
@ok="saveField"
|
|
310
|
+
@cancel="cancelEditField"
|
|
311
|
+
>
|
|
312
|
+
<b-form-row>
|
|
313
|
+
<b-col md="6">
|
|
314
|
+
<b-form-group label="Field ID" label-for="field-id">
|
|
315
|
+
<b-form-input
|
|
316
|
+
id="field-id"
|
|
317
|
+
v-model="currentField.id"
|
|
318
|
+
placeholder="field_id"
|
|
319
|
+
:state="fieldIdState"
|
|
320
|
+
/>
|
|
321
|
+
<small class="form-text text-muted">Unique identifier</small>
|
|
322
|
+
</b-form-group>
|
|
323
|
+
</b-col>
|
|
324
|
+
<b-col md="6">
|
|
325
|
+
<b-form-group label="Field Label" label-for="field-label">
|
|
326
|
+
<b-form-input
|
|
327
|
+
id="field-label"
|
|
328
|
+
v-model="currentField.label"
|
|
329
|
+
placeholder="Field Label"
|
|
330
|
+
/>
|
|
331
|
+
</b-form-group>
|
|
332
|
+
</b-col>
|
|
333
|
+
</b-form-row>
|
|
334
|
+
|
|
335
|
+
<b-form-row>
|
|
336
|
+
<b-col md="6">
|
|
337
|
+
<b-form-group label="Field Type" label-for="field-type">
|
|
338
|
+
<b-form-select
|
|
339
|
+
id="field-type"
|
|
340
|
+
v-model="currentField.type"
|
|
341
|
+
:options="fieldTypes"
|
|
342
|
+
@change="onFieldTypeChange"
|
|
343
|
+
/>
|
|
344
|
+
<small class="form-text text-muted">
|
|
345
|
+
<strong>Text:</strong> Single-line input (can be email, tel, url, password)<br>
|
|
346
|
+
<strong>Textarea:</strong> Multi-line text input<br>
|
|
347
|
+
<strong>Number:</strong> Numeric input with min/max/step<br>
|
|
348
|
+
<strong>Select:</strong> Dropdown menu with options<br>
|
|
349
|
+
<strong>Radio:</strong> Radio button group with options<br>
|
|
350
|
+
<strong>Checkbox:</strong> Boolean checkbox<br>
|
|
351
|
+
<strong>Date:</strong> Date picker
|
|
352
|
+
</small>
|
|
353
|
+
</b-form-group>
|
|
354
|
+
</b-col>
|
|
355
|
+
<b-col md="6">
|
|
356
|
+
<b-form-group label="Input Type" label-for="input-type" v-if="currentField.type === 'text'">
|
|
357
|
+
<b-form-select
|
|
358
|
+
id="input-type"
|
|
359
|
+
v-model="currentField.inputType"
|
|
360
|
+
:options="inputTypes"
|
|
361
|
+
/>
|
|
362
|
+
</b-form-group>
|
|
363
|
+
</b-col>
|
|
364
|
+
</b-form-row>
|
|
365
|
+
|
|
366
|
+
<b-form-row>
|
|
367
|
+
<b-col md="6">
|
|
368
|
+
<b-form-checkbox v-model="currentField.required">
|
|
369
|
+
Required Field
|
|
370
|
+
</b-form-checkbox>
|
|
371
|
+
</b-col>
|
|
372
|
+
<b-col md="6">
|
|
373
|
+
<b-form-group label="Default Value" label-for="default-value">
|
|
374
|
+
<b-form-input
|
|
375
|
+
id="default-value"
|
|
376
|
+
v-model="currentField.defaultValue"
|
|
377
|
+
:type="currentField.type === 'number' ? 'number' : 'text'"
|
|
378
|
+
placeholder="Default value"
|
|
379
|
+
/>
|
|
380
|
+
</b-form-group>
|
|
381
|
+
</b-col>
|
|
382
|
+
</b-form-row>
|
|
383
|
+
|
|
384
|
+
<b-form-group label="Placeholder" label-for="placeholder">
|
|
385
|
+
<b-form-input
|
|
386
|
+
id="placeholder"
|
|
387
|
+
v-model="currentField.placeholder"
|
|
388
|
+
placeholder="Placeholder text"
|
|
389
|
+
/>
|
|
390
|
+
</b-form-group>
|
|
391
|
+
|
|
392
|
+
<b-form-group label="Help Text" label-for="help">
|
|
393
|
+
<b-form-textarea
|
|
394
|
+
id="help"
|
|
395
|
+
v-model="currentField.help"
|
|
396
|
+
placeholder="Help text shown below field"
|
|
397
|
+
rows="2"
|
|
398
|
+
/>
|
|
399
|
+
</b-form-group>
|
|
400
|
+
|
|
401
|
+
<b-form-row v-if="currentField.type === 'textarea'">
|
|
402
|
+
<b-col md="6">
|
|
403
|
+
<b-form-group label="Rows" label-for="rows">
|
|
404
|
+
<b-form-input
|
|
405
|
+
id="rows"
|
|
406
|
+
v-model.number="currentField.rows"
|
|
407
|
+
type="number"
|
|
408
|
+
min="1"
|
|
409
|
+
max="20"
|
|
410
|
+
/>
|
|
411
|
+
</b-form-group>
|
|
412
|
+
</b-col>
|
|
413
|
+
</b-form-row>
|
|
414
|
+
|
|
415
|
+
<b-form-row v-if="currentField.type === 'number'">
|
|
416
|
+
<b-col md="4">
|
|
417
|
+
<b-form-group label="Min" label-for="min">
|
|
418
|
+
<b-form-input
|
|
419
|
+
id="min"
|
|
420
|
+
v-model.number="currentField.min"
|
|
421
|
+
type="number"
|
|
422
|
+
/>
|
|
423
|
+
</b-form-group>
|
|
424
|
+
</b-col>
|
|
425
|
+
<b-col md="4">
|
|
426
|
+
<b-form-group label="Max" label-for="max">
|
|
427
|
+
<b-form-input
|
|
428
|
+
id="max"
|
|
429
|
+
v-model.number="currentField.max"
|
|
430
|
+
type="number"
|
|
431
|
+
/>
|
|
432
|
+
</b-form-group>
|
|
433
|
+
</b-col>
|
|
434
|
+
<b-col md="4">
|
|
435
|
+
<b-form-group label="Step" label-for="step">
|
|
436
|
+
<b-form-input
|
|
437
|
+
id="step"
|
|
438
|
+
v-model.number="currentField.step"
|
|
439
|
+
type="number"
|
|
440
|
+
/>
|
|
441
|
+
</b-form-group>
|
|
442
|
+
</b-col>
|
|
443
|
+
</b-form-row>
|
|
444
|
+
|
|
445
|
+
<b-form-group
|
|
446
|
+
v-if="currentField.type === 'select' || currentField.type === 'radio'"
|
|
447
|
+
label="Options"
|
|
448
|
+
label-for="options"
|
|
449
|
+
>
|
|
450
|
+
<small class="form-text text-muted mb-2">
|
|
451
|
+
<strong>Required for Select/Radio fields.</strong> Each option needs a <strong>value</strong> (stored data) and <strong>text</strong> (display label).
|
|
452
|
+
For a blank/default option, use empty value with text like "Select...".
|
|
453
|
+
</small>
|
|
454
|
+
<div v-if="currentField.options.length === 0" class="alert alert-warning mb-2">
|
|
455
|
+
<i class="fa fa-exclamation-triangle"></i> No options defined. Add at least one option for this field to work.
|
|
456
|
+
</div>
|
|
457
|
+
<div v-for="(option, optIdx) in currentField.options" :key="'option-' + optIdx" class="mb-3 border rounded p-3 bg-light">
|
|
458
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
459
|
+
<small class="text-muted font-weight-bold">Option {{optIdx + 1}}</small>
|
|
460
|
+
<b-button variant="outline-danger" size="sm" @click="removeOption(optIdx)" title="Remove this option">
|
|
461
|
+
<i class="fa fa-trash"></i> Remove
|
|
462
|
+
</b-button>
|
|
463
|
+
</div>
|
|
464
|
+
<b-form-row>
|
|
465
|
+
<b-col md="5">
|
|
466
|
+
<b-form-group label="Value" :label-for="'opt-value-' + optIdx" label-size="sm" class="mb-2">
|
|
467
|
+
<b-form-input
|
|
468
|
+
:id="'opt-value-' + optIdx"
|
|
469
|
+
v-model="option.value"
|
|
470
|
+
placeholder="e.g., 'static' or 'option1'"
|
|
471
|
+
@paste="handleOptionPaste($event, optIdx, 'value')"
|
|
472
|
+
/>
|
|
473
|
+
<small class="form-text text-muted">The stored value (what gets saved)</small>
|
|
474
|
+
</b-form-group>
|
|
475
|
+
</b-col>
|
|
476
|
+
<b-col md="7">
|
|
477
|
+
<b-form-group label="Display Text" :label-for="'opt-text-' + optIdx" label-size="sm" class="mb-2">
|
|
478
|
+
<b-form-input
|
|
479
|
+
:id="'opt-text-' + optIdx"
|
|
480
|
+
v-model="option.text"
|
|
481
|
+
placeholder="e.g., 'Static IP' or 'Option 1'"
|
|
482
|
+
@paste="handleOptionPaste($event, optIdx, 'text')"
|
|
483
|
+
/>
|
|
484
|
+
<small class="form-text text-muted">What users see in the dropdown</small>
|
|
485
|
+
</b-form-group>
|
|
486
|
+
</b-col>
|
|
487
|
+
</b-form-row>
|
|
488
|
+
</div>
|
|
489
|
+
<div class="d-flex justify-content-between align-items-center mt-2">
|
|
490
|
+
<b-button variant="outline-primary" size="sm" @click="addOption">
|
|
491
|
+
<i class="fa fa-plus"></i> Add Option
|
|
492
|
+
</b-button>
|
|
493
|
+
<b-button variant="outline-info" size="sm" @click="showBulkOptions = !showBulkOptions">
|
|
494
|
+
<i class="fa fa-paste"></i> Bulk Add
|
|
495
|
+
</b-button>
|
|
496
|
+
</div>
|
|
497
|
+
<div v-if="showBulkOptions" class="mt-2 p-2 border rounded bg-light">
|
|
498
|
+
<b-form-group label="Paste Options (one per line)" label-for="bulk-options" class="mb-2">
|
|
499
|
+
<b-form-textarea
|
|
500
|
+
id="bulk-options"
|
|
501
|
+
v-model="bulkOptionsText"
|
|
502
|
+
placeholder='value="static" | text="Static IP" value="dhcp" | text="DHCP" value="reserved" | text="Reserved"'
|
|
503
|
+
rows="5"
|
|
504
|
+
/>
|
|
505
|
+
<small class="form-text text-muted">
|
|
506
|
+
Format: <code>value="xxx" | text="YYY"</code> (one per line) or just <code>Option 1</code> (will use same for value and text)
|
|
507
|
+
</small>
|
|
508
|
+
</b-form-group>
|
|
509
|
+
<b-button variant="primary" size="sm" @click="parseBulkOptions" class="mr-2">
|
|
510
|
+
<i class="fa fa-check"></i> Parse & Add
|
|
511
|
+
</b-button>
|
|
512
|
+
<b-button variant="outline-secondary" size="sm" @click="showBulkOptions = false; bulkOptionsText = ''">
|
|
513
|
+
Cancel
|
|
514
|
+
</b-button>
|
|
515
|
+
</div>
|
|
516
|
+
<div class="mt-2">
|
|
517
|
+
<small class="text-muted">
|
|
518
|
+
<strong>Example:</strong> value="developer" | text="Software Developer"
|
|
519
|
+
(user sees "Software Developer", form stores "developer")
|
|
520
|
+
</small>
|
|
521
|
+
</div>
|
|
522
|
+
</b-form-group>
|
|
523
|
+
|
|
524
|
+
<!-- Key-Value Pairs Editor -->
|
|
525
|
+
<b-form-group
|
|
526
|
+
v-if="currentField.type === 'keyvalue'"
|
|
527
|
+
label="Key-Value Configuration"
|
|
528
|
+
label-for="keyvalue-config"
|
|
529
|
+
>
|
|
530
|
+
<b-form-row class="mb-3">
|
|
531
|
+
<b-col md="6">
|
|
532
|
+
<b-form-group label="Input Mode" label-for="keyvalue-mode">
|
|
533
|
+
<b-form-select
|
|
534
|
+
id="keyvalue-mode"
|
|
535
|
+
v-model="currentField.keyvalueMode"
|
|
536
|
+
:options="[
|
|
537
|
+
{ value: 'pairs', text: 'Individual Pairs' },
|
|
538
|
+
{ value: 'delimiter', text: 'Delimiter Text (field=value)' }
|
|
539
|
+
]"
|
|
540
|
+
@change="onKeyValueModeChange"
|
|
541
|
+
/>
|
|
542
|
+
<small class="form-text text-muted">
|
|
543
|
+
<strong>Individual Pairs:</strong> Users add/edit pairs one at a time<br>
|
|
544
|
+
<strong>Delimiter Text:</strong> Users enter "key=value" format in a text area
|
|
545
|
+
</small>
|
|
546
|
+
</b-form-group>
|
|
547
|
+
</b-col>
|
|
548
|
+
<b-col md="6" v-if="currentField.keyvalueMode === 'delimiter'">
|
|
549
|
+
<b-form-group label="Delimiter" label-for="keyvalue-delimiter">
|
|
550
|
+
<b-form-input
|
|
551
|
+
id="keyvalue-delimiter"
|
|
552
|
+
v-model="currentField.keyvalueDelimiter"
|
|
553
|
+
placeholder="="
|
|
554
|
+
maxlength="5"
|
|
555
|
+
/>
|
|
556
|
+
<small class="form-text text-muted">
|
|
557
|
+
Character(s) that separate key from value (e.g., "=", ":", "|")
|
|
558
|
+
</small>
|
|
559
|
+
</b-form-group>
|
|
560
|
+
</b-col>
|
|
561
|
+
</b-form-row>
|
|
562
|
+
|
|
563
|
+
<!-- Individual Pairs Mode -->
|
|
564
|
+
<div v-if="!currentField.keyvalueMode || currentField.keyvalueMode === 'pairs'">
|
|
565
|
+
<small class="form-text text-muted mb-2">
|
|
566
|
+
<strong>Required for Key-Value fields.</strong> Each pair has a <strong>key</strong> (field name) and <strong>value</strong> (field value).
|
|
567
|
+
Users can add multiple pairs dynamically in the form.
|
|
568
|
+
</small>
|
|
569
|
+
<div v-if="currentField.pairs.length === 0" class="alert alert-warning mb-2">
|
|
570
|
+
<i class="fa fa-exclamation-triangle"></i> No pairs defined. Add at least one pair for this field to work.
|
|
571
|
+
</div>
|
|
572
|
+
<div v-for="(pair, pairIdx) in currentField.pairs" :key="'pair-' + pairIdx" class="mb-3 border rounded p-3 bg-light">
|
|
573
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
574
|
+
<small class="text-muted font-weight-bold">Pair {{pairIdx + 1}}</small>
|
|
575
|
+
<b-button variant="outline-danger" size="sm" @click="removePair(pairIdx)" title="Remove this pair">
|
|
576
|
+
<i class="fa fa-trash"></i> Remove
|
|
577
|
+
</b-button>
|
|
578
|
+
</div>
|
|
579
|
+
<b-form-row>
|
|
580
|
+
<b-col md="5">
|
|
581
|
+
<b-form-group label="Key" :label-for="'pair-key-' + pairIdx" label-size="sm" class="mb-2">
|
|
582
|
+
<b-form-input
|
|
583
|
+
:id="'pair-key-' + pairIdx"
|
|
584
|
+
v-model="pair.key"
|
|
585
|
+
placeholder="e.g., 'name' or 'ip_address'"
|
|
586
|
+
/>
|
|
587
|
+
<small class="form-text text-muted">The key/field name</small>
|
|
588
|
+
</b-form-group>
|
|
589
|
+
</b-col>
|
|
590
|
+
<b-col md="7">
|
|
591
|
+
<b-form-group label="Value" :label-for="'pair-value-' + pairIdx" label-size="sm" class="mb-2">
|
|
592
|
+
<b-form-input
|
|
593
|
+
:id="'pair-value-' + pairIdx"
|
|
594
|
+
v-model="pair.value"
|
|
595
|
+
placeholder="e.g., 'John Doe' or '192.168.1.1'"
|
|
596
|
+
/>
|
|
597
|
+
<small class="form-text text-muted">The value for this key</small>
|
|
598
|
+
</b-form-group>
|
|
599
|
+
</b-col>
|
|
600
|
+
</b-form-row>
|
|
601
|
+
</div>
|
|
602
|
+
<b-button variant="outline-primary" size="sm" @click="addPair">
|
|
603
|
+
<i class="fa fa-plus"></i> Add Pair
|
|
604
|
+
</b-button>
|
|
605
|
+
<div class="mt-2">
|
|
606
|
+
<small class="text-muted">
|
|
607
|
+
<strong>Note:</strong> These are default/example pairs. Users can add, edit, and remove pairs in the generated form.
|
|
608
|
+
</small>
|
|
609
|
+
</div>
|
|
610
|
+
</div>
|
|
611
|
+
|
|
612
|
+
<!-- Delimiter Mode -->
|
|
613
|
+
<div v-if="currentField.keyvalueMode === 'delimiter'" class="mt-3">
|
|
614
|
+
<b-alert variant="info" show class="mb-3">
|
|
615
|
+
<strong>Delimiter Text Mode</strong><br>
|
|
616
|
+
Users will enter key-value pairs in a text area, one per line, using the format:
|
|
617
|
+
<code>key{{currentField.keyvalueDelimiter || '='}}value</code>
|
|
618
|
+
</b-alert>
|
|
619
|
+
|
|
620
|
+
<b-alert variant="success" show class="mb-3">
|
|
621
|
+
<i class="fa fa-info-circle"></i> <strong>Preview:</strong> In the generated form, users will see an editable textarea where they can enter key-value pairs, one per line, using the format: <code>key{{currentField.keyvalueDelimiter || '='}}value</code>
|
|
622
|
+
</b-alert>
|
|
623
|
+
|
|
624
|
+
<div class="border rounded p-3 bg-light mt-3">
|
|
625
|
+
<strong>Example Format:</strong>
|
|
626
|
+
<pre class="mb-0 mt-2 bg-white p-2 border rounded"><code>name{{currentField.keyvalueDelimiter || '='}}John Doe
|
|
627
|
+
ip_address{{currentField.keyvalueDelimiter || '='}}192.168.1.1
|
|
628
|
+
email{{currentField.keyvalueDelimiter || '='}}john@example.com</code></pre>
|
|
629
|
+
</div>
|
|
630
|
+
<small class="form-text text-muted mt-2">
|
|
631
|
+
<strong>Note:</strong> The delimiter field above controls what character separates the key from the value.
|
|
632
|
+
Users can add as many pairs as needed in the generated form.
|
|
633
|
+
</small>
|
|
634
|
+
</div>
|
|
635
|
+
</b-form-group>
|
|
636
|
+
|
|
637
|
+
<b-form-group label="Validation" label-for="validate">
|
|
638
|
+
<b-form-select
|
|
639
|
+
id="validate"
|
|
640
|
+
v-model="currentField.validate"
|
|
641
|
+
:options="validationTypes"
|
|
642
|
+
/>
|
|
643
|
+
<b-form-input
|
|
644
|
+
v-if="currentField.validate === 'regex'"
|
|
645
|
+
v-model="currentField.validatePattern"
|
|
646
|
+
placeholder="Regex pattern (e.g., ^[A-Z]{2}\\d{4}$)"
|
|
647
|
+
class="mt-2"
|
|
648
|
+
/>
|
|
649
|
+
<small class="form-text text-muted">
|
|
650
|
+
<strong>Email:</strong> Validates email format (user@domain.com)<br>
|
|
651
|
+
<strong>Phone:</strong> Validates phone number format (10+ digits)<br>
|
|
652
|
+
<strong>Regex:</strong> Custom pattern validation (e.g., ^[A-Z]{2}\\d{4}$ for codes like AB1234)
|
|
653
|
+
</small>
|
|
654
|
+
</b-form-group>
|
|
655
|
+
|
|
656
|
+
<b-form-group label="Validation Message" label-for="validate-msg" v-if="currentField.validate">
|
|
657
|
+
<b-form-input
|
|
658
|
+
id="validate-msg"
|
|
659
|
+
v-model="currentField.validateMessage"
|
|
660
|
+
placeholder="Custom error message"
|
|
661
|
+
/>
|
|
662
|
+
</b-form-group>
|
|
663
|
+
</b-modal>
|
|
664
|
+
|
|
665
|
+
<!-- CSV Import Modal -->
|
|
666
|
+
<b-modal
|
|
667
|
+
id="csv-modal"
|
|
668
|
+
title="Import from CSV"
|
|
669
|
+
size="lg"
|
|
670
|
+
@ok="processCSV"
|
|
671
|
+
@cancel="cancelCSV"
|
|
672
|
+
>
|
|
673
|
+
<b-form-group label="CSV Data" label-for="csv-data">
|
|
674
|
+
<b-form-textarea
|
|
675
|
+
id="csv-data"
|
|
676
|
+
v-model="csvData"
|
|
677
|
+
rows="10"
|
|
678
|
+
placeholder="Paste CSV data here. First row should be headers: id,label,type,required,section"
|
|
679
|
+
/>
|
|
680
|
+
<small class="form-text text-muted">
|
|
681
|
+
Expected columns: id, label, type, required (true/false), section (optional), placeholder, help, validate
|
|
682
|
+
</small>
|
|
683
|
+
</b-form-group>
|
|
684
|
+
<b-alert variant="info" show>
|
|
685
|
+
<strong>CSV Format:</strong><br>
|
|
686
|
+
id,label,type,required,section,placeholder<br>
|
|
687
|
+
fullName,Full Name,text,true,personal,John Doe<br>
|
|
688
|
+
email,Email Address,text,true,personal,john@example.com
|
|
689
|
+
</b-alert>
|
|
690
|
+
</b-modal>
|
|
691
|
+
|
|
692
|
+
<!-- JSON Import Modal -->
|
|
693
|
+
<b-modal
|
|
694
|
+
id="json-modal"
|
|
695
|
+
title="Import from JSON"
|
|
696
|
+
size="lg"
|
|
697
|
+
@ok="processJSON"
|
|
698
|
+
@cancel="cancelJSON"
|
|
699
|
+
>
|
|
700
|
+
<b-form-group label="JSON Schema" label-for="json-data">
|
|
701
|
+
<b-form-textarea
|
|
702
|
+
id="json-data"
|
|
703
|
+
v-model="jsonData"
|
|
704
|
+
rows="15"
|
|
705
|
+
placeholder="Paste JSON schema here"
|
|
706
|
+
/>
|
|
707
|
+
<small class="form-text text-muted">
|
|
708
|
+
Paste a complete schema JSON. Must include schemaVersion, formId, title, and sections array.
|
|
709
|
+
</small>
|
|
710
|
+
</b-form-group>
|
|
711
|
+
</b-modal>
|
|
712
|
+
|
|
713
|
+
<!-- Save Schema Modal -->
|
|
714
|
+
<b-modal
|
|
715
|
+
id="save-modal"
|
|
716
|
+
title="Save Schema"
|
|
717
|
+
@ok="handleSaveOk"
|
|
718
|
+
@cancel="cancelSave"
|
|
719
|
+
@hide="cancelSave"
|
|
720
|
+
ok-title="Save"
|
|
721
|
+
cancel-title="Cancel"
|
|
722
|
+
>
|
|
723
|
+
<b-form-group label="Schema Name" label-for="save-name">
|
|
724
|
+
<b-form-input
|
|
725
|
+
id="save-name"
|
|
726
|
+
v-model="saveName"
|
|
727
|
+
placeholder="My Schema"
|
|
728
|
+
/>
|
|
729
|
+
<small class="form-text text-muted">
|
|
730
|
+
Enter a name for this schema. It will be saved in your browser's local storage.
|
|
731
|
+
</small>
|
|
732
|
+
</b-form-group>
|
|
733
|
+
<b-alert variant="info" show>
|
|
734
|
+
<strong>Saved Schemas:</strong>
|
|
735
|
+
<ul class="mb-0 mt-2" v-if="savedSchemas.length > 0">
|
|
736
|
+
<li v-for="(saved, idx) in savedSchemas" :key="idx">
|
|
737
|
+
{{saved.name}} <small class="text-muted">({{saved.date}})</small>
|
|
738
|
+
<b-button variant="link" size="sm" @click="deleteSavedSchema(idx)" class="p-0 ml-2 text-danger">
|
|
739
|
+
<i class="fa fa-trash"></i>
|
|
740
|
+
</b-button>
|
|
741
|
+
</li>
|
|
742
|
+
</ul>
|
|
743
|
+
<p v-else class="mb-0">No saved schemas yet.</p>
|
|
744
|
+
</b-alert>
|
|
745
|
+
</b-modal>
|
|
746
|
+
|
|
747
|
+
<!-- Load Schema Modal -->
|
|
748
|
+
<b-modal
|
|
749
|
+
id="load-modal"
|
|
750
|
+
title="Load Saved Schema"
|
|
751
|
+
@ok="handleLoadOk"
|
|
752
|
+
@cancel="cancelLoad"
|
|
753
|
+
@hide="cancelLoad"
|
|
754
|
+
ok-title="Load"
|
|
755
|
+
cancel-title="Cancel"
|
|
756
|
+
>
|
|
757
|
+
<b-form-group label="Select Schema" label-for="load-select" v-if="savedSchemas.length > 0">
|
|
758
|
+
<b-form-select
|
|
759
|
+
id="load-select"
|
|
760
|
+
v-model="selectedLoadSchema"
|
|
761
|
+
:options="savedSchemaOptions"
|
|
762
|
+
/>
|
|
763
|
+
<small class="form-text text-muted mt-2">
|
|
764
|
+
Selected schema will replace your current work. Make sure to save first if needed.
|
|
765
|
+
</small>
|
|
766
|
+
</b-form-group>
|
|
767
|
+
<b-alert variant="warning" show v-else>
|
|
768
|
+
No saved schemas found. Save a schema first to load it later.
|
|
769
|
+
</b-alert>
|
|
770
|
+
</b-modal>
|
|
771
|
+
|
|
772
|
+
<!-- Help Modal -->
|
|
773
|
+
<b-modal
|
|
774
|
+
id="help-modal"
|
|
775
|
+
title="Schema Builder Help"
|
|
776
|
+
size="lg"
|
|
777
|
+
v-model="showHelp"
|
|
778
|
+
@hide="showHelp = false"
|
|
779
|
+
ok-only
|
|
780
|
+
ok-title="Got it!"
|
|
781
|
+
>
|
|
782
|
+
<div>
|
|
783
|
+
<h4>Getting Started</h4>
|
|
784
|
+
<ol>
|
|
785
|
+
<li><strong>Set Form Metadata:</strong> Enter a unique Form ID (alphanumeric, underscores, hyphens), title, and optional description.</li>
|
|
786
|
+
<li><strong>Add Sections:</strong> Click "Add Section" to create form sections. Each section groups related fields.</li>
|
|
787
|
+
<li><strong>Add Fields:</strong> Click "Add Field" within a section to add form fields. Configure field properties in the editor.</li>
|
|
788
|
+
<li><strong>Generate Form:</strong> Click "Generate Form" to send the schema to Node-RED and create the form.</li>
|
|
789
|
+
</ol>
|
|
790
|
+
|
|
791
|
+
<h4 class="mt-4">Field Types</h4>
|
|
792
|
+
<ul>
|
|
793
|
+
<li><strong>Text:</strong> Single-line text input. Can be configured as email, tel, url, or password via Input Type.</li>
|
|
794
|
+
<li><strong>Textarea:</strong> Multi-line text input. Set number of rows (default: 3).</li>
|
|
795
|
+
<li><strong>Number:</strong> Numeric input. Set min, max, and step values.</li>
|
|
796
|
+
<li><strong>Select:</strong> Dropdown menu. Requires options (value/text pairs). See "Options" section below.</li>
|
|
797
|
+
<li><strong>Radio:</strong> Radio button group. Requires options (value/text pairs). See "Options" section below.</li>
|
|
798
|
+
<li><strong>Checkbox:</strong> Boolean checkbox. Default value: false.</li>
|
|
799
|
+
<li><strong>Date:</strong> HTML5 date picker.</li>
|
|
800
|
+
</ul>
|
|
801
|
+
|
|
802
|
+
<h4 class="mt-4">Field Properties</h4>
|
|
803
|
+
<ul>
|
|
804
|
+
<li><strong>Field ID:</strong> Unique identifier within the section (alphanumeric, underscores, hyphens). Used as the data key.</li>
|
|
805
|
+
<li><strong>Label:</strong> Display label shown to users.</li>
|
|
806
|
+
<li><strong>Required:</strong> Mark field as required for form validation.</li>
|
|
807
|
+
<li><strong>Placeholder:</strong> Hint text shown in empty fields.</li>
|
|
808
|
+
<li><strong>Help Text:</strong> Additional guidance shown below the field.</li>
|
|
809
|
+
<li><strong>Default Value:</strong> Pre-filled value when form loads.</li>
|
|
810
|
+
<li><strong>Validation:</strong> Email, phone, or custom regex pattern validation.</li>
|
|
811
|
+
</ul>
|
|
812
|
+
|
|
813
|
+
<h4 class="mt-4">Options for Select/Radio Fields</h4>
|
|
814
|
+
<p>Select and Radio fields require <strong>options</strong> to be defined. Each option has two parts:</p>
|
|
815
|
+
<ul>
|
|
816
|
+
<li><strong>Value:</strong> The actual data stored when this option is selected (e.g., "developer", "option1", "")</li>
|
|
817
|
+
<li><strong>Text:</strong> The label displayed to users (e.g., "Software Developer", "Option 1", "Select...")</li>
|
|
818
|
+
</ul>
|
|
819
|
+
<p><strong>How to add options:</strong></p>
|
|
820
|
+
<ol>
|
|
821
|
+
<li>Select field type "Select" or "Radio"</li>
|
|
822
|
+
<li>The Options section will appear in the field editor</li>
|
|
823
|
+
<li>Click "Add Option" to add a new option row</li>
|
|
824
|
+
<li>Fill in the <strong>value</strong> (what gets stored) and <strong>text</strong> (what users see)</li>
|
|
825
|
+
<li>Add as many options as needed</li>
|
|
826
|
+
<li>Remove options using the X button</li>
|
|
827
|
+
</ol>
|
|
828
|
+
<p><strong>Example:</strong></p>
|
|
829
|
+
<pre class="bg-light p-2 rounded">Value: "developer" | Text: "Software Developer"
|
|
830
|
+
Value: "designer" | Text: "UI/UX Designer"
|
|
831
|
+
Value: "" | Text: "Select a position..." (blank/default option)</pre>
|
|
832
|
+
<p><strong>Note:</strong> For a blank/default option (like "Select..."), use an empty value with descriptive text.</p>
|
|
833
|
+
|
|
834
|
+
<h4 class="mt-4">CSV Import Format</h4>
|
|
835
|
+
<p>CSV files should have the following columns:</p>
|
|
836
|
+
<ul>
|
|
837
|
+
<li><strong>Required:</strong> id, label, type</li>
|
|
838
|
+
<li><strong>Optional:</strong> required (true/false), section (groups fields), placeholder, help, validate</li>
|
|
839
|
+
</ul>
|
|
840
|
+
<p><strong>Example:</strong></p>
|
|
841
|
+
<pre class="bg-light p-2 rounded">id,label,type,required,section,placeholder,help,validate
|
|
842
|
+
fullName,Full Name,text,true,personal,John Doe,Enter legal name,
|
|
843
|
+
email,Email Address,text,true,personal,john@example.com,,email
|
|
844
|
+
phone,Phone Number,text,true,personal,+1 555-1234,Include country code,phone</pre>
|
|
845
|
+
|
|
846
|
+
<h4 class="mt-4">JSON Schema Structure</h4>
|
|
847
|
+
<p>Schemas must follow this structure:</p>
|
|
848
|
+
<pre class="bg-light p-2 rounded">{
|
|
849
|
+
"schemaVersion": "1.0",
|
|
850
|
+
"formId": "my_form",
|
|
851
|
+
"title": "My Form",
|
|
852
|
+
"description": "Optional description",
|
|
853
|
+
"sections": [
|
|
854
|
+
{
|
|
855
|
+
"id": "section1",
|
|
856
|
+
"title": "Section Title",
|
|
857
|
+
"description": "Optional",
|
|
858
|
+
"fields": [
|
|
859
|
+
{
|
|
860
|
+
"id": "field1",
|
|
861
|
+
"label": "Field Label",
|
|
862
|
+
"type": "text",
|
|
863
|
+
"required": true
|
|
864
|
+
}
|
|
865
|
+
]
|
|
866
|
+
}
|
|
867
|
+
],
|
|
868
|
+
"actions": []
|
|
869
|
+
}</pre>
|
|
870
|
+
|
|
871
|
+
<h4 class="mt-4">Validation Rules</h4>
|
|
872
|
+
<ul>
|
|
873
|
+
<li><strong>Form ID:</strong> Must be unique, alphanumeric with underscores/hyphens only</li>
|
|
874
|
+
<li><strong>Section ID:</strong> Must be unique within the form</li>
|
|
875
|
+
<li><strong>Field ID:</strong> Must be unique within its section</li>
|
|
876
|
+
<li><strong>Select/Radio:</strong> Must have at least one option defined</li>
|
|
877
|
+
<li><strong>Required Fields:</strong> Will be validated before form submission</li>
|
|
878
|
+
</ul>
|
|
879
|
+
|
|
880
|
+
<h4 class="mt-4">Tips</h4>
|
|
881
|
+
<ul>
|
|
882
|
+
<li>Use descriptive section titles to organize related fields</li>
|
|
883
|
+
<li>Add help text to guide users on expected input formats</li>
|
|
884
|
+
<li>Use validation (email, phone, regex) to ensure data quality</li>
|
|
885
|
+
<li>Export JSON to save your schema for reuse</li>
|
|
886
|
+
<li>Import existing schemas to modify or use as templates</li>
|
|
887
|
+
<li>Check the JSON Preview to see the final schema structure</li>
|
|
888
|
+
</ul>
|
|
889
|
+
|
|
890
|
+
<h4 class="mt-4">Integration</h4>
|
|
891
|
+
<p>After clicking "Generate Form", the schema is sent to Node-RED. Connect your flow:</p>
|
|
892
|
+
<ol>
|
|
893
|
+
<li>Builder uibuilder node → Switch node (route by type: 'generate')</li>
|
|
894
|
+
<li>Switch → Function node (prepare message for formgen node)</li>
|
|
895
|
+
<li>Function → uibuilder-formgen node</li>
|
|
896
|
+
<li>formgen node → Builder uibuilder node (for success response)</li>
|
|
897
|
+
</ol>
|
|
898
|
+
<p>See <code>examples/schema-builder-integration.json</code> for a complete example flow.</p>
|
|
899
|
+
</div>
|
|
900
|
+
</b-modal>
|
|
901
|
+
</div>
|
|
902
|
+
|
|
903
|
+
<!-- jQuery (required for Bootstrap) -->
|
|
904
|
+
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
|
905
|
+
|
|
906
|
+
<!-- Vue 2 -->
|
|
907
|
+
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.min.js"></script>
|
|
908
|
+
|
|
909
|
+
<!-- Bootstrap 4 JS -->
|
|
910
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>
|
|
911
|
+
|
|
912
|
+
<!-- Bootstrap-Vue JS -->
|
|
913
|
+
<script src="https://unpkg.com/bootstrap-vue@2.23.1/dist/bootstrap-vue.min.js"></script>
|
|
914
|
+
|
|
915
|
+
<!-- uibuilder client script -->
|
|
916
|
+
<script defer src="../uibuilder/uibuilder.iife.min.js"></script>
|
|
917
|
+
|
|
918
|
+
<!-- Builder Logic -->
|
|
919
|
+
<script defer src="./index.js"></script>
|
|
920
|
+
</body>
|
|
921
|
+
</html>
|