@formique/semantq 1.0.4 → 1.0.6
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/README.md +17 -12
- package/formique-semantq.js +555 -401
- package/package.json +1 -1
package/formique-semantq.js
CHANGED
|
@@ -50,126 +50,125 @@ class FormBuilder
|
|
|
50
50
|
|
|
51
51
|
// Extended class for specific form rendering methods
|
|
52
52
|
class Formique extends FormBuilder {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
'
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.initDependencyGraph();
|
|
132
|
-
this.registerObservers();
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (this.formSettings.theme && this.themes.includes(this.formSettings.theme)) {
|
|
136
|
-
let theme = this.formSettings.theme;
|
|
137
|
-
this.applyTheme(theme, this.formContainerId);
|
|
138
|
-
} else {
|
|
139
|
-
// Fallback to dark theme if no theme is set or invalid theme
|
|
140
|
-
this.applyTheme('dark', this.formContainerId);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
document.getElementById(`${this.formId}`).addEventListener('submit', function(event) {
|
|
144
|
-
|
|
145
|
-
if (this.formSettings.submitMode === 'email') {
|
|
146
|
-
event.preventDefault(); // Prevent the default form submission
|
|
147
|
-
document.getElementById("formiqueSpinner").style.display = "block";
|
|
148
|
-
//return;
|
|
149
|
-
this.handleEmailSubmission(this.formId);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (this.formSettings.submitOnPage) {
|
|
154
|
-
event.preventDefault(); // Prevent the default form submission
|
|
155
|
-
document.getElementById("formiqueSpinner").style.display = "block";
|
|
156
|
-
this.handleOnPageFormSubmission(this.formId);
|
|
157
|
-
//console.warn("listener fired at least>>", this.formParams.id, this.method);
|
|
158
|
-
}
|
|
159
|
-
}.bind(this)); // Bind `this` to ensure it's correct inside the event listener
|
|
53
|
+
constructor(formSchema, formSettings = {}, formParams = {}) {
|
|
54
|
+
super();
|
|
55
|
+
this.formSchema = formSchema;
|
|
56
|
+
this.formParams = formParams;
|
|
57
|
+
this.formSettings = {
|
|
58
|
+
requiredFieldIndicator: true,
|
|
59
|
+
placeholders: true,
|
|
60
|
+
asteriskHtml: '<span aria-hidden="true" style="color: red;">*</span>',
|
|
61
|
+
...formSettings
|
|
62
|
+
};
|
|
63
|
+
this.divClass = 'input-block';
|
|
64
|
+
this.inputClass = 'form-input';
|
|
65
|
+
this.radioGroupClass = 'radio-group';
|
|
66
|
+
this.checkboxGroupClass = 'checkbox-group';
|
|
67
|
+
this.selectGroupClass = 'form-select';
|
|
68
|
+
this.submitButtonClass = 'form-submit-btn';
|
|
69
|
+
this.formContainerId = formSettings?.formContainerId || 'formique';
|
|
70
|
+
// Ensure formParams.id is used if provided, otherwise generate a new ID
|
|
71
|
+
this.formId = this.formParams?.id || this.generateFormId();
|
|
72
|
+
this.formAction = formParams?.action || 'https://httpbin.org/post';
|
|
73
|
+
this.method = 'POST';
|
|
74
|
+
this.formMarkUp = '';
|
|
75
|
+
this.dependencyGraph = {};
|
|
76
|
+
this.redirect = formSettings?.redirect || '';
|
|
77
|
+
this.redirectURL = formSettings?.redirectURL || '';
|
|
78
|
+
this.themes = [
|
|
79
|
+
"dark", "light", "pink", "indigo", "dark-blue", "light-blue",
|
|
80
|
+
"dark-orange", "bright-yellow", "green", "purple", "midnight-blush",
|
|
81
|
+
"deep-blue", "blue", "brown", "orange"
|
|
82
|
+
];
|
|
83
|
+
this.formiqueEndpoint = "https://formiqueapi.onrender.com/api/send-email";
|
|
84
|
+
|
|
85
|
+
// DISABLE DOM LISTENER
|
|
86
|
+
//document.addEventListener('DOMContentLoaded', () => {
|
|
87
|
+
// 1. Build the form's HTML in memory
|
|
88
|
+
this.formMarkUp += this.renderFormElement(); // Adds opening <form> tag and any hidden inputs
|
|
89
|
+
|
|
90
|
+
// Filter out 'submit' field for rendering, and render all other fields
|
|
91
|
+
const nonSubmitFieldsHtml = this.formSchema
|
|
92
|
+
.filter(field => field[0] !== 'submit')
|
|
93
|
+
.map(field => {
|
|
94
|
+
const [type, name, label, validate, attributes = {}, options] = field;
|
|
95
|
+
return this.renderField(type, name, label, validate, attributes, options);
|
|
96
|
+
}).join('');
|
|
97
|
+
this.formMarkUp += nonSubmitFieldsHtml;
|
|
98
|
+
|
|
99
|
+
// Find and render the submit button separately, at the very end of the form content
|
|
100
|
+
const submitField = this.formSchema.find(field => field[0] === 'submit');
|
|
101
|
+
if (submitField) {
|
|
102
|
+
const [type, name, label, validate, attributes = {}] = submitField;
|
|
103
|
+
const id = attributes.id || name;
|
|
104
|
+
let buttonClass = this.submitButtonClass;
|
|
105
|
+
if ('class' in attributes) {
|
|
106
|
+
buttonClass = attributes.class;
|
|
107
|
+
}
|
|
108
|
+
let additionalAttrs = '';
|
|
109
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
110
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
111
|
+
if (key.startsWith('on')) {
|
|
112
|
+
const eventValue = value.endsWith('()') ? value : `${value}()`;
|
|
113
|
+
additionalAttrs += ` ${key}="${eventValue}"`;
|
|
114
|
+
} else {
|
|
115
|
+
if (value === true) {
|
|
116
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}`;
|
|
117
|
+
} else if (value !== false) {
|
|
118
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
this.formMarkUp += `
|
|
124
|
+
<div id="formiqueSpinner" style="display: flex; align-items: center; gap: 1rem; font-family: sans-serif; display:none;">
|
|
125
|
+
<div class="formique-spinner"></div>
|
|
126
|
+
<p class="message">Hang in tight, we are submitting your details…</p>
|
|
127
|
+
</div>
|
|
128
|
+
<input type="submit" id="${id}" class="${buttonClass}" value="${label}"${additionalAttrs}>
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
160
131
|
|
|
161
132
|
|
|
133
|
+
// 2. Inject the complete form HTML into the DOM
|
|
134
|
+
this.renderFormHTML(); // This puts the form element into the document!
|
|
162
135
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
136
|
+
// 3. Now that the form is in the DOM, attach event listeners
|
|
137
|
+
const formElement = document.getElementById(`${this.formId}`);
|
|
138
|
+
if (formElement) { // Add a check here just in case, although it should now exist
|
|
139
|
+
formElement.addEventListener('submit', function(event) {
|
|
140
|
+
if (this.formSettings.submitMode === 'email') {
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
document.getElementById("formiqueSpinner").style.display = "block";
|
|
143
|
+
this.handleEmailSubmission(this.formId);
|
|
144
|
+
}
|
|
167
145
|
|
|
146
|
+
if (this.formSettings.submitOnPage) {
|
|
147
|
+
event.preventDefault();
|
|
148
|
+
document.getElementById("formiqueSpinner").style.display = "block";
|
|
149
|
+
this.handleOnPageFormSubmission(this.formId);
|
|
150
|
+
}
|
|
151
|
+
}.bind(this));
|
|
152
|
+
} else {
|
|
153
|
+
console.error(`Form with ID ${this.formId} not found after rendering. Event listener could not be attached.`);
|
|
154
|
+
}
|
|
168
155
|
|
|
156
|
+
// Initialize dependency graph and observers after the form is rendered
|
|
157
|
+
this.initDependencyGraph();
|
|
158
|
+
this.registerObservers();
|
|
169
159
|
|
|
170
|
-
//
|
|
171
|
-
|
|
160
|
+
// Apply theme
|
|
161
|
+
if (this.formSettings.theme && this.themes.includes(this.formSettings.theme)) {
|
|
162
|
+
let theme = this.formSettings.theme;
|
|
163
|
+
this.applyTheme(theme, this.formContainerId);
|
|
164
|
+
} else {
|
|
165
|
+
this.applyTheme('dark', this.formContainerId);
|
|
166
|
+
}
|
|
167
|
+
|
|
172
168
|
|
|
169
|
+
//DISABLE DOM LISTNER
|
|
170
|
+
//}); // DOM LISTENER WRAPPER
|
|
171
|
+
|
|
173
172
|
// CONSTRUCTOR WRAPPER FOR FORMIQUE CLASS
|
|
174
173
|
}
|
|
175
174
|
|
|
@@ -346,107 +345,56 @@ registerObservers() {
|
|
|
346
345
|
|
|
347
346
|
|
|
348
347
|
applyTheme(theme, formContainerId) {
|
|
349
|
-
|
|
350
|
-
const spinnerContainer = document.getElementById('formiqueSpinner');
|
|
351
|
-
|
|
352
|
-
if (!formContainer) {
|
|
353
|
-
console.error(`Form container with ID ${formContainerId} not found.`);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
348
|
+
//const stylesheet = document.querySelector('link[formique-style]');
|
|
356
349
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
350
|
+
const stylesheet = document.querySelector('link[href*="formique-css"]');
|
|
351
|
+
|
|
352
|
+
if (!stylesheet) {
|
|
353
|
+
console.error("Stylesheet with 'formique-css' in the name not found!");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
361
356
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
357
|
+
fetch(stylesheet.href)
|
|
358
|
+
.then(response => response.text())
|
|
359
|
+
.then(cssText => {
|
|
360
|
+
// Extract theme-specific CSS rules
|
|
361
|
+
const themeRules = cssText.match(new RegExp(`\\.${theme}-theme\\s*{([^}]*)}`, 'i'));
|
|
367
362
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (!stylesheet) {
|
|
371
|
-
console.error("Stylesheet with 'formique-css' in the name not found!");
|
|
363
|
+
if (!themeRules) {
|
|
364
|
+
console.error(`Theme rules for ${theme} not found in the stylesheet.`);
|
|
372
365
|
return;
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
fetch(stylesheet.href)
|
|
376
|
-
.then(response => response.text())
|
|
377
|
-
.then(cssText => {
|
|
378
|
-
const themeRules = cssText.match(new RegExp(`\\.${theme}-theme\\s*{([^}]*)}`, 'i'));
|
|
379
|
-
if (!themeRules) {
|
|
380
|
-
console.error(`Theme rules for ${theme} not found in the stylesheet.`);
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const themeCSS = themeRules[1].trim();
|
|
385
|
-
formContainer.classList.add(`${theme}-theme`, 'formique');
|
|
386
|
-
spinnerContainer.classList.add(`${theme}-theme`);
|
|
387
|
-
|
|
388
|
-
const clonedStyle = document.createElement('style');
|
|
389
|
-
clonedStyle.textContent = `#${formContainerId} { ${themeCSS} }`;
|
|
390
|
-
formContainer.parentNode.insertBefore(clonedStyle, formContainer);
|
|
391
|
-
})
|
|
392
|
-
.catch(error => {
|
|
393
|
-
console.error('Error loading the stylesheet:', error);
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
applyCustomTheme(formContainerId) {
|
|
398
|
-
const formContainer = document.getElementById(formContainerId);
|
|
399
|
-
const spinnerContainer = document.getElementById('formiqueSpinner');
|
|
366
|
+
}
|
|
400
367
|
|
|
401
|
-
|
|
368
|
+
// Extract CSS rules for the theme
|
|
369
|
+
const themeCSS = themeRules[1].trim();
|
|
402
370
|
|
|
403
|
-
|
|
404
|
-
|
|
371
|
+
// Find the form container element
|
|
372
|
+
const formContainer = document.getElementById(formContainerId);
|
|
405
373
|
|
|
406
|
-
|
|
374
|
+
if (formContainer) {
|
|
375
|
+
// Append the theme class to the form container
|
|
376
|
+
formContainer.classList.add(`${theme}-theme`, 'formique');
|
|
407
377
|
|
|
408
|
-
// Calculate shadow color (semi-transparent themeColor)
|
|
409
|
-
const shadowColor = this.hexToRgbA(this.themeColor, 0.3);
|
|
410
|
-
|
|
411
|
-
// Create custom theme CSS variables
|
|
412
|
-
const customTheme = {
|
|
413
|
-
...this.themeColorMap.primary,
|
|
414
|
-
'--formique-focus-color': this.themeColor,
|
|
415
|
-
'--formique-btn-bg': this.themeColor,
|
|
416
|
-
'--formique-btn-shadow': `0 2px 10px ${shadowColor}`
|
|
417
|
-
};
|
|
418
378
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
${
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
)
|
|
427
|
-
.join('\n')}
|
|
428
|
-
}
|
|
429
|
-
`;
|
|
379
|
+
// Create a <style> tag with the extracted theme styles
|
|
380
|
+
const clonedStyle = document.createElement('style');
|
|
381
|
+
clonedStyle.textContent = `
|
|
382
|
+
#${formContainerId} {
|
|
383
|
+
${themeCSS}
|
|
384
|
+
}
|
|
385
|
+
`;
|
|
430
386
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (existingStyle) existingStyle.remove();
|
|
434
|
-
|
|
435
|
-
styleElement.setAttribute('data-custom-theme', formContainerId);
|
|
436
|
-
formContainer.parentNode.insertBefore(styleElement, formContainer);
|
|
437
|
-
}
|
|
387
|
+
// Insert the <style> tag above the form container
|
|
388
|
+
formContainer.parentNode.insertBefore(clonedStyle, formContainer);
|
|
438
389
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return `rgba(${[(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',')},${alpha})`;
|
|
448
|
-
}
|
|
449
|
-
return `rgba(0, 0, 0, ${alpha})`;
|
|
390
|
+
// console.log(`Applied ${theme} theme to form container: ${formContainerId}`);
|
|
391
|
+
} else {
|
|
392
|
+
console.error(`Form container with ID ${formContainerId} not found.`);
|
|
393
|
+
}
|
|
394
|
+
})
|
|
395
|
+
.catch(error => {
|
|
396
|
+
console.error('Error loading the stylesheet:', error);
|
|
397
|
+
});
|
|
450
398
|
}
|
|
451
399
|
|
|
452
400
|
|
|
@@ -500,6 +448,7 @@ Object.keys(paramsToUse).forEach(key => {
|
|
|
500
448
|
|
|
501
449
|
|
|
502
450
|
// Main renderForm method
|
|
451
|
+
/*
|
|
503
452
|
renderForm() {
|
|
504
453
|
// Process each field synchronously
|
|
505
454
|
const formHTML = this.formSchema.map(field => {
|
|
@@ -508,8 +457,61 @@ renderForm() {
|
|
|
508
457
|
}).join('');
|
|
509
458
|
this.formMarkUp += formHTML;
|
|
510
459
|
}
|
|
460
|
+
*/
|
|
511
461
|
|
|
462
|
+
renderForm() {
|
|
463
|
+
// Filter out the 'submit' type before mapping
|
|
464
|
+
const formHTML = this.formSchema
|
|
465
|
+
.filter(field => field[0] !== 'submit') // Exclude submit button from this loop
|
|
466
|
+
.map(field => {
|
|
467
|
+
const [type, name, label, validate, attributes = {}, options] = field;
|
|
468
|
+
return this.renderField(type, name, label, validate, attributes, options);
|
|
469
|
+
}).join('');
|
|
470
|
+
this.formMarkUp += formHTML;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
// New method to render the submit button specifically
|
|
476
|
+
renderSubmitButtonElement() {
|
|
477
|
+
const submitField = this.formSchema.find(field => field[0] === 'submit');
|
|
478
|
+
if (submitField) {
|
|
479
|
+
const [type, name, label, validate, attributes = {}] = submitField;
|
|
480
|
+
const id = attributes.id || name;
|
|
481
|
+
let buttonClass = this.submitButtonClass;
|
|
482
|
+
if ('class' in attributes) {
|
|
483
|
+
buttonClass = attributes.class;
|
|
484
|
+
}
|
|
485
|
+
let additionalAttrs = '';
|
|
486
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
487
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
488
|
+
if (key.startsWith('on')) {
|
|
489
|
+
const eventValue = value.endsWith('()') ? value : `${value}()`;
|
|
490
|
+
additionalAttrs += ` ${key}="${eventValue}"`;
|
|
491
|
+
} else {
|
|
492
|
+
if (value === true) {
|
|
493
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}`;
|
|
494
|
+
} else if (value !== false) {
|
|
495
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"`;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
512
500
|
|
|
501
|
+
// Include the spinner div before the submit button
|
|
502
|
+
return `
|
|
503
|
+
<div id="formiqueSpinner" style="display: flex; align-items: center; gap: 1rem; font-family: sans-serif; display:none;">
|
|
504
|
+
<div class="formique-spinner"></div>
|
|
505
|
+
<p class="message">Hang in tight, we are submitting your details…</p>
|
|
506
|
+
</div>
|
|
507
|
+
<input type="submit" id="${id}" class="${buttonClass}" value="${label}"${additionalAttrs}>
|
|
508
|
+
`.trim();
|
|
509
|
+
}
|
|
510
|
+
return ''; // Return empty string if no submit button is found in schema
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
/*
|
|
513
515
|
renderField(type, name, label, validate, attributes, options) {
|
|
514
516
|
const fieldRenderMap = {
|
|
515
517
|
'text': this.renderTextField,
|
|
@@ -549,13 +551,93 @@ renderField(type, name, label, validate, attributes, options) {
|
|
|
549
551
|
}
|
|
550
552
|
}
|
|
551
553
|
|
|
554
|
+
*/
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
// renderField method - No change needed here for this issue, but ensure it handles 'submit' type correctly if called directly
|
|
558
|
+
renderField(type, name, label, validate, attributes, options) {
|
|
559
|
+
const fieldRenderMap = {
|
|
560
|
+
'text': this.renderTextField,
|
|
561
|
+
'email': this.renderEmailField,
|
|
562
|
+
'number': this.renderNumberField,
|
|
563
|
+
'password': this.renderPasswordField,
|
|
564
|
+
'textarea': this.renderTextAreaField,
|
|
565
|
+
'tel': this.renderTelField,
|
|
566
|
+
'date': this.renderDateField,
|
|
567
|
+
'time': this.renderTimeField,
|
|
568
|
+
'datetime-local': this.renderDateTimeField,
|
|
569
|
+
'month': this.renderMonthField,
|
|
570
|
+
'week': this.renderWeekField,
|
|
571
|
+
'url': this.renderUrlField,
|
|
572
|
+
'search': this.renderSearchField,
|
|
573
|
+
'color': this.renderColorField,
|
|
574
|
+
'checkbox': this.renderCheckboxField,
|
|
575
|
+
'radio': this.renderRadioField,
|
|
576
|
+
'file': this.renderFileField,
|
|
577
|
+
'hidden': this.renderHiddenField,
|
|
578
|
+
'image': this.renderImageField,
|
|
579
|
+
'singleSelect': this.renderSingleSelectField,
|
|
580
|
+
'multipleSelect': this.renderMultipleSelectField,
|
|
581
|
+
'dynamicSingleSelect': this.renderDynamicSingleSelectField,
|
|
582
|
+
'range': this.renderRangeField,
|
|
583
|
+
'submit': this.renderSubmitButton, // Keep this for completeness, but renderSubmitButtonElement will now handle it
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const renderMethod = fieldRenderMap[type];
|
|
587
|
+
|
|
588
|
+
if (renderMethod) {
|
|
589
|
+
// If the type is 'submit', ensure we use the specific renderSubmitButtonElement
|
|
590
|
+
// Although, with the filter in renderForm(), this branch for 'submit' type
|
|
591
|
+
// might not be hit in the primary rendering flow, it's good practice.
|
|
592
|
+
return renderMethod.call(this, type, name, label, validate, attributes, options);
|
|
593
|
+
|
|
594
|
+
if (type === 'submit') {
|
|
595
|
+
return this.renderSubmitButton(type, name, label, validate, attributes, options);
|
|
596
|
+
}
|
|
597
|
+
//return renderMethod.call(this, type, name, label, validate, attributes, options);
|
|
598
|
+
} else {
|
|
599
|
+
console.warn(`Unsupported field type '${type}' encountered.`);
|
|
600
|
+
return '';
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
renderSubmitButton(type, name, label, validate, attributes) {
|
|
607
|
+
// This method can simply call the dedicated submit button renderer if it's kept separate.
|
|
608
|
+
// Or, if renderField is only used for non-submit fields, this method might not be strictly necessary
|
|
609
|
+
// to be called from renderField's map, but it needs to exist if mapped.
|
|
610
|
+
// For simplicity, I'll make it consistent with the new separation.
|
|
611
|
+
const id = attributes.id || name;
|
|
612
|
+
let buttonClass = this.submitButtonClass;
|
|
613
|
+
if ('class' in attributes) {
|
|
614
|
+
buttonClass = attributes.class;
|
|
615
|
+
}
|
|
616
|
+
let additionalAttrs = '';
|
|
617
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
618
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
619
|
+
if (key.startsWith('on')) {
|
|
620
|
+
const eventValue = value.endsWith('()') ? value : `${value}()`;
|
|
621
|
+
additionalAttrs += ` ${key}="${eventValue}"`;
|
|
622
|
+
} else {
|
|
623
|
+
if (value === true) {
|
|
624
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}`;
|
|
625
|
+
} else if (value !== false) {
|
|
626
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"`;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// No spinner div here, as that's added once by renderSubmitButtonElement
|
|
632
|
+
return `<input type="${type}" id="${id}" class="${buttonClass}" value="${label}"${additionalAttrs}>`;
|
|
633
|
+
}
|
|
552
634
|
|
|
553
635
|
|
|
554
636
|
// Show success/error messages (externalizable)
|
|
555
637
|
showSuccessMessage(message) {
|
|
556
638
|
const container = document.getElementById(this.formContainerId);
|
|
557
639
|
container.innerHTML = `
|
|
558
|
-
<div class="formique-success ${
|
|
640
|
+
<div class="formique-success"> ${message}</div>
|
|
559
641
|
${this.formSettings.redirectURL
|
|
560
642
|
? `<meta http-equiv="refresh" content="2;url=${this.formSettings.redirectURL}">`
|
|
561
643
|
: ""}
|
|
@@ -565,7 +647,7 @@ showSuccessMessage(message) {
|
|
|
565
647
|
showErrorMessage(message) {
|
|
566
648
|
const container = document.getElementById(this.formContainerId);
|
|
567
649
|
const errorDiv = document.createElement("div");
|
|
568
|
-
errorDiv.className =
|
|
650
|
+
errorDiv.className = "formique-error";
|
|
569
651
|
errorDiv.textContent = `${message}`;
|
|
570
652
|
container.prepend(errorDiv);
|
|
571
653
|
}
|
|
@@ -579,92 +661,120 @@ hasFileInputs(form) {
|
|
|
579
661
|
|
|
580
662
|
|
|
581
663
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
timestamp: new Date().toISOString()
|
|
597
|
-
}
|
|
598
|
-
};
|
|
599
|
-
|
|
600
|
-
let senderEmail = '';
|
|
601
|
-
let formSubject = '';
|
|
602
|
-
|
|
603
|
-
new FormData(form).forEach((value, key) => {
|
|
604
|
-
payload.formData[key] = value;
|
|
605
|
-
const lowerKey = key.toLowerCase();
|
|
606
|
-
if ((lowerKey === 'email' || lowerKey.includes('email'))) {
|
|
607
|
-
senderEmail = value;
|
|
608
|
-
}
|
|
609
|
-
if ((lowerKey === 'subject' || lowerKey.includes('subject'))) {
|
|
610
|
-
formSubject = value;
|
|
611
|
-
}
|
|
612
|
-
});
|
|
664
|
+
async handleEmailSubmission(formId) {
|
|
665
|
+
console.log(`Starting email submission for form ID: ${formId}`);
|
|
666
|
+
|
|
667
|
+
const form = document.getElementById(formId);
|
|
668
|
+
if (!form) {
|
|
669
|
+
console.error(`Form with ID ${formId} not found`);
|
|
670
|
+
throw new Error(`Form with ID ${formId} not found`);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Validate required settings - now checks if sendTo is array with at least one item
|
|
674
|
+
if (!Array.isArray(this.formSettings?.sendTo) || this.formSettings.sendTo.length === 0) {
|
|
675
|
+
console.error('formSettings.sendTo must be an array with at least one recipient email');
|
|
676
|
+
throw new Error('formSettings.sendTo must be an array with at least one recipient email');
|
|
677
|
+
}
|
|
613
678
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
679
|
+
// Serialize form data
|
|
680
|
+
const payload = {
|
|
681
|
+
formData: {},
|
|
682
|
+
metadata: {
|
|
683
|
+
recipients: this.formSettings.sendTo, // Now sending array
|
|
684
|
+
timestamp: new Date().toISOString()
|
|
618
685
|
}
|
|
619
|
-
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
let senderName = '';
|
|
689
|
+
let senderEmail = '';
|
|
690
|
+
let formSubject = '';
|
|
620
691
|
|
|
692
|
+
console.log('Initial payload structure:', JSON.parse(JSON.stringify(payload)));
|
|
693
|
+
|
|
694
|
+
// Process form fields (unchanged)
|
|
695
|
+
new FormData(form).forEach((value, key) => {
|
|
696
|
+
console.log(`Processing form field - Key: ${key}, Value: ${value}`);
|
|
697
|
+
payload.formData[key] = value;
|
|
698
|
+
|
|
699
|
+
const lowerKey = key.toLowerCase();
|
|
700
|
+
if ((lowerKey === 'email' || lowerKey.includes('email'))) {
|
|
701
|
+
senderEmail = value;
|
|
702
|
+
}
|
|
703
|
+
if ((lowerKey === 'name' || lowerKey.includes('name'))) {
|
|
704
|
+
senderName = value;
|
|
705
|
+
}
|
|
706
|
+
if ((lowerKey === 'subject' || lowerKey.includes('subject'))) {
|
|
707
|
+
formSubject = value;
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
// Determine the email subject with fallback logic
|
|
712
|
+
payload.metadata.subject = formSubject ||
|
|
713
|
+
this.formSettings.subject ||
|
|
714
|
+
'Message From Contact Form';
|
|
715
|
+
|
|
716
|
+
console.log('Determined email subject:', payload.metadata.subject);
|
|
717
|
+
|
|
718
|
+
// Add sender information to metadata
|
|
719
|
+
if (senderEmail) {
|
|
720
|
+
payload.metadata.sender = senderEmail;
|
|
721
|
+
payload.metadata.replyTo = senderName
|
|
722
|
+
? `${senderName} <${senderEmail}>`
|
|
723
|
+
: senderEmail;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
console.log('Payload after form processing:', JSON.parse(JSON.stringify(payload)));
|
|
727
|
+
|
|
728
|
+
try {
|
|
621
729
|
const endpoint = this.formiqueEndpoint || this.formAction;
|
|
622
730
|
const method = this.method || 'POST';
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
731
|
+
|
|
732
|
+
console.log(`Preparing to send request to: ${endpoint}`);
|
|
733
|
+
console.log(`Request method: ${method}`);
|
|
734
|
+
console.log('Final payload being sent:', payload);
|
|
626
735
|
|
|
627
736
|
const response = await fetch(endpoint, {
|
|
628
737
|
method: method,
|
|
629
|
-
headers: {
|
|
738
|
+
headers: {
|
|
630
739
|
'Content-Type': 'application/json',
|
|
631
|
-
'X-Formique-Version': '1.0'
|
|
740
|
+
'X-Formique-Version': '1.0'
|
|
632
741
|
},
|
|
633
742
|
body: JSON.stringify(payload)
|
|
634
743
|
});
|
|
635
744
|
|
|
636
|
-
|
|
637
|
-
// This will not fail on an empty response.
|
|
638
|
-
const responseBodyText = await response.text();
|
|
639
|
-
let data = {};
|
|
640
|
-
|
|
641
|
-
// Only try to parse if the body isn't empty.
|
|
642
|
-
if (responseBodyText.length > 0) {
|
|
643
|
-
try {
|
|
644
|
-
data = JSON.parse(responseBodyText);
|
|
645
|
-
} catch (err) {
|
|
646
|
-
// This handles cases where the server returns non-JSON data.
|
|
647
|
-
console.warn("Response was not valid JSON or unexpected format:", err);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
745
|
+
console.log(`Received response with status: ${response.status}`);
|
|
650
746
|
|
|
651
747
|
if (!response.ok) {
|
|
652
|
-
const
|
|
653
|
-
|
|
748
|
+
const errorData = await response.json().catch(() => ({}));
|
|
749
|
+
console.error('API Error Response:', errorData);
|
|
750
|
+
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
751
|
+
document.getElementById("formiqueSpinner").style.display = "none";
|
|
752
|
+
|
|
654
753
|
}
|
|
655
754
|
|
|
656
|
-
const
|
|
755
|
+
const data = await response.json();
|
|
756
|
+
console.log('API Success Response:', data);
|
|
757
|
+
|
|
758
|
+
const successMessage = this.formSettings.successMessage ||
|
|
759
|
+
data.message ||
|
|
760
|
+
'Your message has been sent successfully!';
|
|
761
|
+
console.log(`Showing success message: ${successMessage}`);
|
|
762
|
+
|
|
657
763
|
this.showSuccessMessage(successMessage);
|
|
658
764
|
|
|
659
765
|
} catch (error) {
|
|
660
|
-
console.error(
|
|
661
|
-
const errorMessage = this.formSettings.errorMessage ||
|
|
766
|
+
console.error('Email submission failed:', error);
|
|
767
|
+
const errorMessage = this.formSettings.errorMessage ||
|
|
768
|
+
error.message ||
|
|
769
|
+
'Failed to send message. Please try again later.';
|
|
770
|
+
console.log(`Showing error message: ${errorMessage}`);
|
|
662
771
|
this.showErrorMessage(errorMessage);
|
|
663
|
-
} finally {
|
|
664
|
-
// Ensure spinner is hidden on success or failure
|
|
665
772
|
document.getElementById("formiqueSpinner").style.display = "none";
|
|
773
|
+
|
|
666
774
|
}
|
|
667
|
-
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
|
|
668
778
|
|
|
669
779
|
// Email validation helper
|
|
670
780
|
validateEmail(email) {
|
|
@@ -742,7 +852,7 @@ if (formContainer) {
|
|
|
742
852
|
errorMessageDiv.innerHTML = err;
|
|
743
853
|
|
|
744
854
|
// Append the new error message div to the form container
|
|
745
|
-
|
|
855
|
+
formContainer.appendChild(errorMessageDiv);
|
|
746
856
|
}
|
|
747
857
|
});
|
|
748
858
|
|
|
@@ -1422,17 +1532,18 @@ const telInputValidationAttributes = [
|
|
|
1422
1532
|
}
|
|
1423
1533
|
|
|
1424
1534
|
// Handle the binding syntax
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1535
|
+
// Handle the binding syntax
|
|
1536
|
+
let bindingDirective = '';
|
|
1537
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
1538
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
1539
|
+
}
|
|
1540
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
1541
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
1542
|
+
}
|
|
1543
|
+
if (attributes?.binding && !name) {
|
|
1544
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1436
1547
|
|
|
1437
1548
|
// Get the id from attributes or fall back to name
|
|
1438
1549
|
let id = attributes.id || name;
|
|
@@ -1543,13 +1654,13 @@ renderDateField(type, name, label, validate, attributes) {
|
|
|
1543
1654
|
|
|
1544
1655
|
// Handle the binding syntax
|
|
1545
1656
|
let bindingDirective = '';
|
|
1546
|
-
if (attributes
|
|
1657
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
1547
1658
|
bindingDirective = `bind:value="${name}"\n`;
|
|
1548
1659
|
}
|
|
1549
|
-
if (attributes
|
|
1660
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
1550
1661
|
bindingDirective = `bind:value="${name}"\n`;
|
|
1551
1662
|
}
|
|
1552
|
-
if (attributes
|
|
1663
|
+
if (attributes?.binding && !name) {
|
|
1553
1664
|
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
1554
1665
|
return;
|
|
1555
1666
|
}
|
|
@@ -1663,15 +1774,16 @@ renderTimeField(type, name, label, validate, attributes) {
|
|
|
1663
1774
|
});
|
|
1664
1775
|
}
|
|
1665
1776
|
|
|
1777
|
+
// Handle the binding syntax
|
|
1666
1778
|
// Handle the binding syntax
|
|
1667
1779
|
let bindingDirective = '';
|
|
1668
|
-
if (attributes
|
|
1780
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
1669
1781
|
bindingDirective = `bind:value="${name}"\n`;
|
|
1670
1782
|
}
|
|
1671
|
-
if (attributes
|
|
1783
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
1672
1784
|
bindingDirective = `bind:value="${name}"\n`;
|
|
1673
1785
|
}
|
|
1674
|
-
if (attributes
|
|
1786
|
+
if (attributes?.binding && !name) {
|
|
1675
1787
|
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
1676
1788
|
return;
|
|
1677
1789
|
}
|
|
@@ -1915,15 +2027,18 @@ renderMonthField(type, name, label, validate, attributes) {
|
|
|
1915
2027
|
}
|
|
1916
2028
|
|
|
1917
2029
|
// Handle the binding syntax
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
2030
|
+
let bindingDirective = '';
|
|
2031
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2032
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2033
|
+
}
|
|
2034
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2035
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2036
|
+
}
|
|
2037
|
+
if (attributes?.binding && !name) {
|
|
2038
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
1927
2042
|
|
|
1928
2043
|
// Get the id from attributes or fall back to name
|
|
1929
2044
|
let id = attributes.id || name;
|
|
@@ -2037,15 +2152,17 @@ renderWeekField(type, name, label, validate, attributes) {
|
|
|
2037
2152
|
}
|
|
2038
2153
|
|
|
2039
2154
|
// Handle the binding syntax
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2155
|
+
let bindingDirective = '';
|
|
2156
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2157
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2158
|
+
}
|
|
2159
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2160
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2161
|
+
}
|
|
2162
|
+
if (attributes?.binding && !name) {
|
|
2163
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2049
2166
|
|
|
2050
2167
|
// Get the id from attributes or fall back to name
|
|
2051
2168
|
let id = attributes.id || name;
|
|
@@ -2155,15 +2272,18 @@ renderUrlField(type, name, label, validate, attributes) {
|
|
|
2155
2272
|
}
|
|
2156
2273
|
|
|
2157
2274
|
// Handle the binding syntax
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2275
|
+
// Handle the binding syntax
|
|
2276
|
+
let bindingDirective = '';
|
|
2277
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2278
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2279
|
+
}
|
|
2280
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2281
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2282
|
+
}
|
|
2283
|
+
if (attributes?.binding && !name) {
|
|
2284
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2167
2287
|
|
|
2168
2288
|
// Get the id from attributes or fall back to name
|
|
2169
2289
|
let id = attributes.id || name;
|
|
@@ -2272,15 +2392,17 @@ renderSearchField(type, name, label, validate, attributes) {
|
|
|
2272
2392
|
}
|
|
2273
2393
|
|
|
2274
2394
|
// Handle the binding syntax
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
}
|
|
2395
|
+
let bindingDirective = '';
|
|
2396
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2397
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2398
|
+
}
|
|
2399
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2400
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2401
|
+
}
|
|
2402
|
+
if (attributes?.binding && !name) {
|
|
2403
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2284
2406
|
|
|
2285
2407
|
// Get the id from attributes or fall back to name
|
|
2286
2408
|
let id = attributes.id || name;
|
|
@@ -2381,14 +2503,16 @@ renderColorField(type, name, label, validate, attributes) {
|
|
|
2381
2503
|
});
|
|
2382
2504
|
}
|
|
2383
2505
|
|
|
2506
|
+
// Handle the binding syntax
|
|
2384
2507
|
// Handle the binding syntax
|
|
2385
2508
|
let bindingDirective = '';
|
|
2386
|
-
if (attributes
|
|
2509
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2387
2510
|
bindingDirective = `bind:value="${name}"\n`;
|
|
2388
|
-
}
|
|
2511
|
+
}
|
|
2512
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2389
2513
|
bindingDirective = `bind:value="${name}"\n`;
|
|
2390
2514
|
}
|
|
2391
|
-
if (attributes
|
|
2515
|
+
if (attributes?.binding && !name) {
|
|
2392
2516
|
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2393
2517
|
return;
|
|
2394
2518
|
}
|
|
@@ -2419,8 +2543,13 @@ renderColorField(type, name, label, validate, attributes) {
|
|
|
2419
2543
|
if ('class' in attributes) {
|
|
2420
2544
|
inputClass = attributes.class;
|
|
2421
2545
|
} else {
|
|
2422
|
-
|
|
2546
|
+
// inputClass = this.inputClass;
|
|
2423
2547
|
}
|
|
2548
|
+
|
|
2549
|
+
if (type === 'color') {
|
|
2550
|
+
inputClass += ' form-color-input'; // Add the new specific class for color inputs
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2424
2553
|
// Construct the final HTML string
|
|
2425
2554
|
let formHTML = `
|
|
2426
2555
|
<div class="${this.divClass}" id="${id + '-block'}">
|
|
@@ -2493,16 +2622,17 @@ renderFileField(type, name, label, validate, attributes) {
|
|
|
2493
2622
|
}
|
|
2494
2623
|
|
|
2495
2624
|
// Handle the binding syntax
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2625
|
+
let bindingDirective = '';
|
|
2626
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2627
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2628
|
+
}
|
|
2629
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2630
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2631
|
+
}
|
|
2632
|
+
if (attributes?.binding && !name) {
|
|
2633
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2506
2636
|
|
|
2507
2637
|
// Get the id from attributes or fall back to name
|
|
2508
2638
|
let id = attributes.id || name;
|
|
@@ -2606,12 +2736,13 @@ renderHiddenField(type, name, label, validate, attributes) {
|
|
|
2606
2736
|
|
|
2607
2737
|
// Handle the binding syntax
|
|
2608
2738
|
let bindingDirective = '';
|
|
2609
|
-
if (attributes
|
|
2739
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2610
2740
|
bindingDirective = `bind:value="${name}"\n`;
|
|
2611
|
-
}
|
|
2741
|
+
}
|
|
2742
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2612
2743
|
bindingDirective = `bind:value="${name}"\n`;
|
|
2613
2744
|
}
|
|
2614
|
-
if (attributes
|
|
2745
|
+
if (attributes?.binding && !name) {
|
|
2615
2746
|
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2616
2747
|
return;
|
|
2617
2748
|
}
|
|
@@ -2648,9 +2779,9 @@ renderHiddenField(type, name, label, validate, attributes) {
|
|
|
2648
2779
|
// Construct the final HTML string
|
|
2649
2780
|
let formHTML = `
|
|
2650
2781
|
<div class="${this.divClass}" id="${id + '-block'}">
|
|
2651
|
-
|
|
2782
|
+
<!--<label for="${id}">${label}
|
|
2652
2783
|
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
2653
|
-
</label>
|
|
2784
|
+
</label> -->
|
|
2654
2785
|
<input
|
|
2655
2786
|
type="${type}"
|
|
2656
2787
|
name="${name}"
|
|
@@ -2683,7 +2814,7 @@ renderHiddenField(type, name, label, validate, attributes) {
|
|
|
2683
2814
|
}
|
|
2684
2815
|
|
|
2685
2816
|
|
|
2686
|
-
|
|
2817
|
+
/*
|
|
2687
2818
|
renderImageField(type, name, label, validate, attributes) {
|
|
2688
2819
|
// Define valid validation attributes for image upload
|
|
2689
2820
|
const imageUploadValidationAttributes = [
|
|
@@ -2714,12 +2845,18 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2714
2845
|
}
|
|
2715
2846
|
|
|
2716
2847
|
// Handle the binding syntax
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2848
|
+
// Handle the binding syntax
|
|
2849
|
+
let bindingDirective = '';
|
|
2850
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2851
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2852
|
+
}
|
|
2853
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2854
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2855
|
+
}
|
|
2856
|
+
if (attributes?.binding && !name) {
|
|
2857
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2723
2860
|
|
|
2724
2861
|
// Get the id from attributes or fall back to name
|
|
2725
2862
|
let id = attributes.id || name;
|
|
@@ -2786,26 +2923,33 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2786
2923
|
this.formMarkUp +=formattedHtml;
|
|
2787
2924
|
}
|
|
2788
2925
|
|
|
2789
|
-
|
|
2790
|
-
|
|
2926
|
+
*/
|
|
2791
2927
|
|
|
2792
2928
|
renderImageField(type, name, label, validate, attributes) {
|
|
2793
|
-
// Define valid validation attributes for image
|
|
2794
|
-
const
|
|
2929
|
+
// Define valid validation attributes for image input
|
|
2930
|
+
const imageValidationAttributes = [
|
|
2795
2931
|
'accept',
|
|
2796
2932
|
'required',
|
|
2797
2933
|
'minwidth',
|
|
2798
2934
|
'maxwidth',
|
|
2799
2935
|
'minheight',
|
|
2800
2936
|
'maxheight',
|
|
2937
|
+
'src',
|
|
2938
|
+
'alt',
|
|
2939
|
+
'width',
|
|
2940
|
+
'height'
|
|
2801
2941
|
];
|
|
2802
2942
|
|
|
2803
2943
|
// Construct validation attributes
|
|
2804
2944
|
let validationAttrs = '';
|
|
2805
2945
|
if (validate) {
|
|
2806
2946
|
Object.entries(validate).forEach(([key, value]) => {
|
|
2807
|
-
if (
|
|
2808
|
-
|
|
2947
|
+
if (imageValidationAttributes.includes(key)) {
|
|
2948
|
+
if (typeof value === 'boolean' && value) {
|
|
2949
|
+
validationAttrs += ` ${key}\n`;
|
|
2950
|
+
} else {
|
|
2951
|
+
validationAttrs += ` ${key}="${value}"\n`;
|
|
2952
|
+
}
|
|
2809
2953
|
} else {
|
|
2810
2954
|
console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
|
|
2811
2955
|
}
|
|
@@ -2814,9 +2958,17 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2814
2958
|
|
|
2815
2959
|
// Handle the binding syntax
|
|
2816
2960
|
let bindingDirective = '';
|
|
2817
|
-
|
|
2961
|
+
const bindingValue = attributes?.binding;
|
|
2962
|
+
if (bindingValue === 'bind:value' && name) {
|
|
2963
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2964
|
+
}
|
|
2965
|
+
if (typeof bindingValue === 'string' && bindingValue.startsWith('::') && name) {
|
|
2818
2966
|
bindingDirective = `bind:value="${name}"\n`;
|
|
2819
2967
|
}
|
|
2968
|
+
if (bindingValue && !name) {
|
|
2969
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2820
2972
|
|
|
2821
2973
|
// Get the id from attributes or fall back to name
|
|
2822
2974
|
let id = attributes.id || name;
|
|
@@ -2824,7 +2976,8 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2824
2976
|
// Construct additional attributes dynamically
|
|
2825
2977
|
let additionalAttrs = '';
|
|
2826
2978
|
for (const [key, value] of Object.entries(attributes)) {
|
|
2827
|
-
|
|
2979
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
2980
|
+
if (key.startsWith('on')) {
|
|
2828
2981
|
// Handle event attributes
|
|
2829
2982
|
const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
|
|
2830
2983
|
additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
|
|
@@ -2840,44 +2993,56 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2840
2993
|
}
|
|
2841
2994
|
}
|
|
2842
2995
|
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2996
|
+
// Special handling for image submit button
|
|
2997
|
+
let inputElement;
|
|
2998
|
+
if (type === 'image' && name === 'submit') {
|
|
2999
|
+
inputElement = `
|
|
3000
|
+
<input
|
|
3001
|
+
type="image"
|
|
3002
|
+
name="${name}"
|
|
3003
|
+
${bindingDirective}
|
|
3004
|
+
id="${id}"
|
|
3005
|
+
class="${attributes.class || this.inputClass}"
|
|
3006
|
+
src="${attributes.src || 'img_submit.gif'}"
|
|
3007
|
+
alt="${attributes.alt || 'Submit'}"
|
|
3008
|
+
width="${attributes.width || '48'}"
|
|
3009
|
+
height="${attributes.height || '48'}"
|
|
3010
|
+
${additionalAttrs}
|
|
3011
|
+
${validationAttrs}
|
|
3012
|
+
/>`;
|
|
2846
3013
|
} else {
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
// Construct the final HTML string
|
|
2850
|
-
let formHTML = `
|
|
2851
|
-
<div class="${this.divClass}" id="${id + '-block'}">
|
|
2852
|
-
<label for="${id}">${label}
|
|
2853
|
-
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
2854
|
-
</label>
|
|
3014
|
+
// Regular image input field
|
|
3015
|
+
inputElement = `
|
|
2855
3016
|
<input
|
|
2856
3017
|
type="${type}"
|
|
2857
3018
|
name="${name}"
|
|
2858
3019
|
${bindingDirective}
|
|
2859
3020
|
id="${id}"
|
|
2860
|
-
class="${inputClass}"
|
|
3021
|
+
class="${attributes.class || this.inputClass}"
|
|
2861
3022
|
${additionalAttrs}
|
|
2862
3023
|
${validationAttrs}
|
|
2863
|
-
|
|
3024
|
+
/>`;
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
// Construct the final HTML string
|
|
3028
|
+
let formHTML = `
|
|
3029
|
+
<div class="${this.divClass}" id="${id + '-block'}">
|
|
3030
|
+
${type === 'image' && name === 'submit' ? '' : `<label for="${id}">${label}
|
|
3031
|
+
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3032
|
+
</label>`}
|
|
3033
|
+
${inputElement}
|
|
2864
3034
|
</div>
|
|
2865
3035
|
`.replace(/^\s*\n/gm, '').trim();
|
|
2866
3036
|
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
// Apply vertical layout to the <input> element only
|
|
2870
|
-
formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
|
|
2871
|
-
// Reformat attributes into a vertical layout
|
|
3037
|
+
// Format the HTML
|
|
3038
|
+
let formattedHtml = formHTML.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
|
|
2872
3039
|
const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
|
|
2873
3040
|
return `<input\n${attributes}\n/>`;
|
|
2874
3041
|
});
|
|
2875
3042
|
|
|
2876
|
-
// Ensure the <div> block starts on a new line and remove extra blank lines
|
|
2877
3043
|
formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
|
|
2878
|
-
// Ensure <div> starts on a new line
|
|
2879
3044
|
return `\n${match}\n`;
|
|
2880
|
-
}).replace(/\n\s*\n/g, '\n');
|
|
3045
|
+
}).replace(/\n\s*\n/g, '\n');
|
|
2881
3046
|
|
|
2882
3047
|
return formattedHtml;
|
|
2883
3048
|
}
|
|
@@ -2885,8 +3050,6 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2885
3050
|
|
|
2886
3051
|
|
|
2887
3052
|
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
3053
|
// Textarea field rendering
|
|
2891
3054
|
renderTextAreaField(type, name, label, validate, attributes) {
|
|
2892
3055
|
const textAreaValidationAttributes = [
|
|
@@ -3239,6 +3402,7 @@ this.renderSingleSelectField(type, name, label, validate, attributes, mainCatego
|
|
|
3239
3402
|
|
|
3240
3403
|
renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
|
|
3241
3404
|
|
|
3405
|
+
console.log("Within");
|
|
3242
3406
|
// Define valid validation attributes for select fields
|
|
3243
3407
|
const selectValidationAttributes = ['required'];
|
|
3244
3408
|
|
|
@@ -3805,10 +3969,11 @@ renderSubmitButton(type, name, label, validate, attributes) {
|
|
|
3805
3969
|
}
|
|
3806
3970
|
|
|
3807
3971
|
|
|
3808
|
-
const spinner = `<div
|
|
3972
|
+
const spinner = `<div id="formiqueSpinner" style="display: flex; align-items: center; gap: 1rem; font-family: sans-serif; display:none;">
|
|
3809
3973
|
<div class="formique-spinner"></div>
|
|
3810
3974
|
<p class="message">Hang in tight, we are submitting your details…</p>
|
|
3811
|
-
</div
|
|
3975
|
+
</div>
|
|
3976
|
+
`;
|
|
3812
3977
|
// Construct the final HTML string
|
|
3813
3978
|
|
|
3814
3979
|
const formHTML = `
|
|
@@ -3831,17 +3996,15 @@ const spinner = `<div class="" id="formiqueSpinner">
|
|
|
3831
3996
|
|
|
3832
3997
|
|
|
3833
3998
|
|
|
3834
|
-
renderFormHTML
|
|
3835
|
-
|
|
3836
|
-
this.
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
formContainer.innerHTML = this.formMarkUp;
|
|
3844
|
-
}
|
|
3999
|
+
renderFormHTML() {
|
|
4000
|
+
this.formMarkUp += '</form>';
|
|
4001
|
+
const formContainer = document.getElementById(this.formContainerId);
|
|
4002
|
+
if (!formContainer) {
|
|
4003
|
+
console.error(`Error: form container with ID ${this.formContainerId} not found. Please ensure an element with id ${this.formContainerId} exists in the HTML.`);
|
|
4004
|
+
} else {
|
|
4005
|
+
formContainer.innerHTML = this.formMarkUp;
|
|
4006
|
+
}
|
|
4007
|
+
|
|
3845
4008
|
|
|
3846
4009
|
//return this.formMarkUp;
|
|
3847
4010
|
|
|
@@ -3859,12 +4022,3 @@ if (!formContainer) {
|
|
|
3859
4022
|
export default Formique;
|
|
3860
4023
|
|
|
3861
4024
|
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|