@formique/semantq 1.0.6 → 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 +289 -182
- 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';
|
|
@@ -83,7 +100,8 @@ class Formique extends FormBuilder {
|
|
|
83
100
|
this.formiqueEndpoint = "https://formiqueapi.onrender.com/api/send-email";
|
|
84
101
|
|
|
85
102
|
// DISABLE DOM LISTENER
|
|
86
|
-
|
|
103
|
+
|
|
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,17 +174,20 @@ 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
|
-
|
|
168
|
-
|
|
169
|
-
//DISABLE DOM LISTNER
|
|
189
|
+
|
|
190
|
+
// DISABLE DOM LISTENER
|
|
170
191
|
//}); // DOM LISTENER WRAPPER
|
|
171
192
|
|
|
172
193
|
// CONSTRUCTOR WRAPPER FOR FORMIQUE CLASS
|
|
@@ -180,69 +201,69 @@ generateFormId() {
|
|
|
180
201
|
|
|
181
202
|
|
|
182
203
|
initDependencyGraph() {
|
|
183
|
-
|
|
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
|
+
});
|
|
184
229
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const fieldId = attributes.id || name;
|
|
230
|
+
// Add state tracking for the parent field
|
|
231
|
+
this.dependencyGraph[fieldId].push({ state: null });
|
|
188
232
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
this.dependencyGraph[fieldId] = attributes.dependents.map((dependentName) => {
|
|
192
|
-
const dependentField = this.formSchema.find(
|
|
193
|
-
([, depName]) => depName === dependentName
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
if (dependentField) {
|
|
197
|
-
const dependentAttributes = dependentField[4] || {};
|
|
198
|
-
const dependentFieldId = dependentAttributes.id || dependentName; // Get dependent field ID
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
dependent: dependentFieldId,
|
|
202
|
-
condition: dependentAttributes.condition || null,
|
|
203
|
-
};
|
|
204
|
-
} else {
|
|
205
|
-
console.warn(`Dependent field "${dependentName}" not found in schema.`);
|
|
233
|
+
// Attach the input change event listener to the parent field
|
|
234
|
+
this.attachInputChangeListener(fieldId);
|
|
206
235
|
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// Add state tracking for the parent field
|
|
210
|
-
this.dependencyGraph[fieldId].push({ state: null });
|
|
211
|
-
|
|
212
|
-
// console.log("Graph", this.dependencyGraph[fieldId]);
|
|
213
|
-
|
|
214
|
-
// Attach the input change event listener to the parent field
|
|
215
|
-
this.attachInputChangeListener(fieldId);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Hide dependent fields initially
|
|
219
|
-
if (attributes.dependents) {
|
|
220
|
-
|
|
221
|
-
attributes.dependents.forEach((dependentName) => {
|
|
222
|
-
const dependentField = this.formSchema.find(
|
|
223
|
-
([, depName]) => depName === dependentName
|
|
224
|
-
);
|
|
225
|
-
const dependentAttributes = dependentField ? dependentField[4] || {} : {};
|
|
226
|
-
const dependentFieldId = dependentAttributes.id || dependentName;
|
|
227
236
|
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
+
});
|
|
237
263
|
}
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// console.log("Dependency Graph:", this.dependencyGraph);
|
|
264
|
+
});
|
|
243
265
|
}
|
|
244
266
|
|
|
245
|
-
|
|
246
267
|
// Attach Event Listeners
|
|
247
268
|
attachInputChangeListener(parentField) {
|
|
248
269
|
const fieldElement = document.getElementById(parentField);
|
|
@@ -344,6 +365,60 @@ registerObservers() {
|
|
|
344
365
|
}
|
|
345
366
|
|
|
346
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
|
+
|
|
347
422
|
applyTheme(theme, formContainerId) {
|
|
348
423
|
//const stylesheet = document.querySelector('link[formique-style]');
|
|
349
424
|
|
|
@@ -398,6 +473,67 @@ applyTheme(theme, formContainerId) {
|
|
|
398
473
|
}
|
|
399
474
|
|
|
400
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
|
+
|
|
401
537
|
// renderFormElement method
|
|
402
538
|
renderFormElement() {
|
|
403
539
|
let formHTML = '<form';
|
|
@@ -3402,17 +3538,20 @@ this.renderSingleSelectField(type, name, label, validate, attributes, mainCatego
|
|
|
3402
3538
|
|
|
3403
3539
|
renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
|
|
3404
3540
|
|
|
3405
|
-
console.log("Within");
|
|
3541
|
+
console.log("Within renderSingleSelectField");
|
|
3406
3542
|
// Define valid validation attributes for select fields
|
|
3407
3543
|
const selectValidationAttributes = ['required'];
|
|
3408
3544
|
|
|
3409
3545
|
// Construct validation attributes
|
|
3410
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
|
|
3411
3549
|
if (validate) {
|
|
3412
3550
|
Object.entries(validate).forEach(([key, value]) => {
|
|
3413
3551
|
if (selectValidationAttributes.includes(key)) {
|
|
3414
3552
|
if (key === 'required') {
|
|
3415
3553
|
validationAttrs += `${key} `;
|
|
3554
|
+
originalRequired = true; // Mark that it was originally required
|
|
3416
3555
|
}
|
|
3417
3556
|
} else {
|
|
3418
3557
|
console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
|
|
@@ -3423,10 +3562,10 @@ console.log("Within");
|
|
|
3423
3562
|
// Handle the binding syntax
|
|
3424
3563
|
let bindingDirective = '';
|
|
3425
3564
|
if (attributes.binding) {
|
|
3426
|
-
|
|
3427
|
-
|
|
3565
|
+
if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
|
|
3566
|
+
bindingDirective = ` bind:value="${name}" `;
|
|
3567
|
+
}
|
|
3428
3568
|
}
|
|
3429
|
-
}
|
|
3430
3569
|
|
|
3431
3570
|
// Define attributes for the select field
|
|
3432
3571
|
let id = attributes.id || name;
|
|
@@ -3435,7 +3574,8 @@ console.log("Within");
|
|
|
3435
3574
|
// Handle additional attributes
|
|
3436
3575
|
let additionalAttrs = '';
|
|
3437
3576
|
for (const [key, value] of Object.entries(attributes)) {
|
|
3438
|
-
|
|
3577
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
3578
|
+
if (key.startsWith('on')) {
|
|
3439
3579
|
// Handle event attributes
|
|
3440
3580
|
const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
|
|
3441
3581
|
additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
|
|
@@ -3470,32 +3610,33 @@ console.log("Within");
|
|
|
3470
3610
|
|
|
3471
3611
|
let inputClass = attributes.class || this.inputClass;
|
|
3472
3612
|
|
|
3473
|
-
|
|
3474
|
-
|
|
3613
|
+
// Remove `onchange` from HTML; it will be handled by JavaScript event listeners
|
|
3614
|
+
const onchangeAttr = ''; // <--- Ensure this is an empty string
|
|
3615
|
+
|
|
3475
3616
|
let labelDisplay;
|
|
3476
|
-
let rawLabel;
|
|
3617
|
+
let rawLabel;
|
|
3477
3618
|
|
|
3478
3619
|
if (mode === 'dynamicSingleSelect' && subCategoriesOptions) {
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3620
|
+
if (label.includes('-')) {
|
|
3621
|
+
const [mainCategoryLabel] = label.split('-');
|
|
3622
|
+
labelDisplay = mainCategoryLabel;
|
|
3623
|
+
rawLabel = label;
|
|
3624
|
+
} else {
|
|
3625
|
+
labelDisplay = label;
|
|
3626
|
+
rawLabel = label;
|
|
3627
|
+
}
|
|
3487
3628
|
} else {
|
|
3488
|
-
|
|
3629
|
+
labelDisplay = label;
|
|
3489
3630
|
}
|
|
3490
3631
|
|
|
3491
3632
|
|
|
3492
|
-
// Construct the final HTML string
|
|
3633
|
+
// Construct the final HTML string for the main select
|
|
3493
3634
|
let formHTML = `
|
|
3494
3635
|
<fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
|
|
3495
|
-
<legend>${labelDisplay}
|
|
3636
|
+
<legend>${labelDisplay}
|
|
3496
3637
|
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3497
3638
|
</legend>
|
|
3498
|
-
<label for="${id}"> Select ${labelDisplay}
|
|
3639
|
+
<label for="${id}"> Select ${labelDisplay}
|
|
3499
3640
|
<select name="${name}"
|
|
3500
3641
|
${bindingDirective}
|
|
3501
3642
|
${dimensionAttrs}
|
|
@@ -3503,8 +3644,7 @@ console.log("Within");
|
|
|
3503
3644
|
class="${inputClass}"
|
|
3504
3645
|
${additionalAttrs}
|
|
3505
3646
|
${validationAttrs}
|
|
3506
|
-
${
|
|
3507
|
-
>
|
|
3647
|
+
data-original-required="${originalRequired}" >
|
|
3508
3648
|
${selectHTML}
|
|
3509
3649
|
</select>
|
|
3510
3650
|
</fieldset>
|
|
@@ -3524,123 +3664,90 @@ console.log("Within");
|
|
|
3524
3664
|
return `\n${match}\n`;
|
|
3525
3665
|
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3526
3666
|
|
|
3527
|
-
//console.log(formattedHtml);
|
|
3528
3667
|
this.formMarkUp+=formattedHtml;
|
|
3529
|
-
//return formattedHtml;
|
|
3530
|
-
|
|
3531
3668
|
|
|
3532
|
-
/* dynamicSingleSelect */
|
|
3533
3669
|
|
|
3534
|
-
|
|
3670
|
+
/* dynamicSingleSelect - Sub-Category Generation Block */
|
|
3535
3671
|
|
|
3672
|
+
if (mode && mode ==='dynamicSingleSelect' && subCategoriesOptions) {
|
|
3536
3673
|
|
|
3537
|
-
//
|
|
3538
|
-
const targetDiv = document.getElementById(this.formContainerId);
|
|
3674
|
+
const categoryId = attributes.id || name; // This is the ID of the main dynamic select ('languages')
|
|
3539
3675
|
|
|
3540
|
-
|
|
3676
|
+
subCategoriesOptions.forEach(subCategory => {
|
|
3677
|
+
const { id, label, options: subOptions } = subCategory; // Renamed 'options' to 'subOptions' to avoid conflict
|
|
3541
3678
|
|
|
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
|
|
3542
3686
|
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
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('');
|
|
3549
3694
|
|
|
3550
|
-
// Hide all subcategory fields
|
|
3551
|
-
document.querySelectorAll(\`[class*="\${fieldsetid}"]\`).forEach(div => {
|
|
3552
|
-
div.style.display = "none";
|
|
3553
|
-
});
|
|
3554
3695
|
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
if (selectedCategoryFieldset) {
|
|
3558
|
-
selectedCategoryFieldset.style.display = "block";
|
|
3559
|
-
}
|
|
3560
|
-
}
|
|
3561
|
-
`;
|
|
3562
|
-
|
|
3563
|
-
// Append the script element to the target div
|
|
3564
|
-
targetDiv.appendChild(scriptElement);
|
|
3565
|
-
} else {
|
|
3566
|
-
console.error(`Target div with id "${this.formContainerId}" not found.`);
|
|
3567
|
-
}
|
|
3696
|
+
let subCategoryLabel;
|
|
3697
|
+
console.log('Label (rawLabel for sub-category):', rawLabel); // Debug log
|
|
3568
3698
|
|
|
3569
|
-
|
|
3570
|
-
|
|
3699
|
+
if (rawLabel.includes('-')) {
|
|
3700
|
+
subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
|
|
3701
|
+
} else {
|
|
3702
|
+
subCategoryLabel = 'options';
|
|
3703
|
+
}
|
|
3571
3704
|
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
}).join('');
|
|
3705
|
+
let optionsLabel;
|
|
3706
|
+
if (subCategoryLabel !== 'options') {
|
|
3707
|
+
optionsLabel = rawLabel.split('-')?.[1] + ' Option';
|
|
3708
|
+
} else {
|
|
3709
|
+
optionsLabel = subCategoryLabel;
|
|
3710
|
+
}
|
|
3579
3711
|
|
|
3580
3712
|
|
|
3581
|
-
|
|
3582
|
-
|
|
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
|
+
});
|
|
3583
3739
|
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
}
|
|
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');
|
|
3589
3744
|
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
} else {
|
|
3594
|
-
optionsLabel = subCategoryLabel;
|
|
3745
|
+
// Append the generated HTML to formMarkUp
|
|
3746
|
+
this.formMarkUp += subFormHTML;
|
|
3747
|
+
});
|
|
3595
3748
|
}
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
// Create the HTML for the fieldset and select elements
|
|
3599
|
-
let formHTML = `
|
|
3600
|
-
<fieldset class="${this.selectGroupClass} ${categoryId}" id="${id}-options" style="display: none;">
|
|
3601
|
-
<legend> ${label} ${subCategoryLabel} ${this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3602
|
-
</legend>
|
|
3603
|
-
<label for="${id}"> Select ${label} ${optionsLabel}
|
|
3604
|
-
</label>
|
|
3605
|
-
<select name="${id}"
|
|
3606
|
-
${bindingDirective}
|
|
3607
|
-
${dimensionAttrs}
|
|
3608
|
-
id="${id + '-block'}"
|
|
3609
|
-
class="${inputClass}"
|
|
3610
|
-
${additionalAttrs}
|
|
3611
|
-
${validationAttrs}
|
|
3612
|
-
>
|
|
3613
|
-
<option value="">Choose an option</option>
|
|
3614
|
-
${selectHTML}
|
|
3615
|
-
</select>
|
|
3616
|
-
</fieldset>
|
|
3617
|
-
`.replace(/^\s*\n/gm, '').trim();
|
|
3618
|
-
|
|
3619
|
-
// Apply vertical layout to the <select> element and its children
|
|
3620
|
-
formHTML = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3621
|
-
// Reformat attributes into a vertical layout
|
|
3622
|
-
const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
|
|
3623
|
-
return `<select\n${attributes}\n>\n${p2.trim()}\n</select>`;
|
|
3624
|
-
});
|
|
3625
|
-
|
|
3626
|
-
// Ensure the <fieldset> block starts on a new line and remove extra blank lines
|
|
3627
|
-
formHTML = formHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3628
|
-
// Ensure <fieldset> starts on a new line
|
|
3629
|
-
return `\n${match}\n`;
|
|
3630
|
-
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3631
|
-
|
|
3632
|
-
// Append the generated HTML to formMarkUp
|
|
3633
|
-
this.formMarkUp += formHTML;
|
|
3634
|
-
|
|
3635
|
-
//return formHTML;
|
|
3636
|
-
});
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
}
|
|
3640
3749
|
}
|
|
3641
3750
|
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
3751
|
renderMultipleSelectField(type, name, label, validate, attributes, options) {
|
|
3645
3752
|
// Define valid validation attributes for multiple select fields
|
|
3646
3753
|
const selectValidationAttributes = ['required', 'minlength', 'maxlength'];
|