@formique/semantq 1.0.9 → 1.0.11
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/LowCodeParser.js +290 -315
- package/astToFormique.js +415 -330
- package/formique-semantq.js +247 -250
- package/package.json +1 -1
package/formique-semantq.js
CHANGED
|
@@ -64,7 +64,7 @@ class Formique extends FormBuilder {
|
|
|
64
64
|
|
|
65
65
|
const formObjects = new astToFormique(ast);
|
|
66
66
|
|
|
67
|
-
console.log("formSchema", JSON.stringify(formObjects.formSchema,null,2));
|
|
67
|
+
//console.log("formSchema", JSON.stringify(formObjects.formSchema,null,2));
|
|
68
68
|
// Assign from formObjects if a string is passed
|
|
69
69
|
formSchema = formObjects.formSchema;
|
|
70
70
|
finalSettings = { ...formSettings, ...formObjects.formSettings };
|
|
@@ -133,10 +133,14 @@ class Formique extends FormBuilder {
|
|
|
133
133
|
// Filter out 'submit' field for rendering, and render all other fields
|
|
134
134
|
const nonSubmitFieldsHtml = this.formSchema
|
|
135
135
|
.filter(field => field[0] !== 'submit')
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
.map(field => {
|
|
137
|
+
// FIX 1: Add 'subOptions' to capture the 7th element (Index 6)
|
|
138
|
+
const [type, name, label, validate, attributes = {}, options, subOptions] = field;
|
|
139
|
+
|
|
140
|
+
// FIX 2: Pass 'subOptions' through to renderField
|
|
141
|
+
return this.renderField(type, name, label, validate, attributes, options, subOptions);
|
|
142
|
+
}).join('');
|
|
143
|
+
|
|
140
144
|
this.formMarkUp += nonSubmitFieldsHtml;
|
|
141
145
|
|
|
142
146
|
// Find and render the submit button separately, at the very end of the form content
|
|
@@ -311,16 +315,48 @@ initDependencyGraph() {
|
|
|
311
315
|
}
|
|
312
316
|
|
|
313
317
|
// Attach Event Listeners
|
|
318
|
+
// Corrected Attach Event Listeners
|
|
314
319
|
attachInputChangeListener(parentField) {
|
|
315
|
-
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
// Use querySelectorAll to get all elements with the name attribute matching the fieldId.
|
|
321
|
+
// This correctly targets all radio/checkbox inputs in a group.
|
|
322
|
+
const fieldElements = document.querySelectorAll(`[name="${parentField}"]`);
|
|
323
|
+
|
|
324
|
+
// If no elements found by name, fall back to getting the single element by ID
|
|
325
|
+
if (fieldElements.length === 0) {
|
|
326
|
+
const singleElement = document.getElementById(parentField);
|
|
327
|
+
if (singleElement) {
|
|
328
|
+
fieldElements = [singleElement]; // Treat it as a single element array
|
|
329
|
+
} else {
|
|
330
|
+
console.warn(`Parent field element(s) not found for field: ${parentField}`);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
323
333
|
}
|
|
334
|
+
|
|
335
|
+
fieldElements.forEach(fieldElement => {
|
|
336
|
+
// Radio/checkbox groups should use 'change', not 'input'
|
|
337
|
+
const eventType = (fieldElement.type === 'radio' || fieldElement.type === 'checkbox') ? 'change' : 'input';
|
|
338
|
+
|
|
339
|
+
fieldElement.addEventListener(eventType, (event) => {
|
|
340
|
+
let value;
|
|
341
|
+
if (fieldElement.type === 'radio' && !event.target.checked) {
|
|
342
|
+
// Only process the change if the radio button is now checked
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (fieldElement.type === 'checkbox') {
|
|
347
|
+
// For checkboxes, you might need special logic to return an array of checked values
|
|
348
|
+
// For now, let's stick to the change event on a single checkbox
|
|
349
|
+
value = event.target.checked ? event.target.value : '';
|
|
350
|
+
} else {
|
|
351
|
+
value = event.target.value;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Convert value to lowercase for consistent comparison with 'yes' condition
|
|
355
|
+
//this.handleParentFieldChange(parentField, value.toLowerCase());
|
|
356
|
+
|
|
357
|
+
this.handleParentFieldChange(parentField, value.toLowerCase());
|
|
358
|
+
});
|
|
359
|
+
});
|
|
324
360
|
}
|
|
325
361
|
|
|
326
362
|
|
|
@@ -426,7 +462,7 @@ attachDynamicSelectListeners() {
|
|
|
426
462
|
const selectedCategory = event.target.value; // e.g., 'frontend', 'backend', 'server'
|
|
427
463
|
|
|
428
464
|
// Find all sub-category fieldsets related to this main select
|
|
429
|
-
const subCategoryFieldsets = document.querySelectorAll(`.${mainSelectId}
|
|
465
|
+
const subCategoryFieldsets = document.querySelectorAll(`.${mainSelectId}`);
|
|
430
466
|
|
|
431
467
|
subCategoryFieldsets.forEach(fieldset => {
|
|
432
468
|
const subSelect = fieldset.querySelector('select'); // Get the actual select element
|
|
@@ -439,7 +475,7 @@ attachDynamicSelectListeners() {
|
|
|
439
475
|
});
|
|
440
476
|
|
|
441
477
|
// Show the selected sub-category fieldset and manage its required state
|
|
442
|
-
const selectedFieldsetId = selectedCategory
|
|
478
|
+
const selectedFieldsetId = selectedCategory; // Matches the ID format in renderSingleSelectField
|
|
443
479
|
const selectedFieldset = document.getElementById(selectedFieldsetId);
|
|
444
480
|
|
|
445
481
|
if (selectedFieldset) {
|
|
@@ -694,51 +730,10 @@ renderForm() {
|
|
|
694
730
|
}
|
|
695
731
|
|
|
696
732
|
|
|
697
|
-
/*
|
|
698
|
-
renderField(type, name, label, validate, attributes, options) {
|
|
699
|
-
const fieldRenderMap = {
|
|
700
|
-
'text': this.renderTextField,
|
|
701
|
-
'email': this.renderEmailField,
|
|
702
|
-
'number': this.renderNumberField,
|
|
703
|
-
'password': this.renderPasswordField,
|
|
704
|
-
'textarea': this.renderTextAreaField,
|
|
705
|
-
'tel': this.renderTelField,
|
|
706
|
-
'date': this.renderDateField,
|
|
707
|
-
'time': this.renderTimeField,
|
|
708
|
-
'datetime-local': this.renderDateTimeField,
|
|
709
|
-
'month': this.renderMonthField,
|
|
710
|
-
'week': this.renderWeekField,
|
|
711
|
-
'url': this.renderUrlField,
|
|
712
|
-
'search': this.renderSearchField,
|
|
713
|
-
'color': this.renderColorField,
|
|
714
|
-
'checkbox': this.renderCheckboxField,
|
|
715
|
-
'radio': this.renderRadioField,
|
|
716
|
-
'file': this.renderFileField,
|
|
717
|
-
'hidden': this.renderHiddenField,
|
|
718
|
-
'image': this.renderImageField,
|
|
719
|
-
'textarea': this.renderTextAreaField,
|
|
720
|
-
'singleSelect': this.renderSingleSelectField,
|
|
721
|
-
'multipleSelect': this.renderMultipleSelectField,
|
|
722
|
-
'dynamicSingleSelect': this.renderDynamicSingleSelectField,
|
|
723
|
-
'range': this.renderRangeField,
|
|
724
|
-
'submit': this.renderSubmitButton,
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
const renderMethod = fieldRenderMap[type];
|
|
728
|
-
|
|
729
|
-
if (renderMethod) {
|
|
730
|
-
return renderMethod.call(this, type, name, label, validate, attributes, options);
|
|
731
|
-
} else {
|
|
732
|
-
console.warn(`Unsupported field type '${type}' encountered.`);
|
|
733
|
-
return ''; // or handle gracefully
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
*/
|
|
738
733
|
|
|
739
734
|
|
|
740
735
|
// renderField method - No change needed here for this issue, but ensure it handles 'submit' type correctly if called directly
|
|
741
|
-
renderField(type, name, label, validate, attributes, options) {
|
|
736
|
+
renderField(type, name, label, validate, attributes, options, subOptions = undefined) {
|
|
742
737
|
const fieldRenderMap = {
|
|
743
738
|
'text': this.renderTextField,
|
|
744
739
|
'email': this.renderEmailField,
|
|
@@ -773,7 +768,7 @@ renderField(type, name, label, validate, attributes, options) {
|
|
|
773
768
|
// If the type is 'submit', ensure we use the specific renderSubmitButtonElement
|
|
774
769
|
// Although, with the filter in renderForm(), this branch for 'submit' type
|
|
775
770
|
// might not be hit in the primary rendering flow, it's good practice.
|
|
776
|
-
return renderMethod.call(this, type, name, label, validate, attributes, options);
|
|
771
|
+
return renderMethod.call(this, type, name, label, validate, attributes, options,subOptions);
|
|
777
772
|
|
|
778
773
|
if (type === 'submit') {
|
|
779
774
|
return this.renderSubmitButton(type, name, label, validate, attributes, options);
|
|
@@ -3731,239 +3726,240 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
|
|
|
3731
3726
|
/* DYNAMIC SINGLE SELECT BLOCK */
|
|
3732
3727
|
|
|
3733
3728
|
// Function to render the dynamic select field and update based on user selection
|
|
3734
|
-
renderDynamicSingleSelectField(type, name, label, validate, attributes, options) {
|
|
3735
|
-
|
|
3736
|
-
// Step 1: Transform the data into an array of objects
|
|
3737
|
-
const mainCategoryOptions = options.
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
})
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
const mode='dynamicSingleSelect';
|
|
3751
|
-
this.renderSingleSelectField(type, name, label, validate, attributes, mainCategoryOptions, subCategoriesOptions, mode);
|
|
3729
|
+
renderDynamicSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions) {
|
|
3730
|
+
|
|
3731
|
+
// Step 1: Transform the data into an array of objects
|
|
3732
|
+
const mainCategoryOptions = options.map(item => {
|
|
3733
|
+
// CRITICAL GUARD FIX: Check for item.options existence to prevent crash
|
|
3734
|
+
const selected = item.options
|
|
3735
|
+
? item.options.some(option => option.selected === true)
|
|
3736
|
+
: item.selected === true;
|
|
3737
|
+
|
|
3738
|
+
// Create a transformed object
|
|
3739
|
+
return {
|
|
3740
|
+
value: item.value,
|
|
3741
|
+
label: item.label,
|
|
3742
|
+
...(selected && { selected: true })
|
|
3743
|
+
};
|
|
3744
|
+
});
|
|
3752
3745
|
|
|
3753
|
-
|
|
3746
|
+
const mode = 'dynamicSingleSelect';
|
|
3747
|
+
|
|
3748
|
+
// Pass the main options and the nested sub categories options to the single select renderer
|
|
3749
|
+
this.renderSingleSelectField(type, name, label, validate, attributes, mainCategoryOptions, subCategoriesOptions, mode);
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3754
3752
|
|
|
3755
3753
|
|
|
3756
3754
|
renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
|
|
3757
3755
|
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
const selectValidationAttributes = ['required'];
|
|
3761
|
-
|
|
3762
|
-
// Construct validation attributes
|
|
3763
|
-
let validationAttrs = '';
|
|
3764
|
-
// Store original required state for the main select
|
|
3765
|
-
let originalRequired = false; // <--- This variable tracks if the main select was originally required
|
|
3766
|
-
if (validate) {
|
|
3767
|
-
Object.entries(validate).forEach(([key, value]) => {
|
|
3768
|
-
if (selectValidationAttributes.includes(key)) {
|
|
3769
|
-
if (key === 'required') {
|
|
3770
|
-
validationAttrs += `${key} `;
|
|
3771
|
-
originalRequired = true; // Mark that it was originally required
|
|
3772
|
-
}
|
|
3773
|
-
} else {
|
|
3774
|
-
console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
|
|
3775
|
-
}
|
|
3776
|
-
});
|
|
3777
|
-
}
|
|
3756
|
+
// Define valid validation attributes for select fields
|
|
3757
|
+
const selectValidationAttributes = ['required'];
|
|
3778
3758
|
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3759
|
+
// Construct validation attributes
|
|
3760
|
+
let validationAttrs = '';
|
|
3761
|
+
// Store original required state for the main select
|
|
3762
|
+
let originalRequired = false; // <--- This variable tracks if the main select was originally required
|
|
3763
|
+
if (validate) {
|
|
3764
|
+
Object.entries(validate).forEach(([key, value]) => {
|
|
3765
|
+
if (selectValidationAttributes.includes(key)) {
|
|
3766
|
+
if (key === 'required') {
|
|
3767
|
+
validationAttrs += `${key} `;
|
|
3768
|
+
originalRequired = true; // Mark that it was originally required
|
|
3784
3769
|
}
|
|
3770
|
+
} else {
|
|
3771
|
+
// Removed console.warn
|
|
3772
|
+
}
|
|
3773
|
+
});
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
// Handle the binding syntax
|
|
3777
|
+
let bindingDirective = '';
|
|
3778
|
+
if (attributes.binding) {
|
|
3779
|
+
if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
|
|
3780
|
+
bindingDirective = ` bind:value="${name}" `;
|
|
3785
3781
|
}
|
|
3782
|
+
}
|
|
3786
3783
|
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3784
|
+
// Define attributes for the select field
|
|
3785
|
+
let id = attributes.id || name;
|
|
3786
|
+
let dimensionAttrs = ''; // No dimension attributes applicable for select fields
|
|
3790
3787
|
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
}
|
|
3807
|
-
}
|
|
3788
|
+
// Handle additional attributes
|
|
3789
|
+
let additionalAttrs = '';
|
|
3790
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
3791
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
3792
|
+
if (key.startsWith('on')) {
|
|
3793
|
+
// Handle event attributes
|
|
3794
|
+
const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
|
|
3795
|
+
additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
|
|
3796
|
+
} else {
|
|
3797
|
+
// Handle boolean attributes
|
|
3798
|
+
if (value === true) {
|
|
3799
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
|
|
3800
|
+
} else if (value !== false) {
|
|
3801
|
+
// Convert underscores to hyphens and set the attribute
|
|
3802
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
|
|
3808
3803
|
}
|
|
3804
|
+
}
|
|
3809
3805
|
}
|
|
3806
|
+
}
|
|
3810
3807
|
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3808
|
+
// Construct select options HTML based on options
|
|
3809
|
+
let selectHTML = '';
|
|
3810
|
+
if (Array.isArray(options)) {
|
|
3811
|
+
// Add a default option
|
|
3812
|
+
selectHTML += `
|
|
3813
|
+
<option value="">Choose an option</option>
|
|
3814
|
+
`;
|
|
3818
3815
|
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3816
|
+
// Add the provided options
|
|
3817
|
+
selectHTML += options.map((option) => {
|
|
3818
|
+
const isSelected = option.selected ? ' selected' : '';
|
|
3819
|
+
return `
|
|
3820
|
+
<option value="${option.value}"${isSelected}>${option.label}</option>
|
|
3821
|
+
`;
|
|
3822
|
+
}).join('');
|
|
3823
|
+
}
|
|
3827
3824
|
|
|
3828
|
-
|
|
3825
|
+
let inputClass = attributes.class || this.inputClass;
|
|
3829
3826
|
|
|
3830
|
-
|
|
3831
|
-
|
|
3827
|
+
// Remove `onchange` from HTML; it will be handled by JavaScript event listeners
|
|
3828
|
+
const onchangeAttr = ''; // <--- Ensure this is an empty string
|
|
3832
3829
|
|
|
3833
|
-
|
|
3834
|
-
|
|
3830
|
+
let labelDisplay;
|
|
3831
|
+
let rawLabel;
|
|
3835
3832
|
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
} else {
|
|
3842
|
-
labelDisplay = label;
|
|
3843
|
-
rawLabel = label;
|
|
3844
|
-
}
|
|
3833
|
+
if (mode === 'dynamicSingleSelect' && subCategoriesOptions) {
|
|
3834
|
+
if (label.includes('-')) {
|
|
3835
|
+
const [mainCategoryLabel] = label.split('-');
|
|
3836
|
+
labelDisplay = mainCategoryLabel;
|
|
3837
|
+
rawLabel = label;
|
|
3845
3838
|
} else {
|
|
3846
|
-
|
|
3839
|
+
labelDisplay = label;
|
|
3840
|
+
rawLabel = label;
|
|
3847
3841
|
}
|
|
3842
|
+
} else {
|
|
3843
|
+
labelDisplay = label;
|
|
3844
|
+
}
|
|
3848
3845
|
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
<
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
</fieldset>
|
|
3846
|
+
// Construct the final HTML string for the main select
|
|
3847
|
+
let formHTML = `
|
|
3848
|
+
<fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
|
|
3849
|
+
<legend>${labelDisplay}
|
|
3850
|
+
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3851
|
+
</legend>
|
|
3852
|
+
<label for="${id}"> Select ${labelDisplay}
|
|
3853
|
+
<select name="${name}"
|
|
3854
|
+
${bindingDirective}
|
|
3855
|
+
${dimensionAttrs}
|
|
3856
|
+
id="${id}"
|
|
3857
|
+
class="${inputClass}"
|
|
3858
|
+
${additionalAttrs}
|
|
3859
|
+
${validationAttrs}
|
|
3860
|
+
data-original-required="${originalRequired}" >
|
|
3861
|
+
${selectHTML}
|
|
3862
|
+
</select>
|
|
3863
|
+
</fieldset>
|
|
3868
3864
|
`.replace(/^\s*\n/gm, '').trim();
|
|
3869
3865
|
|
|
3866
|
+
// FIXED: Apply vertical layout to the <select> element and its children
|
|
3867
|
+
// Only split on actual attribute boundaries, not within attribute values
|
|
3868
|
+
let formattedHtml = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3869
|
+
// Use regex to match complete attribute="value" pairs
|
|
3870
|
+
const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
|
|
3871
|
+
const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
|
|
3872
|
+
return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
|
|
3873
|
+
});
|
|
3870
3874
|
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
});
|
|
3875
|
+
// Ensure the <fieldset> block starts on a new line and remove extra blank lines
|
|
3876
|
+
formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3877
|
+
// Ensure <fieldset> starts on a new line
|
|
3878
|
+
return `\n${match}\n`;
|
|
3879
|
+
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3877
3880
|
|
|
3878
|
-
|
|
3879
|
-
formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3880
|
-
// Ensure <fieldset> starts on a new line
|
|
3881
|
-
return `\n${match}\n`;
|
|
3882
|
-
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3881
|
+
this.formMarkUp+=formattedHtml;
|
|
3883
3882
|
|
|
3884
|
-
this.formMarkUp+=formattedHtml;
|
|
3885
3883
|
|
|
3884
|
+
/* dynamicSingleSelect - Sub-Category Generation Block */
|
|
3886
3885
|
|
|
3887
|
-
|
|
3886
|
+
if (mode && mode ==='dynamicSingleSelect' && subCategoriesOptions) {
|
|
3888
3887
|
|
|
3889
|
-
|
|
3888
|
+
const categoryId = attributes.id || name; // This is the ID of the main dynamic select ('languages')
|
|
3890
3889
|
|
|
3891
|
-
|
|
3890
|
+
subCategoriesOptions.forEach((subCategory) => {
|
|
3891
|
+
const { id, label, options: subOptions } = subCategory; // Renamed 'options' to 'subOptions' to avoid conflict
|
|
3892
3892
|
|
|
3893
|
-
|
|
3894
|
-
|
|
3893
|
+
// IMPORTANT: Sub-category selects are *initially hidden*
|
|
3894
|
+
// Therefore, by default, they are NOT required until they are revealed.
|
|
3895
|
+
let isSubCategoryRequired = false; // Default to false as they are hidden
|
|
3896
|
+
const subCategoryValidationAttrs = ''; // No direct 'required' in HTML initially
|
|
3895
3897
|
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3898
|
+
// Build the select options HTML for sub-category
|
|
3899
|
+
const subSelectHTML = subOptions.map(option => {
|
|
3900
|
+
const isSelected = option.selected ? ' selected' : '';
|
|
3901
|
+
return `
|
|
3902
|
+
<option value="${option.value}"${isSelected}>${option.label}</option>
|
|
3903
|
+
`;
|
|
3904
|
+
}).join('');
|
|
3903
3905
|
|
|
3904
|
-
// Build the select options HTML for sub-category
|
|
3905
|
-
const subSelectHTML = subOptions.map(option => {
|
|
3906
|
-
const isSelected = option.selected ? ' selected' : '';
|
|
3907
|
-
return `
|
|
3908
|
-
<option value="${option.value}"${isSelected}>${option.label}</option>
|
|
3909
|
-
`;
|
|
3910
|
-
}).join('');
|
|
3911
3906
|
|
|
3907
|
+
let subCategoryLabel;
|
|
3908
|
+
|
|
3909
|
+
if (rawLabel.includes('-')) {
|
|
3910
|
+
subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
|
|
3911
|
+
} else {
|
|
3912
|
+
subCategoryLabel = 'options';
|
|
3913
|
+
}
|
|
3912
3914
|
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
+
let optionsLabel;
|
|
3916
|
+
if (subCategoryLabel !== 'options') {
|
|
3917
|
+
optionsLabel = rawLabel.split('-')?.[1] + ' Option';
|
|
3918
|
+
} else {
|
|
3919
|
+
optionsLabel = subCategoryLabel;
|
|
3920
|
+
}
|
|
3915
3921
|
|
|
3916
|
-
if (rawLabel.includes('-')) {
|
|
3917
|
-
subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
|
|
3918
|
-
} else {
|
|
3919
|
-
subCategoryLabel = 'options';
|
|
3920
|
-
}
|
|
3921
3922
|
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3923
|
+
// Create the HTML for the sub-category fieldset and select elements
|
|
3924
|
+
// Added a class based on the main select's ID for easy grouping/selection
|
|
3925
|
+
let subFormHTML = `
|
|
3926
|
+
<fieldset class="${this.selectGroupClass} ${categoryId}" id="${id}" style="display: none;"> <legend>${label} ${subCategoryLabel} ${isSubCategoryRequired && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3927
|
+
</legend>
|
|
3928
|
+
<label for="${id}"> Select ${label} ${optionsLabel}
|
|
3929
|
+
</label>
|
|
3930
|
+
<select name="${id}"
|
|
3931
|
+
${bindingDirective}
|
|
3932
|
+
${dimensionAttrs}
|
|
3933
|
+
id="${id}"
|
|
3934
|
+
class="${inputClass}"
|
|
3935
|
+
${additionalAttrs}
|
|
3936
|
+
${subCategoryValidationAttrs}
|
|
3937
|
+
data-original-required="${isSubCategoryRequired}" >
|
|
3938
|
+
<option value="">Choose an option</option>
|
|
3939
|
+
${subSelectHTML}
|
|
3940
|
+
</select>
|
|
3941
|
+
</fieldset>
|
|
3942
|
+
`.replace(/^\s*\n/gm, '').trim();
|
|
3943
|
+
|
|
3944
|
+
// FIXED: Apply the same corrected formatting to sub-category selects
|
|
3945
|
+
subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3946
|
+
const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
|
|
3947
|
+
const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
|
|
3948
|
+
return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
|
|
3949
|
+
});
|
|
3928
3950
|
|
|
3951
|
+
// Ensure the <fieldset> block starts on a new line and remove extra blank lines
|
|
3952
|
+
subFormHTML = subFormHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3953
|
+
return `\n${match}\n`;
|
|
3954
|
+
}).replace(/\n\s*\n/g, '\n');
|
|
3929
3955
|
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
<label for="${id}"> Select ${label} ${optionsLabel}
|
|
3936
|
-
</label>
|
|
3937
|
-
<select name="${id}"
|
|
3938
|
-
${bindingDirective}
|
|
3939
|
-
${dimensionAttrs}
|
|
3940
|
-
id="${id}"
|
|
3941
|
-
class="${inputClass}"
|
|
3942
|
-
${additionalAttrs}
|
|
3943
|
-
${subCategoryValidationAttrs}
|
|
3944
|
-
data-original-required="${isSubCategoryRequired}" >
|
|
3945
|
-
<option value="">Choose an option</option>
|
|
3946
|
-
${subSelectHTML}
|
|
3947
|
-
</select>
|
|
3948
|
-
</fieldset>
|
|
3949
|
-
`.replace(/^\s*\n/gm, '').trim();
|
|
3950
|
-
|
|
3951
|
-
// Apply vertical layout to the <select> element and its children
|
|
3952
|
-
subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3953
|
-
const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
|
|
3954
|
-
return `<select\n${attributes}\n>\n${p2.trim()}\n</select>`;
|
|
3955
|
-
});
|
|
3956
|
+
// Append the generated HTML to formMarkUp
|
|
3957
|
+
this.formMarkUp += subFormHTML;
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3956
3961
|
|
|
3957
|
-
// Ensure the <fieldset> block starts on a new line and remove extra blank lines
|
|
3958
|
-
subFormHTML = subFormHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3959
|
-
return `\n${match}\n`;
|
|
3960
|
-
}).replace(/\n\s*\n/g, '\n');
|
|
3961
3962
|
|
|
3962
|
-
// Append the generated HTML to formMarkUp
|
|
3963
|
-
this.formMarkUp += subFormHTML;
|
|
3964
|
-
});
|
|
3965
|
-
}
|
|
3966
|
-
}
|
|
3967
3963
|
|
|
3968
3964
|
renderMultipleSelectField(type, name, label, validate, attributes, options) {
|
|
3969
3965
|
// Define valid validation attributes for multiple select fields
|
|
@@ -4181,6 +4177,7 @@ renderRecaptchaField(type, name, label, validate, attributes = {}) {
|
|
|
4181
4177
|
}
|
|
4182
4178
|
|
|
4183
4179
|
|
|
4180
|
+
|
|
4184
4181
|
/*
|
|
4185
4182
|
renderRangeField(type, name, label, validate, attributes) {
|
|
4186
4183
|
const rangeValidationAttributes = ['required', 'min', 'max', 'step'];
|