@dmitryvim/form-builder 0.1.41 → 0.2.0

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.
Files changed (35) hide show
  1. package/README.md +244 -22
  2. package/dist/browser/formbuilder.min.js +179 -0
  3. package/dist/browser/formbuilder.v0.2.0.min.js +179 -0
  4. package/dist/cjs/index.cjs +3582 -0
  5. package/dist/cjs/index.cjs.map +1 -0
  6. package/dist/esm/index.js +3534 -0
  7. package/dist/esm/index.js.map +1 -0
  8. package/dist/form-builder.js +152 -3372
  9. package/dist/types/components/container.d.ts +15 -0
  10. package/dist/types/components/file.d.ts +26 -0
  11. package/dist/types/components/group.d.ts +24 -0
  12. package/dist/types/components/index.d.ts +11 -0
  13. package/dist/types/components/number.d.ts +11 -0
  14. package/dist/types/components/registry.d.ts +15 -0
  15. package/dist/types/components/select.d.ts +11 -0
  16. package/dist/types/components/text.d.ts +11 -0
  17. package/dist/types/components/textarea.d.ts +11 -0
  18. package/dist/types/index.d.ts +33 -0
  19. package/dist/types/instance/FormBuilderInstance.d.ts +134 -0
  20. package/dist/types/instance/state.d.ts +13 -0
  21. package/dist/types/styles/theme.d.ts +63 -0
  22. package/dist/types/types/component-operations.d.ts +45 -0
  23. package/dist/types/types/config.d.ts +44 -0
  24. package/dist/types/types/index.d.ts +4 -0
  25. package/dist/types/types/schema.d.ts +115 -0
  26. package/dist/types/types/state.d.ts +11 -0
  27. package/dist/types/utils/helpers.d.ts +4 -0
  28. package/dist/types/utils/styles.d.ts +21 -0
  29. package/dist/types/utils/translation.d.ts +8 -0
  30. package/dist/types/utils/validation.d.ts +2 -0
  31. package/package.json +35 -15
  32. package/dist/demo.js +0 -861
  33. package/dist/elements.html +0 -1130
  34. package/dist/elements.js +0 -488
  35. package/dist/index.html +0 -315
package/dist/demo.js DELETED
@@ -1,861 +0,0 @@
1
- // Demo Application for Form Builder - Redesigned 3-Column Layout
2
- // This file contains demo-specific code and should not be part of the core library
3
-
4
- // Example schema for demonstration
5
- const EXAMPLE_SCHEMA = {
6
- version: "0.3",
7
- title: "Asset Uploader with Slides",
8
- actions: [
9
- {
10
- key: "test-missing",
11
- label: "Другая потеря"
12
- },
13
- {
14
- key: "save-draft",
15
- label: "Сохранить черновик"
16
- }
17
- ],
18
- elements: [
19
- {
20
- type: "file",
21
- key: "cover",
22
- label: "Изображение товара",
23
- description:
24
- "Source of facts: Use only the description text and visible labels/markings in the photo. Target scene: Show the product in a real-life usage context, highlighting its purpose and benefits. Product integrity: Do not change geometry, proportions, color, texture, hardware/stitching, or functional elements.",
25
- required: true,
26
- accept: {
27
- extensions: ["png", "jpg", "gif"],
28
- mime: ["image/png", "image/jpeg", "image/gif"],
29
- },
30
- maxSizeMB: 10,
31
- actions: [
32
- { key: "retry", label: "А давай ещё разок" },
33
- { key: "enhance", label: "Улучшить качество" },
34
- { key: "crop", label: "Обрезать изображение" },
35
- ],
36
- },
37
- {
38
- type: "file",
39
- key: "assets",
40
- label: "Другие изображения",
41
- description:
42
- "Additional images that provide context, show different angles, or demonstrate product usage. These will be used to enhance the infographic composition.",
43
- required: false,
44
- multiple: true,
45
- minCount: 0,
46
- maxCount: 10,
47
- accept: {
48
- extensions: ["png", "jpg", "gif"],
49
- mime: ["image/png", "image/jpeg", "image/gif"],
50
- },
51
- maxSizeMB: 5,
52
- },
53
- {
54
- type: "text",
55
- key: "title",
56
- label: "Название товара",
57
- placeholder: "Введите название товара",
58
- description:
59
- "The exact product name as it should appear on the infographic. Use the official product name without marketing terms or decorative elements.",
60
- required: true,
61
- multiple: true,
62
- minCount: 1,
63
- maxCount: 4,
64
- minLength: 1,
65
- maxLength: 100,
66
- pattern: "^[A-Za-zА-Яа-я0-9 _*-]+$",
67
- default: "",
68
- },
69
- {
70
- type: "textarea",
71
- key: "description",
72
- label: "Описание товара",
73
- placeholder: "Введите описание товара",
74
- description:
75
- "Detailed product description focusing on key features, benefits, and usage scenarios. This text will be used to create compelling infographic content.",
76
- required: false,
77
- minLength: 0,
78
- maxLength: 500,
79
- pattern: "^[A-Za-zА-Яа-я0-9 ,.!?()_-]*$",
80
- default: "",
81
- },
82
- {
83
- type: "select",
84
- key: "theme",
85
- label: "Тема",
86
- placeholder: "Выберите тему",
87
- description:
88
- "Visual theme that determines color scheme, typography, and overall aesthetic of the generated infographic.",
89
- required: true,
90
- options: [
91
- { value: "", label: "Выберите тему" },
92
- { value: "modern-minimal", label: "Современный минимализм" },
93
- { value: "warm-vintage", label: "Теплый винтаж" },
94
- { value: "tech-blue", label: "Технологичный синий" },
95
- { value: "nature-green", label: "Природный зеленый" },
96
- { value: "luxury-gold", label: "Роскошный золотой" },
97
- ],
98
- default: "",
99
- },
100
- {
101
- type: "number",
102
- key: "opacity",
103
- label: "Прозрачность",
104
- placeholder: "0.50",
105
- description:
106
- "Transparency level for background elements in the infographic. Values between 0 (fully transparent) and 1 (fully opaque).",
107
- required: true,
108
- min: 0,
109
- max: 1,
110
- decimals: 2,
111
- step: 0.01,
112
- default: 0.5,
113
- },
114
- {
115
- type: "text",
116
- key: "tags",
117
- label: "Теги товара",
118
- description: "Добавьте ключевые теги для лучшего описания товара",
119
- placeholder: "Введите тег",
120
- required: false,
121
- multiple: true,
122
- minCount: 1,
123
- maxCount: 5,
124
- minLength: 2,
125
- maxLength: 20,
126
- default: "popular",
127
- },
128
- {
129
- type: "textarea",
130
- key: "features",
131
- label: "Особенности товара",
132
- description: "Опишите ключевые особенности и преимущества товара",
133
- placeholder: "Опишите особенность...",
134
- required: false,
135
- multiple: true,
136
- minCount: 2,
137
- maxCount: 4,
138
- minLength: 10,
139
- maxLength: 200,
140
- rows: 3,
141
- },
142
- {
143
- type: "number",
144
- key: "dimensions",
145
- label: "Размеры (см)",
146
- description:
147
- "Укажите размеры товара в сантиметрах: длина, ширина, высота",
148
- placeholder: "0",
149
- required: false,
150
- multiple: true,
151
- minCount: 2,
152
- maxCount: 3,
153
- min: 0,
154
- max: 500,
155
- step: 0.1,
156
- decimals: 1,
157
- },
158
- {
159
- type: "select",
160
- key: "colors",
161
- label: "Доступные цвета",
162
- description: "Выберите доступные цветовые варианты товара",
163
- required: false,
164
- multiple: true,
165
- minCount: 1,
166
- maxCount: 3,
167
- options: [
168
- { value: "white", label: "Белый" },
169
- { value: "black", label: "Черный" },
170
- { value: "red", label: "Красный" },
171
- { value: "blue", label: "Синий" },
172
- { value: "green", label: "Зеленый" },
173
- { value: "gray", label: "Серый" },
174
- ],
175
- default: "white",
176
- },
177
- {
178
- type: "file",
179
- key: "video",
180
- label: "Video Content",
181
- description:
182
- "Optional video content to enhance the infographic. Supports MP4, WebM, and MOV formats for dynamic product demonstrations.",
183
- required: false,
184
- accept: {
185
- extensions: ["mp4", "webm", "mov"],
186
- mime: ["video/mp4", "video/webm", "video/quicktime"],
187
- },
188
- maxSizeMB: 50,
189
- },
190
- {
191
- type: "container",
192
- key: "slides",
193
- label: "Слайды",
194
- description:
195
- "Additional slides to showcase different aspects of your product. Each slide can have its own main image and decorative elements to create a comprehensive infographic presentation.",
196
- multiple: true,
197
- minCount: 1,
198
- maxCount: 5,
199
- elements: [
200
- {
201
- type: "text",
202
- key: "title",
203
- label: "Заголовок слайда",
204
- required: true,
205
- minLength: 1,
206
- maxLength: 80,
207
- pattern: "^[A-Za-zА-Яа-я0-9 _*-]+$",
208
- default: "",
209
- },
210
- {
211
- type: "textarea",
212
- key: "body",
213
- label: "Текст слайда",
214
- required: true,
215
- minLength: 1,
216
- maxLength: 1000,
217
- pattern: "^[A-Za-zА-Яа-я0-9 ,.!?()_*-]*$",
218
- default: "",
219
- },
220
- ],
221
- },
222
- {
223
- type: "text",
224
- key: "session_id",
225
- label: "Session ID",
226
- description: "Hidden session identifier for tracking purposes",
227
- hidden: true,
228
- default: `session_${Math.random().toString(36).substr(2, 9)}`,
229
- },
230
- ],
231
- };
232
-
233
- // In-Memory File Storage System
234
- class InMemoryFileStorage {
235
- constructor() {
236
- this.files = new Map(); // resourceId -> { blob, metadata }
237
- this.resourceCounter = 0;
238
- }
239
-
240
- // Generate unique resource ID
241
- generateResourceId() {
242
- return `res_${Date.now()}_${++this.resourceCounter}`;
243
- }
244
-
245
- // Store file in memory and return resource ID
246
- async storeFile(file) {
247
- const resourceId = this.generateResourceId();
248
-
249
- // Create blob from file
250
- const blob = new Blob([file], { type: file.type });
251
-
252
- // Store file data
253
- this.files.set(resourceId, {
254
- blob,
255
- metadata: {
256
- name: file.name,
257
- type: file.type,
258
- size: file.size,
259
- uploadedAt: new Date().toISOString(),
260
- },
261
- });
262
-
263
- return resourceId;
264
- }
265
-
266
- // Retrieve file blob by resource ID
267
- getFile(resourceId) {
268
- const fileData = this.files.get(resourceId);
269
- return fileData ? fileData.blob : null;
270
- }
271
-
272
- // Get file metadata
273
- getMetadata(resourceId) {
274
- const fileData = this.files.get(resourceId);
275
- return fileData ? fileData.metadata : null;
276
- }
277
-
278
- // Generate thumbnail URL for preview
279
- getThumbnailUrl(resourceId) {
280
- const blob = this.getFile(resourceId);
281
- if (!blob) return null;
282
-
283
- // For images and videos, return object URL for preview
284
- if (blob.type.startsWith("image/") || blob.type.startsWith("video/")) {
285
- return URL.createObjectURL(blob);
286
- }
287
-
288
- // For other files, return null (no thumbnail)
289
- return null;
290
- }
291
-
292
- // Download file from memory
293
- downloadFile(resourceId, fileName) {
294
- const blob = this.getFile(resourceId);
295
- const metadata = this.getMetadata(resourceId);
296
-
297
- if (!blob) {
298
- console.error("File not found:", resourceId);
299
- return;
300
- }
301
-
302
- // Use provided fileName or fall back to original name
303
- const downloadName = fileName || metadata.name;
304
-
305
- // Create download link
306
- const url = URL.createObjectURL(blob);
307
- const link = document.createElement("a");
308
- link.href = url;
309
- link.download = downloadName;
310
- document.body.appendChild(link);
311
- link.click();
312
- document.body.removeChild(link);
313
-
314
- // Clean up object URL
315
- setTimeout(() => URL.revokeObjectURL(url), 100);
316
- }
317
-
318
- // Clear all stored files
319
- clear() {
320
- // Revoke any object URLs that might be in use
321
- this.files.forEach((fileData, resourceId) => {
322
- const thumbnailUrl = this.getThumbnailUrl(resourceId);
323
- if (thumbnailUrl) {
324
- URL.revokeObjectURL(thumbnailUrl);
325
- }
326
- });
327
-
328
- this.files.clear();
329
- this.resourceCounter = 0;
330
- }
331
- }
332
-
333
- // Initialize file storage
334
- const fileStorage = new InMemoryFileStorage();
335
-
336
-
337
- // DOM element references
338
- const el = {
339
- schemaInput: document.getElementById("schemaInput"),
340
- schemaErrors: document.getElementById("schemaErrors"),
341
- applySchemaBtn: document.getElementById("applySchemaBtn"),
342
- resetSchemaBtn: document.getElementById("resetSchemaBtn"),
343
- formatSchemaBtn: document.getElementById("formatSchemaBtn"),
344
- readOnlyToggle: document.getElementById("readOnlyToggle"),
345
- formContainer: document.getElementById("formContainer"),
346
- formErrors: document.getElementById("formErrors"),
347
- submitBtn: document.getElementById("submitBtn"),
348
- clearFormBtn: document.getElementById("clearFormBtn"),
349
- dataTextarea: document.getElementById("dataTextarea"),
350
- prefillBtn: document.getElementById("prefillBtn"),
351
- copyDataBtn: document.getElementById("copyDataBtn"),
352
- downloadDataBtn: document.getElementById("downloadDataBtn"),
353
- dataErrors: document.getElementById("dataErrors"),
354
- actionsTextarea: document.getElementById("actionsTextarea"),
355
- formatActionsBtn: document.getElementById("formatActionsBtn"),
356
- clearActionsBtn: document.getElementById("clearActionsBtn"),
357
- actionsErrors: document.getElementById("actionsErrors"),
358
- };
359
-
360
- // Utility functions
361
- function pretty(obj) {
362
- return FormBuilder.pretty(obj);
363
- }
364
-
365
- function showError(element, message) {
366
- if (element) {
367
- element.textContent = message;
368
- element.classList.remove("hidden");
369
- }
370
- }
371
-
372
- function clearError(element) {
373
- if (element) {
374
- element.textContent = "";
375
- element.classList.add("hidden");
376
- }
377
- }
378
-
379
- function downloadFile(filename, content) {
380
- const blob = new Blob([content], { type: "text/plain" });
381
- const url = URL.createObjectURL(blob);
382
- const a = document.createElement("a");
383
- a.href = url;
384
- a.download = filename;
385
- document.body.appendChild(a);
386
- a.click();
387
- document.body.removeChild(a);
388
- URL.revokeObjectURL(url);
389
- }
390
-
391
- // Parse and validate external actions
392
- function parseActions(actionsText) {
393
- if (!actionsText || actionsText.trim() === "") {
394
- return [];
395
- }
396
-
397
- try {
398
- const actions = JSON.parse(actionsText);
399
-
400
- if (!Array.isArray(actions)) {
401
- throw new Error("Actions must be an array");
402
- }
403
-
404
- // Validate each action
405
- for (let i = 0; i < actions.length; i++) {
406
- const action = actions[i];
407
- if (!action || typeof action !== "object") {
408
- throw new Error(`Action at index ${i} must be an object`);
409
- }
410
- if (!action.key || typeof action.key !== "string") {
411
- throw new Error(
412
- `Action at index ${i} missing valid 'key' property`,
413
- );
414
- }
415
- if (!action.value || typeof action.value !== "string") {
416
- throw new Error(`Action at index ${i} missing valid 'value' property`);
417
- }
418
- // related_field is optional - if not provided, action is form-level
419
- if (action.related_field && typeof action.related_field !== "string") {
420
- throw new Error(`Action at index ${i} has invalid 'related_field' property type`);
421
- }
422
- // Label is optional - will be resolved from schema or key
423
- if (action.label && typeof action.label !== "string") {
424
- throw new Error(`Action at index ${i} has invalid 'label' property type`);
425
- }
426
- }
427
-
428
- return actions;
429
- } catch (error) {
430
- throw new Error(`Actions JSON error: ${error.message}`);
431
- }
432
- }
433
-
434
- // Configure FormBuilder with in-memory handlers
435
- function setupFormBuilder() {
436
- // Set form container
437
- FormBuilder.setFormRoot(el.formContainer);
438
-
439
- // Upload handler - store file in memory and return resource ID
440
- FormBuilder.setUploadHandler(async (file) => {
441
- try {
442
- console.log("Uploading file to memory:", file.name);
443
- const resourceId = await fileStorage.storeFile(file);
444
- console.log("File stored with resource ID:", resourceId);
445
- return resourceId;
446
- } catch (error) {
447
- console.error("Upload failed:", error);
448
- throw error;
449
- }
450
- });
451
-
452
- // Download handler - download file from memory
453
- FormBuilder.setDownloadHandler((resourceId, fileName) => {
454
- console.log("Downloading file from memory:", resourceId, fileName);
455
- fileStorage.downloadFile(resourceId, fileName);
456
- });
457
-
458
- // Thumbnail handler - get thumbnail URL for preview
459
- FormBuilder.setThumbnailHandler((resourceId) => {
460
- const thumbnailUrl = fileStorage.getThumbnailUrl(resourceId);
461
- console.log("Getting thumbnail for:", resourceId, thumbnailUrl);
462
- return thumbnailUrl;
463
- });
464
-
465
- // Action handler - display message when action button is clicked
466
- // New system: value, key, related_field parameters
467
- FormBuilder.setActionHandler((value, key, relatedField) => {
468
- console.log("Action clicked:", {
469
- value,
470
- key,
471
- relatedField,
472
- type: relatedField ? "field-level" : "form-level",
473
- });
474
-
475
- // Show message to user (compatible with all environments)
476
- if (typeof window !== "undefined" && window.alert) {
477
- if (relatedField) {
478
- window.alert(
479
- `Field Action: "${key}" clicked for field "${relatedField}" with value: ${value}`,
480
- );
481
- } else {
482
- window.alert(`Form Action: "${key}" clicked with value: ${value}`);
483
- }
484
- } else {
485
- console.log(
486
- `Demo action: ${key} clicked with value: ${value}`,
487
- relatedField ? ` for field: ${relatedField}` : " (form-level)",
488
- );
489
- }
490
- });
491
-
492
- console.log(
493
- "FormBuilder configured with in-memory file handlers and action handler",
494
- );
495
- }
496
-
497
- // Schema management functions
498
- function applyCurrentSchema() {
499
- clearError(el.schemaErrors);
500
- clearError(el.formErrors);
501
- clearError(el.actionsErrors);
502
-
503
- try {
504
- const schema = JSON.parse(el.schemaInput.value);
505
- const errors = FormBuilder.validateSchema(schema);
506
-
507
- if (errors.length > 0) {
508
- showError(
509
- el.schemaErrors,
510
- `Schema validation errors: ${errors.join(", ")}`,
511
- );
512
- return false;
513
- }
514
-
515
- // Parse external actions
516
- let externalActions = [];
517
- try {
518
- externalActions = parseActions(el.actionsTextarea.value);
519
- } catch (error) {
520
- showError(el.actionsErrors, error.message);
521
- return false;
522
- }
523
-
524
-
525
- // Set mode based on toggle
526
- const isReadOnly = el.readOnlyToggle.checked;
527
- FormBuilder.setMode(isReadOnly ? "readonly" : "edit");
528
-
529
- // Get current data to preserve during re-render
530
- let currentData = {};
531
- try {
532
- const formResult = FormBuilder.getFormData();
533
- if (formResult.valid) {
534
- currentData = formResult.data;
535
- }
536
- } catch {
537
- // Ignore errors when getting current data
538
- }
539
-
540
- // Render form with current data and external actions
541
- FormBuilder.renderForm(schema, currentData, externalActions);
542
-
543
- console.log(`Form rendered in ${isReadOnly ? "readonly" : "edit"} mode`);
544
- console.log(`External actions:`, externalActions);
545
-
546
- return true;
547
- } catch (e) {
548
- showError(el.schemaErrors, `JSON parse error: ${e.message}`);
549
- return false;
550
- }
551
- }
552
-
553
- // Event handlers
554
- el.applySchemaBtn.addEventListener("click", () => {
555
- applyCurrentSchema();
556
- });
557
-
558
- el.resetSchemaBtn.addEventListener("click", () => {
559
- el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
560
- clearError(el.schemaErrors);
561
- clearError(el.formErrors);
562
- clearError(el.dataErrors);
563
- clearError(el.actionsErrors);
564
- el.dataTextarea.value = "";
565
- el.actionsTextarea.value = "";
566
-
567
- // Clear file storage
568
- fileStorage.clear();
569
-
570
- // Apply schema
571
- applyCurrentSchema();
572
- });
573
-
574
- el.formatSchemaBtn.addEventListener("click", () => {
575
- try {
576
- const parsed = JSON.parse(el.schemaInput.value);
577
- el.schemaInput.value = pretty(parsed);
578
- clearError(el.schemaErrors);
579
- } catch (e) {
580
- showError(el.schemaErrors, `Format: JSON parse error: ${e.message}`);
581
- }
582
- });
583
-
584
- // Read-only toggle handler
585
- el.readOnlyToggle.addEventListener("change", () => {
586
- applyCurrentSchema(); // Re-render form with new mode
587
- });
588
-
589
- // Form interaction handlers
590
- el.submitBtn.addEventListener("click", () => {
591
- clearError(el.formErrors);
592
- clearError(el.dataErrors);
593
-
594
- const result = FormBuilder.getFormData();
595
-
596
- if (!result.valid) {
597
- showError(el.formErrors, `Validation errors: ${result.errors.join(", ")}`);
598
- return;
599
- }
600
-
601
- // For demo purposes, we can show either raw data or enhanced data
602
- // Let's provide both options - you can choose which one to use
603
-
604
- // Option 1: Show raw data with just resource IDs (what you might want)
605
- const rawData = JSON.parse(JSON.stringify(result.data));
606
-
607
- // Option 2: Show enhanced data with file metadata (for demo visualization)
608
- const enhancedData = JSON.parse(JSON.stringify(result.data));
609
-
610
- function replaceResourceIds(obj) {
611
- for (const key in obj) {
612
- if (typeof obj[key] === "string" && obj[key].startsWith("res_")) {
613
- const metadata = fileStorage.getMetadata(obj[key]);
614
- if (metadata) {
615
- obj[key] = {
616
- resourceId: obj[key],
617
- name: metadata.name,
618
- type: metadata.type,
619
- size: metadata.size,
620
- uploadedAt: metadata.uploadedAt,
621
- };
622
- }
623
- } else if (Array.isArray(obj[key])) {
624
- obj[key].forEach((item, index) => {
625
- if (typeof item === "string" && item.startsWith("res_")) {
626
- const metadata = fileStorage.getMetadata(item);
627
- if (metadata) {
628
- obj[key][index] = {
629
- resourceId: item,
630
- name: metadata.name,
631
- type: metadata.type,
632
- size: metadata.size,
633
- uploadedAt: metadata.uploadedAt,
634
- };
635
- }
636
- } else if (typeof item === "object" && item !== null) {
637
- replaceResourceIds(item);
638
- }
639
- });
640
- } else if (typeof obj[key] === "object" && obj[key] !== null) {
641
- replaceResourceIds(obj[key]);
642
- }
643
- }
644
- }
645
-
646
- replaceResourceIds(enhancedData);
647
-
648
- // Show raw data (just resource IDs) - change to enhancedData for metadata
649
- el.dataTextarea.value = pretty(rawData);
650
- console.log("Form submitted successfully");
651
- console.log("Raw data:", rawData);
652
- console.log("Enhanced data:", enhancedData);
653
- });
654
-
655
- el.clearFormBtn.addEventListener("click", () => {
656
- FormBuilder.clearForm();
657
- clearError(el.formErrors);
658
- console.log("Form values cleared");
659
- });
660
-
661
- // Data management handlers
662
- el.prefillBtn.addEventListener("click", () => {
663
- clearError(el.dataErrors);
664
- clearError(el.formErrors);
665
-
666
- try {
667
- const prefillData = JSON.parse(el.dataTextarea.value || "{}");
668
- const currentSchema = JSON.parse(el.schemaInput.value);
669
-
670
- // Parse external actions
671
- let externalActions = [];
672
- try {
673
- externalActions = parseActions(el.actionsTextarea.value);
674
- } catch (error) {
675
- showError(el.actionsErrors, error.message);
676
- return;
677
- }
678
-
679
- // Convert enhanced data back to raw format for prefilling
680
- const processedData = JSON.parse(JSON.stringify(prefillData));
681
-
682
- function extractResourceIds(obj) {
683
- for (const key in obj) {
684
- // Handle enhanced file objects - extract just the resourceId
685
- if (
686
- obj[key] &&
687
- typeof obj[key] === "object" &&
688
- obj[key].resourceId &&
689
- typeof obj[key].resourceId === "string" &&
690
- obj[key].resourceId.startsWith("res_")
691
- ) {
692
- obj[key] = obj[key].resourceId;
693
- } else if (Array.isArray(obj[key])) {
694
- obj[key].forEach((item, index) => {
695
- if (
696
- item &&
697
- typeof item === "object" &&
698
- item.resourceId &&
699
- typeof item.resourceId === "string" &&
700
- item.resourceId.startsWith("res_")
701
- ) {
702
- obj[key][index] = item.resourceId;
703
- } else if (typeof item === "object" && item !== null) {
704
- extractResourceIds(item);
705
- }
706
- });
707
- } else if (typeof obj[key] === "object" && obj[key] !== null) {
708
- extractResourceIds(obj[key]);
709
- }
710
- }
711
- }
712
-
713
- extractResourceIds(processedData);
714
-
715
- // Set mode based on toggle
716
- const isReadOnly = el.readOnlyToggle.checked;
717
- FormBuilder.setMode(isReadOnly ? "readonly" : "edit");
718
-
719
- FormBuilder.renderForm(currentSchema, processedData, externalActions);
720
- console.log("Form prefilled with data");
721
- console.log("Processed prefill data:", processedData);
722
- console.log("External actions:", externalActions);
723
- } catch (e) {
724
- showError(el.dataErrors, `JSON parse error: ${e.message}`);
725
- console.error("Prefill error:", e);
726
- }
727
- });
728
-
729
- el.copyDataBtn.addEventListener("click", async () => {
730
- try {
731
- await navigator.clipboard.writeText(el.dataTextarea.value || "");
732
- el.copyDataBtn.textContent = "Copied!";
733
- setTimeout(() => {
734
- el.copyDataBtn.textContent = "Copy JSON";
735
- }, 1000);
736
- console.log("Data copied to clipboard");
737
- } catch (e) {
738
- console.warn("Copy failed:", e);
739
- }
740
- });
741
-
742
- el.downloadDataBtn.addEventListener("click", () => {
743
- downloadFile("form-data.json", el.dataTextarea.value || "{}");
744
- console.log("Data downloaded");
745
- });
746
-
747
- // Actions management handlers
748
- el.formatActionsBtn.addEventListener("click", () => {
749
- try {
750
- const actions = parseActions(el.actionsTextarea.value);
751
- el.actionsTextarea.value = pretty(actions);
752
- clearError(el.actionsErrors);
753
- console.log("Actions formatted");
754
- } catch (e) {
755
- showError(el.actionsErrors, e.message);
756
- console.error("Format actions error:", e);
757
- }
758
- });
759
-
760
- el.clearActionsBtn.addEventListener("click", () => {
761
- el.actionsTextarea.value = "";
762
- clearError(el.actionsErrors);
763
- // Re-apply schema to remove external actions from the form
764
- applyCurrentSchema();
765
- console.log("Actions cleared");
766
- });
767
-
768
- // Example external actions for demonstration
769
- const EXAMPLE_ACTIONS = [
770
- // Field-level actions using predefined labels from schema
771
- {
772
- related_field: "cover",
773
- key: "retry",
774
- value: "regenerate_cover_image", // Specific action value for handler
775
- },
776
- {
777
- related_field: "cover",
778
- key: "enhance",
779
- value: "enhance_cover_quality", // Specific action value for handler
780
- },
781
- {
782
- related_field: "cover",
783
- key: "crop",
784
- value: "auto_crop_cover", // Specific action value for handler
785
- },
786
- // Field-level actions with custom labels
787
- {
788
- related_field: "title[0]",
789
- key: "generate",
790
- value: "ai_generate_title",
791
- label: "🤖 Generate Title", // Custom label overrides schema
792
- },
793
- {
794
- related_field: "description",
795
- key: "improve",
796
- value: "ai_improve_description", // Key will be used as fallback label
797
- },
798
- {
799
- related_field: "slides[0].title",
800
- key: "optimize",
801
- value: "ai_optimize_slide_title",
802
- label: "🎯 Optimize Slide Title", // Custom label
803
- },
804
- // Form-level actions (no related_field)
805
- {
806
- key: "save-draft",
807
- value: "save_form_draft",
808
- label: "💾 Save Draft",
809
- },
810
- // Action with missing related field (should appear at bottom of form)
811
- {
812
- related_field: "non_existent_field",
813
- key: "test-missing",
814
- value: "test_missing_field_action",
815
- label: "🔍 Test Missing Field Action",
816
- },
817
- {
818
- key: "preview",
819
- value: "preview_infographic",
820
- label: "👁️ Preview",
821
- },
822
- {
823
- key: "export",
824
- value: "export_json_data",
825
- label: "📄 Export JSON",
826
- },
827
- ];
828
-
829
- // Initialize demo application
830
- function initDemo() {
831
- // Set up FormBuilder
832
- setupFormBuilder();
833
-
834
- // Initialize with example schema
835
- el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
836
-
837
- // Initialize with example actions
838
- el.actionsTextarea.value = pretty(EXAMPLE_ACTIONS);
839
-
840
- // Apply initial schema
841
- applyCurrentSchema();
842
-
843
- console.log("Demo initialized successfully");
844
- }
845
-
846
- // Start the demo when the page loads
847
- document.addEventListener("DOMContentLoaded", () => {
848
- // Wait for FormBuilder to be available
849
- if (typeof FormBuilder !== "undefined") {
850
- initDemo();
851
- } else {
852
- // Fallback: wait a bit for scripts to load
853
- setTimeout(() => {
854
- if (typeof FormBuilder !== "undefined") {
855
- initDemo();
856
- } else {
857
- console.error("FormBuilder not found!");
858
- }
859
- }, 100);
860
- }
861
- });