@formique/semantq 1.0.4 → 1.0.5
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 +553 -393
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ Formique Semantq is a native Semantq JS framework Schema Defintion Language (SDL
|
|
|
42
42
|
- **Mobile Responsive**: Forms are mobile responsive out of the box.
|
|
43
43
|
- **Nested Dynamic Conditional Logic**: Implement complex conditional logic to show or hide form fields based on user input.
|
|
44
44
|
- **Dynamic Dropdowns**: Create dropdowns whose options change dynamically based on other field selections.
|
|
45
|
-
- **JavaScript-Driven Themes**: Apply themes dynamically using JavaScript for a customizable user interface.
|
|
45
|
+
- **JavaScript-Driven Themes**: Apply themes or theme colors dynamically using JavaScript for a customizable user interface.
|
|
46
46
|
- **WAI-ARIA and WCAG-Compliant HTML**: Ensure all form elements are accessible and meet WCAG standards.
|
|
47
47
|
- **Progressive Enhancement**: Forms function with or without JavaScript, ensuring accessibility and functionality across all environments.
|
|
48
48
|
|
|
@@ -60,14 +60,6 @@ npx sv create
|
|
|
60
60
|
npx sv create my-app
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
#### Select the following options:
|
|
64
|
-
|
|
65
|
-
- **SemantqKit minimal** (optional but preferred)
|
|
66
|
-
- **Type checking with TypeScript** (optional but preferred)
|
|
67
|
-
- **ESLint** (optional but preferred)
|
|
68
|
-
- **npm** (required)
|
|
69
|
-
|
|
70
|
-
|
|
71
63
|
> **Note:** Always refer to the latest official Semantq guide on how to create a Semantq app, as this may change. [Semantq Documentation: Creating a Project](https://Semantq.dev/docs/kit/creating-a-project)
|
|
72
64
|
|
|
73
65
|
|
|
@@ -77,7 +69,7 @@ npx sv create my-app
|
|
|
77
69
|
npm run dev
|
|
78
70
|
|
|
79
71
|
# or start the server and open the app in a new browser tab
|
|
80
|
-
npm run dev
|
|
72
|
+
npm run dev
|
|
81
73
|
```
|
|
82
74
|
|
|
83
75
|
|
|
@@ -101,12 +93,25 @@ For demo purposes, let's create a new route (page) in `src/routes/registration`.
|
|
|
101
93
|
|
|
102
94
|
## Step 2: Add the CSS (Optional)
|
|
103
95
|
|
|
104
|
-
|
|
96
|
+
The NPM option:
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
npm i formique-css
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
import 'formique-css';
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The CDN Option:
|
|
107
|
+
|
|
108
|
+
Include this inside the @head .. @end block of page layout file: `@layout.smq`
|
|
105
109
|
|
|
106
110
|
```html
|
|
107
|
-
<link rel="stylesheet" href="https://
|
|
111
|
+
<link rel="stylesheet" href="https://unpkg.com/formique-css@1.0.11/formique-css.css" />
|
|
108
112
|
```
|
|
109
113
|
|
|
114
|
+
|
|
110
115
|
**Note:** The provided Formique CSS is optional. Formique will function fully without it, allowing you full flexibility to apply your own styles. However, for convenience, a set of default class names is available to help you quickly style form containers, form elements, and input types. See the sections below for a complete list of available class names.
|
|
111
116
|
|
|
112
117
|
## Step 3: Install `@formique/semantq`
|
package/formique-semantq.js
CHANGED
|
@@ -50,126 +50,122 @@ 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
|
|
160
|
-
|
|
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
|
+
|
|
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
|
+
}
|
|
161
131
|
|
|
162
132
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
formContainer.setAttribute("style", this.formContainerStyle);
|
|
166
|
-
}
|
|
133
|
+
// 2. Inject the complete form HTML into the DOM
|
|
134
|
+
this.renderFormHTML(); // This puts the form element into the document!
|
|
167
135
|
|
|
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
|
+
}
|
|
168
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
|
+
}
|
|
169
155
|
|
|
170
|
-
//
|
|
171
|
-
|
|
156
|
+
// Initialize dependency graph and observers after the form is rendered
|
|
157
|
+
this.initDependencyGraph();
|
|
158
|
+
this.registerObservers();
|
|
172
159
|
|
|
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
|
+
}); // DOM LISTENER WRAPPER
|
|
168
|
+
|
|
173
169
|
// CONSTRUCTOR WRAPPER FOR FORMIQUE CLASS
|
|
174
170
|
}
|
|
175
171
|
|
|
@@ -346,107 +342,56 @@ registerObservers() {
|
|
|
346
342
|
|
|
347
343
|
|
|
348
344
|
applyTheme(theme, formContainerId) {
|
|
349
|
-
|
|
350
|
-
const spinnerContainer = document.getElementById('formiqueSpinner');
|
|
345
|
+
//const stylesheet = document.querySelector('link[formique-style]');
|
|
351
346
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
347
|
+
const stylesheet = document.querySelector('link[href*="formique-css"]');
|
|
348
|
+
|
|
349
|
+
if (!stylesheet) {
|
|
350
|
+
console.error("Stylesheet with 'formique-css' in the name not found!");
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
356
353
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
354
|
+
fetch(stylesheet.href)
|
|
355
|
+
.then(response => response.text())
|
|
356
|
+
.then(cssText => {
|
|
357
|
+
// Extract theme-specific CSS rules
|
|
358
|
+
const themeRules = cssText.match(new RegExp(`\\.${theme}-theme\\s*{([^}]*)}`, 'i'));
|
|
361
359
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
this.applyCustomTheme(formContainerId);
|
|
360
|
+
if (!themeRules) {
|
|
361
|
+
console.error(`Theme rules for ${theme} not found in the stylesheet.`);
|
|
365
362
|
return;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
// Fall back to predefined theme if no themeColor
|
|
369
|
-
const stylesheet = document.querySelector('link[href*="formique-css"]');
|
|
370
|
-
if (!stylesheet) {
|
|
371
|
-
console.error("Stylesheet with 'formique-css' in the name not found!");
|
|
372
|
-
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
|
-
}
|
|
363
|
+
}
|
|
396
364
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const spinnerContainer = document.getElementById('formiqueSpinner');
|
|
365
|
+
// Extract CSS rules for the theme
|
|
366
|
+
const themeCSS = themeRules[1].trim();
|
|
400
367
|
|
|
401
|
-
|
|
368
|
+
// Find the form container element
|
|
369
|
+
const formContainer = document.getElementById(formContainerId);
|
|
402
370
|
|
|
403
|
-
|
|
404
|
-
|
|
371
|
+
if (formContainer) {
|
|
372
|
+
// Append the theme class to the form container
|
|
373
|
+
formContainer.classList.add(`${theme}-theme`, 'formique');
|
|
405
374
|
|
|
406
|
-
this.activeTheme = 'custom-theme';
|
|
407
375
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
'--formique-btn-bg': this.themeColor,
|
|
416
|
-
'--formique-btn-shadow': `0 2px 10px ${shadowColor}`
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
// Apply the styles
|
|
420
|
-
const styleElement = document.createElement('style');
|
|
421
|
-
styleElement.textContent = `
|
|
422
|
-
#${formContainerId} {
|
|
423
|
-
${Object.entries(customTheme)
|
|
424
|
-
.map(([varName, value]) =>
|
|
425
|
-
value ? `${varName}: ${value};` : ''
|
|
426
|
-
)
|
|
427
|
-
.join('\n')}
|
|
428
|
-
}
|
|
429
|
-
`;
|
|
376
|
+
// Create a <style> tag with the extracted theme styles
|
|
377
|
+
const clonedStyle = document.createElement('style');
|
|
378
|
+
clonedStyle.textContent = `
|
|
379
|
+
#${formContainerId} {
|
|
380
|
+
${themeCSS}
|
|
381
|
+
}
|
|
382
|
+
`;
|
|
430
383
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (existingStyle) existingStyle.remove();
|
|
434
|
-
|
|
435
|
-
styleElement.setAttribute('data-custom-theme', formContainerId);
|
|
436
|
-
formContainer.parentNode.insertBefore(styleElement, formContainer);
|
|
437
|
-
}
|
|
384
|
+
// Insert the <style> tag above the form container
|
|
385
|
+
formContainer.parentNode.insertBefore(clonedStyle, formContainer);
|
|
438
386
|
|
|
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})`;
|
|
387
|
+
// console.log(`Applied ${theme} theme to form container: ${formContainerId}`);
|
|
388
|
+
} else {
|
|
389
|
+
console.error(`Form container with ID ${formContainerId} not found.`);
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
.catch(error => {
|
|
393
|
+
console.error('Error loading the stylesheet:', error);
|
|
394
|
+
});
|
|
450
395
|
}
|
|
451
396
|
|
|
452
397
|
|
|
@@ -500,6 +445,7 @@ Object.keys(paramsToUse).forEach(key => {
|
|
|
500
445
|
|
|
501
446
|
|
|
502
447
|
// Main renderForm method
|
|
448
|
+
/*
|
|
503
449
|
renderForm() {
|
|
504
450
|
// Process each field synchronously
|
|
505
451
|
const formHTML = this.formSchema.map(field => {
|
|
@@ -508,8 +454,61 @@ renderForm() {
|
|
|
508
454
|
}).join('');
|
|
509
455
|
this.formMarkUp += formHTML;
|
|
510
456
|
}
|
|
457
|
+
*/
|
|
511
458
|
|
|
459
|
+
renderForm() {
|
|
460
|
+
// Filter out the 'submit' type before mapping
|
|
461
|
+
const formHTML = this.formSchema
|
|
462
|
+
.filter(field => field[0] !== 'submit') // Exclude submit button from this loop
|
|
463
|
+
.map(field => {
|
|
464
|
+
const [type, name, label, validate, attributes = {}, options] = field;
|
|
465
|
+
return this.renderField(type, name, label, validate, attributes, options);
|
|
466
|
+
}).join('');
|
|
467
|
+
this.formMarkUp += formHTML;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
// New method to render the submit button specifically
|
|
473
|
+
renderSubmitButtonElement() {
|
|
474
|
+
const submitField = this.formSchema.find(field => field[0] === 'submit');
|
|
475
|
+
if (submitField) {
|
|
476
|
+
const [type, name, label, validate, attributes = {}] = submitField;
|
|
477
|
+
const id = attributes.id || name;
|
|
478
|
+
let buttonClass = this.submitButtonClass;
|
|
479
|
+
if ('class' in attributes) {
|
|
480
|
+
buttonClass = attributes.class;
|
|
481
|
+
}
|
|
482
|
+
let additionalAttrs = '';
|
|
483
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
484
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
485
|
+
if (key.startsWith('on')) {
|
|
486
|
+
const eventValue = value.endsWith('()') ? value : `${value}()`;
|
|
487
|
+
additionalAttrs += ` ${key}="${eventValue}"`;
|
|
488
|
+
} else {
|
|
489
|
+
if (value === true) {
|
|
490
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}`;
|
|
491
|
+
} else if (value !== false) {
|
|
492
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"`;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
512
497
|
|
|
498
|
+
// Include the spinner div before the submit button
|
|
499
|
+
return `
|
|
500
|
+
<div id="formiqueSpinner" style="display: flex; align-items: center; gap: 1rem; font-family: sans-serif; display:none;">
|
|
501
|
+
<div class="formique-spinner"></div>
|
|
502
|
+
<p class="message">Hang in tight, we are submitting your details…</p>
|
|
503
|
+
</div>
|
|
504
|
+
<input type="submit" id="${id}" class="${buttonClass}" value="${label}"${additionalAttrs}>
|
|
505
|
+
`.trim();
|
|
506
|
+
}
|
|
507
|
+
return ''; // Return empty string if no submit button is found in schema
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
/*
|
|
513
512
|
renderField(type, name, label, validate, attributes, options) {
|
|
514
513
|
const fieldRenderMap = {
|
|
515
514
|
'text': this.renderTextField,
|
|
@@ -549,13 +548,93 @@ renderField(type, name, label, validate, attributes, options) {
|
|
|
549
548
|
}
|
|
550
549
|
}
|
|
551
550
|
|
|
551
|
+
*/
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
// renderField method - No change needed here for this issue, but ensure it handles 'submit' type correctly if called directly
|
|
555
|
+
renderField(type, name, label, validate, attributes, options) {
|
|
556
|
+
const fieldRenderMap = {
|
|
557
|
+
'text': this.renderTextField,
|
|
558
|
+
'email': this.renderEmailField,
|
|
559
|
+
'number': this.renderNumberField,
|
|
560
|
+
'password': this.renderPasswordField,
|
|
561
|
+
'textarea': this.renderTextAreaField,
|
|
562
|
+
'tel': this.renderTelField,
|
|
563
|
+
'date': this.renderDateField,
|
|
564
|
+
'time': this.renderTimeField,
|
|
565
|
+
'datetime-local': this.renderDateTimeField,
|
|
566
|
+
'month': this.renderMonthField,
|
|
567
|
+
'week': this.renderWeekField,
|
|
568
|
+
'url': this.renderUrlField,
|
|
569
|
+
'search': this.renderSearchField,
|
|
570
|
+
'color': this.renderColorField,
|
|
571
|
+
'checkbox': this.renderCheckboxField,
|
|
572
|
+
'radio': this.renderRadioField,
|
|
573
|
+
'file': this.renderFileField,
|
|
574
|
+
'hidden': this.renderHiddenField,
|
|
575
|
+
'image': this.renderImageField,
|
|
576
|
+
'singleSelect': this.renderSingleSelectField,
|
|
577
|
+
'multipleSelect': this.renderMultipleSelectField,
|
|
578
|
+
'dynamicSingleSelect': this.renderDynamicSingleSelectField,
|
|
579
|
+
'range': this.renderRangeField,
|
|
580
|
+
'submit': this.renderSubmitButton, // Keep this for completeness, but renderSubmitButtonElement will now handle it
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const renderMethod = fieldRenderMap[type];
|
|
584
|
+
|
|
585
|
+
if (renderMethod) {
|
|
586
|
+
// If the type is 'submit', ensure we use the specific renderSubmitButtonElement
|
|
587
|
+
// Although, with the filter in renderForm(), this branch for 'submit' type
|
|
588
|
+
// might not be hit in the primary rendering flow, it's good practice.
|
|
589
|
+
return renderMethod.call(this, type, name, label, validate, attributes, options);
|
|
590
|
+
|
|
591
|
+
if (type === 'submit') {
|
|
592
|
+
return this.renderSubmitButton(type, name, label, validate, attributes, options);
|
|
593
|
+
}
|
|
594
|
+
//return renderMethod.call(this, type, name, label, validate, attributes, options);
|
|
595
|
+
} else {
|
|
596
|
+
console.warn(`Unsupported field type '${type}' encountered.`);
|
|
597
|
+
return '';
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
renderSubmitButton(type, name, label, validate, attributes) {
|
|
604
|
+
// This method can simply call the dedicated submit button renderer if it's kept separate.
|
|
605
|
+
// Or, if renderField is only used for non-submit fields, this method might not be strictly necessary
|
|
606
|
+
// to be called from renderField's map, but it needs to exist if mapped.
|
|
607
|
+
// For simplicity, I'll make it consistent with the new separation.
|
|
608
|
+
const id = attributes.id || name;
|
|
609
|
+
let buttonClass = this.submitButtonClass;
|
|
610
|
+
if ('class' in attributes) {
|
|
611
|
+
buttonClass = attributes.class;
|
|
612
|
+
}
|
|
613
|
+
let additionalAttrs = '';
|
|
614
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
615
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
616
|
+
if (key.startsWith('on')) {
|
|
617
|
+
const eventValue = value.endsWith('()') ? value : `${value}()`;
|
|
618
|
+
additionalAttrs += ` ${key}="${eventValue}"`;
|
|
619
|
+
} else {
|
|
620
|
+
if (value === true) {
|
|
621
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}`;
|
|
622
|
+
} else if (value !== false) {
|
|
623
|
+
additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"`;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// No spinner div here, as that's added once by renderSubmitButtonElement
|
|
629
|
+
return `<input type="${type}" id="${id}" class="${buttonClass}" value="${label}"${additionalAttrs}>`;
|
|
630
|
+
}
|
|
552
631
|
|
|
553
632
|
|
|
554
633
|
// Show success/error messages (externalizable)
|
|
555
634
|
showSuccessMessage(message) {
|
|
556
635
|
const container = document.getElementById(this.formContainerId);
|
|
557
636
|
container.innerHTML = `
|
|
558
|
-
<div class="formique-success ${
|
|
637
|
+
<div class="formique-success"> ${message}</div>
|
|
559
638
|
${this.formSettings.redirectURL
|
|
560
639
|
? `<meta http-equiv="refresh" content="2;url=${this.formSettings.redirectURL}">`
|
|
561
640
|
: ""}
|
|
@@ -565,7 +644,7 @@ showSuccessMessage(message) {
|
|
|
565
644
|
showErrorMessage(message) {
|
|
566
645
|
const container = document.getElementById(this.formContainerId);
|
|
567
646
|
const errorDiv = document.createElement("div");
|
|
568
|
-
errorDiv.className =
|
|
647
|
+
errorDiv.className = "formique-error";
|
|
569
648
|
errorDiv.textContent = `${message}`;
|
|
570
649
|
container.prepend(errorDiv);
|
|
571
650
|
}
|
|
@@ -579,92 +658,120 @@ hasFileInputs(form) {
|
|
|
579
658
|
|
|
580
659
|
|
|
581
660
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
// --- Start of Payload and Method Logic (as provided previously) ---
|
|
592
|
-
const payload = {
|
|
593
|
-
formData: {},
|
|
594
|
-
metadata: {
|
|
595
|
-
recipients: this.formSettings.sendTo,
|
|
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
|
-
});
|
|
661
|
+
async handleEmailSubmission(formId) {
|
|
662
|
+
console.log(`Starting email submission for form ID: ${formId}`);
|
|
663
|
+
|
|
664
|
+
const form = document.getElementById(formId);
|
|
665
|
+
if (!form) {
|
|
666
|
+
console.error(`Form with ID ${formId} not found`);
|
|
667
|
+
throw new Error(`Form with ID ${formId} not found`);
|
|
668
|
+
}
|
|
613
669
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
670
|
+
// Validate required settings - now checks if sendTo is array with at least one item
|
|
671
|
+
if (!Array.isArray(this.formSettings?.sendTo) || this.formSettings.sendTo.length === 0) {
|
|
672
|
+
console.error('formSettings.sendTo must be an array with at least one recipient email');
|
|
673
|
+
throw new Error('formSettings.sendTo must be an array with at least one recipient email');
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Serialize form data
|
|
677
|
+
const payload = {
|
|
678
|
+
formData: {},
|
|
679
|
+
metadata: {
|
|
680
|
+
recipients: this.formSettings.sendTo, // Now sending array
|
|
681
|
+
timestamp: new Date().toISOString()
|
|
618
682
|
}
|
|
619
|
-
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
let senderName = '';
|
|
686
|
+
let senderEmail = '';
|
|
687
|
+
let formSubject = '';
|
|
688
|
+
|
|
689
|
+
console.log('Initial payload structure:', JSON.parse(JSON.stringify(payload)));
|
|
690
|
+
|
|
691
|
+
// Process form fields (unchanged)
|
|
692
|
+
new FormData(form).forEach((value, key) => {
|
|
693
|
+
console.log(`Processing form field - Key: ${key}, Value: ${value}`);
|
|
694
|
+
payload.formData[key] = value;
|
|
695
|
+
|
|
696
|
+
const lowerKey = key.toLowerCase();
|
|
697
|
+
if ((lowerKey === 'email' || lowerKey.includes('email'))) {
|
|
698
|
+
senderEmail = value;
|
|
699
|
+
}
|
|
700
|
+
if ((lowerKey === 'name' || lowerKey.includes('name'))) {
|
|
701
|
+
senderName = value;
|
|
702
|
+
}
|
|
703
|
+
if ((lowerKey === 'subject' || lowerKey.includes('subject'))) {
|
|
704
|
+
formSubject = value;
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// Determine the email subject with fallback logic
|
|
709
|
+
payload.metadata.subject = formSubject ||
|
|
710
|
+
this.formSettings.subject ||
|
|
711
|
+
'Message From Contact Form';
|
|
712
|
+
|
|
713
|
+
console.log('Determined email subject:', payload.metadata.subject);
|
|
714
|
+
|
|
715
|
+
// Add sender information to metadata
|
|
716
|
+
if (senderEmail) {
|
|
717
|
+
payload.metadata.sender = senderEmail;
|
|
718
|
+
payload.metadata.replyTo = senderName
|
|
719
|
+
? `${senderName} <${senderEmail}>`
|
|
720
|
+
: senderEmail;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
console.log('Payload after form processing:', JSON.parse(JSON.stringify(payload)));
|
|
620
724
|
|
|
725
|
+
try {
|
|
621
726
|
const endpoint = this.formiqueEndpoint || this.formAction;
|
|
622
727
|
const method = this.method || 'POST';
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
728
|
+
|
|
729
|
+
console.log(`Preparing to send request to: ${endpoint}`);
|
|
730
|
+
console.log(`Request method: ${method}`);
|
|
731
|
+
console.log('Final payload being sent:', payload);
|
|
626
732
|
|
|
627
733
|
const response = await fetch(endpoint, {
|
|
628
734
|
method: method,
|
|
629
|
-
headers: {
|
|
735
|
+
headers: {
|
|
630
736
|
'Content-Type': 'application/json',
|
|
631
|
-
'X-Formique-Version': '1.0'
|
|
737
|
+
'X-Formique-Version': '1.0'
|
|
632
738
|
},
|
|
633
739
|
body: JSON.stringify(payload)
|
|
634
740
|
});
|
|
635
741
|
|
|
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
|
-
}
|
|
742
|
+
console.log(`Received response with status: ${response.status}`);
|
|
650
743
|
|
|
651
744
|
if (!response.ok) {
|
|
652
|
-
const
|
|
653
|
-
|
|
745
|
+
const errorData = await response.json().catch(() => ({}));
|
|
746
|
+
console.error('API Error Response:', errorData);
|
|
747
|
+
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
748
|
+
document.getElementById("formiqueSpinner").style.display = "none";
|
|
749
|
+
|
|
654
750
|
}
|
|
655
751
|
|
|
656
|
-
const
|
|
752
|
+
const data = await response.json();
|
|
753
|
+
console.log('API Success Response:', data);
|
|
754
|
+
|
|
755
|
+
const successMessage = this.formSettings.successMessage ||
|
|
756
|
+
data.message ||
|
|
757
|
+
'Your message has been sent successfully!';
|
|
758
|
+
console.log(`Showing success message: ${successMessage}`);
|
|
759
|
+
|
|
657
760
|
this.showSuccessMessage(successMessage);
|
|
658
761
|
|
|
659
762
|
} catch (error) {
|
|
660
|
-
console.error(
|
|
661
|
-
const errorMessage = this.formSettings.errorMessage ||
|
|
763
|
+
console.error('Email submission failed:', error);
|
|
764
|
+
const errorMessage = this.formSettings.errorMessage ||
|
|
765
|
+
error.message ||
|
|
766
|
+
'Failed to send message. Please try again later.';
|
|
767
|
+
console.log(`Showing error message: ${errorMessage}`);
|
|
662
768
|
this.showErrorMessage(errorMessage);
|
|
663
|
-
} finally {
|
|
664
|
-
// Ensure spinner is hidden on success or failure
|
|
665
769
|
document.getElementById("formiqueSpinner").style.display = "none";
|
|
770
|
+
|
|
666
771
|
}
|
|
667
|
-
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
|
|
668
775
|
|
|
669
776
|
// Email validation helper
|
|
670
777
|
validateEmail(email) {
|
|
@@ -742,7 +849,7 @@ if (formContainer) {
|
|
|
742
849
|
errorMessageDiv.innerHTML = err;
|
|
743
850
|
|
|
744
851
|
// Append the new error message div to the form container
|
|
745
|
-
|
|
852
|
+
formContainer.appendChild(errorMessageDiv);
|
|
746
853
|
}
|
|
747
854
|
});
|
|
748
855
|
|
|
@@ -1422,17 +1529,18 @@ const telInputValidationAttributes = [
|
|
|
1422
1529
|
}
|
|
1423
1530
|
|
|
1424
1531
|
// Handle the binding syntax
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1532
|
+
// Handle the binding syntax
|
|
1533
|
+
let bindingDirective = '';
|
|
1534
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
1535
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
1536
|
+
}
|
|
1537
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
1538
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
1539
|
+
}
|
|
1540
|
+
if (attributes?.binding && !name) {
|
|
1541
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1436
1544
|
|
|
1437
1545
|
// Get the id from attributes or fall back to name
|
|
1438
1546
|
let id = attributes.id || name;
|
|
@@ -1543,13 +1651,13 @@ renderDateField(type, name, label, validate, attributes) {
|
|
|
1543
1651
|
|
|
1544
1652
|
// Handle the binding syntax
|
|
1545
1653
|
let bindingDirective = '';
|
|
1546
|
-
if (attributes
|
|
1654
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
1547
1655
|
bindingDirective = `bind:value="${name}"\n`;
|
|
1548
1656
|
}
|
|
1549
|
-
if (attributes
|
|
1657
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
1550
1658
|
bindingDirective = `bind:value="${name}"\n`;
|
|
1551
1659
|
}
|
|
1552
|
-
if (attributes
|
|
1660
|
+
if (attributes?.binding && !name) {
|
|
1553
1661
|
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
1554
1662
|
return;
|
|
1555
1663
|
}
|
|
@@ -1663,15 +1771,16 @@ renderTimeField(type, name, label, validate, attributes) {
|
|
|
1663
1771
|
});
|
|
1664
1772
|
}
|
|
1665
1773
|
|
|
1774
|
+
// Handle the binding syntax
|
|
1666
1775
|
// Handle the binding syntax
|
|
1667
1776
|
let bindingDirective = '';
|
|
1668
|
-
if (attributes
|
|
1777
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
1669
1778
|
bindingDirective = `bind:value="${name}"\n`;
|
|
1670
1779
|
}
|
|
1671
|
-
if (attributes
|
|
1780
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
1672
1781
|
bindingDirective = `bind:value="${name}"\n`;
|
|
1673
1782
|
}
|
|
1674
|
-
if (attributes
|
|
1783
|
+
if (attributes?.binding && !name) {
|
|
1675
1784
|
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
1676
1785
|
return;
|
|
1677
1786
|
}
|
|
@@ -1915,15 +2024,18 @@ renderMonthField(type, name, label, validate, attributes) {
|
|
|
1915
2024
|
}
|
|
1916
2025
|
|
|
1917
2026
|
// Handle the binding syntax
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
2027
|
+
let bindingDirective = '';
|
|
2028
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2029
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2030
|
+
}
|
|
2031
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2032
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2033
|
+
}
|
|
2034
|
+
if (attributes?.binding && !name) {
|
|
2035
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
1927
2039
|
|
|
1928
2040
|
// Get the id from attributes or fall back to name
|
|
1929
2041
|
let id = attributes.id || name;
|
|
@@ -2037,15 +2149,17 @@ renderWeekField(type, name, label, validate, attributes) {
|
|
|
2037
2149
|
}
|
|
2038
2150
|
|
|
2039
2151
|
// Handle the binding syntax
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2152
|
+
let bindingDirective = '';
|
|
2153
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2154
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2155
|
+
}
|
|
2156
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2157
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2158
|
+
}
|
|
2159
|
+
if (attributes?.binding && !name) {
|
|
2160
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2049
2163
|
|
|
2050
2164
|
// Get the id from attributes or fall back to name
|
|
2051
2165
|
let id = attributes.id || name;
|
|
@@ -2155,15 +2269,18 @@ renderUrlField(type, name, label, validate, attributes) {
|
|
|
2155
2269
|
}
|
|
2156
2270
|
|
|
2157
2271
|
// Handle the binding syntax
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2272
|
+
// Handle the binding syntax
|
|
2273
|
+
let bindingDirective = '';
|
|
2274
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2275
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2276
|
+
}
|
|
2277
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2278
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2279
|
+
}
|
|
2280
|
+
if (attributes?.binding && !name) {
|
|
2281
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2167
2284
|
|
|
2168
2285
|
// Get the id from attributes or fall back to name
|
|
2169
2286
|
let id = attributes.id || name;
|
|
@@ -2272,15 +2389,17 @@ renderSearchField(type, name, label, validate, attributes) {
|
|
|
2272
2389
|
}
|
|
2273
2390
|
|
|
2274
2391
|
// Handle the binding syntax
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
}
|
|
2392
|
+
let bindingDirective = '';
|
|
2393
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2394
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2395
|
+
}
|
|
2396
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2397
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2398
|
+
}
|
|
2399
|
+
if (attributes?.binding && !name) {
|
|
2400
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2284
2403
|
|
|
2285
2404
|
// Get the id from attributes or fall back to name
|
|
2286
2405
|
let id = attributes.id || name;
|
|
@@ -2381,14 +2500,16 @@ renderColorField(type, name, label, validate, attributes) {
|
|
|
2381
2500
|
});
|
|
2382
2501
|
}
|
|
2383
2502
|
|
|
2503
|
+
// Handle the binding syntax
|
|
2384
2504
|
// Handle the binding syntax
|
|
2385
2505
|
let bindingDirective = '';
|
|
2386
|
-
if (attributes
|
|
2506
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2387
2507
|
bindingDirective = `bind:value="${name}"\n`;
|
|
2388
|
-
}
|
|
2508
|
+
}
|
|
2509
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2389
2510
|
bindingDirective = `bind:value="${name}"\n`;
|
|
2390
2511
|
}
|
|
2391
|
-
if (attributes
|
|
2512
|
+
if (attributes?.binding && !name) {
|
|
2392
2513
|
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2393
2514
|
return;
|
|
2394
2515
|
}
|
|
@@ -2419,8 +2540,13 @@ renderColorField(type, name, label, validate, attributes) {
|
|
|
2419
2540
|
if ('class' in attributes) {
|
|
2420
2541
|
inputClass = attributes.class;
|
|
2421
2542
|
} else {
|
|
2422
|
-
|
|
2543
|
+
// inputClass = this.inputClass;
|
|
2423
2544
|
}
|
|
2545
|
+
|
|
2546
|
+
if (type === 'color') {
|
|
2547
|
+
inputClass += ' form-color-input'; // Add the new specific class for color inputs
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2424
2550
|
// Construct the final HTML string
|
|
2425
2551
|
let formHTML = `
|
|
2426
2552
|
<div class="${this.divClass}" id="${id + '-block'}">
|
|
@@ -2493,16 +2619,17 @@ renderFileField(type, name, label, validate, attributes) {
|
|
|
2493
2619
|
}
|
|
2494
2620
|
|
|
2495
2621
|
// Handle the binding syntax
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2622
|
+
let bindingDirective = '';
|
|
2623
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2624
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2625
|
+
}
|
|
2626
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2627
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2628
|
+
}
|
|
2629
|
+
if (attributes?.binding && !name) {
|
|
2630
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2506
2633
|
|
|
2507
2634
|
// Get the id from attributes or fall back to name
|
|
2508
2635
|
let id = attributes.id || name;
|
|
@@ -2606,12 +2733,13 @@ renderHiddenField(type, name, label, validate, attributes) {
|
|
|
2606
2733
|
|
|
2607
2734
|
// Handle the binding syntax
|
|
2608
2735
|
let bindingDirective = '';
|
|
2609
|
-
if (attributes
|
|
2736
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2610
2737
|
bindingDirective = `bind:value="${name}"\n`;
|
|
2611
|
-
}
|
|
2738
|
+
}
|
|
2739
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2612
2740
|
bindingDirective = `bind:value="${name}"\n`;
|
|
2613
2741
|
}
|
|
2614
|
-
if (attributes
|
|
2742
|
+
if (attributes?.binding && !name) {
|
|
2615
2743
|
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2616
2744
|
return;
|
|
2617
2745
|
}
|
|
@@ -2648,9 +2776,9 @@ renderHiddenField(type, name, label, validate, attributes) {
|
|
|
2648
2776
|
// Construct the final HTML string
|
|
2649
2777
|
let formHTML = `
|
|
2650
2778
|
<div class="${this.divClass}" id="${id + '-block'}">
|
|
2651
|
-
|
|
2779
|
+
<!--<label for="${id}">${label}
|
|
2652
2780
|
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
2653
|
-
</label>
|
|
2781
|
+
</label> -->
|
|
2654
2782
|
<input
|
|
2655
2783
|
type="${type}"
|
|
2656
2784
|
name="${name}"
|
|
@@ -2683,7 +2811,7 @@ renderHiddenField(type, name, label, validate, attributes) {
|
|
|
2683
2811
|
}
|
|
2684
2812
|
|
|
2685
2813
|
|
|
2686
|
-
|
|
2814
|
+
/*
|
|
2687
2815
|
renderImageField(type, name, label, validate, attributes) {
|
|
2688
2816
|
// Define valid validation attributes for image upload
|
|
2689
2817
|
const imageUploadValidationAttributes = [
|
|
@@ -2714,12 +2842,18 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2714
2842
|
}
|
|
2715
2843
|
|
|
2716
2844
|
// Handle the binding syntax
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2845
|
+
// Handle the binding syntax
|
|
2846
|
+
let bindingDirective = '';
|
|
2847
|
+
if (attributes?.binding === 'bind:value' && name) {
|
|
2848
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2849
|
+
}
|
|
2850
|
+
if (attributes?.binding?.startsWith('::') && name) {
|
|
2851
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2852
|
+
}
|
|
2853
|
+
if (attributes?.binding && !name) {
|
|
2854
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2723
2857
|
|
|
2724
2858
|
// Get the id from attributes or fall back to name
|
|
2725
2859
|
let id = attributes.id || name;
|
|
@@ -2786,26 +2920,33 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2786
2920
|
this.formMarkUp +=formattedHtml;
|
|
2787
2921
|
}
|
|
2788
2922
|
|
|
2789
|
-
|
|
2790
|
-
|
|
2923
|
+
*/
|
|
2791
2924
|
|
|
2792
2925
|
renderImageField(type, name, label, validate, attributes) {
|
|
2793
|
-
// Define valid validation attributes for image
|
|
2794
|
-
const
|
|
2926
|
+
// Define valid validation attributes for image input
|
|
2927
|
+
const imageValidationAttributes = [
|
|
2795
2928
|
'accept',
|
|
2796
2929
|
'required',
|
|
2797
2930
|
'minwidth',
|
|
2798
2931
|
'maxwidth',
|
|
2799
2932
|
'minheight',
|
|
2800
2933
|
'maxheight',
|
|
2934
|
+
'src',
|
|
2935
|
+
'alt',
|
|
2936
|
+
'width',
|
|
2937
|
+
'height'
|
|
2801
2938
|
];
|
|
2802
2939
|
|
|
2803
2940
|
// Construct validation attributes
|
|
2804
2941
|
let validationAttrs = '';
|
|
2805
2942
|
if (validate) {
|
|
2806
2943
|
Object.entries(validate).forEach(([key, value]) => {
|
|
2807
|
-
if (
|
|
2808
|
-
|
|
2944
|
+
if (imageValidationAttributes.includes(key)) {
|
|
2945
|
+
if (typeof value === 'boolean' && value) {
|
|
2946
|
+
validationAttrs += ` ${key}\n`;
|
|
2947
|
+
} else {
|
|
2948
|
+
validationAttrs += ` ${key}="${value}"\n`;
|
|
2949
|
+
}
|
|
2809
2950
|
} else {
|
|
2810
2951
|
console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
|
|
2811
2952
|
}
|
|
@@ -2814,9 +2955,17 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2814
2955
|
|
|
2815
2956
|
// Handle the binding syntax
|
|
2816
2957
|
let bindingDirective = '';
|
|
2817
|
-
|
|
2958
|
+
const bindingValue = attributes?.binding;
|
|
2959
|
+
if (bindingValue === 'bind:value' && name) {
|
|
2960
|
+
bindingDirective = `bind:value="${name}"\n`;
|
|
2961
|
+
}
|
|
2962
|
+
if (typeof bindingValue === 'string' && bindingValue.startsWith('::') && name) {
|
|
2818
2963
|
bindingDirective = `bind:value="${name}"\n`;
|
|
2819
2964
|
}
|
|
2965
|
+
if (bindingValue && !name) {
|
|
2966
|
+
console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2820
2969
|
|
|
2821
2970
|
// Get the id from attributes or fall back to name
|
|
2822
2971
|
let id = attributes.id || name;
|
|
@@ -2824,7 +2973,8 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2824
2973
|
// Construct additional attributes dynamically
|
|
2825
2974
|
let additionalAttrs = '';
|
|
2826
2975
|
for (const [key, value] of Object.entries(attributes)) {
|
|
2827
|
-
|
|
2976
|
+
if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
|
|
2977
|
+
if (key.startsWith('on')) {
|
|
2828
2978
|
// Handle event attributes
|
|
2829
2979
|
const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
|
|
2830
2980
|
additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
|
|
@@ -2840,44 +2990,56 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2840
2990
|
}
|
|
2841
2991
|
}
|
|
2842
2992
|
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2993
|
+
// Special handling for image submit button
|
|
2994
|
+
let inputElement;
|
|
2995
|
+
if (type === 'image' && name === 'submit') {
|
|
2996
|
+
inputElement = `
|
|
2997
|
+
<input
|
|
2998
|
+
type="image"
|
|
2999
|
+
name="${name}"
|
|
3000
|
+
${bindingDirective}
|
|
3001
|
+
id="${id}"
|
|
3002
|
+
class="${attributes.class || this.inputClass}"
|
|
3003
|
+
src="${attributes.src || 'img_submit.gif'}"
|
|
3004
|
+
alt="${attributes.alt || 'Submit'}"
|
|
3005
|
+
width="${attributes.width || '48'}"
|
|
3006
|
+
height="${attributes.height || '48'}"
|
|
3007
|
+
${additionalAttrs}
|
|
3008
|
+
${validationAttrs}
|
|
3009
|
+
/>`;
|
|
2846
3010
|
} 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>
|
|
3011
|
+
// Regular image input field
|
|
3012
|
+
inputElement = `
|
|
2855
3013
|
<input
|
|
2856
3014
|
type="${type}"
|
|
2857
3015
|
name="${name}"
|
|
2858
3016
|
${bindingDirective}
|
|
2859
3017
|
id="${id}"
|
|
2860
|
-
class="${inputClass}"
|
|
3018
|
+
class="${attributes.class || this.inputClass}"
|
|
2861
3019
|
${additionalAttrs}
|
|
2862
3020
|
${validationAttrs}
|
|
2863
|
-
|
|
3021
|
+
/>`;
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
// Construct the final HTML string
|
|
3025
|
+
let formHTML = `
|
|
3026
|
+
<div class="${this.divClass}" id="${id + '-block'}">
|
|
3027
|
+
${type === 'image' && name === 'submit' ? '' : `<label for="${id}">${label}
|
|
3028
|
+
${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
|
|
3029
|
+
</label>`}
|
|
3030
|
+
${inputElement}
|
|
2864
3031
|
</div>
|
|
2865
3032
|
`.replace(/^\s*\n/gm, '').trim();
|
|
2866
3033
|
|
|
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
|
|
3034
|
+
// Format the HTML
|
|
3035
|
+
let formattedHtml = formHTML.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
|
|
2872
3036
|
const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
|
|
2873
3037
|
return `<input\n${attributes}\n/>`;
|
|
2874
3038
|
});
|
|
2875
3039
|
|
|
2876
|
-
// Ensure the <div> block starts on a new line and remove extra blank lines
|
|
2877
3040
|
formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
|
|
2878
|
-
// Ensure <div> starts on a new line
|
|
2879
3041
|
return `\n${match}\n`;
|
|
2880
|
-
}).replace(/\n\s*\n/g, '\n');
|
|
3042
|
+
}).replace(/\n\s*\n/g, '\n');
|
|
2881
3043
|
|
|
2882
3044
|
return formattedHtml;
|
|
2883
3045
|
}
|
|
@@ -2885,8 +3047,6 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
2885
3047
|
|
|
2886
3048
|
|
|
2887
3049
|
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
3050
|
// Textarea field rendering
|
|
2891
3051
|
renderTextAreaField(type, name, label, validate, attributes) {
|
|
2892
3052
|
const textAreaValidationAttributes = [
|
|
@@ -3239,6 +3399,7 @@ this.renderSingleSelectField(type, name, label, validate, attributes, mainCatego
|
|
|
3239
3399
|
|
|
3240
3400
|
renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
|
|
3241
3401
|
|
|
3402
|
+
console.log("Within");
|
|
3242
3403
|
// Define valid validation attributes for select fields
|
|
3243
3404
|
const selectValidationAttributes = ['required'];
|
|
3244
3405
|
|
|
@@ -3805,10 +3966,11 @@ renderSubmitButton(type, name, label, validate, attributes) {
|
|
|
3805
3966
|
}
|
|
3806
3967
|
|
|
3807
3968
|
|
|
3808
|
-
const spinner = `<div
|
|
3969
|
+
const spinner = `<div id="formiqueSpinner" style="display: flex; align-items: center; gap: 1rem; font-family: sans-serif; display:none;">
|
|
3809
3970
|
<div class="formique-spinner"></div>
|
|
3810
3971
|
<p class="message">Hang in tight, we are submitting your details…</p>
|
|
3811
|
-
</div
|
|
3972
|
+
</div>
|
|
3973
|
+
`;
|
|
3812
3974
|
// Construct the final HTML string
|
|
3813
3975
|
|
|
3814
3976
|
const formHTML = `
|
|
@@ -3831,17 +3993,15 @@ const spinner = `<div class="" id="formiqueSpinner">
|
|
|
3831
3993
|
|
|
3832
3994
|
|
|
3833
3995
|
|
|
3834
|
-
renderFormHTML
|
|
3835
|
-
|
|
3836
|
-
this.
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
formContainer.innerHTML = this.formMarkUp;
|
|
3844
|
-
}
|
|
3996
|
+
renderFormHTML() {
|
|
3997
|
+
this.formMarkUp += '</form>';
|
|
3998
|
+
const formContainer = document.getElementById(this.formContainerId);
|
|
3999
|
+
if (!formContainer) {
|
|
4000
|
+
console.error(`Error: form container with ID ${this.formContainerId} not found. Please ensure an element with id ${this.formContainerId} exists in the HTML.`);
|
|
4001
|
+
} else {
|
|
4002
|
+
formContainer.innerHTML = this.formMarkUp;
|
|
4003
|
+
}
|
|
4004
|
+
|
|
3845
4005
|
|
|
3846
4006
|
//return this.formMarkUp;
|
|
3847
4007
|
|