@formique/semantq 1.0.5 → 1.0.7
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/formique-semantq.js +290 -189
- package/package.json +1 -1
package/formique-semantq.js
CHANGED
|
@@ -60,6 +60,23 @@ class Formique extends FormBuilder {
|
|
|
60
60
|
asteriskHtml: '<span aria-hidden="true" style="color: red;">*</span>',
|
|
61
61
|
...formSettings
|
|
62
62
|
};
|
|
63
|
+
|
|
64
|
+
this.themeColor = formSettings.themeColor || null;
|
|
65
|
+
|
|
66
|
+
this.themeColorMap = {
|
|
67
|
+
'primary': {
|
|
68
|
+
'--formique-base-bg': '#ffffff',
|
|
69
|
+
'--formique-base-text': '#333333',
|
|
70
|
+
'--formique-base-shadow': '0 10px 30px rgba(0, 0, 0, 0.1)',
|
|
71
|
+
'--formique-base-label': '#555555',
|
|
72
|
+
'--formique-input-border': '#dddddd',
|
|
73
|
+
'--formique-focus-color': null, // Will be set to themeColor
|
|
74
|
+
'--formique-btn-bg': null, // Will be set to themeColor
|
|
75
|
+
'--formique-btn-text': '#ffffff',
|
|
76
|
+
'--formique-btn-shadow': null // Will be calculated from themeColor
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
63
80
|
this.divClass = 'input-block';
|
|
64
81
|
this.inputClass = 'form-input';
|
|
65
82
|
this.radioGroupClass = 'radio-group';
|
|
@@ -82,8 +99,9 @@ class Formique extends FormBuilder {
|
|
|
82
99
|
];
|
|
83
100
|
this.formiqueEndpoint = "https://formiqueapi.onrender.com/api/send-email";
|
|
84
101
|
|
|
102
|
+
// DISABLE DOM LISTENER
|
|
85
103
|
|
|
86
|
-
|
|
104
|
+
// document.addEventListener('DOMContentLoaded', () => {
|
|
87
105
|
// 1. Build the form's HTML in memory
|
|
88
106
|
this.formMarkUp += this.renderFormElement(); // Adds opening <form> tag and any hidden inputs
|
|
89
107
|
|
|
@@ -156,15 +174,21 @@ class Formique extends FormBuilder {
|
|
|
156
174
|
// Initialize dependency graph and observers after the form is rendered
|
|
157
175
|
this.initDependencyGraph();
|
|
158
176
|
this.registerObservers();
|
|
177
|
+
this.attachDynamicSelectListeners();
|
|
159
178
|
|
|
160
179
|
// Apply theme
|
|
161
|
-
if (this.
|
|
180
|
+
if (this.themeColor) {
|
|
181
|
+
this.applyCustomTheme(this.themeColor, this.formContainerId); // <--- NEW: Apply custom theme
|
|
182
|
+
} else if (this.formSettings.theme && this.themes.includes(this.formSettings.theme)) {
|
|
162
183
|
let theme = this.formSettings.theme;
|
|
163
184
|
this.applyTheme(theme, this.formContainerId);
|
|
164
185
|
} else {
|
|
165
|
-
|
|
186
|
+
// Fallback if no themeColor and no valid theme specified
|
|
187
|
+
this.applyTheme('dark', this.formContainerId); // Default to 'dark'
|
|
166
188
|
}
|
|
167
|
-
|
|
189
|
+
|
|
190
|
+
// DISABLE DOM LISTENER
|
|
191
|
+
//}); // DOM LISTENER WRAPPER
|
|
168
192
|
|
|
169
193
|
// CONSTRUCTOR WRAPPER FOR FORMIQUE CLASS
|
|
170
194
|
}
|
|
@@ -177,69 +201,69 @@ generateFormId() {
|
|
|
177
201
|
|
|
178
202
|
|
|
179
203
|
initDependencyGraph() {
|
|
180
|
-
|
|
204
|
+
this.dependencyGraph = {};
|
|
205
|
+
|
|
206
|
+
this.formSchema.forEach((field) => {
|
|
207
|
+
const [type, name, label, validate, attributes = {}] = field;
|
|
208
|
+
const fieldId = attributes.id || name;
|
|
209
|
+
|
|
210
|
+
if (attributes.dependents) {
|
|
211
|
+
// Initialize dependency array for the parent field
|
|
212
|
+
this.dependencyGraph[fieldId] = attributes.dependents.map((dependentName) => {
|
|
213
|
+
const dependentField = this.formSchema.find(
|
|
214
|
+
([, depName]) => depName === dependentName
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
if (dependentField) {
|
|
218
|
+
const dependentAttributes = dependentField[4] || {};
|
|
219
|
+
const dependentFieldId = dependentAttributes.id || dependentName; // Get dependent field ID
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
dependent: dependentFieldId,
|
|
223
|
+
condition: dependentAttributes.condition || null,
|
|
224
|
+
};
|
|
225
|
+
} else {
|
|
226
|
+
console.warn(`Dependent field "${dependentName}" not found in schema.`);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
181
229
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const fieldId = attributes.id || name;
|
|
230
|
+
// Add state tracking for the parent field
|
|
231
|
+
this.dependencyGraph[fieldId].push({ state: null });
|
|
185
232
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this.dependencyGraph[fieldId] = attributes.dependents.map((dependentName) => {
|
|
189
|
-
const dependentField = this.formSchema.find(
|
|
190
|
-
([, depName]) => depName === dependentName
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
if (dependentField) {
|
|
194
|
-
const dependentAttributes = dependentField[4] || {};
|
|
195
|
-
const dependentFieldId = dependentAttributes.id || dependentName; // Get dependent field ID
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
dependent: dependentFieldId,
|
|
199
|
-
condition: dependentAttributes.condition || null,
|
|
200
|
-
};
|
|
201
|
-
} else {
|
|
202
|
-
console.warn(`Dependent field "${dependentName}" not found in schema.`);
|
|
233
|
+
// Attach the input change event listener to the parent field
|
|
234
|
+
this.attachInputChangeListener(fieldId);
|
|
203
235
|
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// Add state tracking for the parent field
|
|
207
|
-
this.dependencyGraph[fieldId].push({ state: null });
|
|
208
|
-
|
|
209
|
-
// console.log("Graph", this.dependencyGraph[fieldId]);
|
|
210
|
-
|
|
211
|
-
// Attach the input change event listener to the parent field
|
|
212
|
-
this.attachInputChangeListener(fieldId);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Hide dependent fields initially
|
|
216
|
-
if (attributes.dependents) {
|
|
217
|
-
|
|
218
|
-
attributes.dependents.forEach((dependentName) => {
|
|
219
|
-
const dependentField = this.formSchema.find(
|
|
220
|
-
([, depName]) => depName === dependentName
|
|
221
|
-
);
|
|
222
|
-
const dependentAttributes = dependentField ? dependentField[4] || {} : {};
|
|
223
|
-
const dependentFieldId = dependentAttributes.id || dependentName;
|
|
224
236
|
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
// Hide dependent fields initially and set their required state
|
|
238
|
+
if (attributes.dependents) {
|
|
239
|
+
attributes.dependents.forEach((dependentName) => {
|
|
240
|
+
const dependentField = this.formSchema.find(
|
|
241
|
+
([, depName]) => depName === dependentName
|
|
242
|
+
);
|
|
243
|
+
const dependentAttributes = dependentField ? dependentField[4] || {} : {};
|
|
244
|
+
const dependentFieldId = dependentAttributes.id || dependentName;
|
|
245
|
+
|
|
246
|
+
const inputBlock = document.querySelector(`#${dependentFieldId}-block`);
|
|
247
|
+
|
|
248
|
+
if (inputBlock) {
|
|
249
|
+
inputBlock.style.display = 'none'; // Hide dependent field by default
|
|
250
|
+
// Save original required state and set to false
|
|
251
|
+
const inputs = inputBlock.querySelectorAll('input, select, textarea');
|
|
252
|
+
inputs.forEach((input) => {
|
|
253
|
+
// Check if the input was originally required in the schema
|
|
254
|
+
if (input.hasAttribute('required') && input.required === true) {
|
|
255
|
+
input.setAttribute('data-original-required', 'true'); // Save original required state
|
|
256
|
+
input.required = false; // Remove required attribute when hiding
|
|
257
|
+
} else {
|
|
258
|
+
input.setAttribute('data-original-required', 'false'); // Explicitly mark as not originally required
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
});
|
|
234
263
|
}
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
// console.log("Dependency Graph:", this.dependencyGraph);
|
|
264
|
+
});
|
|
240
265
|
}
|
|
241
266
|
|
|
242
|
-
|
|
243
267
|
// Attach Event Listeners
|
|
244
268
|
attachInputChangeListener(parentField) {
|
|
245
269
|
const fieldElement = document.getElementById(parentField);
|
|
@@ -341,6 +365,60 @@ registerObservers() {
|
|
|
341
365
|
}
|
|
342
366
|
|
|
343
367
|
|
|
368
|
+
// --- NEW METHOD FOR DYNAMIC SELECT LISTENERS ---
|
|
369
|
+
attachDynamicSelectListeners() {
|
|
370
|
+
this.formSchema.forEach(field => {
|
|
371
|
+
const [type, name, label, validate, attributes = {}] = field;
|
|
372
|
+
|
|
373
|
+
if (type === 'dynamicSingleSelect') {
|
|
374
|
+
const mainSelectId = attributes.id || name;
|
|
375
|
+
const mainSelectElement = document.getElementById(mainSelectId);
|
|
376
|
+
|
|
377
|
+
if (mainSelectElement) {
|
|
378
|
+
mainSelectElement.addEventListener('change', (event) => {
|
|
379
|
+
const selectedCategory = event.target.value; // e.g., 'frontend', 'backend', 'server'
|
|
380
|
+
|
|
381
|
+
// Find all sub-category fieldsets related to this main select
|
|
382
|
+
const subCategoryFieldsets = document.querySelectorAll(`.${mainSelectId}-subcategory-group`);
|
|
383
|
+
|
|
384
|
+
subCategoryFieldsets.forEach(fieldset => {
|
|
385
|
+
const subSelect = fieldset.querySelector('select'); // Get the actual select element
|
|
386
|
+
if (subSelect) {
|
|
387
|
+
// Save original required state (if it was true) then set to false if hidden
|
|
388
|
+
subSelect.setAttribute('data-original-required', subSelect.required.toString());
|
|
389
|
+
subSelect.required = false; // Always set to false when hiding
|
|
390
|
+
}
|
|
391
|
+
fieldset.style.display = 'none'; // Hide all sub-category fieldsets initially
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Show the selected sub-category fieldset and manage its required state
|
|
395
|
+
const selectedFieldsetId = selectedCategory + '-options'; // Matches the ID format in renderSingleSelectField
|
|
396
|
+
const selectedFieldset = document.getElementById(selectedFieldsetId);
|
|
397
|
+
|
|
398
|
+
if (selectedFieldset) {
|
|
399
|
+
selectedFieldset.style.display = 'block'; // Show the selected one
|
|
400
|
+
const selectedSubSelect = selectedFieldset.querySelector('select');
|
|
401
|
+
if (selectedSubSelect) {
|
|
402
|
+
// Restore original required state for the visible select
|
|
403
|
+
selectedSubSelect.required = selectedSubSelect.getAttribute('data-original-required') === 'true';
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// IMPORTANT: Trigger the change listener once on load if a default option is selected
|
|
409
|
+
// This ensures correct initial visibility and required states if there's a pre-selected main category.
|
|
410
|
+
// We do this by dispatching a 'change' event programmatically if the select has a value.
|
|
411
|
+
if (mainSelectElement.value) {
|
|
412
|
+
const event = new Event('change');
|
|
413
|
+
mainSelectElement.dispatchEvent(event);
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
console.warn(`Main dynamic select element with ID ${mainSelectId} not found.`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
344
422
|
applyTheme(theme, formContainerId) {
|
|
345
423
|
//const stylesheet = document.querySelector('link[formique-style]');
|
|
346
424
|
|
|
@@ -395,6 +473,67 @@ applyTheme(theme, formContainerId) {
|
|
|
395
473
|
}
|
|
396
474
|
|
|
397
475
|
|
|
476
|
+
|
|
477
|
+
// New method to apply a custom theme based on a color
|
|
478
|
+
applyCustomTheme(color, formContainerId) {
|
|
479
|
+
const formContainer = document.getElementById(formContainerId);
|
|
480
|
+
|
|
481
|
+
if (!formContainer) {
|
|
482
|
+
console.error(`Form container with ID "${formContainerId}" not found. Cannot apply custom theme.`);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// You can add 'formique' class here as well if not already added
|
|
487
|
+
formContainer.classList.add('formique');
|
|
488
|
+
|
|
489
|
+
// Generate a slightly darker shade for the button shadow if needed
|
|
490
|
+
// This is a simplified example; for robust color manipulation, consider a library
|
|
491
|
+
const darkenColor = (hex, percent) => {
|
|
492
|
+
const f = parseInt(hex.slice(1), 16);
|
|
493
|
+
const t = percent < 0 ? 0 : 255;
|
|
494
|
+
const p = percent < 0 ? percent * -1 : percent;
|
|
495
|
+
const R = f >> 16;
|
|
496
|
+
const G = (f >> 8) & 0x00FF;
|
|
497
|
+
const B = f & 0x0000FF;
|
|
498
|
+
return "#" + (0x1000000 + (Math.round((t - R) * p) + R) * 0x10000 + (Math.round((t - G) * p) + G) * 0x100 + (Math.round((t - B) * p) + B)).toString(16).slice(1);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const shadowColor = darkenColor(color, 0.2); // Darken the theme color by 20% for shadow
|
|
502
|
+
|
|
503
|
+
// Define the custom CSS variables, prioritizing the provided color
|
|
504
|
+
const customCssVars = {
|
|
505
|
+
'--formique-base-bg': '#ffffff', // Light theme base background
|
|
506
|
+
'--formique-base-text': '#333333', // Light theme base text
|
|
507
|
+
'--formique-base-shadow': '0 10px 30px rgba(0, 0, 0, 0.1)', // Light theme shadow
|
|
508
|
+
'--formique-base-label': '#555555', // Light theme label
|
|
509
|
+
'--formique-input-border': '#dddddd', // Light theme input border
|
|
510
|
+
'--formique-focus-color': color, // Set to the provided custom color
|
|
511
|
+
'--formique-btn-bg': color, // Set to the provided custom color
|
|
512
|
+
'--formique-btn-text': '#ffffff', // White text for buttons
|
|
513
|
+
'--formique-btn-shadow': `0 2px 10px ${shadowColor || 'rgba(0, 0, 0, 0.1)'}` // Dynamic button shadow
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
let styleContent = '';
|
|
517
|
+
for (const [prop, val] of Object.entries(customCssVars)) {
|
|
518
|
+
styleContent += ` ${prop}: ${val};\n`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Create a <style> tag for the custom theme
|
|
522
|
+
const styleElement = document.createElement('style');
|
|
523
|
+
styleElement.textContent = `
|
|
524
|
+
#${formContainerId}.formique {
|
|
525
|
+
${styleContent}
|
|
526
|
+
}
|
|
527
|
+
`;
|
|
528
|
+
|
|
529
|
+
// Insert the style element into the head or before the form container
|
|
530
|
+
formContainer.parentNode.insertBefore(styleElement, formContainer);
|
|
531
|
+
|
|
532
|
+
console.log(`Applied custom theme with color: ${color} to form container: ${formContainerId}`);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
|
|
398
537
|
// renderFormElement method
|
|
399
538
|
renderFormElement() {
|
|
400
539
|
let formHTML = '<form';
|
|
@@ -3399,17 +3538,20 @@ this.renderSingleSelectField(type, name, label, validate, attributes, mainCatego
|
|
|
3399
3538
|
|
|
3400
3539
|
renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
|
|
3401
3540
|
|
|
3402
|
-
console.log("Within");
|
|
3541
|
+
console.log("Within renderSingleSelectField");
|
|
3403
3542
|
// Define valid validation attributes for select fields
|
|
3404
3543
|
const selectValidationAttributes = ['required'];
|
|
3405
3544
|
|
|
3406
3545
|
// Construct validation attributes
|
|
3407
3546
|
let validationAttrs = '';
|
|
3547
|
+
// Store original required state for the main select
|
|
3548
|
+
let originalRequired = false; // <--- This variable tracks if the main select was originally required
|
|
3408
3549
|
if (validate) {
|
|
3409
3550
|
Object.entries(validate).forEach(([key, value]) => {
|
|
3410
3551
|
if (selectValidationAttributes.includes(key)) {
|
|
3411
3552
|
if (key === 'required') {
|
|
3412
3553
|
validationAttrs += `${key} `;
|
|
3554
|
+
originalRequired = true; // Mark that it was originally required
|
|
3413
3555
|
}
|
|
3414
3556
|
} else {
|
|
3415
3557
|
console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
|
|
@@ -3420,10 +3562,10 @@ console.log("Within");
|
|
|
3420
3562
|
// Handle the binding syntax
|
|
3421
3563
|
let bindingDirective = '';
|
|
3422
3564
|
if (attributes.binding) {
|
|
3423
|
-
|
|
3424
|
-
|
|
3565
|
+
if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
|
|
3566
|
+
bindingDirective = ` bind:value="${name}" `;
|
|
3567
|
+
}
|
|
3425
3568
|
}
|
|
3426
|
-
}
|
|
3427
3569
|
|
|
3428
3570
|
// Define attributes for the select field
|
|
3429
3571
|
let id = attributes.id || name;
|
|
@@ -3432,7 +3574,8 @@ console.log("Within");
|
|
|
3432
3574
|
// Handle additional attributes
|
|
3433
3575
|
let additionalAttrs = '';
|
|
3434
3576
|
for (const [key, value] of Object.entries(attributes)) {
|
|
3435
|
-
|
|
3577
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
3578
|
+
if (key.startsWith('on')) {
|
|
3436
3579
|
// Handle event attributes
|
|
3437
3580
|
const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
|
|
3438
3581
|
additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
|
|
@@ -3467,32 +3610,33 @@ console.log("Within");
|
|
|
3467
3610
|
|
|
3468
3611
|
let inputClass = attributes.class || this.inputClass;
|
|
3469
3612
|
|
|
3470
|
-
|
|
3471
|
-
|
|
3613
|
+
// Remove `onchange` from HTML; it will be handled by JavaScript event listeners
|
|
3614
|
+
const onchangeAttr = ''; // <--- Ensure this is an empty string
|
|
3615
|
+
|
|
3472
3616
|
let labelDisplay;
|
|
3473
|
-
let rawLabel;
|
|
3617
|
+
let rawLabel;
|
|
3474
3618
|
|
|
3475
3619
|
if (mode === 'dynamicSingleSelect' && subCategoriesOptions) {
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3620
|
+
if (label.includes('-')) {
|
|
3621
|
+
const [mainCategoryLabel] = label.split('-');
|
|
3622
|
+
labelDisplay = mainCategoryLabel;
|
|
3623
|
+
rawLabel = label;
|
|
3624
|
+
} else {
|
|
3625
|
+
labelDisplay = label;
|
|
3626
|
+
rawLabel = label;
|
|
3627
|
+
}
|
|
3484
3628
|
} else {
|
|
3485
|
-
|
|
3629
|
+
labelDisplay = label;
|
|
3486
3630
|
}
|
|
3487
3631
|
|
|
3488
3632
|
|
|
3489
|
-
// Construct the final HTML string
|
|
3633
|
+
// Construct the final HTML string for the main select
|
|
3490
3634
|
let formHTML = `
|
|
3491
3635
|
<fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
|
|
3492
|
-
<legend>${labelDisplay}
|
|
3636
|
+
<legend>${labelDisplay}
|
|
3493
3637
|
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3494
3638
|
</legend>
|
|
3495
|
-
<label for="${id}"> Select ${labelDisplay}
|
|
3639
|
+
<label for="${id}"> Select ${labelDisplay}
|
|
3496
3640
|
<select name="${name}"
|
|
3497
3641
|
${bindingDirective}
|
|
3498
3642
|
${dimensionAttrs}
|
|
@@ -3500,8 +3644,7 @@ console.log("Within");
|
|
|
3500
3644
|
class="${inputClass}"
|
|
3501
3645
|
${additionalAttrs}
|
|
3502
3646
|
${validationAttrs}
|
|
3503
|
-
${
|
|
3504
|
-
>
|
|
3647
|
+
data-original-required="${originalRequired}" >
|
|
3505
3648
|
${selectHTML}
|
|
3506
3649
|
</select>
|
|
3507
3650
|
</fieldset>
|
|
@@ -3521,122 +3664,89 @@ console.log("Within");
|
|
|
3521
3664
|
return `\n${match}\n`;
|
|
3522
3665
|
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3523
3666
|
|
|
3524
|
-
//console.log(formattedHtml);
|
|
3525
3667
|
this.formMarkUp+=formattedHtml;
|
|
3526
|
-
//return formattedHtml;
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
/* dynamicSingleSelect */
|
|
3530
3668
|
|
|
3531
|
-
if (mode && mode ==='dynamicSingleSelect' && subCategoriesOptions) {
|
|
3532
3669
|
|
|
3670
|
+
/* dynamicSingleSelect - Sub-Category Generation Block */
|
|
3533
3671
|
|
|
3534
|
-
|
|
3535
|
-
const targetDiv = document.getElementById(this.formContainerId);
|
|
3672
|
+
if (mode && mode ==='dynamicSingleSelect' && subCategoriesOptions) {
|
|
3536
3673
|
|
|
3537
|
-
|
|
3674
|
+
const categoryId = attributes.id || name; // This is the ID of the main dynamic select ('languages')
|
|
3538
3675
|
|
|
3676
|
+
subCategoriesOptions.forEach(subCategory => {
|
|
3677
|
+
const { id, label, options: subOptions } = subCategory; // Renamed 'options' to 'subOptions' to avoid conflict
|
|
3539
3678
|
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3679
|
+
// IMPORTANT: Sub-category selects are *initially hidden*
|
|
3680
|
+
// Therefore, by default, they are NOT required until they are revealed.
|
|
3681
|
+
// If your schema later allows specific sub-categories to be inherently required
|
|
3682
|
+
// when shown, you'd need to extract that validation from your schema here.
|
|
3683
|
+
// For now, they are considered non-required until JavaScript makes them required.
|
|
3684
|
+
let isSubCategoryRequired = false; // Default to false as they are hidden
|
|
3685
|
+
const subCategoryValidationAttrs = ''; // No direct 'required' in HTML initially
|
|
3546
3686
|
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3687
|
+
// Build the select options HTML for sub-category
|
|
3688
|
+
const subSelectHTML = subOptions.map(option => {
|
|
3689
|
+
const isSelected = option.selected ? ' selected' : '';
|
|
3690
|
+
return `
|
|
3691
|
+
<option value="${option.value}"${isSelected}>${option.label}</option>
|
|
3692
|
+
`;
|
|
3693
|
+
}).join('');
|
|
3551
3694
|
|
|
3552
|
-
// Show the selected category
|
|
3553
|
-
const selectedCategoryFieldset = document.getElementById(category + '-options');
|
|
3554
|
-
if (selectedCategoryFieldset) {
|
|
3555
|
-
selectedCategoryFieldset.style.display = "block";
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
|
-
`;
|
|
3559
3695
|
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
} else {
|
|
3563
|
-
console.error(`Target div with id "${this.formContainerId}" not found.`);
|
|
3564
|
-
}
|
|
3696
|
+
let subCategoryLabel;
|
|
3697
|
+
console.log('Label (rawLabel for sub-category):', rawLabel); // Debug log
|
|
3565
3698
|
|
|
3566
|
-
|
|
3567
|
-
|
|
3699
|
+
if (rawLabel.includes('-')) {
|
|
3700
|
+
subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
|
|
3701
|
+
} else {
|
|
3702
|
+
subCategoryLabel = 'options';
|
|
3703
|
+
}
|
|
3568
3704
|
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
}).join('');
|
|
3705
|
+
let optionsLabel;
|
|
3706
|
+
if (subCategoryLabel !== 'options') {
|
|
3707
|
+
optionsLabel = rawLabel.split('-')?.[1] + ' Option';
|
|
3708
|
+
} else {
|
|
3709
|
+
optionsLabel = subCategoryLabel;
|
|
3710
|
+
}
|
|
3576
3711
|
|
|
3577
3712
|
|
|
3578
|
-
|
|
3579
|
-
|
|
3713
|
+
// Create the HTML for the sub-category fieldset and select elements
|
|
3714
|
+
// Added a class based on the main select's ID for easy grouping/selection
|
|
3715
|
+
let subFormHTML = `
|
|
3716
|
+
<fieldset class="${this.selectGroupClass} ${categoryId}-subcategory-group" id="${id}-options" style="display: none;"> <legend>${label} ${subCategoryLabel} ${isSubCategoryRequired && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3717
|
+
</legend>
|
|
3718
|
+
<label for="${id}"> Select ${label} ${optionsLabel}
|
|
3719
|
+
</label>
|
|
3720
|
+
<select name="${id}"
|
|
3721
|
+
${bindingDirective}
|
|
3722
|
+
${dimensionAttrs}
|
|
3723
|
+
id="${id}"
|
|
3724
|
+
class="${inputClass}"
|
|
3725
|
+
${additionalAttrs}
|
|
3726
|
+
${subCategoryValidationAttrs}
|
|
3727
|
+
data-original-required="${isSubCategoryRequired}" >
|
|
3728
|
+
<option value="">Choose an option</option>
|
|
3729
|
+
${subSelectHTML}
|
|
3730
|
+
</select>
|
|
3731
|
+
</fieldset>
|
|
3732
|
+
`.replace(/^\s*\n/gm, '').trim();
|
|
3733
|
+
|
|
3734
|
+
// Apply vertical layout to the <select> element and its children
|
|
3735
|
+
subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3736
|
+
const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
|
|
3737
|
+
return `<select\n${attributes}\n>\n${p2.trim()}\n</select>`;
|
|
3738
|
+
});
|
|
3580
3739
|
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
}
|
|
3740
|
+
// Ensure the <fieldset> block starts on a new line and remove extra blank lines
|
|
3741
|
+
subFormHTML = subFormHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3742
|
+
return `\n${match}\n`;
|
|
3743
|
+
}).replace(/\n\s*\n/g, '\n');
|
|
3586
3744
|
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
} else {
|
|
3591
|
-
optionsLabel = subCategoryLabel;
|
|
3745
|
+
// Append the generated HTML to formMarkUp
|
|
3746
|
+
this.formMarkUp += subFormHTML;
|
|
3747
|
+
});
|
|
3592
3748
|
}
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
// Create the HTML for the fieldset and select elements
|
|
3596
|
-
let formHTML = `
|
|
3597
|
-
<fieldset class="${this.selectGroupClass} ${categoryId}" id="${id}-options" style="display: none;">
|
|
3598
|
-
<legend> ${label} ${subCategoryLabel} ${this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3599
|
-
</legend>
|
|
3600
|
-
<label for="${id}"> Select ${label} ${optionsLabel}
|
|
3601
|
-
</label>
|
|
3602
|
-
<select name="${id}"
|
|
3603
|
-
${bindingDirective}
|
|
3604
|
-
${dimensionAttrs}
|
|
3605
|
-
id="${id + '-block'}"
|
|
3606
|
-
class="${inputClass}"
|
|
3607
|
-
${additionalAttrs}
|
|
3608
|
-
${validationAttrs}
|
|
3609
|
-
>
|
|
3610
|
-
<option value="">Choose an option</option>
|
|
3611
|
-
${selectHTML}
|
|
3612
|
-
</select>
|
|
3613
|
-
</fieldset>
|
|
3614
|
-
`.replace(/^\s*\n/gm, '').trim();
|
|
3615
|
-
|
|
3616
|
-
// Apply vertical layout to the <select> element and its children
|
|
3617
|
-
formHTML = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3618
|
-
// Reformat attributes into a vertical layout
|
|
3619
|
-
const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
|
|
3620
|
-
return `<select\n${attributes}\n>\n${p2.trim()}\n</select>`;
|
|
3621
|
-
});
|
|
3622
|
-
|
|
3623
|
-
// Ensure the <fieldset> block starts on a new line and remove extra blank lines
|
|
3624
|
-
formHTML = formHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3625
|
-
// Ensure <fieldset> starts on a new line
|
|
3626
|
-
return `\n${match}\n`;
|
|
3627
|
-
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3628
|
-
|
|
3629
|
-
// Append the generated HTML to formMarkUp
|
|
3630
|
-
this.formMarkUp += formHTML;
|
|
3631
|
-
|
|
3632
|
-
//return formHTML;
|
|
3633
|
-
});
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
3749
|
}
|
|
3637
|
-
}
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
3750
|
|
|
3641
3751
|
renderMultipleSelectField(type, name, label, validate, attributes, options) {
|
|
3642
3752
|
// Define valid validation attributes for multiple select fields
|
|
@@ -4019,12 +4129,3 @@ const spinner = `<div id="formiqueSpinner" style="display: flex; align-items: ce
|
|
|
4019
4129
|
export default Formique;
|
|
4020
4130
|
|
|
4021
4131
|
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|