@formique/semantq 1.1.1 → 1.1.3
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 +327 -150
- package/README.md +319 -151
- package/astToFormique.js +15 -32
- package/formique-semantq.js +264 -252
- package/package.json +1 -1
package/formique-semantq.js
CHANGED
|
@@ -782,51 +782,48 @@ renderForm() {
|
|
|
782
782
|
|
|
783
783
|
// renderField method - No change needed here for this issue, but ensure it handles 'submit' type correctly if called directly
|
|
784
784
|
renderField(type, name, label, validate, attributes, options, subOptions = undefined) {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const renderMethod = fieldRenderMap[type];
|
|
785
|
+
const fieldRenderMap = {
|
|
786
|
+
'text': this.renderTextField,
|
|
787
|
+
'email': this.renderEmailField,
|
|
788
|
+
'number': this.renderNumberField,
|
|
789
|
+
'password': this.renderPasswordField,
|
|
790
|
+
'textarea': this.renderTextAreaField,
|
|
791
|
+
'tel': this.renderTelField,
|
|
792
|
+
'date': this.renderDateField,
|
|
793
|
+
'time': this.renderTimeField,
|
|
794
|
+
'datetime-local': this.renderDateTimeField,
|
|
795
|
+
'month': this.renderMonthField,
|
|
796
|
+
'week': this.renderWeekField,
|
|
797
|
+
'url': this.renderUrlField,
|
|
798
|
+
'search': this.renderSearchField,
|
|
799
|
+
'color': this.renderColorField,
|
|
800
|
+
'checkbox': this.renderCheckboxField,
|
|
801
|
+
'radio': this.renderRadioField,
|
|
802
|
+
'file': this.renderFileField,
|
|
803
|
+
'hidden': this.renderHiddenField,
|
|
804
|
+
'image': this.renderImageField,
|
|
805
|
+
'singleSelect': this.renderSingleSelectField,
|
|
806
|
+
'multipleSelect': this.renderMultipleSelectField,
|
|
807
|
+
'dynamicSingleSelect': this.renderDynamicSingleSelectField,
|
|
808
|
+
'range': this.renderRangeField,
|
|
809
|
+
'recaptcha': this.renderRecaptchaField,
|
|
810
|
+
'html': this.renderHtmlField,
|
|
811
|
+
'submit': this.renderSubmitButton,
|
|
812
|
+
};
|
|
814
813
|
|
|
815
|
-
|
|
816
|
-
// If the type is 'submit', ensure we use the specific renderSubmitButtonElement
|
|
817
|
-
// Although, with the filter in renderForm(), this branch for 'submit' type
|
|
818
|
-
// might not be hit in the primary rendering flow, it's good practice.
|
|
819
|
-
return renderMethod.call(this, type, name, label, validate, attributes, options,subOptions);
|
|
814
|
+
const renderMethod = fieldRenderMap[type];
|
|
820
815
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
return '';
|
|
828
|
-
}
|
|
816
|
+
if (renderMethod) {
|
|
817
|
+
// IMPORTANT: Pass ALL arguments including subOptions
|
|
818
|
+
return renderMethod.call(this, type, name, label, validate, attributes, options, subOptions);
|
|
819
|
+
} else {
|
|
820
|
+
console.warn(`Unsupported field type '${type}' encountered.`);
|
|
821
|
+
return '';
|
|
829
822
|
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
|
|
830
827
|
|
|
831
828
|
|
|
832
829
|
|
|
@@ -3641,244 +3638,227 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
|
|
|
3641
3638
|
/* DYNAMIC SINGLE SELECT BLOCK */
|
|
3642
3639
|
|
|
3643
3640
|
// Function to render the dynamic select field and update based on user selection
|
|
3644
|
-
renderDynamicSingleSelectField(type, name, label, validate, attributes, options) {
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
// Use item.id as the value for the main select
|
|
3650
|
-
return {
|
|
3651
|
-
value: item.id, // ← FIXED: Use id, not value
|
|
3652
|
-
label: item.label,
|
|
3653
|
-
// You can add selected logic if needed
|
|
3654
|
-
};
|
|
3655
|
-
});
|
|
3656
|
-
|
|
3657
|
-
// Step 2: The nested options ARE your sub-categories!
|
|
3658
|
-
// Transform the structure
|
|
3659
|
-
const subCategoriesOptions = options.map(item => ({
|
|
3660
|
-
id: item.id, // Same as main category value
|
|
3661
|
-
label: item.label + ' Technologies', // Or customize
|
|
3662
|
-
options: item.options // The nested options array
|
|
3663
|
-
}));
|
|
3664
|
-
|
|
3665
|
-
//console.log('Main categories:', mainCategoryOptions);
|
|
3666
|
-
//console.log('Sub categories:', subCategoriesOptions);
|
|
3667
|
-
|
|
3668
|
-
const mode = 'dynamicSingleSelect';
|
|
3669
|
-
|
|
3670
|
-
// Pass both to the renderer
|
|
3671
|
-
this.renderSingleSelectField(type, name, label, validate, attributes, mainCategoryOptions, subCategoriesOptions, mode);
|
|
3672
|
-
}
|
|
3673
|
-
|
|
3674
|
-
renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
|
|
3675
|
-
|
|
3676
|
-
// Define valid validation attributes for select fields
|
|
3677
|
-
const selectValidationAttributes = ['required'];
|
|
3678
|
-
|
|
3679
|
-
// Construct validation attributes
|
|
3680
|
-
let validationAttrs = '';
|
|
3681
|
-
// Store original required state for the main select
|
|
3682
|
-
let originalRequired = false; // <--- This variable tracks if the main select was originally required
|
|
3683
|
-
if (validate) {
|
|
3684
|
-
Object.entries(validate).forEach(([key, value]) => {
|
|
3685
|
-
if (selectValidationAttributes.includes(key)) {
|
|
3686
|
-
if (key === 'required') {
|
|
3687
|
-
validationAttrs += `${key} `;
|
|
3688
|
-
originalRequired = true; // Mark that it was originally required
|
|
3689
|
-
}
|
|
3690
|
-
} else {
|
|
3691
|
-
// Removed console.warn
|
|
3692
|
-
}
|
|
3641
|
+
renderDynamicSingleSelectField(type, name, label, validate, attributes, options, subOptions) {
|
|
3642
|
+
console.log('DEBUG: renderDynamicSingleSelectField called', {
|
|
3643
|
+
type, name, label,
|
|
3644
|
+
options: options ? options.length : 'none',
|
|
3645
|
+
subOptions: subOptions ? subOptions.length : 'none'
|
|
3693
3646
|
});
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
|
|
3700
|
-
bindingDirective = ` bind:value="${name}" `;
|
|
3647
|
+
|
|
3648
|
+
// Check if options exist
|
|
3649
|
+
if (!options || !Array.isArray(options)) {
|
|
3650
|
+
console.warn('Dynamic single select field missing options:', name);
|
|
3651
|
+
options = [];
|
|
3701
3652
|
}
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
if (value === true) {
|
|
3719
|
-
additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
|
|
3720
|
-
} else if (value !== false) {
|
|
3721
|
-
// Convert underscores to hyphens and set the attribute
|
|
3722
|
-
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
|
|
3653
|
+
|
|
3654
|
+
// Step 1: Extract main categories from options
|
|
3655
|
+
// Options should already be in the correct format from the parser
|
|
3656
|
+
const mainCategoryOptions = options.map(item => {
|
|
3657
|
+
// Handle both string and object formats
|
|
3658
|
+
if (typeof item === 'string') {
|
|
3659
|
+
return {
|
|
3660
|
+
value: item.toLowerCase().replace(/\s+/g, '-'),
|
|
3661
|
+
label: item
|
|
3662
|
+
};
|
|
3663
|
+
} else {
|
|
3664
|
+
// Already an object with value/label
|
|
3665
|
+
return {
|
|
3666
|
+
value: item.value || item.id || item,
|
|
3667
|
+
label: item.label || item.value || item
|
|
3668
|
+
};
|
|
3723
3669
|
}
|
|
3724
|
-
|
|
3670
|
+
});
|
|
3671
|
+
|
|
3672
|
+
// Step 2: Handle subCategoriesOptions (scenario blocks)
|
|
3673
|
+
let subCategoriesOptions = [];
|
|
3674
|
+
|
|
3675
|
+
if (subOptions && Array.isArray(subOptions)) {
|
|
3676
|
+
// Use the subOptions exactly as provided by the parser
|
|
3677
|
+
// They should already have the correct structure
|
|
3678
|
+
subCategoriesOptions = subOptions.map(item => {
|
|
3679
|
+
// Ensure each subCategory has the required structure
|
|
3680
|
+
return {
|
|
3681
|
+
id: item.id || item.value || '',
|
|
3682
|
+
label: item.label || item.id || '',
|
|
3683
|
+
options: Array.isArray(item.options) ? item.options : []
|
|
3684
|
+
};
|
|
3685
|
+
});
|
|
3725
3686
|
}
|
|
3726
|
-
|
|
3687
|
+
|
|
3688
|
+
console.log('Main categories:', mainCategoryOptions);
|
|
3689
|
+
console.log('Sub categories:', subCategoriesOptions);
|
|
3690
|
+
|
|
3691
|
+
// Pass both to the renderer with the mode flag
|
|
3692
|
+
this.renderSingleSelectField(
|
|
3693
|
+
type,
|
|
3694
|
+
name,
|
|
3695
|
+
label,
|
|
3696
|
+
validate,
|
|
3697
|
+
attributes,
|
|
3698
|
+
mainCategoryOptions,
|
|
3699
|
+
subCategoriesOptions,
|
|
3700
|
+
'dynamicSingleSelect'
|
|
3701
|
+
);
|
|
3702
|
+
}
|
|
3727
3703
|
|
|
3728
|
-
// Construct select options HTML based on options
|
|
3729
|
-
let selectHTML = '';
|
|
3730
|
-
if (Array.isArray(options)) {
|
|
3731
|
-
// Add a default option
|
|
3732
|
-
selectHTML += `
|
|
3733
|
-
<option value="">Choose an option</option>
|
|
3734
|
-
`;
|
|
3735
3704
|
|
|
3736
|
-
// Add the provided options
|
|
3737
|
-
selectHTML += options.map((option) => {
|
|
3738
|
-
const isSelected = option.selected ? ' selected' : '';
|
|
3739
|
-
return `
|
|
3740
|
-
<option value="${option.value}"${isSelected}>${option.label}</option>
|
|
3741
|
-
`;
|
|
3742
|
-
}).join('');
|
|
3743
|
-
}
|
|
3744
3705
|
|
|
3745
|
-
let inputClass = attributes.class || this.inputClass;
|
|
3746
3706
|
|
|
3747
|
-
// Remove `onchange` from HTML; it will be handled by JavaScript event listeners
|
|
3748
|
-
const onchangeAttr = ''; // <--- Ensure this is an empty string
|
|
3749
3707
|
|
|
3750
|
-
|
|
3751
|
-
|
|
3708
|
+
renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
|
|
3709
|
+
// Define valid validation attributes for select fields
|
|
3710
|
+
const selectValidationAttributes = ['required'];
|
|
3752
3711
|
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3712
|
+
// Construct validation attributes
|
|
3713
|
+
let validationAttrs = '';
|
|
3714
|
+
let originalRequired = false;
|
|
3715
|
+
if (validate) {
|
|
3716
|
+
Object.entries(validate).forEach(([key, value]) => {
|
|
3717
|
+
if (selectValidationAttributes.includes(key)) {
|
|
3718
|
+
if (key === 'required') {
|
|
3719
|
+
validationAttrs += `${key} `;
|
|
3720
|
+
originalRequired = true;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
});
|
|
3761
3724
|
}
|
|
3762
|
-
} else {
|
|
3763
|
-
labelDisplay = label;
|
|
3764
|
-
}
|
|
3765
|
-
|
|
3766
|
-
// Construct the final HTML string for the main select
|
|
3767
|
-
let formHTML = `
|
|
3768
|
-
<fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
|
|
3769
|
-
<legend>${labelDisplay}
|
|
3770
|
-
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3771
|
-
</legend>
|
|
3772
|
-
<label for="${id}"> Select ${labelDisplay}
|
|
3773
|
-
<select name="${name}"
|
|
3774
|
-
${bindingDirective}
|
|
3775
|
-
${dimensionAttrs}
|
|
3776
|
-
id="${id}"
|
|
3777
|
-
class="${inputClass}"
|
|
3778
|
-
${additionalAttrs}
|
|
3779
|
-
${validationAttrs}
|
|
3780
|
-
data-original-required="${originalRequired}" >
|
|
3781
|
-
${selectHTML}
|
|
3782
|
-
</select>
|
|
3783
|
-
</fieldset>
|
|
3784
|
-
`.replace(/^\s*\n/gm, '').trim();
|
|
3785
|
-
|
|
3786
|
-
// FIXED: Apply vertical layout to the <select> element and its children
|
|
3787
|
-
// Only split on actual attribute boundaries, not within attribute values
|
|
3788
|
-
let formattedHtml = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3789
|
-
// Use regex to match complete attribute="value" pairs
|
|
3790
|
-
const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
|
|
3791
|
-
const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
|
|
3792
|
-
return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
|
|
3793
|
-
});
|
|
3794
|
-
|
|
3795
|
-
// Ensure the <fieldset> block starts on a new line and remove extra blank lines
|
|
3796
|
-
formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3797
|
-
// Ensure <fieldset> starts on a new line
|
|
3798
|
-
return `\n${match}\n`;
|
|
3799
|
-
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3800
|
-
|
|
3801
|
-
this.formMarkUp+=formattedHtml;
|
|
3802
|
-
|
|
3803
3725
|
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3726
|
+
// Handle the binding syntax
|
|
3727
|
+
let bindingDirective = '';
|
|
3728
|
+
if (attributes.binding) {
|
|
3729
|
+
if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
|
|
3730
|
+
bindingDirective = ` bind:value="${name}" `;
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3809
3733
|
|
|
3810
|
-
|
|
3811
|
-
|
|
3734
|
+
// Define attributes for the select field
|
|
3735
|
+
let id = attributes.id || name;
|
|
3736
|
+
let dimensionAttrs = '';
|
|
3812
3737
|
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3738
|
+
// Handle additional attributes
|
|
3739
|
+
let additionalAttrs = '';
|
|
3740
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
3741
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
3742
|
+
if (key.startsWith('on')) {
|
|
3743
|
+
const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
|
|
3744
|
+
additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
|
|
3745
|
+
} else {
|
|
3746
|
+
if (value === true) {
|
|
3747
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
|
|
3748
|
+
} else if (value !== false) {
|
|
3749
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3817
3754
|
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3755
|
+
// Construct select options HTML based on options
|
|
3756
|
+
let selectHTML = '';
|
|
3757
|
+
if (Array.isArray(options) && options.length > 0) {
|
|
3758
|
+
// Add a default option
|
|
3759
|
+
selectHTML += `
|
|
3760
|
+
<option value="">Choose an option</option>
|
|
3823
3761
|
`;
|
|
3824
|
-
}).join('');
|
|
3825
|
-
|
|
3826
3762
|
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
optionsLabel = rawLabel.split('-')?.[1] + ' Option';
|
|
3838
|
-
} else {
|
|
3839
|
-
optionsLabel = subCategoryLabel;
|
|
3840
|
-
}
|
|
3763
|
+
// Add the provided options
|
|
3764
|
+
selectHTML += options.map((option) => {
|
|
3765
|
+
const optionValue = option.value || option;
|
|
3766
|
+
const optionLabel = option.label || option;
|
|
3767
|
+
const isSelected = option.selected ? ' selected' : '';
|
|
3768
|
+
return `
|
|
3769
|
+
<option value="${optionValue}"${isSelected}>${optionLabel}</option>
|
|
3770
|
+
`;
|
|
3771
|
+
}).join('');
|
|
3772
|
+
}
|
|
3841
3773
|
|
|
3774
|
+
let inputClass = attributes.class || this.inputClass;
|
|
3842
3775
|
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
<
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3776
|
+
// Construct the final HTML string for the main select
|
|
3777
|
+
let formHTML = `
|
|
3778
|
+
<fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
|
|
3779
|
+
<legend>${label}
|
|
3780
|
+
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3781
|
+
</legend>
|
|
3782
|
+
<label for="${id}"> Select ${label}
|
|
3783
|
+
<select name="${name}"
|
|
3851
3784
|
${bindingDirective}
|
|
3852
3785
|
${dimensionAttrs}
|
|
3853
3786
|
id="${id}"
|
|
3854
3787
|
class="${inputClass}"
|
|
3855
3788
|
${additionalAttrs}
|
|
3856
|
-
${
|
|
3857
|
-
data-original-required="${
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3789
|
+
${validationAttrs}
|
|
3790
|
+
data-original-required="${originalRequired}" >
|
|
3791
|
+
${selectHTML}
|
|
3792
|
+
</select>
|
|
3793
|
+
</fieldset>
|
|
3794
|
+
`.replace(/^\s*\n/gm, '').trim();
|
|
3795
|
+
|
|
3796
|
+
// Format the HTML
|
|
3797
|
+
let formattedHtml = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3866
3798
|
const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
|
|
3867
3799
|
const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
|
|
3868
3800
|
return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
|
|
3869
|
-
|
|
3801
|
+
});
|
|
3870
3802
|
|
|
3871
|
-
|
|
3872
|
-
subFormHTML = subFormHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3873
|
-
return `\n${match}\n`;
|
|
3874
|
-
}).replace(/\n\s*\n/g, '\n');
|
|
3803
|
+
this.formMarkUp += formattedHtml;
|
|
3875
3804
|
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3805
|
+
/* dynamicSingleSelect - Sub-Category Generation Block */
|
|
3806
|
+
if (mode === 'dynamicSingleSelect' && subCategoriesOptions && Array.isArray(subCategoriesOptions) && subCategoriesOptions.length > 0) {
|
|
3807
|
+
const categoryId = attributes.id || name;
|
|
3808
|
+
|
|
3809
|
+
subCategoriesOptions.forEach((subCategory) => {
|
|
3810
|
+
// Skip invalid subCategories
|
|
3811
|
+
if (!subCategory || !subCategory.id) {
|
|
3812
|
+
console.warn('Invalid subCategory in dynamic select:', subCategory);
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
const { id, label: subLabel, options: subOptions } = subCategory;
|
|
3817
|
+
|
|
3818
|
+
// Ensure subOptions is an array
|
|
3819
|
+
const subOptionArray = Array.isArray(subOptions) ? subOptions : [];
|
|
3820
|
+
|
|
3821
|
+
// Build the select options HTML for sub-category
|
|
3822
|
+
const subSelectHTML = subOptionArray.length > 0 ?
|
|
3823
|
+
subOptionArray.map(option => {
|
|
3824
|
+
const optionValue = option.value || option;
|
|
3825
|
+
const optionLabel = option.label || option;
|
|
3826
|
+
const isSelected = option.selected ? ' selected' : '';
|
|
3827
|
+
return `
|
|
3828
|
+
<option value="${optionValue}"${isSelected}>${optionLabel}</option>
|
|
3829
|
+
`;
|
|
3830
|
+
}).join('') :
|
|
3831
|
+
'<option value="">No options available</option>';
|
|
3832
|
+
|
|
3833
|
+
// Create the HTML for the sub-category fieldset
|
|
3834
|
+
let subFormHTML = `
|
|
3835
|
+
<fieldset class="${this.selectGroupClass} ${categoryId}" id="${id}" style="display: none;">
|
|
3836
|
+
<legend>${subLabel || id}</legend>
|
|
3837
|
+
<label for="${id}"> Select ${subLabel || id}</label>
|
|
3838
|
+
<select name="${id}"
|
|
3839
|
+
${bindingDirective}
|
|
3840
|
+
${dimensionAttrs}
|
|
3841
|
+
id="${id}"
|
|
3842
|
+
class="${inputClass}"
|
|
3843
|
+
${additionalAttrs}
|
|
3844
|
+
data-original-required="false">
|
|
3845
|
+
<option value="">Choose an option</option>
|
|
3846
|
+
${subSelectHTML}
|
|
3847
|
+
</select>
|
|
3848
|
+
</fieldset>
|
|
3849
|
+
`.replace(/^\s*\n/gm, '').trim();
|
|
3850
|
+
|
|
3851
|
+
// Format the HTML
|
|
3852
|
+
subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3853
|
+
const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
|
|
3854
|
+
const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
|
|
3855
|
+
return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
|
|
3856
|
+
});
|
|
3881
3857
|
|
|
3858
|
+
this.formMarkUp += subFormHTML;
|
|
3859
|
+
});
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3882
3862
|
|
|
3883
3863
|
|
|
3884
3864
|
renderMultipleSelectField(type, name, label, validate, attributes, options) {
|
|
@@ -4189,10 +4169,42 @@ renderRangeField(type, name, label, validate, attributes) {
|
|
|
4189
4169
|
|
|
4190
4170
|
|
|
4191
4171
|
|
|
4172
|
+
/* END DYNAMIC SINGLE SELECT BLOCK */
|
|
4192
4173
|
|
|
4193
4174
|
|
|
4194
|
-
|
|
4195
|
-
|
|
4175
|
+
renderHtmlField(type, element, contents, validate, attributes) {
|
|
4176
|
+
// Get the id from attributes or generate one
|
|
4177
|
+
const id = attributes.id || `html-${Math.random().toString(36).substr(2, 9)}`;
|
|
4178
|
+
|
|
4179
|
+
// Build class string
|
|
4180
|
+
let elementClass = 'html-content';
|
|
4181
|
+
if ('class' in attributes) {
|
|
4182
|
+
elementClass = attributes.class;
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4185
|
+
// Build additional attributes (excluding id and class)
|
|
4186
|
+
let additionalAttrs = '';
|
|
4187
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
4188
|
+
if (key !== 'id' && key !== 'class' && value !== undefined) {
|
|
4189
|
+
if (value === true) {
|
|
4190
|
+
additionalAttrs += ` ${key}`;
|
|
4191
|
+
} else if (value !== false) {
|
|
4192
|
+
additionalAttrs += ` ${key}="${value}"`;
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
|
|
4197
|
+
// Construct HTML with form-group wrapper
|
|
4198
|
+
const formHTML = `
|
|
4199
|
+
<div class="form-group" id="${id}-block">
|
|
4200
|
+
<${element} id="${id}" class="${elementClass}"${additionalAttrs}>
|
|
4201
|
+
${contents}
|
|
4202
|
+
</${element}>
|
|
4203
|
+
</div>
|
|
4204
|
+
`;
|
|
4205
|
+
|
|
4206
|
+
this.formMarkUp += formHTML;
|
|
4207
|
+
}
|
|
4196
4208
|
|
|
4197
4209
|
|
|
4198
4210
|
|