@formique/semantq 1.0.8 → 1.0.10
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 +1551 -0
- package/astToFormique.js +1128 -0
- package/formique-semantq.js +682 -527
- package/package.json +1 -1
package/formique-semantq.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
import LowCodeParser from './LowCodeParser.js';
|
|
3
|
+
import astToFormique from './astToFormique.js';
|
|
2
4
|
/**
|
|
3
5
|
* Formique Semantq Class Library
|
|
4
6
|
*
|
|
@@ -50,18 +52,42 @@ class FormBuilder
|
|
|
50
52
|
|
|
51
53
|
// Extended class for specific form rendering methods
|
|
52
54
|
class Formique extends FormBuilder {
|
|
53
|
-
constructor(
|
|
55
|
+
constructor(formDefinition, formSettings = {}, formParams = {}) {
|
|
54
56
|
super();
|
|
57
|
+
let formSchema;
|
|
58
|
+
let finalSettings = formSettings;
|
|
59
|
+
let finalParams = formParams;
|
|
60
|
+
|
|
61
|
+
if (typeof formDefinition === 'string') {
|
|
62
|
+
const ast = LowCodeParser.parse(formDefinition.trim());
|
|
63
|
+
// console.log("AST", JSON.stringify(ast, null,2));
|
|
64
|
+
|
|
65
|
+
const formObjects = new astToFormique(ast);
|
|
66
|
+
|
|
67
|
+
//console.log("formSchema", JSON.stringify(formObjects.formSchema,null,2));
|
|
68
|
+
// Assign from formObjects if a string is passed
|
|
69
|
+
formSchema = formObjects.formSchema;
|
|
70
|
+
finalSettings = { ...formSettings, ...formObjects.formSettings };
|
|
71
|
+
finalParams = { ...formParams, ...formObjects.formParams };
|
|
72
|
+
} else {
|
|
73
|
+
// Assign from the parameters if formDefinition is not a string
|
|
74
|
+
formSchema = formDefinition;
|
|
75
|
+
}
|
|
76
|
+
|
|
55
77
|
this.formSchema = formSchema;
|
|
56
|
-
this.formParams =
|
|
78
|
+
this.formParams = finalParams;
|
|
57
79
|
this.formSettings = {
|
|
58
80
|
requiredFieldIndicator: true,
|
|
59
81
|
placeholders: true,
|
|
60
82
|
asteriskHtml: '<span aria-hidden="true" style="color: red;">*</span>',
|
|
61
|
-
...
|
|
83
|
+
...finalSettings
|
|
62
84
|
};
|
|
63
85
|
|
|
64
|
-
|
|
86
|
+
//console.log("constructor",this.formSettings);
|
|
87
|
+
|
|
88
|
+
this.themeColor = this.formSettings.themeColor || null;
|
|
89
|
+
|
|
90
|
+
//console.log("color set?", this.themeColor);
|
|
65
91
|
|
|
66
92
|
this.themeColorMap = {
|
|
67
93
|
'primary': {
|
|
@@ -100,17 +126,21 @@ class Formique extends FormBuilder {
|
|
|
100
126
|
this.formiqueEndpoint = "https://formiqueapi.onrender.com/api/send-email";
|
|
101
127
|
|
|
102
128
|
// DISABLE DOM LISTENER
|
|
103
|
-
|
|
129
|
+
// document.addEventListener('DOMContentLoaded', () => {
|
|
104
130
|
// 1. Build the form's HTML in memory
|
|
105
131
|
this.formMarkUp += this.renderFormElement(); // Adds opening <form> tag and any hidden inputs
|
|
106
132
|
|
|
107
133
|
// Filter out 'submit' field for rendering, and render all other fields
|
|
108
134
|
const nonSubmitFieldsHtml = this.formSchema
|
|
109
135
|
.filter(field => field[0] !== 'submit')
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
|
|
114
144
|
this.formMarkUp += nonSubmitFieldsHtml;
|
|
115
145
|
|
|
116
146
|
// Find and render the submit button separately, at the very end of the form content
|
|
@@ -148,46 +178,67 @@ class Formique extends FormBuilder {
|
|
|
148
178
|
|
|
149
179
|
|
|
150
180
|
// 2. Inject the complete form HTML into the DOM
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
181
|
+
// A conceptual snippet from your form initialization method
|
|
182
|
+
this.renderFormHTML(); // This puts the form element into the document!
|
|
183
|
+
|
|
184
|
+
// 3. Now that the form is in the DOM, get the element and attach a single event listener
|
|
185
|
+
const formElement = document.getElementById(`${this.formId}`);
|
|
186
|
+
if (formElement) {
|
|
187
|
+
// Attach a single, unified submit event listener
|
|
188
|
+
formElement.addEventListener('submit', (event) => {
|
|
189
|
+
// Prevent default submission behavior immediately
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
|
|
192
|
+
// Check if reCAPTCHA is present in the form schema
|
|
193
|
+
const recaptchaField = this.formSchema.find(field => field[0] === 'recaptcha');
|
|
194
|
+
|
|
195
|
+
// If reCAPTCHA is required, validate it first
|
|
196
|
+
if (recaptchaField) {
|
|
197
|
+
const recaptchaToken = grecaptcha.getResponse();
|
|
198
|
+
|
|
199
|
+
if (!recaptchaToken) {
|
|
200
|
+
// If reCAPTCHA is not checked, display an error and stop
|
|
201
|
+
document.getElementById("formiqueSpinner").style.display = "none";
|
|
202
|
+
alert('Please verify that you are not a robot.');
|
|
203
|
+
return; // Stop execution of the handler
|
|
171
204
|
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// If reCAPTCHA is not required or is validated, proceed with submission logic
|
|
208
|
+
document.getElementById("formiqueSpinner").style.display = "block";
|
|
209
|
+
|
|
210
|
+
if (this.formSettings.submitMode === 'email' || this.formSettings.submitMode === 'rsvp') {
|
|
211
|
+
this.handleEmailSubmission(this.formId);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (this.formSettings.submitOnPage) {
|
|
215
|
+
this.handleOnPageFormSubmission(this.formId);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
} else {
|
|
220
|
+
console.error(`Form with ID ${this.formId} not found after rendering. Event listener could not be attached.`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Initialize dependency graph and observers after the form is rendered
|
|
224
|
+
this.initDependencyGraph();
|
|
225
|
+
this.registerObservers();
|
|
226
|
+
this.attachDynamicSelectListeners();
|
|
227
|
+
|
|
228
|
+
// Apply theme
|
|
229
|
+
if (this.themeColor) {
|
|
230
|
+
this.applyCustomTheme(this.themeColor, this.formContainerId);
|
|
231
|
+
} else if (this.formSettings.theme && this.themes.includes(this.formSettings.theme)) {
|
|
232
|
+
let theme = this.formSettings.theme;
|
|
233
|
+
this.applyTheme(theme, this.formContainerId);
|
|
234
|
+
} else {
|
|
235
|
+
this.applyTheme('dark', this.formContainerId); // Default to 'dark'
|
|
236
|
+
}
|
|
237
|
+
|
|
172
238
|
|
|
173
|
-
// Initialize dependency graph and observers after the form is rendered
|
|
174
|
-
this.initDependencyGraph();
|
|
175
|
-
this.registerObservers();
|
|
176
|
-
this.attachDynamicSelectListeners();
|
|
177
|
-
|
|
178
|
-
// Apply theme
|
|
179
|
-
if (this.themeColor) {
|
|
180
|
-
this.applyCustomTheme(this.themeColor, this.formContainerId); // <--- NEW: Apply custom theme
|
|
181
|
-
} else if (this.formSettings.theme && this.themes.includes(this.formSettings.theme)) {
|
|
182
|
-
let theme = this.formSettings.theme;
|
|
183
|
-
this.applyTheme(theme, this.formContainerId);
|
|
184
|
-
} else {
|
|
185
|
-
// Fallback if no themeColor and no valid theme specified
|
|
186
|
-
this.applyTheme('dark', this.formContainerId); // Default to 'dark'
|
|
187
|
-
}
|
|
188
239
|
|
|
189
240
|
// DISABLE DOM LISTENER
|
|
190
|
-
|
|
241
|
+
// }); // DOM LISTENER WRAPPER
|
|
191
242
|
|
|
192
243
|
// CONSTRUCTOR WRAPPER FOR FORMIQUE CLASS
|
|
193
244
|
}
|
|
@@ -264,16 +315,48 @@ initDependencyGraph() {
|
|
|
264
315
|
}
|
|
265
316
|
|
|
266
317
|
// Attach Event Listeners
|
|
318
|
+
// Corrected Attach Event Listeners
|
|
267
319
|
attachInputChangeListener(parentField) {
|
|
268
|
-
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
+
}
|
|
276
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
|
+
});
|
|
277
360
|
}
|
|
278
361
|
|
|
279
362
|
|
|
@@ -327,6 +410,7 @@ handleParentFieldChange(parentFieldId, value) {
|
|
|
327
410
|
}
|
|
328
411
|
}
|
|
329
412
|
|
|
413
|
+
|
|
330
414
|
// Register observers for each dependent field
|
|
331
415
|
registerObservers() {
|
|
332
416
|
this.formSchema.forEach((field) => {
|
|
@@ -378,7 +462,7 @@ attachDynamicSelectListeners() {
|
|
|
378
462
|
const selectedCategory = event.target.value; // e.g., 'frontend', 'backend', 'server'
|
|
379
463
|
|
|
380
464
|
// Find all sub-category fieldsets related to this main select
|
|
381
|
-
const subCategoryFieldsets = document.querySelectorAll(`.${mainSelectId}
|
|
465
|
+
const subCategoryFieldsets = document.querySelectorAll(`.${mainSelectId}`);
|
|
382
466
|
|
|
383
467
|
subCategoryFieldsets.forEach(fieldset => {
|
|
384
468
|
const subSelect = fieldset.querySelector('select'); // Get the actual select element
|
|
@@ -391,7 +475,7 @@ attachDynamicSelectListeners() {
|
|
|
391
475
|
});
|
|
392
476
|
|
|
393
477
|
// Show the selected sub-category fieldset and manage its required state
|
|
394
|
-
const selectedFieldsetId = selectedCategory
|
|
478
|
+
const selectedFieldsetId = selectedCategory; // Matches the ID format in renderSingleSelectField
|
|
395
479
|
const selectedFieldset = document.getElementById(selectedFieldsetId);
|
|
396
480
|
|
|
397
481
|
if (selectedFieldset) {
|
|
@@ -646,51 +730,10 @@ renderForm() {
|
|
|
646
730
|
}
|
|
647
731
|
|
|
648
732
|
|
|
649
|
-
/*
|
|
650
|
-
renderField(type, name, label, validate, attributes, options) {
|
|
651
|
-
const fieldRenderMap = {
|
|
652
|
-
'text': this.renderTextField,
|
|
653
|
-
'email': this.renderEmailField,
|
|
654
|
-
'number': this.renderNumberField,
|
|
655
|
-
'password': this.renderPasswordField,
|
|
656
|
-
'textarea': this.renderTextAreaField,
|
|
657
|
-
'tel': this.renderTelField,
|
|
658
|
-
'date': this.renderDateField,
|
|
659
|
-
'time': this.renderTimeField,
|
|
660
|
-
'datetime-local': this.renderDateTimeField,
|
|
661
|
-
'month': this.renderMonthField,
|
|
662
|
-
'week': this.renderWeekField,
|
|
663
|
-
'url': this.renderUrlField,
|
|
664
|
-
'search': this.renderSearchField,
|
|
665
|
-
'color': this.renderColorField,
|
|
666
|
-
'checkbox': this.renderCheckboxField,
|
|
667
|
-
'radio': this.renderRadioField,
|
|
668
|
-
'file': this.renderFileField,
|
|
669
|
-
'hidden': this.renderHiddenField,
|
|
670
|
-
'image': this.renderImageField,
|
|
671
|
-
'textarea': this.renderTextAreaField,
|
|
672
|
-
'singleSelect': this.renderSingleSelectField,
|
|
673
|
-
'multipleSelect': this.renderMultipleSelectField,
|
|
674
|
-
'dynamicSingleSelect': this.renderDynamicSingleSelectField,
|
|
675
|
-
'range': this.renderRangeField,
|
|
676
|
-
'submit': this.renderSubmitButton,
|
|
677
|
-
};
|
|
678
|
-
|
|
679
|
-
const renderMethod = fieldRenderMap[type];
|
|
680
|
-
|
|
681
|
-
if (renderMethod) {
|
|
682
|
-
return renderMethod.call(this, type, name, label, validate, attributes, options);
|
|
683
|
-
} else {
|
|
684
|
-
console.warn(`Unsupported field type '${type}' encountered.`);
|
|
685
|
-
return ''; // or handle gracefully
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
*/
|
|
690
733
|
|
|
691
734
|
|
|
692
735
|
// renderField method - No change needed here for this issue, but ensure it handles 'submit' type correctly if called directly
|
|
693
|
-
renderField(type, name, label, validate, attributes, options) {
|
|
736
|
+
renderField(type, name, label, validate, attributes, options, subOptions = undefined) {
|
|
694
737
|
const fieldRenderMap = {
|
|
695
738
|
'text': this.renderTextField,
|
|
696
739
|
'email': this.renderEmailField,
|
|
@@ -715,6 +758,7 @@ renderField(type, name, label, validate, attributes, options) {
|
|
|
715
758
|
'multipleSelect': this.renderMultipleSelectField,
|
|
716
759
|
'dynamicSingleSelect': this.renderDynamicSingleSelectField,
|
|
717
760
|
'range': this.renderRangeField,
|
|
761
|
+
'recaptcha': this.renderRecaptchaField,
|
|
718
762
|
'submit': this.renderSubmitButton, // Keep this for completeness, but renderSubmitButtonElement will now handle it
|
|
719
763
|
};
|
|
720
764
|
|
|
@@ -724,7 +768,7 @@ renderField(type, name, label, validate, attributes, options) {
|
|
|
724
768
|
// If the type is 'submit', ensure we use the specific renderSubmitButtonElement
|
|
725
769
|
// Although, with the filter in renderForm(), this branch for 'submit' type
|
|
726
770
|
// might not be hit in the primary rendering flow, it's good practice.
|
|
727
|
-
return renderMethod.call(this, type, name, label, validate, attributes, options);
|
|
771
|
+
return renderMethod.call(this, type, name, label, validate, attributes, options,subOptions);
|
|
728
772
|
|
|
729
773
|
if (type === 'submit') {
|
|
730
774
|
return this.renderSubmitButton(type, name, label, validate, attributes, options);
|
|
@@ -797,168 +841,173 @@ hasFileInputs(form) {
|
|
|
797
841
|
|
|
798
842
|
|
|
799
843
|
async handleEmailSubmission(formId) {
|
|
800
|
-
|
|
844
|
+
console.log(`Starting email submission for form ID: ${formId}`);
|
|
801
845
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
// Validate required settings for 'sendTo'
|
|
809
|
-
if (!Array.isArray(this.formSettings?.sendTo) || this.formSettings.sendTo.length === 0) {
|
|
810
|
-
console.error('formSettings.sendTo must be an array with at least one recipient email');
|
|
811
|
-
throw new Error('formSettings.sendTo must be an array with at least one recipient email');
|
|
812
|
-
}
|
|
846
|
+
const form = document.getElementById(formId);
|
|
847
|
+
if (!form) {
|
|
848
|
+
console.error(`Form with ID ${formId} not found`);
|
|
849
|
+
throw new Error(`Form with ID ${formId} not found`);
|
|
850
|
+
}
|
|
813
851
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
timestamp: new Date().toISOString(),
|
|
820
|
-
},
|
|
821
|
-
};
|
|
852
|
+
// Validate required settings for 'sendTo'
|
|
853
|
+
if (!Array.isArray(this.formSettings?.sendTo) || this.formSettings.sendTo.length === 0) {
|
|
854
|
+
console.error('formSettings.sendTo must be an array with at least one recipient email');
|
|
855
|
+
throw new Error('formSettings.sendTo must be an array with at least one recipient email');
|
|
856
|
+
}
|
|
822
857
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
858
|
+
// Serialize form data
|
|
859
|
+
const payload = {
|
|
860
|
+
formData: {},
|
|
861
|
+
metadata: {
|
|
862
|
+
recipients: this.formSettings.sendTo,
|
|
863
|
+
timestamp: new Date().toISOString(),
|
|
864
|
+
},
|
|
865
|
+
};
|
|
827
866
|
|
|
828
|
-
|
|
867
|
+
let senderName = '';
|
|
868
|
+
let senderEmail = '';
|
|
869
|
+
let formSubject = '';
|
|
870
|
+
let registrantEmail = '';
|
|
829
871
|
|
|
830
|
-
|
|
831
|
-
const formData = new FormData(form);
|
|
832
|
-
formData.forEach((value, key) => {
|
|
833
|
-
console.log(`Processing form field - Key: ${key}, Value: ${value}`);
|
|
834
|
-
payload.formData[key] = value;
|
|
872
|
+
console.log('Initial payload structure:', JSON.parse(JSON.stringify(payload)));
|
|
835
873
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
senderName = value;
|
|
842
|
-
}
|
|
843
|
-
if (lowerKey.includes('subject')) {
|
|
844
|
-
formSubject = value;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
// NEW: Check if the current field is the registrant's email
|
|
848
|
-
if (this.formSettings.emailField && key === this.formSettings.emailField) {
|
|
849
|
-
registrantEmail = value;
|
|
850
|
-
}
|
|
851
|
-
});
|
|
874
|
+
// Process form fields and find registrant's email
|
|
875
|
+
const formData = new FormData(form);
|
|
876
|
+
formData.forEach((value, key) => {
|
|
877
|
+
console.log(`Processing form field - Key: ${key}, Value: ${value}`);
|
|
878
|
+
payload.formData[key] = value;
|
|
852
879
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
880
|
+
const lowerKey = key.toLowerCase();
|
|
881
|
+
if (lowerKey.includes('email')) {
|
|
882
|
+
senderEmail = value;
|
|
883
|
+
}
|
|
884
|
+
if (lowerKey.includes('name')) {
|
|
885
|
+
senderName = value;
|
|
886
|
+
}
|
|
887
|
+
if (lowerKey.includes('subject')) {
|
|
888
|
+
formSubject = value;
|
|
889
|
+
}
|
|
857
890
|
|
|
858
|
-
|
|
891
|
+
// Check if the current field is the registrant's email
|
|
892
|
+
if (this.formSettings.emailField && key === this.formSettings.emailField) {
|
|
893
|
+
registrantEmail = value;
|
|
894
|
+
}
|
|
895
|
+
});
|
|
859
896
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
? `${senderName} <${senderEmail}>`
|
|
865
|
-
: senderEmail;
|
|
866
|
-
}
|
|
897
|
+
// Determine the email subject with fallback logic
|
|
898
|
+
payload.metadata.subject = formSubject ||
|
|
899
|
+
this.formSettings.subject ||
|
|
900
|
+
'Message From Contact Form';
|
|
867
901
|
|
|
868
|
-
|
|
902
|
+
console.log('Determined email subject:', payload.metadata.subject);
|
|
869
903
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
904
|
+
// Add sender information to metadata
|
|
905
|
+
if (senderEmail) {
|
|
906
|
+
payload.metadata.sender = senderEmail;
|
|
907
|
+
payload.metadata.replyTo = senderName
|
|
908
|
+
? `${senderName} <${senderEmail}>`
|
|
909
|
+
: senderEmail;
|
|
910
|
+
}
|
|
873
911
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
912
|
+
// **NEW:** Add reCAPTCHA secret key to the metadata object
|
|
913
|
+
if (this.formSettings.recaptchaSecretKey) {
|
|
914
|
+
payload.metadata.recaptchaSecretKey = this.formSettings.recaptchaSecretKey;
|
|
915
|
+
}
|
|
877
916
|
|
|
878
|
-
|
|
879
|
-
const response = await fetch(endpoint, {
|
|
880
|
-
method: method,
|
|
881
|
-
headers: {
|
|
882
|
-
'Content-Type': 'application/json',
|
|
883
|
-
'X-Formique-Version': '1.0',
|
|
884
|
-
},
|
|
885
|
-
body: JSON.stringify(payload),
|
|
886
|
-
});
|
|
917
|
+
console.log('Payload after form processing:', JSON.parse(JSON.stringify(payload)));
|
|
887
918
|
|
|
888
|
-
|
|
919
|
+
// ... (The rest of your code remains the same)
|
|
920
|
+
try {
|
|
921
|
+
const endpoint = this.formiqueEndpoint || this.formAction;
|
|
922
|
+
const method = this.method || 'POST';
|
|
889
923
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
console.
|
|
893
|
-
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
894
|
-
}
|
|
924
|
+
console.log(`Preparing to send primary request to: ${endpoint}`);
|
|
925
|
+
console.log(`Request method: ${method}`);
|
|
926
|
+
console.log('Final payload being sent to recipients:', payload);
|
|
895
927
|
|
|
896
|
-
|
|
897
|
-
|
|
928
|
+
// Send the first email to the 'sendTo' recipients
|
|
929
|
+
const response = await fetch(endpoint, {
|
|
930
|
+
method: method,
|
|
931
|
+
headers: {
|
|
932
|
+
'Content-Type': 'application/json',
|
|
933
|
+
'X-Formique-Version': '1.0',
|
|
934
|
+
},
|
|
935
|
+
body: JSON.stringify(payload),
|
|
936
|
+
});
|
|
898
937
|
|
|
899
|
-
|
|
900
|
-
if (this.formSettings.submitMode === 'rsvp' && registrantEmail && this.formSettings.registrantMessage) {
|
|
901
|
-
console.log('RSVP mode detected. Sending confirmation email to registrant.');
|
|
938
|
+
console.log(`Received response for primary email with status: ${response.status}`);
|
|
902
939
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
timestamp: new Date().toISOString(),
|
|
909
|
-
subject: this.formSettings.registrantSubject || 'RSVP Confirmation',
|
|
910
|
-
body: this.processDynamicMessage(this.formSettings.registrantMessage, payload.formData),
|
|
911
|
-
sender: this.formSettings.sendFrom || 'noreply@yourdomain.com',
|
|
912
|
-
replyTo: this.formSettings.sendFrom || 'noreply@yourdomain.com',
|
|
913
|
-
},
|
|
914
|
-
};
|
|
940
|
+
if (!response.ok) {
|
|
941
|
+
const errorData = await response.json().catch(() => ({}));
|
|
942
|
+
console.error('API Error Response:', errorData);
|
|
943
|
+
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
944
|
+
}
|
|
915
945
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
946
|
+
const data = await response.json();
|
|
947
|
+
console.log('Primary API Success Response:', data);
|
|
948
|
+
|
|
949
|
+
// ------------------- NEW RSVP LOGIC -------------------
|
|
950
|
+
if (this.formSettings.submitMode === 'rsvp' && registrantEmail && this.formSettings.registrantMessage) {
|
|
951
|
+
console.log('RSVP mode detected. Sending confirmation email to registrant.');
|
|
952
|
+
|
|
953
|
+
// Create a new payload for the registrant
|
|
954
|
+
const rsvpPayload = {
|
|
955
|
+
formData: payload.formData,
|
|
956
|
+
metadata: {
|
|
957
|
+
recipients: [registrantEmail], // Send only to the registrant
|
|
958
|
+
timestamp: new Date().toISOString(),
|
|
959
|
+
subject: this.formSettings.registrantSubject || 'RSVP Confirmation',
|
|
960
|
+
body: this.processDynamicMessage(this.formSettings.registrantMessage, payload.formData),
|
|
961
|
+
sender: this.formSettings.sendFrom || 'noreply@yourdomain.com',
|
|
962
|
+
replyTo: this.formSettings.sendFrom || 'noreply@yourdomain.com',
|
|
923
963
|
},
|
|
924
|
-
|
|
925
|
-
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
try {
|
|
967
|
+
console.log('Preparing to send RSVP email. Final payload:', rsvpPayload);
|
|
968
|
+
const rsvpResponse = await fetch(endpoint, {
|
|
969
|
+
method: method,
|
|
970
|
+
headers: {
|
|
971
|
+
'Content-Type': 'application/json',
|
|
972
|
+
'X-Formique-Version': '1.0',
|
|
973
|
+
},
|
|
974
|
+
body: JSON.stringify(rsvpPayload),
|
|
975
|
+
});
|
|
926
976
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
977
|
+
if (!rsvpResponse.ok) {
|
|
978
|
+
const rsvpErrorData = await rsvpResponse.json().catch(() => ({}));
|
|
979
|
+
console.error('RSVP API Error Response:', rsvpErrorData);
|
|
980
|
+
// Log the error but don't fail the entire submission since the primary email was sent
|
|
981
|
+
console.warn('Failed to send RSVP email to registrant, but primary submission was successful.');
|
|
982
|
+
} else {
|
|
983
|
+
console.log('RSVP email sent successfully to registrant.');
|
|
984
|
+
}
|
|
985
|
+
} catch (rsvpError) {
|
|
986
|
+
console.error('RSVP email submission failed:', rsvpError);
|
|
931
987
|
console.warn('Failed to send RSVP email to registrant, but primary submission was successful.');
|
|
932
|
-
} else {
|
|
933
|
-
console.log('RSVP email sent successfully to registrant.');
|
|
934
988
|
}
|
|
935
|
-
} catch (rsvpError) {
|
|
936
|
-
console.error('RSVP email submission failed:', rsvpError);
|
|
937
|
-
console.warn('Failed to send RSVP email to registrant, but primary submission was successful.');
|
|
938
989
|
}
|
|
990
|
+
// ------------------- END NEW RSVP LOGIC -------------------
|
|
991
|
+
|
|
992
|
+
const successMessage = this.formSettings.successMessage ||
|
|
993
|
+
data.message ||
|
|
994
|
+
'Your message has been sent successfully!';
|
|
995
|
+
console.log(`Showing success message: ${successMessage}`);
|
|
996
|
+
|
|
997
|
+
this.showSuccessMessage(successMessage);
|
|
998
|
+
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
console.error('Email submission failed:', error);
|
|
1001
|
+
const errorMessage = this.formSettings.errorMessage ||
|
|
1002
|
+
error.message ||
|
|
1003
|
+
'Failed to send message. Please try again later.';
|
|
1004
|
+
console.log(`Showing error message: ${errorMessage}`);
|
|
1005
|
+
this.showErrorMessage(errorMessage);
|
|
1006
|
+
} finally {
|
|
1007
|
+
document.getElementById("formiqueSpinner").style.display = "none";
|
|
939
1008
|
}
|
|
940
|
-
// ------------------- END NEW RSVP LOGIC -------------------
|
|
941
|
-
|
|
942
|
-
const successMessage = this.formSettings.successMessage ||
|
|
943
|
-
data.message ||
|
|
944
|
-
'Your message has been sent successfully!';
|
|
945
|
-
console.log(`Showing success message: ${successMessage}`);
|
|
946
|
-
|
|
947
|
-
this.showSuccessMessage(successMessage);
|
|
948
|
-
|
|
949
|
-
} catch (error) {
|
|
950
|
-
console.error('Email submission failed:', error);
|
|
951
|
-
const errorMessage = this.formSettings.errorMessage ||
|
|
952
|
-
error.message ||
|
|
953
|
-
'Failed to send message. Please try again later.';
|
|
954
|
-
console.log(`Showing error message: ${errorMessage}`);
|
|
955
|
-
this.showErrorMessage(errorMessage);
|
|
956
|
-
} finally {
|
|
957
|
-
document.getElementById("formiqueSpinner").style.display = "none";
|
|
958
|
-
}
|
|
959
1009
|
}
|
|
960
1010
|
|
|
961
|
-
|
|
962
1011
|
// Add this method to your Formique class
|
|
963
1012
|
processDynamicMessage(message, formData) {
|
|
964
1013
|
let processedMessage = message;
|
|
@@ -985,84 +1034,145 @@ validateEmail(email) {
|
|
|
985
1034
|
}
|
|
986
1035
|
|
|
987
1036
|
|
|
1037
|
+
attachSubmitListener() {
|
|
1038
|
+
this.formElement.addEventListener('submit', (e) => {
|
|
1039
|
+
// Find the reCAPTCHA field in the form schema.
|
|
1040
|
+
const recaptchaField = this.formSchema.find(field => field[0] === 'recaptcha');
|
|
1041
|
+
|
|
1042
|
+
// If a reCAPTCHA field is present, check its state.
|
|
1043
|
+
if (recaptchaField) {
|
|
1044
|
+
const recaptchaToken = grecaptcha.getResponse();
|
|
988
1045
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
fetch(this.formAction, {
|
|
1000
|
-
method: this.method,
|
|
1001
|
-
body: formData
|
|
1002
|
-
})
|
|
1003
|
-
.then(response => response.json())
|
|
1004
|
-
.then(data => {
|
|
1005
|
-
console.log('Success:', data);
|
|
1006
|
-
// Handle the response data here, e.g., show a success message
|
|
1007
|
-
|
|
1008
|
-
// Get the form container element
|
|
1009
|
-
const formContainer = document.getElementById(this.formContainerId);
|
|
1046
|
+
if (!recaptchaToken) {
|
|
1047
|
+
// Prevent the default form submission.
|
|
1048
|
+
e.preventDefault();
|
|
1049
|
+
|
|
1050
|
+
// Display the alert and handle UI.
|
|
1051
|
+
alert('Please verify that you are not a robot.');
|
|
1052
|
+
document.getElementById("formiqueSpinner").style.display = "none";
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1010
1056
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
}
|
|
1057
|
+
// If reCAPTCHA is valid or not present, proceed with submission logic.
|
|
1058
|
+
this.handleOnPageFormSubmission(e);
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1014
1061
|
|
|
1015
1062
|
|
|
1016
|
-
if (formContainer) {
|
|
1017
|
-
// Create a new div element for the success message
|
|
1018
|
-
const successMessageDiv = document.createElement('div');
|
|
1019
1063
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1064
|
+
// Method to handle on-page form submissions
|
|
1065
|
+
handleOnPageFormSubmission(formId) {
|
|
1066
|
+
const formElement = document.getElementById(formId);
|
|
1067
|
+
|
|
1068
|
+
if (formElement) {
|
|
1069
|
+
// Intercept the form's native submit event
|
|
1070
|
+
formElement.addEventListener('submit', (e) => {
|
|
1071
|
+
// Find the reCAPTCHA field in the form schema.
|
|
1072
|
+
const recaptchaField = this.formSchema.find(field => field[0] === 'recaptcha');
|
|
1073
|
+
|
|
1074
|
+
// If a reCAPTCHA field exists, perform client-side validation.
|
|
1075
|
+
if (recaptchaField) {
|
|
1076
|
+
const recaptchaToken = grecaptcha.getResponse();
|
|
1077
|
+
|
|
1078
|
+
// If the token is empty, the reCAPTCHA challenge has not been completed.
|
|
1079
|
+
if (!recaptchaToken) {
|
|
1080
|
+
e.preventDefault(); // <-- The crucial line to stop default form submission
|
|
1081
|
+
|
|
1082
|
+
// Hide the spinner to indicate the submission was halted.
|
|
1083
|
+
document.getElementById("formiqueSpinner").style.display = "none";
|
|
1084
|
+
|
|
1085
|
+
// Display a user-friendly error message.
|
|
1086
|
+
alert('Please verify that you are not a robot.');
|
|
1087
|
+
|
|
1088
|
+
// Stop the function's execution to prevent form submission.
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1022
1092
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1093
|
+
// At this point, reCAPTCHA is validated (or not present), so we can proceed with the fetch request.
|
|
1094
|
+
// Show the spinner as submission is now beginning.
|
|
1095
|
+
document.getElementById("formiqueSpinner").style.display = "block";
|
|
1025
1096
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1097
|
+
// Gather form data.
|
|
1098
|
+
const formData = {};
|
|
1099
|
+
new FormData(formElement).forEach((value, key) => {
|
|
1100
|
+
formData[key] = value;
|
|
1101
|
+
});
|
|
1030
1102
|
|
|
1103
|
+
console.log("Setting Object",this.formSettings);
|
|
1031
1104
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1105
|
+
// Create the full payload with formData and metadata, including the secret key.
|
|
1106
|
+
const payload = {
|
|
1107
|
+
formData: formData,
|
|
1108
|
+
metadata: {
|
|
1109
|
+
...this.formSettings, // Include all formSettings
|
|
1110
|
+
// Other metadata like recipients and sender will be included from this.formSettings
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
// Submit form data using fetch to the endpoint.
|
|
1115
|
+
fetch(this.formAction, {
|
|
1116
|
+
method: this.method,
|
|
1117
|
+
headers: {
|
|
1118
|
+
'Content-Type': 'application/json' // Important: set the content type
|
|
1119
|
+
},
|
|
1120
|
+
body: JSON.stringify(payload) // Send the combined payload as JSON
|
|
1121
|
+
})
|
|
1122
|
+
.then(response => {
|
|
1123
|
+
// Check if the response status is OK (200-299).
|
|
1124
|
+
if (!response.ok) {
|
|
1125
|
+
return response.json().then(errorData => {
|
|
1126
|
+
throw new Error(errorData.error || `HTTP error! Status: ${response.status}`);
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
return response.json();
|
|
1130
|
+
})
|
|
1131
|
+
.then(data => {
|
|
1132
|
+
console.log('Success:', data);
|
|
1133
|
+
|
|
1134
|
+
// Hide the spinner on success.
|
|
1135
|
+
document.getElementById("formiqueSpinner").style.display = "none";
|
|
1136
|
+
|
|
1137
|
+
const formContainer = document.getElementById(this.formContainerId);
|
|
1138
|
+
if (this.redirect && this.redirectURL) {
|
|
1139
|
+
window.location.href = this.redirectURL;
|
|
1140
|
+
}
|
|
1141
|
+
if (formContainer) {
|
|
1142
|
+
const successMessageDiv = document.createElement('div');
|
|
1143
|
+
successMessageDiv.classList.add('success-message', 'message-container');
|
|
1144
|
+
successMessageDiv.innerHTML = this.formSettings.successMessage || 'Your details have been successfully submitted!';
|
|
1145
|
+
formContainer.innerHTML = '';
|
|
1146
|
+
formContainer.appendChild(successMessageDiv);
|
|
1147
|
+
}
|
|
1148
|
+
})
|
|
1149
|
+
.catch(error => {
|
|
1150
|
+
console.error('Error:', error);
|
|
1151
|
+
|
|
1152
|
+
// Hide the spinner on error.
|
|
1153
|
+
document.getElementById("formiqueSpinner").style.display = "none";
|
|
1154
|
+
|
|
1155
|
+
const formContainer = document.getElementById(this.formContainerId);
|
|
1156
|
+
if (formContainer) {
|
|
1157
|
+
let existingErrorDiv = formContainer.querySelector('.error-message');
|
|
1158
|
+
if (existingErrorDiv) {
|
|
1159
|
+
existingErrorDiv.remove();
|
|
1160
|
+
}
|
|
1161
|
+
const errorMessageDiv = document.createElement('div');
|
|
1162
|
+
errorMessageDiv.classList.add('error-message', 'message-container');
|
|
1163
|
+
let err = this.formSettings.errorMessage || 'An error occurred while submitting the form. Please try again.';
|
|
1164
|
+
err = `${err}<br/>Details: ${error.message}`;
|
|
1165
|
+
errorMessageDiv.innerHTML = err;
|
|
1166
|
+
formContainer.appendChild(errorMessageDiv);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1035
1169
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
let existingErrorDiv = formContainer.querySelector('.error-message');
|
|
1040
|
-
if (existingErrorDiv) {
|
|
1041
|
-
existingErrorDiv.remove();
|
|
1170
|
+
// Return false to ensure no other default action is taken, especially for legacy browsers.
|
|
1171
|
+
return false;
|
|
1172
|
+
});
|
|
1042
1173
|
}
|
|
1043
|
-
|
|
1044
|
-
// Create a new div element for the error message
|
|
1045
|
-
const errorMessageDiv = document.createElement('div');
|
|
1046
|
-
|
|
1047
|
-
// Add custom classes for styling the error message
|
|
1048
|
-
errorMessageDiv.classList.add('error-message', 'message-container');
|
|
1049
|
-
|
|
1050
|
-
// Set the error message text
|
|
1051
|
-
let err = this.formSettings.errorMessage || 'An error occurred while submitting the form. Please try again.';
|
|
1052
|
-
err = `${err}<br/>Details: ${error.message}`;
|
|
1053
|
-
errorMessageDiv.innerHTML = err;
|
|
1054
|
-
|
|
1055
|
-
// Append the new error message div to the form container
|
|
1056
|
-
formContainer.appendChild(errorMessageDiv);
|
|
1057
|
-
}
|
|
1058
|
-
});
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
1174
|
}
|
|
1062
1175
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
1176
|
// text field rendering
|
|
1067
1177
|
renderTextField(type, name, label, validate, attributes) {
|
|
1068
1178
|
const textInputValidationAttributes = [
|
|
@@ -3345,6 +3455,8 @@ renderTextAreaField(type, name, label, validate, attributes) {
|
|
|
3345
3455
|
|
|
3346
3456
|
renderRadioField(type, name, label, validate, attributes, options) {
|
|
3347
3457
|
// Define valid validation attributes for radio fields
|
|
3458
|
+
console.log("RADIO DEBUG - options:", JSON.stringify(options, null, 2));
|
|
3459
|
+
|
|
3348
3460
|
const radioValidationAttributes = ['required'];
|
|
3349
3461
|
|
|
3350
3462
|
// Construct validation attributes
|
|
@@ -3376,15 +3488,15 @@ renderRadioField(type, name, label, validate, attributes, options) {
|
|
|
3376
3488
|
// Handle the binding syntax
|
|
3377
3489
|
let bindingDirective = '';
|
|
3378
3490
|
if (attributes.binding) {
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3491
|
+
if (attributes.binding === 'bind:value' && name) {
|
|
3492
|
+
bindingDirective = ` bind:value="${name}"\n`;
|
|
3493
|
+
} else if (attributes.binding.startsWith('::') && name) {
|
|
3494
|
+
bindingDirective = ` bind:value="${name}"\n`;
|
|
3495
|
+
} else if (attributes.binding && !name) {
|
|
3496
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3386
3499
|
}
|
|
3387
|
-
}
|
|
3388
3500
|
|
|
3389
3501
|
// Define attributes for the radio inputs
|
|
3390
3502
|
let id = attributes.id || name;
|
|
@@ -3392,7 +3504,8 @@ renderRadioField(type, name, label, validate, attributes, options) {
|
|
|
3392
3504
|
// Construct additional attributes dynamically
|
|
3393
3505
|
let additionalAttrs = '';
|
|
3394
3506
|
for (const [key, value] of Object.entries(attributes)) {
|
|
3395
|
-
|
|
3507
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
3508
|
+
if (key.startsWith('on')) {
|
|
3396
3509
|
// Handle event attributes
|
|
3397
3510
|
const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
|
|
3398
3511
|
additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
|
|
@@ -3410,10 +3523,28 @@ renderRadioField(type, name, label, validate, attributes, options) {
|
|
|
3410
3523
|
|
|
3411
3524
|
let inputClass = attributes.class || this.inputClass;
|
|
3412
3525
|
|
|
3526
|
+
// Determine which option should be selected
|
|
3527
|
+
let selectedValue = null;
|
|
3528
|
+
|
|
3529
|
+
// Check options array for selected: true
|
|
3530
|
+
if (options && options.length) {
|
|
3531
|
+
const selectedOption = options.find(opt => opt.selected === true);
|
|
3532
|
+
console.log("RADIO DEBUG - selectedOption:", selectedOption);
|
|
3533
|
+
if (selectedOption) {
|
|
3534
|
+
selectedValue = selectedOption.value;
|
|
3535
|
+
console.log("RADIO DEBUG - selectedValue:", selectedValue);
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3413
3539
|
// Construct radio button HTML based on options
|
|
3414
3540
|
let optionsHTML = '';
|
|
3415
3541
|
if (options && options.length) {
|
|
3416
3542
|
optionsHTML = options.map((option) => {
|
|
3543
|
+
// Check if this option should be selected
|
|
3544
|
+
const isSelected = (option.value === selectedValue);
|
|
3545
|
+
console.log("RADIO DEBUG - option:", option.value, "isSelected:", isSelected);
|
|
3546
|
+
const checkedAttr = isSelected ? ' checked' : '';
|
|
3547
|
+
|
|
3417
3548
|
return `
|
|
3418
3549
|
<div>
|
|
3419
3550
|
<input
|
|
@@ -3425,6 +3556,7 @@ renderRadioField(type, name, label, validate, attributes, options) {
|
|
|
3425
3556
|
${attributes.id ? `id="${id}-${option.value}"` : `id="${id}-${option.value}"`}
|
|
3426
3557
|
class="${inputClass}"
|
|
3427
3558
|
${validationAttrs}
|
|
3559
|
+
${checkedAttr}
|
|
3428
3560
|
/>
|
|
3429
3561
|
<label
|
|
3430
3562
|
for="${attributes.id ? `${id}-${option.value}` : `${id}-${option.value}`}">
|
|
@@ -3459,13 +3591,13 @@ renderRadioField(type, name, label, validate, attributes, options) {
|
|
|
3459
3591
|
return `\n${match}\n`;
|
|
3460
3592
|
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3461
3593
|
|
|
3462
|
-
|
|
3463
|
-
this.formMarkUp +=formattedHtml;
|
|
3594
|
+
this.formMarkUp += formattedHtml;
|
|
3464
3595
|
}
|
|
3465
3596
|
|
|
3466
|
-
|
|
3467
3597
|
renderCheckboxField(type, name, label, validate, attributes, options) {
|
|
3468
3598
|
// Define valid validation attributes for checkbox fields
|
|
3599
|
+
console.log("CHECKBOX DEBUG - options:", JSON.stringify(options, null, 2));
|
|
3600
|
+
|
|
3469
3601
|
const checkboxValidationAttributes = ['required'];
|
|
3470
3602
|
|
|
3471
3603
|
// Construct validation attributes
|
|
@@ -3485,12 +3617,12 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
|
|
|
3485
3617
|
// Handle the binding syntax
|
|
3486
3618
|
let bindingDirective = '';
|
|
3487
3619
|
if (attributes.binding) {
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3620
|
+
if (attributes.binding === 'bind:checked') {
|
|
3621
|
+
bindingDirective = ` bind:checked="${name}"\n`;
|
|
3622
|
+
} else if (attributes.binding.startsWith('::')) {
|
|
3623
|
+
bindingDirective = ` bind:checked="${name}"\n`;
|
|
3624
|
+
}
|
|
3492
3625
|
}
|
|
3493
|
-
}
|
|
3494
3626
|
|
|
3495
3627
|
// Define attributes for the checkbox inputs
|
|
3496
3628
|
let id = attributes.id || name;
|
|
@@ -3498,7 +3630,8 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
|
|
|
3498
3630
|
// Handle additional attributes
|
|
3499
3631
|
let additionalAttrs = '';
|
|
3500
3632
|
for (const [key, value] of Object.entries(attributes)) {
|
|
3501
|
-
|
|
3633
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
3634
|
+
if (key.startsWith('on')) {
|
|
3502
3635
|
// Handle event attributes
|
|
3503
3636
|
const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
|
|
3504
3637
|
additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
|
|
@@ -3514,30 +3647,45 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
|
|
|
3514
3647
|
}
|
|
3515
3648
|
}
|
|
3516
3649
|
|
|
3517
|
-
|
|
3518
3650
|
let inputClass;
|
|
3519
3651
|
if ('class' in attributes) {
|
|
3520
3652
|
inputClass = attributes.class;
|
|
3521
3653
|
} else {
|
|
3522
|
-
|
|
3654
|
+
inputClass = this.inputClass;
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
// Determine which options should be checked
|
|
3658
|
+
const checkedValues = [];
|
|
3659
|
+
if (options && options.length) {
|
|
3660
|
+
options.forEach(option => {
|
|
3661
|
+
if (option.checked === true || option.selected === true) {
|
|
3662
|
+
checkedValues.push(option.value);
|
|
3663
|
+
}
|
|
3664
|
+
});
|
|
3523
3665
|
}
|
|
3666
|
+
console.log("CHECKBOX DEBUG - checkedValues:", checkedValues);
|
|
3524
3667
|
|
|
3525
3668
|
// Construct checkbox HTML based on options
|
|
3526
3669
|
let optionsHTML = '';
|
|
3527
3670
|
if (Array.isArray(options)) {
|
|
3528
3671
|
optionsHTML = options.map((option) => {
|
|
3529
3672
|
const optionId = `${id}-${option.value}`;
|
|
3673
|
+
const isChecked = checkedValues.includes(option.value);
|
|
3674
|
+
console.log("CHECKBOX DEBUG - option:", option.value, "isChecked:", isChecked);
|
|
3675
|
+
const checkedAttr = isChecked ? ' checked' : '';
|
|
3676
|
+
|
|
3530
3677
|
return `
|
|
3531
3678
|
<div>
|
|
3532
3679
|
<input
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3680
|
+
type="checkbox"
|
|
3681
|
+
name="${name}"
|
|
3682
|
+
value="${option.value}"${bindingDirective} ${additionalAttrs}
|
|
3536
3683
|
${attributes.id ? `id="${optionId}"` : `id="${optionId}"`}
|
|
3537
3684
|
class="${inputClass}"
|
|
3685
|
+
${checkedAttr}
|
|
3538
3686
|
/>
|
|
3539
3687
|
<label
|
|
3540
|
-
|
|
3688
|
+
for="${optionId}">
|
|
3541
3689
|
${option.label}
|
|
3542
3690
|
</label>
|
|
3543
3691
|
</div>
|
|
@@ -3570,8 +3718,7 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
|
|
|
3570
3718
|
return `\n${match}\n`;
|
|
3571
3719
|
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3572
3720
|
|
|
3573
|
-
|
|
3574
|
-
this.formMarkUp +=formattedHtml;
|
|
3721
|
+
this.formMarkUp += formattedHtml;
|
|
3575
3722
|
}
|
|
3576
3723
|
|
|
3577
3724
|
|
|
@@ -3579,239 +3726,240 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
|
|
|
3579
3726
|
/* DYNAMIC SINGLE SELECT BLOCK */
|
|
3580
3727
|
|
|
3581
3728
|
// Function to render the dynamic select field and update based on user selection
|
|
3582
|
-
renderDynamicSingleSelectField(type, name, label, validate, attributes, options) {
|
|
3583
|
-
|
|
3584
|
-
// Step 1: Transform the data into an array of objects
|
|
3585
|
-
const mainCategoryOptions = options.
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
})
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
const mode='dynamicSingleSelect';
|
|
3599
|
-
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
|
+
});
|
|
3600
3745
|
|
|
3601
|
-
|
|
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
|
+
|
|
3602
3752
|
|
|
3603
3753
|
|
|
3604
3754
|
renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
|
|
3605
3755
|
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
const selectValidationAttributes = ['required'];
|
|
3756
|
+
// Define valid validation attributes for select fields
|
|
3757
|
+
const selectValidationAttributes = ['required'];
|
|
3609
3758
|
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
}
|
|
3621
|
-
} else {
|
|
3622
|
-
console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
|
|
3623
|
-
}
|
|
3624
|
-
});
|
|
3625
|
-
}
|
|
3626
|
-
|
|
3627
|
-
// Handle the binding syntax
|
|
3628
|
-
let bindingDirective = '';
|
|
3629
|
-
if (attributes.binding) {
|
|
3630
|
-
if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
|
|
3631
|
-
bindingDirective = ` bind:value="${name}" `;
|
|
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
|
|
3632
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}" `;
|
|
3633
3781
|
}
|
|
3782
|
+
}
|
|
3634
3783
|
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3784
|
+
// Define attributes for the select field
|
|
3785
|
+
let id = attributes.id || name;
|
|
3786
|
+
let dimensionAttrs = ''; // No dimension attributes applicable for select fields
|
|
3638
3787
|
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
}
|
|
3655
|
-
}
|
|
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`;
|
|
3656
3803
|
}
|
|
3804
|
+
}
|
|
3657
3805
|
}
|
|
3806
|
+
}
|
|
3658
3807
|
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
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
|
+
`;
|
|
3666
3815
|
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
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
|
+
}
|
|
3675
3824
|
|
|
3676
|
-
|
|
3825
|
+
let inputClass = attributes.class || this.inputClass;
|
|
3677
3826
|
|
|
3678
|
-
|
|
3679
|
-
|
|
3827
|
+
// Remove `onchange` from HTML; it will be handled by JavaScript event listeners
|
|
3828
|
+
const onchangeAttr = ''; // <--- Ensure this is an empty string
|
|
3680
3829
|
|
|
3681
|
-
|
|
3682
|
-
|
|
3830
|
+
let labelDisplay;
|
|
3831
|
+
let rawLabel;
|
|
3683
3832
|
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
} else {
|
|
3690
|
-
labelDisplay = label;
|
|
3691
|
-
rawLabel = label;
|
|
3692
|
-
}
|
|
3833
|
+
if (mode === 'dynamicSingleSelect' && subCategoriesOptions) {
|
|
3834
|
+
if (label.includes('-')) {
|
|
3835
|
+
const [mainCategoryLabel] = label.split('-');
|
|
3836
|
+
labelDisplay = mainCategoryLabel;
|
|
3837
|
+
rawLabel = label;
|
|
3693
3838
|
} else {
|
|
3694
|
-
|
|
3839
|
+
labelDisplay = label;
|
|
3840
|
+
rawLabel = label;
|
|
3695
3841
|
}
|
|
3842
|
+
} else {
|
|
3843
|
+
labelDisplay = label;
|
|
3844
|
+
}
|
|
3696
3845
|
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
<
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
</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>
|
|
3716
3864
|
`.replace(/^\s*\n/gm, '').trim();
|
|
3717
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
|
+
});
|
|
3718
3874
|
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
});
|
|
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
|
|
3725
3880
|
|
|
3726
|
-
|
|
3727
|
-
formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3728
|
-
// Ensure <fieldset> starts on a new line
|
|
3729
|
-
return `\n${match}\n`;
|
|
3730
|
-
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
3881
|
+
this.formMarkUp+=formattedHtml;
|
|
3731
3882
|
|
|
3732
|
-
this.formMarkUp+=formattedHtml;
|
|
3733
3883
|
|
|
3884
|
+
/* dynamicSingleSelect - Sub-Category Generation Block */
|
|
3734
3885
|
|
|
3735
|
-
|
|
3886
|
+
if (mode && mode ==='dynamicSingleSelect' && subCategoriesOptions) {
|
|
3736
3887
|
|
|
3737
|
-
|
|
3888
|
+
const categoryId = attributes.id || name; // This is the ID of the main dynamic select ('languages')
|
|
3738
3889
|
|
|
3739
|
-
|
|
3890
|
+
subCategoriesOptions.forEach((subCategory) => {
|
|
3891
|
+
const { id, label, options: subOptions } = subCategory; // Renamed 'options' to 'subOptions' to avoid conflict
|
|
3740
3892
|
|
|
3741
|
-
|
|
3742
|
-
|
|
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
|
|
3743
3897
|
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
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('');
|
|
3751
3905
|
|
|
3752
|
-
// Build the select options HTML for sub-category
|
|
3753
|
-
const subSelectHTML = subOptions.map(option => {
|
|
3754
|
-
const isSelected = option.selected ? ' selected' : '';
|
|
3755
|
-
return `
|
|
3756
|
-
<option value="${option.value}"${isSelected}>${option.label}</option>
|
|
3757
|
-
`;
|
|
3758
|
-
}).join('');
|
|
3759
3906
|
|
|
3907
|
+
let subCategoryLabel;
|
|
3908
|
+
|
|
3909
|
+
if (rawLabel.includes('-')) {
|
|
3910
|
+
subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
|
|
3911
|
+
} else {
|
|
3912
|
+
subCategoryLabel = 'options';
|
|
3913
|
+
}
|
|
3760
3914
|
|
|
3761
|
-
|
|
3762
|
-
|
|
3915
|
+
let optionsLabel;
|
|
3916
|
+
if (subCategoryLabel !== 'options') {
|
|
3917
|
+
optionsLabel = rawLabel.split('-')?.[1] + ' Option';
|
|
3918
|
+
} else {
|
|
3919
|
+
optionsLabel = subCategoryLabel;
|
|
3920
|
+
}
|
|
3763
3921
|
|
|
3764
|
-
if (rawLabel.includes('-')) {
|
|
3765
|
-
subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
|
|
3766
|
-
} else {
|
|
3767
|
-
subCategoryLabel = 'options';
|
|
3768
|
-
}
|
|
3769
3922
|
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
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
|
+
});
|
|
3776
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');
|
|
3777
3955
|
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
<label for="${id}"> Select ${label} ${optionsLabel}
|
|
3784
|
-
</label>
|
|
3785
|
-
<select name="${id}"
|
|
3786
|
-
${bindingDirective}
|
|
3787
|
-
${dimensionAttrs}
|
|
3788
|
-
id="${id}"
|
|
3789
|
-
class="${inputClass}"
|
|
3790
|
-
${additionalAttrs}
|
|
3791
|
-
${subCategoryValidationAttrs}
|
|
3792
|
-
data-original-required="${isSubCategoryRequired}" >
|
|
3793
|
-
<option value="">Choose an option</option>
|
|
3794
|
-
${subSelectHTML}
|
|
3795
|
-
</select>
|
|
3796
|
-
</fieldset>
|
|
3797
|
-
`.replace(/^\s*\n/gm, '').trim();
|
|
3798
|
-
|
|
3799
|
-
// Apply vertical layout to the <select> element and its children
|
|
3800
|
-
subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
|
|
3801
|
-
const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
|
|
3802
|
-
return `<select\n${attributes}\n>\n${p2.trim()}\n</select>`;
|
|
3803
|
-
});
|
|
3956
|
+
// Append the generated HTML to formMarkUp
|
|
3957
|
+
this.formMarkUp += subFormHTML;
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3804
3961
|
|
|
3805
|
-
// Ensure the <fieldset> block starts on a new line and remove extra blank lines
|
|
3806
|
-
subFormHTML = subFormHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
|
|
3807
|
-
return `\n${match}\n`;
|
|
3808
|
-
}).replace(/\n\s*\n/g, '\n');
|
|
3809
3962
|
|
|
3810
|
-
// Append the generated HTML to formMarkUp
|
|
3811
|
-
this.formMarkUp += subFormHTML;
|
|
3812
|
-
});
|
|
3813
|
-
}
|
|
3814
|
-
}
|
|
3815
3963
|
|
|
3816
3964
|
renderMultipleSelectField(type, name, label, validate, attributes, options) {
|
|
3817
3965
|
// Define valid validation attributes for multiple select fields
|
|
@@ -4011,6 +4159,24 @@ renderRangeField(type, name, label, validate, attributes) {
|
|
|
4011
4159
|
}
|
|
4012
4160
|
|
|
4013
4161
|
|
|
4162
|
+
renderRecaptchaField(type, name, label, validate, attributes = {}) {
|
|
4163
|
+
const fieldId = attributes.id || name;
|
|
4164
|
+
const siteKey = attributes.siteKey;
|
|
4165
|
+
// Check for the presence of a siteKey
|
|
4166
|
+
if (!siteKey) {
|
|
4167
|
+
console.error('reCAPTCHA siteKey is missing from the field attributes.');
|
|
4168
|
+
return ''; // Do not render if the key is missing
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
return `
|
|
4172
|
+
<div class="${this.divClass}" id="${fieldId}-block">
|
|
4173
|
+
<label for="${fieldId}">${label}</label>
|
|
4174
|
+
<div class="g-recaptcha" id="${fieldId}" data-sitekey="${siteKey}"></div>
|
|
4175
|
+
</div>
|
|
4176
|
+
`;
|
|
4177
|
+
}
|
|
4178
|
+
|
|
4179
|
+
|
|
4014
4180
|
|
|
4015
4181
|
/*
|
|
4016
4182
|
renderRangeField(type, name, label, validate, attributes) {
|
|
@@ -4192,14 +4358,3 @@ const spinner = `<div id="formiqueSpinner" style="display: flex; align-items: ce
|
|
|
4192
4358
|
|
|
4193
4359
|
|
|
4194
4360
|
export default Formique;
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|