@dmitryvim/form-builder 0.1.35 → 0.1.38
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/README.md +10 -5
- package/dist/demo.js +182 -54
- package/dist/elements.html +588 -160
- package/dist/elements.js +270 -232
- package/dist/form-builder.js +426 -134
- package/dist/index.html +70 -20
- package/package.json +1 -1
- package/docs/13_form_builder.html +0 -1337
- package/docs/REQUIREMENTS.md +0 -313
- package/docs/integration.md +0 -480
- package/docs/schema.md +0 -433
package/README.md
CHANGED
|
@@ -105,9 +105,9 @@ npm install @dmitryvim/form-builder
|
|
|
105
105
|
"extensions": ["jpg", "png", "webp"]
|
|
106
106
|
},
|
|
107
107
|
"actions": [
|
|
108
|
-
{ "
|
|
109
|
-
{ "
|
|
110
|
-
{ "
|
|
108
|
+
{ "key": "enhance", "label": "Enhance Quality" },
|
|
109
|
+
{ "key": "crop", "label": "Auto Crop" },
|
|
110
|
+
{ "key": "retry", "label": "Try Again" }
|
|
111
111
|
]
|
|
112
112
|
},
|
|
113
113
|
{
|
|
@@ -152,9 +152,14 @@ FormBuilder.setUploadHandler(async (file) => {
|
|
|
152
152
|
// Your upload logic - return resource ID
|
|
153
153
|
return "resource-123";
|
|
154
154
|
});
|
|
155
|
-
FormBuilder.setActionHandler((value) => {
|
|
155
|
+
FormBuilder.setActionHandler((value, key, relatedField) => {
|
|
156
156
|
// Handle action button clicks
|
|
157
|
-
console.log("Action clicked:", value);
|
|
157
|
+
console.log("Action clicked:", { value, key, relatedField });
|
|
158
|
+
if (relatedField) {
|
|
159
|
+
// Field-level action
|
|
160
|
+
} else {
|
|
161
|
+
// Form-level action
|
|
162
|
+
}
|
|
158
163
|
});
|
|
159
164
|
FormBuilder.renderForm(schema, prefillData);
|
|
160
165
|
```
|
package/dist/demo.js
CHANGED
|
@@ -19,9 +19,9 @@ const EXAMPLE_SCHEMA = {
|
|
|
19
19
|
},
|
|
20
20
|
maxSizeMB: 10,
|
|
21
21
|
actions: [
|
|
22
|
-
{
|
|
23
|
-
{
|
|
24
|
-
{
|
|
22
|
+
{ key: "retry", label: "А давай ещё разок" },
|
|
23
|
+
{ key: "enhance", label: "Улучшить качество" },
|
|
24
|
+
{ key: "crop", label: "Обрезать изображение" },
|
|
25
25
|
],
|
|
26
26
|
},
|
|
27
27
|
{
|
|
@@ -113,7 +113,7 @@ const EXAMPLE_SCHEMA = {
|
|
|
113
113
|
maxCount: 5,
|
|
114
114
|
minLength: 2,
|
|
115
115
|
maxLength: 20,
|
|
116
|
-
default: "popular"
|
|
116
|
+
default: "popular",
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
119
|
type: "textarea",
|
|
@@ -127,13 +127,14 @@ const EXAMPLE_SCHEMA = {
|
|
|
127
127
|
maxCount: 4,
|
|
128
128
|
minLength: 10,
|
|
129
129
|
maxLength: 200,
|
|
130
|
-
rows: 3
|
|
130
|
+
rows: 3,
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
type: "number",
|
|
134
134
|
key: "dimensions",
|
|
135
135
|
label: "Размеры (см)",
|
|
136
|
-
description:
|
|
136
|
+
description:
|
|
137
|
+
"Укажите размеры товара в сантиметрах: длина, ширина, высота",
|
|
137
138
|
placeholder: "0",
|
|
138
139
|
required: false,
|
|
139
140
|
multiple: true,
|
|
@@ -142,7 +143,7 @@ const EXAMPLE_SCHEMA = {
|
|
|
142
143
|
min: 0,
|
|
143
144
|
max: 500,
|
|
144
145
|
step: 0.1,
|
|
145
|
-
decimals: 1
|
|
146
|
+
decimals: 1,
|
|
146
147
|
},
|
|
147
148
|
{
|
|
148
149
|
type: "select",
|
|
@@ -159,9 +160,9 @@ const EXAMPLE_SCHEMA = {
|
|
|
159
160
|
{ value: "red", label: "Красный" },
|
|
160
161
|
{ value: "blue", label: "Синий" },
|
|
161
162
|
{ value: "green", label: "Зеленый" },
|
|
162
|
-
{ value: "gray", label: "Серый" }
|
|
163
|
+
{ value: "gray", label: "Серый" },
|
|
163
164
|
],
|
|
164
|
-
default: "white"
|
|
165
|
+
default: "white",
|
|
165
166
|
},
|
|
166
167
|
{
|
|
167
168
|
type: "file",
|
|
@@ -214,7 +215,7 @@ const EXAMPLE_SCHEMA = {
|
|
|
214
215
|
label: "Session ID",
|
|
215
216
|
description: "Hidden session identifier for tracking purposes",
|
|
216
217
|
hidden: true,
|
|
217
|
-
default:
|
|
218
|
+
default: `session_${Math.random().toString(36).substr(2, 9)}`,
|
|
218
219
|
},
|
|
219
220
|
],
|
|
220
221
|
};
|
|
@@ -322,38 +323,6 @@ class InMemoryFileStorage {
|
|
|
322
323
|
// Initialize file storage
|
|
323
324
|
const fileStorage = new InMemoryFileStorage();
|
|
324
325
|
|
|
325
|
-
// Cache for action value -> label mapping for efficient lookup
|
|
326
|
-
let actionLabelMap = new Map();
|
|
327
|
-
|
|
328
|
-
// Build action value -> label mapping from schema for efficient lookup
|
|
329
|
-
function buildActionLabelMap(schema) {
|
|
330
|
-
const map = new Map();
|
|
331
|
-
|
|
332
|
-
function processElements(elements) {
|
|
333
|
-
if (!Array.isArray(elements)) return;
|
|
334
|
-
|
|
335
|
-
for (const element of elements) {
|
|
336
|
-
if (element.actions && Array.isArray(element.actions)) {
|
|
337
|
-
for (const action of element.actions) {
|
|
338
|
-
if (action.value && action.label) {
|
|
339
|
-
map.set(action.value, action.label);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Process nested group elements
|
|
345
|
-
if (element.elements && Array.isArray(element.elements)) {
|
|
346
|
-
processElements(element.elements);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (schema && schema.elements) {
|
|
352
|
-
processElements(schema.elements);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return map;
|
|
356
|
-
}
|
|
357
326
|
|
|
358
327
|
// DOM element references
|
|
359
328
|
const el = {
|
|
@@ -372,6 +341,10 @@ const el = {
|
|
|
372
341
|
copyDataBtn: document.getElementById("copyDataBtn"),
|
|
373
342
|
downloadDataBtn: document.getElementById("downloadDataBtn"),
|
|
374
343
|
dataErrors: document.getElementById("dataErrors"),
|
|
344
|
+
actionsTextarea: document.getElementById("actionsTextarea"),
|
|
345
|
+
formatActionsBtn: document.getElementById("formatActionsBtn"),
|
|
346
|
+
clearActionsBtn: document.getElementById("clearActionsBtn"),
|
|
347
|
+
actionsErrors: document.getElementById("actionsErrors"),
|
|
375
348
|
};
|
|
376
349
|
|
|
377
350
|
// Utility functions
|
|
@@ -405,6 +378,49 @@ function downloadFile(filename, content) {
|
|
|
405
378
|
URL.revokeObjectURL(url);
|
|
406
379
|
}
|
|
407
380
|
|
|
381
|
+
// Parse and validate external actions
|
|
382
|
+
function parseActions(actionsText) {
|
|
383
|
+
if (!actionsText || actionsText.trim() === "") {
|
|
384
|
+
return [];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
const actions = JSON.parse(actionsText);
|
|
389
|
+
|
|
390
|
+
if (!Array.isArray(actions)) {
|
|
391
|
+
throw new Error("Actions must be an array");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Validate each action
|
|
395
|
+
for (let i = 0; i < actions.length; i++) {
|
|
396
|
+
const action = actions[i];
|
|
397
|
+
if (!action || typeof action !== "object") {
|
|
398
|
+
throw new Error(`Action at index ${i} must be an object`);
|
|
399
|
+
}
|
|
400
|
+
if (!action.key || typeof action.key !== "string") {
|
|
401
|
+
throw new Error(
|
|
402
|
+
`Action at index ${i} missing valid 'key' property`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
if (!action.value || typeof action.value !== "string") {
|
|
406
|
+
throw new Error(`Action at index ${i} missing valid 'value' property`);
|
|
407
|
+
}
|
|
408
|
+
// related_field is optional - if not provided, action is form-level
|
|
409
|
+
if (action.related_field && typeof action.related_field !== "string") {
|
|
410
|
+
throw new Error(`Action at index ${i} has invalid 'related_field' property type`);
|
|
411
|
+
}
|
|
412
|
+
// Label is optional - will be resolved from schema or key
|
|
413
|
+
if (action.label && typeof action.label !== "string") {
|
|
414
|
+
throw new Error(`Action at index ${i} has invalid 'label' property type`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return actions;
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw new Error(`Actions JSON error: ${error.message}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
408
424
|
// Configure FormBuilder with in-memory handlers
|
|
409
425
|
function setupFormBuilder() {
|
|
410
426
|
// Set form container
|
|
@@ -437,17 +453,29 @@ function setupFormBuilder() {
|
|
|
437
453
|
});
|
|
438
454
|
|
|
439
455
|
// Action handler - display message when action button is clicked
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
456
|
+
// New system: value, key, related_field parameters
|
|
457
|
+
FormBuilder.setActionHandler((value, key, relatedField) => {
|
|
458
|
+
console.log("Action clicked:", {
|
|
459
|
+
value,
|
|
460
|
+
key,
|
|
461
|
+
relatedField,
|
|
462
|
+
type: relatedField ? "field-level" : "form-level",
|
|
463
|
+
});
|
|
443
464
|
|
|
444
|
-
console.log("Action clicked:", { label: actionLabel, value });
|
|
445
|
-
|
|
446
465
|
// Show message to user (compatible with all environments)
|
|
447
466
|
if (typeof window !== "undefined" && window.alert) {
|
|
448
|
-
|
|
467
|
+
if (relatedField) {
|
|
468
|
+
window.alert(
|
|
469
|
+
`Field Action: "${key}" clicked for field "${relatedField}" with value: ${value}`,
|
|
470
|
+
);
|
|
471
|
+
} else {
|
|
472
|
+
window.alert(`Form Action: "${key}" clicked with value: ${value}`);
|
|
473
|
+
}
|
|
449
474
|
} else {
|
|
450
|
-
console.log(
|
|
475
|
+
console.log(
|
|
476
|
+
`Demo action: ${key} clicked with value: ${value}`,
|
|
477
|
+
relatedField ? ` for field: ${relatedField}` : " (form-level)",
|
|
478
|
+
);
|
|
451
479
|
}
|
|
452
480
|
});
|
|
453
481
|
|
|
@@ -460,6 +488,7 @@ function setupFormBuilder() {
|
|
|
460
488
|
function applyCurrentSchema() {
|
|
461
489
|
clearError(el.schemaErrors);
|
|
462
490
|
clearError(el.formErrors);
|
|
491
|
+
clearError(el.actionsErrors);
|
|
463
492
|
|
|
464
493
|
try {
|
|
465
494
|
const schema = JSON.parse(el.schemaInput.value);
|
|
@@ -473,8 +502,15 @@ function applyCurrentSchema() {
|
|
|
473
502
|
return false;
|
|
474
503
|
}
|
|
475
504
|
|
|
476
|
-
//
|
|
477
|
-
|
|
505
|
+
// Parse external actions
|
|
506
|
+
let externalActions = [];
|
|
507
|
+
try {
|
|
508
|
+
externalActions = parseActions(el.actionsTextarea.value);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
showError(el.actionsErrors, error.message);
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
|
|
478
514
|
|
|
479
515
|
// Set mode based on toggle
|
|
480
516
|
const isReadOnly = el.readOnlyToggle.checked;
|
|
@@ -491,10 +527,12 @@ function applyCurrentSchema() {
|
|
|
491
527
|
// Ignore errors when getting current data
|
|
492
528
|
}
|
|
493
529
|
|
|
494
|
-
// Render form with current data
|
|
495
|
-
FormBuilder.renderForm(schema, currentData);
|
|
530
|
+
// Render form with current data and external actions
|
|
531
|
+
FormBuilder.renderForm(schema, currentData, externalActions);
|
|
496
532
|
|
|
497
533
|
console.log(`Form rendered in ${isReadOnly ? "readonly" : "edit"} mode`);
|
|
534
|
+
console.log(`External actions:`, externalActions);
|
|
535
|
+
|
|
498
536
|
return true;
|
|
499
537
|
} catch (e) {
|
|
500
538
|
showError(el.schemaErrors, `JSON parse error: ${e.message}`);
|
|
@@ -512,7 +550,9 @@ el.resetSchemaBtn.addEventListener("click", () => {
|
|
|
512
550
|
clearError(el.schemaErrors);
|
|
513
551
|
clearError(el.formErrors);
|
|
514
552
|
clearError(el.dataErrors);
|
|
553
|
+
clearError(el.actionsErrors);
|
|
515
554
|
el.dataTextarea.value = "";
|
|
555
|
+
el.actionsTextarea.value = "";
|
|
516
556
|
|
|
517
557
|
// Clear file storage
|
|
518
558
|
fileStorage.clear();
|
|
@@ -617,6 +657,15 @@ el.prefillBtn.addEventListener("click", () => {
|
|
|
617
657
|
const prefillData = JSON.parse(el.dataTextarea.value || "{}");
|
|
618
658
|
const currentSchema = JSON.parse(el.schemaInput.value);
|
|
619
659
|
|
|
660
|
+
// Parse external actions
|
|
661
|
+
let externalActions = [];
|
|
662
|
+
try {
|
|
663
|
+
externalActions = parseActions(el.actionsTextarea.value);
|
|
664
|
+
} catch (error) {
|
|
665
|
+
showError(el.actionsErrors, error.message);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
620
669
|
// Convert enhanced data back to raw format for prefilling
|
|
621
670
|
const processedData = JSON.parse(JSON.stringify(prefillData));
|
|
622
671
|
|
|
@@ -657,9 +706,10 @@ el.prefillBtn.addEventListener("click", () => {
|
|
|
657
706
|
const isReadOnly = el.readOnlyToggle.checked;
|
|
658
707
|
FormBuilder.setMode(isReadOnly ? "readonly" : "edit");
|
|
659
708
|
|
|
660
|
-
FormBuilder.renderForm(currentSchema, processedData);
|
|
709
|
+
FormBuilder.renderForm(currentSchema, processedData, externalActions);
|
|
661
710
|
console.log("Form prefilled with data");
|
|
662
711
|
console.log("Processed prefill data:", processedData);
|
|
712
|
+
console.log("External actions:", externalActions);
|
|
663
713
|
} catch (e) {
|
|
664
714
|
showError(el.dataErrors, `JSON parse error: ${e.message}`);
|
|
665
715
|
console.error("Prefill error:", e);
|
|
@@ -684,6 +734,81 @@ el.downloadDataBtn.addEventListener("click", () => {
|
|
|
684
734
|
console.log("Data downloaded");
|
|
685
735
|
});
|
|
686
736
|
|
|
737
|
+
// Actions management handlers
|
|
738
|
+
el.formatActionsBtn.addEventListener("click", () => {
|
|
739
|
+
try {
|
|
740
|
+
const actions = parseActions(el.actionsTextarea.value);
|
|
741
|
+
el.actionsTextarea.value = pretty(actions);
|
|
742
|
+
clearError(el.actionsErrors);
|
|
743
|
+
console.log("Actions formatted");
|
|
744
|
+
} catch (e) {
|
|
745
|
+
showError(el.actionsErrors, e.message);
|
|
746
|
+
console.error("Format actions error:", e);
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
el.clearActionsBtn.addEventListener("click", () => {
|
|
751
|
+
el.actionsTextarea.value = "";
|
|
752
|
+
clearError(el.actionsErrors);
|
|
753
|
+
// Re-apply schema to remove external actions from the form
|
|
754
|
+
applyCurrentSchema();
|
|
755
|
+
console.log("Actions cleared");
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// Example external actions for demonstration
|
|
759
|
+
const EXAMPLE_ACTIONS = [
|
|
760
|
+
// Field-level actions using predefined labels from schema
|
|
761
|
+
{
|
|
762
|
+
related_field: "cover",
|
|
763
|
+
key: "retry",
|
|
764
|
+
value: "regenerate_cover_image", // Specific action value for handler
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
related_field: "cover",
|
|
768
|
+
key: "enhance",
|
|
769
|
+
value: "enhance_cover_quality", // Specific action value for handler
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
related_field: "cover",
|
|
773
|
+
key: "crop",
|
|
774
|
+
value: "auto_crop_cover", // Specific action value for handler
|
|
775
|
+
},
|
|
776
|
+
// Field-level actions with custom labels
|
|
777
|
+
{
|
|
778
|
+
related_field: "title[0]",
|
|
779
|
+
key: "generate",
|
|
780
|
+
value: "ai_generate_title",
|
|
781
|
+
label: "🤖 Generate Title", // Custom label overrides schema
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
related_field: "description",
|
|
785
|
+
key: "improve",
|
|
786
|
+
value: "ai_improve_description", // Key will be used as fallback label
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
related_field: "slides[0].title",
|
|
790
|
+
key: "optimize",
|
|
791
|
+
value: "ai_optimize_slide_title",
|
|
792
|
+
label: "🎯 Optimize Slide Title", // Custom label
|
|
793
|
+
},
|
|
794
|
+
// Form-level actions (no related_field)
|
|
795
|
+
{
|
|
796
|
+
key: "save-draft",
|
|
797
|
+
value: "save_form_draft",
|
|
798
|
+
label: "💾 Save Draft",
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
key: "preview",
|
|
802
|
+
value: "preview_infographic",
|
|
803
|
+
label: "👁️ Preview",
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
key: "export",
|
|
807
|
+
value: "export_json_data",
|
|
808
|
+
label: "📄 Export JSON",
|
|
809
|
+
},
|
|
810
|
+
];
|
|
811
|
+
|
|
687
812
|
// Initialize demo application
|
|
688
813
|
function initDemo() {
|
|
689
814
|
// Set up FormBuilder
|
|
@@ -692,6 +817,9 @@ function initDemo() {
|
|
|
692
817
|
// Initialize with example schema
|
|
693
818
|
el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
|
|
694
819
|
|
|
820
|
+
// Initialize with example actions
|
|
821
|
+
el.actionsTextarea.value = pretty(EXAMPLE_ACTIONS);
|
|
822
|
+
|
|
695
823
|
// Apply initial schema
|
|
696
824
|
applyCurrentSchema();
|
|
697
825
|
|