@dmitryvim/form-builder 0.1.25 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/demo.js +314 -321
- package/dist/form-builder.js +772 -487
- package/dist/index.html +89 -129
- package/package.json +6 -2
package/dist/demo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Demo Application for Form Builder
|
|
1
|
+
// Demo Application for Form Builder - Redesigned 3-Column Layout
|
|
2
2
|
// This file contains demo-specific code and should not be part of the core library
|
|
3
3
|
|
|
4
4
|
// Example schema for demonstration
|
|
@@ -92,6 +92,19 @@ const EXAMPLE_SCHEMA = {
|
|
|
92
92
|
step: 0.01,
|
|
93
93
|
default: 0.5,
|
|
94
94
|
},
|
|
95
|
+
{
|
|
96
|
+
type: "file",
|
|
97
|
+
key: "video",
|
|
98
|
+
label: "Video Content",
|
|
99
|
+
description:
|
|
100
|
+
"Optional video content to enhance the infographic. Supports MP4, WebM, and MOV formats for dynamic product demonstrations.",
|
|
101
|
+
required: false,
|
|
102
|
+
accept: {
|
|
103
|
+
extensions: ["mp4", "webm", "mov"],
|
|
104
|
+
mime: ["video/mp4", "video/webm", "video/quicktime"],
|
|
105
|
+
},
|
|
106
|
+
maxSizeMB: 50,
|
|
107
|
+
},
|
|
95
108
|
{
|
|
96
109
|
type: "group",
|
|
97
110
|
key: "slides",
|
|
@@ -125,37 +138,129 @@ const EXAMPLE_SCHEMA = {
|
|
|
125
138
|
],
|
|
126
139
|
};
|
|
127
140
|
|
|
128
|
-
//
|
|
141
|
+
// In-Memory File Storage System
|
|
142
|
+
class InMemoryFileStorage {
|
|
143
|
+
constructor() {
|
|
144
|
+
this.files = new Map(); // resourceId -> { blob, metadata }
|
|
145
|
+
this.resourceCounter = 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Generate unique resource ID
|
|
149
|
+
generateResourceId() {
|
|
150
|
+
return `res_${Date.now()}_${++this.resourceCounter}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Store file in memory and return resource ID
|
|
154
|
+
async storeFile(file) {
|
|
155
|
+
const resourceId = this.generateResourceId();
|
|
156
|
+
|
|
157
|
+
// Create blob from file
|
|
158
|
+
const blob = new Blob([file], { type: file.type });
|
|
159
|
+
|
|
160
|
+
// Store file data
|
|
161
|
+
this.files.set(resourceId, {
|
|
162
|
+
blob,
|
|
163
|
+
metadata: {
|
|
164
|
+
name: file.name,
|
|
165
|
+
type: file.type,
|
|
166
|
+
size: file.size,
|
|
167
|
+
uploadedAt: new Date().toISOString(),
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return resourceId;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Retrieve file blob by resource ID
|
|
175
|
+
getFile(resourceId) {
|
|
176
|
+
const fileData = this.files.get(resourceId);
|
|
177
|
+
return fileData ? fileData.blob : null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Get file metadata
|
|
181
|
+
getMetadata(resourceId) {
|
|
182
|
+
const fileData = this.files.get(resourceId);
|
|
183
|
+
return fileData ? fileData.metadata : null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Generate thumbnail URL for preview
|
|
187
|
+
getThumbnailUrl(resourceId) {
|
|
188
|
+
const blob = this.getFile(resourceId);
|
|
189
|
+
if (!blob) return null;
|
|
190
|
+
|
|
191
|
+
// For images and videos, return object URL for preview
|
|
192
|
+
if (blob.type.startsWith("image/") || blob.type.startsWith("video/")) {
|
|
193
|
+
return URL.createObjectURL(blob);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// For other files, return null (no thumbnail)
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Download file from memory
|
|
201
|
+
downloadFile(resourceId, fileName) {
|
|
202
|
+
const blob = this.getFile(resourceId);
|
|
203
|
+
const metadata = this.getMetadata(resourceId);
|
|
204
|
+
|
|
205
|
+
if (!blob) {
|
|
206
|
+
console.error("File not found:", resourceId);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Use provided fileName or fall back to original name
|
|
211
|
+
const downloadName = fileName || metadata.name;
|
|
212
|
+
|
|
213
|
+
// Create download link
|
|
214
|
+
const url = URL.createObjectURL(blob);
|
|
215
|
+
const link = document.createElement("a");
|
|
216
|
+
link.href = url;
|
|
217
|
+
link.download = downloadName;
|
|
218
|
+
document.body.appendChild(link);
|
|
219
|
+
link.click();
|
|
220
|
+
document.body.removeChild(link);
|
|
221
|
+
|
|
222
|
+
// Clean up object URL
|
|
223
|
+
setTimeout(() => URL.revokeObjectURL(url), 100);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Clear all stored files
|
|
227
|
+
clear() {
|
|
228
|
+
// Revoke any object URLs that might be in use
|
|
229
|
+
this.files.forEach((fileData, resourceId) => {
|
|
230
|
+
const thumbnailUrl = this.getThumbnailUrl(resourceId);
|
|
231
|
+
if (thumbnailUrl) {
|
|
232
|
+
URL.revokeObjectURL(thumbnailUrl);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
this.files.clear();
|
|
237
|
+
this.resourceCounter = 0;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Initialize file storage
|
|
242
|
+
const fileStorage = new InMemoryFileStorage();
|
|
243
|
+
|
|
244
|
+
// DOM element references
|
|
129
245
|
const el = {
|
|
130
246
|
schemaInput: document.getElementById("schemaInput"),
|
|
131
247
|
schemaErrors: document.getElementById("schemaErrors"),
|
|
132
248
|
applySchemaBtn: document.getElementById("applySchemaBtn"),
|
|
133
249
|
resetSchemaBtn: document.getElementById("resetSchemaBtn"),
|
|
134
|
-
|
|
135
|
-
|
|
250
|
+
formatSchemaBtn: document.getElementById("formatSchemaBtn"),
|
|
251
|
+
readOnlyToggle: document.getElementById("readOnlyToggle"),
|
|
136
252
|
formContainer: document.getElementById("formContainer"),
|
|
137
253
|
formErrors: document.getElementById("formErrors"),
|
|
138
254
|
submitBtn: document.getElementById("submitBtn"),
|
|
139
|
-
saveDraftBtn: document.getElementById("saveDraftBtn"),
|
|
140
255
|
clearFormBtn: document.getElementById("clearFormBtn"),
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
loadPrefillBtn: document.getElementById("loadPrefillBtn"),
|
|
147
|
-
copyTemplateBtn: document.getElementById("copyTemplateBtn"),
|
|
148
|
-
prefillErrors: document.getElementById("prefillErrors"),
|
|
149
|
-
urlInfo: document.getElementById("urlInfo"),
|
|
150
|
-
// Readonly demo elements
|
|
151
|
-
readonlySchemaInput: document.getElementById("readonlySchemaInput"),
|
|
152
|
-
applyReadonlyBtn: document.getElementById("applyReadonlyBtn"),
|
|
153
|
-
clearReadonlyBtn: document.getElementById("clearReadonlyBtn"),
|
|
154
|
-
readonlyErrors: document.getElementById("readonlyErrors"),
|
|
155
|
-
readonlyDemoContainer: document.getElementById("readonlyDemoContainer"),
|
|
256
|
+
dataTextarea: document.getElementById("dataTextarea"),
|
|
257
|
+
prefillBtn: document.getElementById("prefillBtn"),
|
|
258
|
+
copyDataBtn: document.getElementById("copyDataBtn"),
|
|
259
|
+
downloadDataBtn: document.getElementById("downloadDataBtn"),
|
|
260
|
+
dataErrors: document.getElementById("dataErrors"),
|
|
156
261
|
};
|
|
157
262
|
|
|
158
|
-
// Utility functions
|
|
263
|
+
// Utility functions
|
|
159
264
|
function pretty(obj) {
|
|
160
265
|
return FormBuilder.pretty(obj);
|
|
161
266
|
}
|
|
@@ -180,13 +285,51 @@ function downloadFile(filename, content) {
|
|
|
180
285
|
const a = document.createElement("a");
|
|
181
286
|
a.href = url;
|
|
182
287
|
a.download = filename;
|
|
288
|
+
document.body.appendChild(a);
|
|
183
289
|
a.click();
|
|
290
|
+
document.body.removeChild(a);
|
|
184
291
|
URL.revokeObjectURL(url);
|
|
185
292
|
}
|
|
186
293
|
|
|
187
|
-
//
|
|
188
|
-
|
|
294
|
+
// Configure FormBuilder with in-memory handlers
|
|
295
|
+
function setupFormBuilder() {
|
|
296
|
+
// Set form container
|
|
297
|
+
FormBuilder.setFormRoot(el.formContainer);
|
|
298
|
+
|
|
299
|
+
// Upload handler - store file in memory and return resource ID
|
|
300
|
+
FormBuilder.setUploadHandler(async (file) => {
|
|
301
|
+
try {
|
|
302
|
+
console.log("Uploading file to memory:", file.name);
|
|
303
|
+
const resourceId = await fileStorage.storeFile(file);
|
|
304
|
+
console.log("File stored with resource ID:", resourceId);
|
|
305
|
+
return resourceId;
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error("Upload failed:", error);
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Download handler - download file from memory
|
|
313
|
+
FormBuilder.setDownloadHandler((resourceId, fileName) => {
|
|
314
|
+
console.log("Downloading file from memory:", resourceId, fileName);
|
|
315
|
+
fileStorage.downloadFile(resourceId, fileName);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Thumbnail handler - get thumbnail URL for preview
|
|
319
|
+
FormBuilder.setThumbnailHandler((resourceId) => {
|
|
320
|
+
const thumbnailUrl = fileStorage.getThumbnailUrl(resourceId);
|
|
321
|
+
console.log("Getting thumbnail for:", resourceId, thumbnailUrl);
|
|
322
|
+
return thumbnailUrl;
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
console.log("FormBuilder configured with in-memory file handlers");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Schema management functions
|
|
329
|
+
function applyCurrentSchema() {
|
|
189
330
|
clearError(el.schemaErrors);
|
|
331
|
+
clearError(el.formErrors);
|
|
332
|
+
|
|
190
333
|
try {
|
|
191
334
|
const schema = JSON.parse(el.schemaInput.value);
|
|
192
335
|
const errors = FormBuilder.validateSchema(schema);
|
|
@@ -194,160 +337,119 @@ el.applySchemaBtn.addEventListener("click", () => {
|
|
|
194
337
|
if (errors.length > 0) {
|
|
195
338
|
showError(
|
|
196
339
|
el.schemaErrors,
|
|
197
|
-
|
|
340
|
+
`Schema validation errors: ${errors.join(", ")}`,
|
|
198
341
|
);
|
|
199
|
-
return;
|
|
342
|
+
return false;
|
|
200
343
|
}
|
|
201
344
|
|
|
202
|
-
|
|
203
|
-
el.
|
|
204
|
-
|
|
205
|
-
} catch (e) {
|
|
206
|
-
showError(el.schemaErrors, "JSON parse error: " + e.message);
|
|
207
|
-
}
|
|
208
|
-
});
|
|
345
|
+
// Set mode based on toggle
|
|
346
|
+
const isReadOnly = el.readOnlyToggle.checked;
|
|
347
|
+
FormBuilder.setMode(isReadOnly ? "readonly" : "edit");
|
|
209
348
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
349
|
+
// Get current data to preserve during re-render
|
|
350
|
+
let currentData = {};
|
|
351
|
+
try {
|
|
352
|
+
const formResult = FormBuilder.getFormData();
|
|
353
|
+
if (formResult.valid) {
|
|
354
|
+
currentData = formResult.data;
|
|
355
|
+
}
|
|
356
|
+
} catch {
|
|
357
|
+
// Ignore errors when getting current data
|
|
358
|
+
}
|
|
218
359
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
360
|
+
// Render form with current data
|
|
361
|
+
FormBuilder.renderForm(schema, currentData);
|
|
362
|
+
|
|
363
|
+
console.log(`Form rendered in ${isReadOnly ? "readonly" : "edit"} mode`);
|
|
364
|
+
return true;
|
|
223
365
|
} catch (e) {
|
|
224
|
-
showError(el.schemaErrors,
|
|
366
|
+
showError(el.schemaErrors, `JSON parse error: ${e.message}`);
|
|
367
|
+
return false;
|
|
225
368
|
}
|
|
226
|
-
}
|
|
369
|
+
}
|
|
227
370
|
|
|
228
|
-
|
|
229
|
-
|
|
371
|
+
// Event handlers
|
|
372
|
+
el.applySchemaBtn.addEventListener("click", () => {
|
|
373
|
+
applyCurrentSchema();
|
|
230
374
|
});
|
|
231
375
|
|
|
232
|
-
el.
|
|
233
|
-
|
|
376
|
+
el.resetSchemaBtn.addEventListener("click", () => {
|
|
377
|
+
el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
|
|
378
|
+
clearError(el.schemaErrors);
|
|
234
379
|
clearError(el.formErrors);
|
|
235
|
-
|
|
380
|
+
clearError(el.dataErrors);
|
|
381
|
+
el.dataTextarea.value = "";
|
|
236
382
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
await navigator.clipboard.writeText(el.outputJson.value || "");
|
|
240
|
-
el.copyOutputBtn.textContent = "Copied!";
|
|
241
|
-
setTimeout(() => {
|
|
242
|
-
el.copyOutputBtn.textContent = "Copy JSON";
|
|
243
|
-
}, 1000);
|
|
244
|
-
} catch (e) {
|
|
245
|
-
console.warn("Copy failed:", e);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
383
|
+
// Clear file storage
|
|
384
|
+
fileStorage.clear();
|
|
248
385
|
|
|
249
|
-
|
|
250
|
-
|
|
386
|
+
// Apply schema
|
|
387
|
+
applyCurrentSchema();
|
|
251
388
|
});
|
|
252
389
|
|
|
253
|
-
el.
|
|
390
|
+
el.formatSchemaBtn.addEventListener("click", () => {
|
|
254
391
|
try {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
navigator.clipboard.writeText(url);
|
|
259
|
-
el.shareUrlBtn.textContent = "URL Copied!";
|
|
260
|
-
setTimeout(() => {
|
|
261
|
-
el.shareUrlBtn.textContent = "Share URL";
|
|
262
|
-
}, 2000);
|
|
263
|
-
} catch (e) {
|
|
264
|
-
alert("Please apply a valid schema first");
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
el.loadPrefillBtn.addEventListener("click", () => {
|
|
269
|
-
clearError(el.prefillErrors);
|
|
270
|
-
try {
|
|
271
|
-
const pre = JSON.parse(el.prefillInput.value || "{}");
|
|
272
|
-
const currentSchema = JSON.parse(el.schemaInput.value);
|
|
273
|
-
FormBuilder.renderForm(currentSchema, pre);
|
|
274
|
-
el.outputJson.value = "";
|
|
275
|
-
clearError(el.formErrors);
|
|
392
|
+
const parsed = JSON.parse(el.schemaInput.value);
|
|
393
|
+
el.schemaInput.value = pretty(parsed);
|
|
394
|
+
clearError(el.schemaErrors);
|
|
276
395
|
} catch (e) {
|
|
277
|
-
showError(el.
|
|
396
|
+
showError(el.schemaErrors, `Format: JSON parse error: ${e.message}`);
|
|
278
397
|
}
|
|
279
398
|
});
|
|
280
399
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const template = {};
|
|
285
|
-
|
|
286
|
-
function processElements(elements, target) {
|
|
287
|
-
elements.forEach((element) => {
|
|
288
|
-
if (element.type === "group" && element.repeat) {
|
|
289
|
-
target[element.key] = [{}];
|
|
290
|
-
if (element.elements) {
|
|
291
|
-
processElements(element.elements, target[element.key][0]);
|
|
292
|
-
}
|
|
293
|
-
} else if (element.type === "group") {
|
|
294
|
-
target[element.key] = {};
|
|
295
|
-
if (element.elements) {
|
|
296
|
-
processElements(element.elements, target[element.key]);
|
|
297
|
-
}
|
|
298
|
-
} else {
|
|
299
|
-
target[element.key] = element.default || null;
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
processElements(schema.elements, template);
|
|
305
|
-
el.prefillInput.value = pretty(template);
|
|
306
|
-
} catch (e) {
|
|
307
|
-
showError(el.prefillErrors, "Schema parse error: " + e.message);
|
|
308
|
-
}
|
|
400
|
+
// Read-only toggle handler
|
|
401
|
+
el.readOnlyToggle.addEventListener("change", () => {
|
|
402
|
+
applyCurrentSchema(); // Re-render form with new mode
|
|
309
403
|
});
|
|
310
404
|
|
|
311
|
-
// Form
|
|
312
|
-
|
|
405
|
+
// Form interaction handlers
|
|
406
|
+
el.submitBtn.addEventListener("click", () => {
|
|
313
407
|
clearError(el.formErrors);
|
|
408
|
+
clearError(el.dataErrors);
|
|
314
409
|
|
|
315
410
|
const result = FormBuilder.getFormData();
|
|
316
411
|
|
|
317
412
|
if (!result.valid) {
|
|
318
|
-
showError(el.formErrors,
|
|
413
|
+
showError(el.formErrors, `Validation errors: ${result.errors.join(", ")}`);
|
|
319
414
|
return;
|
|
320
415
|
}
|
|
321
416
|
|
|
322
|
-
//
|
|
323
|
-
|
|
417
|
+
// For demo purposes, we can show either raw data or enhanced data
|
|
418
|
+
// Let's provide both options - you can choose which one to use
|
|
419
|
+
|
|
420
|
+
// Option 1: Show raw data with just resource IDs (what you might want)
|
|
421
|
+
const rawData = JSON.parse(JSON.stringify(result.data));
|
|
422
|
+
|
|
423
|
+
// Option 2: Show enhanced data with file metadata (for demo visualization)
|
|
424
|
+
const enhancedData = JSON.parse(JSON.stringify(result.data));
|
|
324
425
|
|
|
325
426
|
function replaceResourceIds(obj) {
|
|
326
427
|
for (const key in obj) {
|
|
327
428
|
if (typeof obj[key] === "string" && obj[key].startsWith("res_")) {
|
|
328
|
-
const
|
|
329
|
-
if (
|
|
429
|
+
const metadata = fileStorage.getMetadata(obj[key]);
|
|
430
|
+
if (metadata) {
|
|
330
431
|
obj[key] = {
|
|
331
432
|
resourceId: obj[key],
|
|
332
|
-
name:
|
|
333
|
-
type:
|
|
334
|
-
size:
|
|
433
|
+
name: metadata.name,
|
|
434
|
+
type: metadata.type,
|
|
435
|
+
size: metadata.size,
|
|
436
|
+
uploadedAt: metadata.uploadedAt,
|
|
335
437
|
};
|
|
336
438
|
}
|
|
337
439
|
} else if (Array.isArray(obj[key])) {
|
|
338
|
-
obj[key].forEach((item) => {
|
|
440
|
+
obj[key].forEach((item, index) => {
|
|
339
441
|
if (typeof item === "string" && item.startsWith("res_")) {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
if (meta) {
|
|
442
|
+
const metadata = fileStorage.getMetadata(item);
|
|
443
|
+
if (metadata) {
|
|
343
444
|
obj[key][index] = {
|
|
344
445
|
resourceId: item,
|
|
345
|
-
name:
|
|
346
|
-
type:
|
|
347
|
-
size:
|
|
446
|
+
name: metadata.name,
|
|
447
|
+
type: metadata.type,
|
|
448
|
+
size: metadata.size,
|
|
449
|
+
uploadedAt: metadata.uploadedAt,
|
|
348
450
|
};
|
|
349
451
|
}
|
|
350
|
-
} else if (typeof item === "object") {
|
|
452
|
+
} else if (typeof item === "object" && item !== null) {
|
|
351
453
|
replaceResourceIds(item);
|
|
352
454
|
}
|
|
353
455
|
});
|
|
@@ -357,218 +459,109 @@ function submitFormEnhanced() {
|
|
|
357
459
|
}
|
|
358
460
|
}
|
|
359
461
|
|
|
360
|
-
replaceResourceIds(
|
|
462
|
+
replaceResourceIds(enhancedData);
|
|
361
463
|
|
|
362
|
-
|
|
363
|
-
|
|
464
|
+
// Show raw data (just resource IDs) - change to enhancedData for metadata
|
|
465
|
+
el.dataTextarea.value = pretty(rawData);
|
|
466
|
+
console.log("Form submitted successfully");
|
|
467
|
+
console.log("Raw data:", rawData);
|
|
468
|
+
console.log("Enhanced data:", enhancedData);
|
|
469
|
+
});
|
|
364
470
|
|
|
365
|
-
el.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
471
|
+
el.clearFormBtn.addEventListener("click", () => {
|
|
472
|
+
FormBuilder.clearForm();
|
|
473
|
+
clearError(el.formErrors);
|
|
474
|
+
console.log("Form values cleared");
|
|
369
475
|
});
|
|
370
476
|
|
|
371
|
-
//
|
|
372
|
-
el.
|
|
373
|
-
clearError(el.
|
|
477
|
+
// Data management handlers
|
|
478
|
+
el.prefillBtn.addEventListener("click", () => {
|
|
479
|
+
clearError(el.dataErrors);
|
|
480
|
+
clearError(el.formErrors);
|
|
481
|
+
|
|
374
482
|
try {
|
|
375
|
-
const
|
|
376
|
-
const
|
|
483
|
+
const prefillData = JSON.parse(el.dataTextarea.value || "{}");
|
|
484
|
+
const currentSchema = JSON.parse(el.schemaInput.value);
|
|
377
485
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
)
|
|
383
|
-
|
|
486
|
+
// Convert enhanced data back to raw format for prefilling
|
|
487
|
+
const processedData = JSON.parse(JSON.stringify(prefillData));
|
|
488
|
+
|
|
489
|
+
function extractResourceIds(obj) {
|
|
490
|
+
for (const key in obj) {
|
|
491
|
+
// Handle enhanced file objects - extract just the resourceId
|
|
492
|
+
if (
|
|
493
|
+
obj[key] &&
|
|
494
|
+
typeof obj[key] === "object" &&
|
|
495
|
+
obj[key].resourceId &&
|
|
496
|
+
typeof obj[key].resourceId === "string" &&
|
|
497
|
+
obj[key].resourceId.startsWith("res_")
|
|
498
|
+
) {
|
|
499
|
+
obj[key] = obj[key].resourceId;
|
|
500
|
+
} else if (Array.isArray(obj[key])) {
|
|
501
|
+
obj[key].forEach((item, index) => {
|
|
502
|
+
if (
|
|
503
|
+
item &&
|
|
504
|
+
typeof item === "object" &&
|
|
505
|
+
item.resourceId &&
|
|
506
|
+
typeof item.resourceId === "string" &&
|
|
507
|
+
item.resourceId.startsWith("res_")
|
|
508
|
+
) {
|
|
509
|
+
obj[key][index] = item.resourceId;
|
|
510
|
+
} else if (typeof item === "object" && item !== null) {
|
|
511
|
+
extractResourceIds(item);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
} else if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
515
|
+
extractResourceIds(obj[key]);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
384
518
|
}
|
|
385
519
|
|
|
386
|
-
|
|
387
|
-
el.readonlyDemoContainer.innerHTML = "";
|
|
520
|
+
extractResourceIds(processedData);
|
|
388
521
|
|
|
389
|
-
//
|
|
390
|
-
const
|
|
391
|
-
|
|
522
|
+
// Set mode based on toggle
|
|
523
|
+
const isReadOnly = el.readOnlyToggle.checked;
|
|
524
|
+
FormBuilder.setMode(isReadOnly ? "readonly" : "edit");
|
|
392
525
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
FormBuilder.setMode("readonly");
|
|
397
|
-
FormBuilder.renderForm(schema, DEFAULT_READONLY_DATA);
|
|
398
|
-
} finally {
|
|
399
|
-
// Restore original settings
|
|
400
|
-
FormBuilder.setFormRoot(originalFormRoot);
|
|
401
|
-
FormBuilder.state.config.readonly = originalMode;
|
|
402
|
-
}
|
|
526
|
+
FormBuilder.renderForm(currentSchema, processedData);
|
|
527
|
+
console.log("Form prefilled with data");
|
|
528
|
+
console.log("Processed prefill data:", processedData);
|
|
403
529
|
} catch (e) {
|
|
404
|
-
showError(el.
|
|
530
|
+
showError(el.dataErrors, `JSON parse error: ${e.message}`);
|
|
531
|
+
console.error("Prefill error:", e);
|
|
405
532
|
}
|
|
406
533
|
});
|
|
407
534
|
|
|
408
|
-
el.
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (schemaParam) {
|
|
420
|
-
try {
|
|
421
|
-
const schema = JSON.parse(atob(schemaParam));
|
|
422
|
-
el.schemaInput.value = pretty(schema);
|
|
423
|
-
FormBuilder.renderForm(schema, {});
|
|
424
|
-
el.urlInfo.classList.remove("hidden");
|
|
425
|
-
} catch (e) {
|
|
426
|
-
console.warn("Failed to load schema from URL:", e);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Read-only demo functions
|
|
432
|
-
function downloadDemoFile(filePath, fileName) {
|
|
433
|
-
console.log("downloadDemoFile called with:", filePath, fileName);
|
|
434
|
-
|
|
435
|
-
// Check if we're running on a local dev server
|
|
436
|
-
const isLocalDev =
|
|
437
|
-
window.location.protocol === "http:" &&
|
|
438
|
-
window.location.hostname === "localhost";
|
|
439
|
-
|
|
440
|
-
if (isLocalDev) {
|
|
441
|
-
console.log("Running on local dev server, using fetch method");
|
|
442
|
-
// Force download by fetching the file and creating a blob
|
|
443
|
-
fetch(filePath)
|
|
444
|
-
.then((response) => {
|
|
445
|
-
console.log("Fetch response:", response.ok, response.status);
|
|
446
|
-
return response.blob();
|
|
447
|
-
})
|
|
448
|
-
.then((blob) => {
|
|
449
|
-
console.log("Creating download link for blob:", blob.size, "bytes");
|
|
450
|
-
const link = document.createElement("a");
|
|
451
|
-
link.href = URL.createObjectURL(blob);
|
|
452
|
-
link.download = fileName;
|
|
453
|
-
document.body.appendChild(link);
|
|
454
|
-
link.click();
|
|
455
|
-
document.body.removeChild(link);
|
|
456
|
-
URL.revokeObjectURL(link.href);
|
|
457
|
-
console.log("Download completed successfully");
|
|
458
|
-
})
|
|
459
|
-
.catch((error) => {
|
|
460
|
-
console.error("Fetch download failed:", error);
|
|
461
|
-
simpleLinkDownload(filePath, fileName);
|
|
462
|
-
});
|
|
463
|
-
} else {
|
|
464
|
-
console.log("Not on local dev server, using simple link method");
|
|
465
|
-
simpleLinkDownload(filePath, fileName);
|
|
535
|
+
el.copyDataBtn.addEventListener("click", async () => {
|
|
536
|
+
try {
|
|
537
|
+
await navigator.clipboard.writeText(el.dataTextarea.value || "");
|
|
538
|
+
el.copyDataBtn.textContent = "Copied!";
|
|
539
|
+
setTimeout(() => {
|
|
540
|
+
el.copyDataBtn.textContent = "Copy JSON";
|
|
541
|
+
}, 1000);
|
|
542
|
+
console.log("Data copied to clipboard");
|
|
543
|
+
} catch (e) {
|
|
544
|
+
console.warn("Copy failed:", e);
|
|
466
545
|
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function simpleLinkDownload(filePath, fileName) {
|
|
470
|
-
console.log("Using simple link download:", filePath, fileName);
|
|
471
|
-
const link = document.createElement("a");
|
|
472
|
-
link.href = filePath;
|
|
473
|
-
link.download = fileName;
|
|
474
|
-
link.style.display = "none";
|
|
475
|
-
document.body.appendChild(link);
|
|
476
|
-
link.click();
|
|
477
|
-
document.body.removeChild(link);
|
|
478
|
-
console.log("Simple link download triggered");
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Configure download handler for readonly demo
|
|
482
|
-
function setupReadonlyDemo() {
|
|
483
|
-
// Set up download handler that works with local demo files
|
|
484
|
-
FormBuilder.setDownloadHandler((resourceId, fileName) => {
|
|
485
|
-
const demoFileMap = {
|
|
486
|
-
demo_infographic: "images/infographic_draft.jpg",
|
|
487
|
-
demo_video: "images/final_video.mp4",
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
const filePath = demoFileMap[resourceId] || `images/${fileName}`;
|
|
491
|
-
downloadDemoFile(filePath, fileName);
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
// Set up thumbnail handler for demo (returns URL directly, not Promise)
|
|
495
|
-
FormBuilder.setThumbnailHandler((resourceId) => {
|
|
496
|
-
const demoFileMap = {
|
|
497
|
-
demo_infographic: "images/infographic_draft.jpg",
|
|
498
|
-
demo_video: "images/final_video.mp4",
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
return demoFileMap[resourceId] || null;
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
// Pre-populate demo resource metadata
|
|
505
|
-
FormBuilder.state.resourceIndex.set("demo_infographic", {
|
|
506
|
-
name: "infographic_result.jpg",
|
|
507
|
-
type: "image/jpeg",
|
|
508
|
-
size: 150000,
|
|
509
|
-
file: null,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
FormBuilder.state.resourceIndex.set("demo_video", {
|
|
513
|
-
name: "final_video.mp4",
|
|
514
|
-
type: "video/mp4",
|
|
515
|
-
size: 2500000,
|
|
516
|
-
file: null,
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Default readonly schema for demo
|
|
521
|
-
const DEFAULT_READONLY_SCHEMA = {
|
|
522
|
-
version: "0.3",
|
|
523
|
-
title: "Результаты работы",
|
|
524
|
-
elements: [
|
|
525
|
-
{
|
|
526
|
-
type: "file",
|
|
527
|
-
key: "result_image",
|
|
528
|
-
label: "Изображение результата",
|
|
529
|
-
description: "Готовая инфографика на основе загруженных данных",
|
|
530
|
-
},
|
|
531
|
-
{
|
|
532
|
-
type: "file",
|
|
533
|
-
key: "result_video",
|
|
534
|
-
label: "Видео результат",
|
|
535
|
-
description: "Финальное видео презентации",
|
|
536
|
-
},
|
|
537
|
-
],
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
// Default readonly data
|
|
541
|
-
const DEFAULT_READONLY_DATA = {
|
|
542
|
-
result_image: "demo_infographic",
|
|
543
|
-
result_video: "demo_video",
|
|
544
|
-
};
|
|
546
|
+
});
|
|
545
547
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
textarea.value = pretty(DEFAULT_READONLY_SCHEMA);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
548
|
+
el.downloadDataBtn.addEventListener("click", () => {
|
|
549
|
+
downloadFile("form-data.json", el.dataTextarea.value || "{}");
|
|
550
|
+
console.log("Data downloaded");
|
|
551
|
+
});
|
|
553
552
|
|
|
554
553
|
// Initialize demo application
|
|
555
554
|
function initDemo() {
|
|
556
|
-
// Set up
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
// Set up handlers for readonly demo
|
|
560
|
-
setupReadonlyDemo();
|
|
555
|
+
// Set up FormBuilder
|
|
556
|
+
setupFormBuilder();
|
|
561
557
|
|
|
562
558
|
// Initialize with example schema
|
|
563
559
|
el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
|
|
564
|
-
FormBuilder.setMode("edit"); // Ensure main form is in edit mode
|
|
565
|
-
FormBuilder.renderForm(EXAMPLE_SCHEMA, {});
|
|
566
560
|
|
|
567
|
-
//
|
|
568
|
-
|
|
561
|
+
// Apply initial schema
|
|
562
|
+
applyCurrentSchema();
|
|
569
563
|
|
|
570
|
-
|
|
571
|
-
setTimeout(renderReadonlyDemo, 500);
|
|
564
|
+
console.log("Demo initialized successfully");
|
|
572
565
|
}
|
|
573
566
|
|
|
574
567
|
// Start the demo when the page loads
|