@formique/semantq 1.1.0 → 1.1.2
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/astToFormique.js +15 -32
- package/formique-semantq.js +274 -428
- package/package.json +1 -1
package/formique-semantq.js
CHANGED
|
@@ -782,51 +782,47 @@ 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
|
+
'submit': this.renderSubmitButton,
|
|
811
|
+
};
|
|
814
812
|
|
|
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);
|
|
813
|
+
const renderMethod = fieldRenderMap[type];
|
|
820
814
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
return '';
|
|
828
|
-
}
|
|
815
|
+
if (renderMethod) {
|
|
816
|
+
// IMPORTANT: Pass ALL arguments including subOptions
|
|
817
|
+
return renderMethod.call(this, type, name, label, validate, attributes, options, subOptions);
|
|
818
|
+
} else {
|
|
819
|
+
console.warn(`Unsupported field type '${type}' encountered.`);
|
|
820
|
+
return '';
|
|
829
821
|
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
|
|
830
826
|
|
|
831
827
|
|
|
832
828
|
|
|
@@ -1728,12 +1724,10 @@ if (attributes.binding === 'bind:value' && name) {
|
|
|
1728
1724
|
// Textarea field rendering
|
|
1729
1725
|
|
|
1730
1726
|
renderTextAreaField(type, name, label, validate, attributes) {
|
|
1731
|
-
const textInputValidationAttributes = [
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
'
|
|
1735
|
-
'pattern',
|
|
1736
|
-
];
|
|
1727
|
+
const textInputValidationAttributes = ['required', 'minlength', 'maxlength', 'pattern'];
|
|
1728
|
+
|
|
1729
|
+
// 1. Extract content value to place between tags later
|
|
1730
|
+
const textareaValue = attributes.value || '';
|
|
1737
1731
|
|
|
1738
1732
|
// Construct validation attributes
|
|
1739
1733
|
let validationAttrs = '';
|
|
@@ -1743,115 +1737,74 @@ renderTextAreaField(type, name, label, validate, attributes) {
|
|
|
1743
1737
|
if (typeof value === 'boolean' && value) {
|
|
1744
1738
|
validationAttrs += ` ${key}\n`;
|
|
1745
1739
|
} else {
|
|
1746
|
-
|
|
1747
|
-
case 'pattern':
|
|
1748
|
-
case 'minlength':
|
|
1749
|
-
case 'maxlength':
|
|
1750
|
-
validationAttrs += ` ${key}="${value}"\n`;
|
|
1751
|
-
break;
|
|
1752
|
-
default:
|
|
1753
|
-
if (!textInputValidationAttributes.includes(key)) {
|
|
1754
|
-
console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'number'.\x1b[0m`);
|
|
1755
|
-
}
|
|
1756
|
-
break;
|
|
1757
|
-
}
|
|
1740
|
+
validationAttrs += ` ${key}="${value}"\n`;
|
|
1758
1741
|
}
|
|
1759
1742
|
} else {
|
|
1760
|
-
console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '
|
|
1743
|
+
console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'textarea'.\x1b[0m`);
|
|
1761
1744
|
}
|
|
1762
1745
|
});
|
|
1763
1746
|
}
|
|
1764
1747
|
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
1748
|
// Handle the binding syntax
|
|
1768
1749
|
let bindingDirective = '';
|
|
1769
|
-
if (attributes.binding) {
|
|
1770
|
-
if (attributes.binding === 'bind:value' && name) {
|
|
1750
|
+
if (attributes.binding && name) {
|
|
1771
1751
|
bindingDirective = `bind:value="${name}"\n`;
|
|
1772
1752
|
}
|
|
1773
|
-
if (attributes.binding.startsWith('::') && name) {
|
|
1774
|
-
bindingDirective = `bind:value="${name}"\n`;
|
|
1775
|
-
}
|
|
1776
|
-
if (attributes.binding && !name) {
|
|
1777
|
-
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
1778
|
-
return;
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
1753
|
|
|
1784
|
-
// Get the id from attributes or fall back to name
|
|
1785
1754
|
let id = attributes.id || name;
|
|
1786
|
-
// Determine if semanti is true based on formSettings
|
|
1787
1755
|
const framework = this.formSettings?.framework || false;
|
|
1788
1756
|
|
|
1789
|
-
// Construct additional attributes
|
|
1757
|
+
// Construct additional attributes (excluding internal keys and the 'value' content)
|
|
1790
1758
|
let additionalAttrs = '';
|
|
1791
1759
|
for (const [key, value] of Object.entries(attributes)) {
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
|
|
1797
|
-
} else {
|
|
1798
|
-
// Add parentheses if not present
|
|
1799
|
-
const eventValue = value.endsWith('()') ? value : `${value}()`;
|
|
1800
|
-
additionalAttrs += ` ${key}="${eventValue}"\n`;
|
|
1801
|
-
}
|
|
1760
|
+
if (!['id', 'class', 'dependsOn', 'dependents', 'value', 'binding'].includes(key) && value !== undefined) {
|
|
1761
|
+
if (key.startsWith('on')) {
|
|
1762
|
+
const eventValue = value.endsWith('()') ? value : `${value}()`;
|
|
1763
|
+
additionalAttrs += ` ${key}="${eventValue}"\n`;
|
|
1802
1764
|
} else {
|
|
1803
|
-
|
|
1765
|
+
const attrName = key.replace(/_/g, '-');
|
|
1804
1766
|
if (value === true) {
|
|
1805
|
-
additionalAttrs += ` ${
|
|
1767
|
+
additionalAttrs += ` ${attrName}\n`;
|
|
1806
1768
|
} else if (value !== false) {
|
|
1807
|
-
|
|
1808
|
-
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
|
|
1769
|
+
additionalAttrs += ` ${attrName}="${value}"\n`;
|
|
1809
1770
|
}
|
|
1810
1771
|
}
|
|
1811
1772
|
}
|
|
1812
1773
|
}
|
|
1813
1774
|
|
|
1775
|
+
let inputClass = attributes.class || this.inputClass;
|
|
1814
1776
|
|
|
1777
|
+
// Build the raw HTML structure
|
|
1778
|
+
let formHTML = `
|
|
1779
|
+
<div class="${this.divClass}" id="${id + '-block'}">
|
|
1780
|
+
<label for="${id}">${label}
|
|
1781
|
+
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
1782
|
+
</label>
|
|
1783
|
+
<textarea
|
|
1784
|
+
name="${name}"
|
|
1785
|
+
${bindingDirective}
|
|
1786
|
+
id="${id}"
|
|
1787
|
+
class="${inputClass}"
|
|
1788
|
+
${additionalAttrs}
|
|
1789
|
+
${validationAttrs}
|
|
1790
|
+
${(additionalAttrs.includes('placeholder') || attributes.placeholder) ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>${textareaValue}</textarea>
|
|
1791
|
+
</div>`.replace(/^\s*\n/gm, '').trim();
|
|
1792
|
+
|
|
1793
|
+
// Vertical layout formatting for attributes while preserving content
|
|
1794
|
+
let formattedHtml = formHTML.replace(/<textarea\s+([\s\S]*?)>([\s\S]*?)<\/textarea>/, (match, attrPart, content) => {
|
|
1795
|
+
// Split by whitespace only if it is NOT inside quotes (prevents stacking text/placeholders)
|
|
1796
|
+
const attrs = attrPart.trim()
|
|
1797
|
+
.split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/)
|
|
1798
|
+
.filter(a => a.trim() !== '')
|
|
1799
|
+
.map(attr => ` ${attr.trim()}`)
|
|
1800
|
+
.join('\n');
|
|
1801
|
+
|
|
1802
|
+
return `<textarea\n${attrs}\n>${content}</textarea>`;
|
|
1803
|
+
});
|
|
1815
1804
|
|
|
1816
|
-
|
|
1817
|
-
if ('class' in attributes) {
|
|
1818
|
-
inputClass = attributes.class;
|
|
1819
|
-
} else {
|
|
1820
|
-
inputClass = this.inputClass;
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
// Construct the final HTML string for textarea
|
|
1824
|
-
let formHTML = `
|
|
1825
|
-
<div class="${this.divClass}" id="${id + '-block'}">
|
|
1826
|
-
<label for="${id}">${label}
|
|
1827
|
-
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
1828
|
-
</label>
|
|
1829
|
-
<textarea
|
|
1830
|
-
name="${name}"
|
|
1831
|
-
${bindingDirective}
|
|
1832
|
-
id="${id}"
|
|
1833
|
-
class="${inputClass}"
|
|
1834
|
-
${additionalAttrs}
|
|
1835
|
-
${validationAttrs}
|
|
1836
|
-
${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>
|
|
1837
|
-
</textarea>
|
|
1838
|
-
</div>
|
|
1839
|
-
`.replace(/^\s*\n/gm, '').trim();
|
|
1840
|
-
|
|
1841
|
-
let formattedHtml = formHTML;
|
|
1842
|
-
|
|
1843
|
-
// Apply vertical layout to the <textarea> element only
|
|
1844
|
-
formattedHtml = formattedHtml.replace(/<textarea\s+([^>]*)>\s*<\/textarea>/, (match, p1) => {
|
|
1845
|
-
// Reformat attributes into a vertical layout
|
|
1846
|
-
const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
|
|
1847
|
-
return `<textarea\n${attributes}\n></textarea>`;
|
|
1848
|
-
});
|
|
1849
|
-
|
|
1850
|
-
this.formMarkUp += formattedHtml;
|
|
1851
|
-
|
|
1805
|
+
this.formMarkUp += formattedHtml;
|
|
1852
1806
|
}
|
|
1853
1807
|
|
|
1854
|
-
|
|
1855
1808
|
// New method for rendering tel fields
|
|
1856
1809
|
renderTelField(type, name, label, validate, attributes) {
|
|
1857
1810
|
|
|
@@ -3409,97 +3362,7 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
3409
3362
|
|
|
3410
3363
|
|
|
3411
3364
|
|
|
3412
|
-
//
|
|
3413
|
-
renderTextAreaField(type, name, label, validate, attributes) {
|
|
3414
|
-
const textAreaValidationAttributes = [
|
|
3415
|
-
'required',
|
|
3416
|
-
'minlength',
|
|
3417
|
-
'maxlength'
|
|
3418
|
-
];
|
|
3419
|
-
|
|
3420
|
-
// Construct validation attributes
|
|
3421
|
-
let validationAttrs = '';
|
|
3422
|
-
if (validate) {
|
|
3423
|
-
Object.entries(validate).forEach(([key, value]) => {
|
|
3424
|
-
if (textAreaValidationAttributes.includes(key)) {
|
|
3425
|
-
if (typeof value === 'boolean' && value) {
|
|
3426
|
-
validationAttrs += ` ${key}\n`;
|
|
3427
|
-
} else {
|
|
3428
|
-
validationAttrs += ` ${key}="${value}"\n`;
|
|
3429
|
-
}
|
|
3430
|
-
} else {
|
|
3431
|
-
console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
|
|
3432
|
-
}
|
|
3433
|
-
});
|
|
3434
|
-
}
|
|
3435
|
-
|
|
3436
|
-
// Handle the binding syntax
|
|
3437
|
-
let bindingDirective = '';
|
|
3438
|
-
if (attributes.binding) {
|
|
3439
|
-
if (attributes.binding === 'bind:value' && name) {
|
|
3440
|
-
bindingDirective = `bind:value="${name}"\n`;
|
|
3441
|
-
}
|
|
3442
|
-
if (attributes.binding.startsWith('::') && name) {
|
|
3443
|
-
bindingDirective = `bind:value="${name}"\n`;
|
|
3444
|
-
}
|
|
3445
|
-
if (attributes.binding && !name) {
|
|
3446
|
-
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
3447
|
-
return;
|
|
3448
|
-
}
|
|
3449
|
-
}
|
|
3450
|
-
|
|
3451
|
-
// Get the id from attributes or fall back to name
|
|
3452
|
-
let id = attributes.id || name;
|
|
3453
|
-
|
|
3454
|
-
// Construct additional attributes dynamically
|
|
3455
|
-
let additionalAttrs = '';
|
|
3456
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
3457
|
-
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
3458
|
-
if (key.startsWith('on')) {
|
|
3459
|
-
const eventValue = value.endsWith('()') ? value : `${value}()`;
|
|
3460
|
-
additionalAttrs += ` ${key}="${eventValue}"\n`;
|
|
3461
|
-
} else {
|
|
3462
|
-
if (value === true) {
|
|
3463
|
-
additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
|
|
3464
|
-
} else if (value !== false) {
|
|
3465
|
-
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
|
|
3466
|
-
}
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
}
|
|
3470
|
-
|
|
3471
|
-
let inputClass = attributes.class || this.inputClass;
|
|
3472
|
-
|
|
3473
|
-
// Construct the final HTML string
|
|
3474
|
-
let formHTML = `
|
|
3475
|
-
<div class="${this.divClass}" id="${id + '-block'}">
|
|
3476
|
-
<label for="${id}">${label}
|
|
3477
|
-
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3478
|
-
</label>
|
|
3479
|
-
<textarea
|
|
3480
|
-
name="${name}"
|
|
3481
|
-
${bindingDirective}
|
|
3482
|
-
id="${id}"
|
|
3483
|
-
class="${inputClass}"
|
|
3484
|
-
${additionalAttrs}
|
|
3485
|
-
${validationAttrs}
|
|
3486
|
-
${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>
|
|
3487
|
-
</textarea>
|
|
3488
|
-
</div>
|
|
3489
|
-
`.replace(/^\s*\n/gm, '').trim();
|
|
3490
|
-
|
|
3491
|
-
let formattedHtml = formHTML;
|
|
3492
|
-
|
|
3493
|
-
// Apply vertical layout to the <textarea> element only
|
|
3494
|
-
formattedHtml = formattedHtml.replace(/<textarea\s+([^>]*)>\s*<\/textarea>/, (match, p1) => {
|
|
3495
|
-
const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
|
|
3496
|
-
return `<textarea\n${attributes}\n></textarea>`;
|
|
3497
|
-
});
|
|
3498
|
-
|
|
3499
|
-
this.formMarkUp += formattedHtml;
|
|
3500
|
-
}
|
|
3501
|
-
|
|
3502
|
-
|
|
3365
|
+
// Radio field rendering
|
|
3503
3366
|
|
|
3504
3367
|
renderRadioField(type, name, label, validate, attributes, options) {
|
|
3505
3368
|
// Define valid validation attributes for radio fields
|
|
@@ -3774,244 +3637,227 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
|
|
|
3774
3637
|
/* DYNAMIC SINGLE SELECT BLOCK */
|
|
3775
3638
|
|
|
3776
3639
|
// Function to render the dynamic select field and update based on user selection
|
|
3777
|
-
renderDynamicSingleSelectField(type, name, label, validate, attributes, options) {
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
// Use item.id as the value for the main select
|
|
3783
|
-
return {
|
|
3784
|
-
value: item.id, // ← FIXED: Use id, not value
|
|
3785
|
-
label: item.label,
|
|
3786
|
-
// You can add selected logic if needed
|
|
3787
|
-
};
|
|
3788
|
-
});
|
|
3789
|
-
|
|
3790
|
-
// Step 2: The nested options ARE your sub-categories!
|
|
3791
|
-
// Transform the structure
|
|
3792
|
-
const subCategoriesOptions = options.map(item => ({
|
|
3793
|
-
id: item.id, // Same as main category value
|
|
3794
|
-
label: item.label + ' Technologies', // Or customize
|
|
3795
|
-
options: item.options // The nested options array
|
|
3796
|
-
}));
|
|
3797
|
-
|
|
3798
|
-
//console.log('Main categories:', mainCategoryOptions);
|
|
3799
|
-
//console.log('Sub categories:', subCategoriesOptions);
|
|
3800
|
-
|
|
3801
|
-
const mode = 'dynamicSingleSelect';
|
|
3802
|
-
|
|
3803
|
-
// Pass both to the renderer
|
|
3804
|
-
this.renderSingleSelectField(type, name, label, validate, attributes, mainCategoryOptions, subCategoriesOptions, mode);
|
|
3805
|
-
}
|
|
3806
|
-
|
|
3807
|
-
renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
|
|
3808
|
-
|
|
3809
|
-
// Define valid validation attributes for select fields
|
|
3810
|
-
const selectValidationAttributes = ['required'];
|
|
3811
|
-
|
|
3812
|
-
// Construct validation attributes
|
|
3813
|
-
let validationAttrs = '';
|
|
3814
|
-
// Store original required state for the main select
|
|
3815
|
-
let originalRequired = false; // <--- This variable tracks if the main select was originally required
|
|
3816
|
-
if (validate) {
|
|
3817
|
-
Object.entries(validate).forEach(([key, value]) => {
|
|
3818
|
-
if (selectValidationAttributes.includes(key)) {
|
|
3819
|
-
if (key === 'required') {
|
|
3820
|
-
validationAttrs += `${key} `;
|
|
3821
|
-
originalRequired = true; // Mark that it was originally required
|
|
3822
|
-
}
|
|
3823
|
-
} else {
|
|
3824
|
-
// Removed console.warn
|
|
3825
|
-
}
|
|
3640
|
+
renderDynamicSingleSelectField(type, name, label, validate, attributes, options, subOptions) {
|
|
3641
|
+
console.log('DEBUG: renderDynamicSingleSelectField called', {
|
|
3642
|
+
type, name, label,
|
|
3643
|
+
options: options ? options.length : 'none',
|
|
3644
|
+
subOptions: subOptions ? subOptions.length : 'none'
|
|
3826
3645
|
});
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
|
|
3833
|
-
bindingDirective = ` bind:value="${name}" `;
|
|
3646
|
+
|
|
3647
|
+
// Check if options exist
|
|
3648
|
+
if (!options || !Array.isArray(options)) {
|
|
3649
|
+
console.warn('Dynamic single select field missing options:', name);
|
|
3650
|
+
options = [];
|
|
3834
3651
|
}
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
if (value === true) {
|
|
3852
|
-
additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
|
|
3853
|
-
} else if (value !== false) {
|
|
3854
|
-
// Convert underscores to hyphens and set the attribute
|
|
3855
|
-
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
|
|
3652
|
+
|
|
3653
|
+
// Step 1: Extract main categories from options
|
|
3654
|
+
// Options should already be in the correct format from the parser
|
|
3655
|
+
const mainCategoryOptions = options.map(item => {
|
|
3656
|
+
// Handle both string and object formats
|
|
3657
|
+
if (typeof item === 'string') {
|
|
3658
|
+
return {
|
|
3659
|
+
value: item.toLowerCase().replace(/\s+/g, '-'),
|
|
3660
|
+
label: item
|
|
3661
|
+
};
|
|
3662
|
+
} else {
|
|
3663
|
+
// Already an object with value/label
|
|
3664
|
+
return {
|
|
3665
|
+
value: item.value || item.id || item,
|
|
3666
|
+
label: item.label || item.value || item
|
|
3667
|
+
};
|
|
3856
3668
|
}
|
|
3857
|
-
|
|
3669
|
+
});
|
|
3670
|
+
|
|
3671
|
+
// Step 2: Handle subCategoriesOptions (scenario blocks)
|
|
3672
|
+
let subCategoriesOptions = [];
|
|
3673
|
+
|
|
3674
|
+
if (subOptions && Array.isArray(subOptions)) {
|
|
3675
|
+
// Use the subOptions exactly as provided by the parser
|
|
3676
|
+
// They should already have the correct structure
|
|
3677
|
+
subCategoriesOptions = subOptions.map(item => {
|
|
3678
|
+
// Ensure each subCategory has the required structure
|
|
3679
|
+
return {
|
|
3680
|
+
id: item.id || item.value || '',
|
|
3681
|
+
label: item.label || item.id || '',
|
|
3682
|
+
options: Array.isArray(item.options) ? item.options : []
|
|
3683
|
+
};
|
|
3684
|
+
});
|
|
3858
3685
|
}
|
|
3859
|
-
|
|
3686
|
+
|
|
3687
|
+
console.log('Main categories:', mainCategoryOptions);
|
|
3688
|
+
console.log('Sub categories:', subCategoriesOptions);
|
|
3689
|
+
|
|
3690
|
+
// Pass both to the renderer with the mode flag
|
|
3691
|
+
this.renderSingleSelectField(
|
|
3692
|
+
type,
|
|
3693
|
+
name,
|
|
3694
|
+
label,
|
|
3695
|
+
validate,
|
|
3696
|
+
attributes,
|
|
3697
|
+
mainCategoryOptions,
|
|
3698
|
+
subCategoriesOptions,
|
|
3699
|
+
'dynamicSingleSelect'
|
|
3700
|
+
);
|
|
3701
|
+
}
|
|
3860
3702
|
|
|
3861
|
-
// Construct select options HTML based on options
|
|
3862
|
-
let selectHTML = '';
|
|
3863
|
-
if (Array.isArray(options)) {
|
|
3864
|
-
// Add a default option
|
|
3865
|
-
selectHTML += `
|
|
3866
|
-
<option value="">Choose an option</option>
|
|
3867
|
-
`;
|
|
3868
3703
|
|
|
3869
|
-
// Add the provided options
|
|
3870
|
-
selectHTML += options.map((option) => {
|
|
3871
|
-
const isSelected = option.selected ? ' selected' : '';
|
|
3872
|
-
return `
|
|
3873
|
-
<option value="${option.value}"${isSelected}>${option.label}</option>
|
|
3874
|
-
`;
|
|
3875
|
-
}).join('');
|
|
3876
|
-
}
|
|
3877
3704
|
|
|
3878
|
-
let inputClass = attributes.class || this.inputClass;
|
|
3879
3705
|
|
|
3880
|
-
// Remove `onchange` from HTML; it will be handled by JavaScript event listeners
|
|
3881
|
-
const onchangeAttr = ''; // <--- Ensure this is an empty string
|
|
3882
3706
|
|
|
3883
|
-
|
|
3884
|
-
|
|
3707
|
+
renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
|
|
3708
|
+
// Define valid validation attributes for select fields
|
|
3709
|
+
const selectValidationAttributes = ['required'];
|
|
3885
3710
|
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3711
|
+
// Construct validation attributes
|
|
3712
|
+
let validationAttrs = '';
|
|
3713
|
+
let originalRequired = false;
|
|
3714
|
+
if (validate) {
|
|
3715
|
+
Object.entries(validate).forEach(([key, value]) => {
|
|
3716
|
+
if (selectValidationAttributes.includes(key)) {
|
|
3717
|
+
if (key === 'required') {
|
|
3718
|
+
validationAttrs += `${key} `;
|
|
3719
|
+
originalRequired = true;
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
});
|
|
3894
3723
|
}
|
|
3895
|
-
} else {
|
|
3896
|
-
labelDisplay = label;
|
|
3897
|
-
}
|
|
3898
|
-
|
|
3899
|
-
// Construct the final HTML string for the main select
|
|
3900
|
-
let formHTML = `
|
|
3901
|
-
<fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
|
|
3902
|
-
<legend>${labelDisplay}
|
|
3903
|
-
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3904
|
-
</legend>
|
|
3905
|
-
<label for="${id}"> Select ${labelDisplay}
|
|
3906
|
-
<select name="${name}"
|
|
3907
|
-
${bindingDirective}
|
|
3908
|
-
${dimensionAttrs}
|
|
3909
|
-
id="${id}"
|
|
3910
|
-
class="${inputClass}"
|
|
3911
|
-
${additionalAttrs}
|
|
3912
|
-
${validationAttrs}
|
|
3913
|
-
data-original-required="${originalRequired}" >
|
|
3914
|
-
${selectHTML}
|
|
3915
|
-
</select>
|
|
3916
|
-
</fieldset>
|
|
3917
|
-
`.replace(/^\s*\n/gm, '').trim();
|
|
3918
|
-
|
|
3919
|
-
// FIXED: Apply vertical layout to the <select> element and its children
|
|
3920
|
-
// Only split on actual attribute boundaries, not within attribute values
|
|
3921
|
-
let formattedHtml = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3922
|
-
// Use regex to match complete attribute="value" pairs
|
|
3923
|
-
const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
|
|
3924
|
-
const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
|
|
3925
|
-
return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
|
|
3926
|
-
});
|
|
3927
|
-
|
|
3928
|
-
// Ensure the <fieldset> block starts on a new line and remove extra blank lines
|
|
3929
|
-
formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3930
|
-
// Ensure <fieldset> starts on a new line
|
|
3931
|
-
return `\n${match}\n`;
|
|
3932
|
-
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3933
|
-
|
|
3934
|
-
this.formMarkUp+=formattedHtml;
|
|
3935
|
-
|
|
3936
3724
|
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3725
|
+
// Handle the binding syntax
|
|
3726
|
+
let bindingDirective = '';
|
|
3727
|
+
if (attributes.binding) {
|
|
3728
|
+
if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
|
|
3729
|
+
bindingDirective = ` bind:value="${name}" `;
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3942
3732
|
|
|
3943
|
-
|
|
3944
|
-
|
|
3733
|
+
// Define attributes for the select field
|
|
3734
|
+
let id = attributes.id || name;
|
|
3735
|
+
let dimensionAttrs = '';
|
|
3945
3736
|
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3737
|
+
// Handle additional attributes
|
|
3738
|
+
let additionalAttrs = '';
|
|
3739
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
3740
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
3741
|
+
if (key.startsWith('on')) {
|
|
3742
|
+
const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
|
|
3743
|
+
additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
|
|
3744
|
+
} else {
|
|
3745
|
+
if (value === true) {
|
|
3746
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
|
|
3747
|
+
} else if (value !== false) {
|
|
3748
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3950
3753
|
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3754
|
+
// Construct select options HTML based on options
|
|
3755
|
+
let selectHTML = '';
|
|
3756
|
+
if (Array.isArray(options) && options.length > 0) {
|
|
3757
|
+
// Add a default option
|
|
3758
|
+
selectHTML += `
|
|
3759
|
+
<option value="">Choose an option</option>
|
|
3956
3760
|
`;
|
|
3957
|
-
}).join('');
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
let subCategoryLabel;
|
|
3961
|
-
|
|
3962
|
-
if (rawLabel.includes('-')) {
|
|
3963
|
-
subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
|
|
3964
|
-
} else {
|
|
3965
|
-
subCategoryLabel = 'options';
|
|
3966
|
-
}
|
|
3967
3761
|
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3762
|
+
// Add the provided options
|
|
3763
|
+
selectHTML += options.map((option) => {
|
|
3764
|
+
const optionValue = option.value || option;
|
|
3765
|
+
const optionLabel = option.label || option;
|
|
3766
|
+
const isSelected = option.selected ? ' selected' : '';
|
|
3767
|
+
return `
|
|
3768
|
+
<option value="${optionValue}"${isSelected}>${optionLabel}</option>
|
|
3769
|
+
`;
|
|
3770
|
+
}).join('');
|
|
3771
|
+
}
|
|
3974
3772
|
|
|
3773
|
+
let inputClass = attributes.class || this.inputClass;
|
|
3975
3774
|
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
<
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3775
|
+
// Construct the final HTML string for the main select
|
|
3776
|
+
let formHTML = `
|
|
3777
|
+
<fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
|
|
3778
|
+
<legend>${label}
|
|
3779
|
+
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3780
|
+
</legend>
|
|
3781
|
+
<label for="${id}"> Select ${label}
|
|
3782
|
+
<select name="${name}"
|
|
3984
3783
|
${bindingDirective}
|
|
3985
3784
|
${dimensionAttrs}
|
|
3986
3785
|
id="${id}"
|
|
3987
3786
|
class="${inputClass}"
|
|
3988
3787
|
${additionalAttrs}
|
|
3989
|
-
${
|
|
3990
|
-
data-original-required="${
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3788
|
+
${validationAttrs}
|
|
3789
|
+
data-original-required="${originalRequired}" >
|
|
3790
|
+
${selectHTML}
|
|
3791
|
+
</select>
|
|
3792
|
+
</fieldset>
|
|
3793
|
+
`.replace(/^\s*\n/gm, '').trim();
|
|
3794
|
+
|
|
3795
|
+
// Format the HTML
|
|
3796
|
+
let formattedHtml = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3999
3797
|
const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
|
|
4000
3798
|
const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
|
|
4001
3799
|
return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
|
|
4002
|
-
|
|
3800
|
+
});
|
|
4003
3801
|
|
|
4004
|
-
|
|
4005
|
-
subFormHTML = subFormHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
4006
|
-
return `\n${match}\n`;
|
|
4007
|
-
}).replace(/\n\s*\n/g, '\n');
|
|
3802
|
+
this.formMarkUp += formattedHtml;
|
|
4008
3803
|
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
3804
|
+
/* dynamicSingleSelect - Sub-Category Generation Block */
|
|
3805
|
+
if (mode === 'dynamicSingleSelect' && subCategoriesOptions && Array.isArray(subCategoriesOptions) && subCategoriesOptions.length > 0) {
|
|
3806
|
+
const categoryId = attributes.id || name;
|
|
3807
|
+
|
|
3808
|
+
subCategoriesOptions.forEach((subCategory) => {
|
|
3809
|
+
// Skip invalid subCategories
|
|
3810
|
+
if (!subCategory || !subCategory.id) {
|
|
3811
|
+
console.warn('Invalid subCategory in dynamic select:', subCategory);
|
|
3812
|
+
return;
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
const { id, label: subLabel, options: subOptions } = subCategory;
|
|
3816
|
+
|
|
3817
|
+
// Ensure subOptions is an array
|
|
3818
|
+
const subOptionArray = Array.isArray(subOptions) ? subOptions : [];
|
|
3819
|
+
|
|
3820
|
+
// Build the select options HTML for sub-category
|
|
3821
|
+
const subSelectHTML = subOptionArray.length > 0 ?
|
|
3822
|
+
subOptionArray.map(option => {
|
|
3823
|
+
const optionValue = option.value || option;
|
|
3824
|
+
const optionLabel = option.label || option;
|
|
3825
|
+
const isSelected = option.selected ? ' selected' : '';
|
|
3826
|
+
return `
|
|
3827
|
+
<option value="${optionValue}"${isSelected}>${optionLabel}</option>
|
|
3828
|
+
`;
|
|
3829
|
+
}).join('') :
|
|
3830
|
+
'<option value="">No options available</option>';
|
|
3831
|
+
|
|
3832
|
+
// Create the HTML for the sub-category fieldset
|
|
3833
|
+
let subFormHTML = `
|
|
3834
|
+
<fieldset class="${this.selectGroupClass} ${categoryId}" id="${id}" style="display: none;">
|
|
3835
|
+
<legend>${subLabel || id}</legend>
|
|
3836
|
+
<label for="${id}"> Select ${subLabel || id}</label>
|
|
3837
|
+
<select name="${id}"
|
|
3838
|
+
${bindingDirective}
|
|
3839
|
+
${dimensionAttrs}
|
|
3840
|
+
id="${id}"
|
|
3841
|
+
class="${inputClass}"
|
|
3842
|
+
${additionalAttrs}
|
|
3843
|
+
data-original-required="false">
|
|
3844
|
+
<option value="">Choose an option</option>
|
|
3845
|
+
${subSelectHTML}
|
|
3846
|
+
</select>
|
|
3847
|
+
</fieldset>
|
|
3848
|
+
`.replace(/^\s*\n/gm, '').trim();
|
|
3849
|
+
|
|
3850
|
+
// Format the HTML
|
|
3851
|
+
subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3852
|
+
const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
|
|
3853
|
+
const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
|
|
3854
|
+
return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
|
|
3855
|
+
});
|
|
4014
3856
|
|
|
3857
|
+
this.formMarkUp += subFormHTML;
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
4015
3861
|
|
|
4016
3862
|
|
|
4017
3863
|
renderMultipleSelectField(type, name, label, validate, attributes, options) {
|