@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.
Files changed (3) hide show
  1. package/README.md +17 -12
  2. package/formique-semantq.js +555 -401
  3. package/package.json +1 -1
@@ -50,126 +50,125 @@ class FormBuilder
50
50
 
51
51
  // Extended class for specific form rendering methods
52
52
  class Formique extends FormBuilder {
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
- this.formContainerStyle = formSettings?.formContainerStyle || null;
71
- this.formId = this.formParams?.id || this.generateFormId();
72
- //console.log(this.formId);
73
- this.formAction = formParams?.action || 'https://httpbin.org/post';
74
- this.method = 'POST';
75
- this.formMarkUp = '';
76
- this.dependencyGraph = {};
77
- this.redirect = formSettings?.redirect ||'';
78
- this.redirectURL = formSettings?.redirectURL ||'';
79
- this.activeTheme = formSettings.theme || null;
80
- this.themeColor = formSettings.themeColor || null;
81
- this.themeColorMap = {
82
- 'primary': {
83
- '--formique-base-bg': '#ffffff',
84
- '--formique-base-text': '#333333',
85
- '--formique-base-shadow': '0 10px 30px rgba(0, 0, 0, 0.1)',
86
- '--formique-base-label': '#555555',
87
- '--formique-input-border': '#dddddd',
88
- '--formique-focus-color': null, // Will be set to themeColor
89
- '--formique-btn-bg': null, // Will be set to themeColor
90
- '--formique-btn-text': '#ffffff',
91
- '--formique-btn-shadow': null // Will be calculated from themeColor
92
- }
93
- };
94
-
95
-
96
- this.themes = [
97
- "dark",
98
- "light",
99
- "pink",
100
- "light",
101
- "indigo",
102
- "dark-blue",
103
- "light-blue",
104
- "dark-orange",
105
- "bright-yellow",
106
- "green",
107
- "purple",
108
- "midnight-blush",
109
- "deep-blue",
110
- "blue",
111
- "brown",
112
- "orange"
113
- ];
114
-
115
- //this.formiqueEndpoint = "http://localhost:3000/api/send-email";
116
- this.formiqueEndpoint = "https://formiqueapi.onrender.com/api/send-email";
117
-
118
- // DISABLE EVENT LISTENER
119
- // document.addEventListener('DOMContentLoaded', () => {
120
-
121
- /*
122
- if (this.formParams && Object.keys(this.formParams).length > 0) {
123
- this.formMarkUp += this.renderFormElement();
124
- } */
125
-
126
- this.formMarkUp += this.renderFormElement();
127
-
128
-
129
- this.renderForm();
130
- this.renderFormHTML();
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
- if (this.formContainerStyle) {
164
- const formContainer = document.getElementById(this.formContainerId);
165
- formContainer.setAttribute("style", this.formContainerStyle);
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
- // disable wrapper for DOM event listener
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
- const formContainer = document.getElementById(formContainerId);
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
- // Clear any existing theme classes
358
- this.themes.forEach(t => formContainer.classList.remove(`${t}-theme`));
359
- formContainer.classList.remove('custom-theme');
360
- spinnerContainer.classList.remove('custom-theme');
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
- // If themeColor is provided, use it to create a custom theme
363
- if (this.themeColor) {
364
- this.applyCustomTheme(formContainerId);
365
- return;
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
- // 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!");
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
- if (!formContainer) return;
368
+ // Extract CSS rules for the theme
369
+ const themeCSS = themeRules[1].trim();
402
370
 
403
- formContainer.classList.add('custom-theme', 'formique');
404
- spinnerContainer.classList.add('custom-theme');
371
+ // Find the form container element
372
+ const formContainer = document.getElementById(formContainerId);
405
373
 
406
- this.activeTheme = 'custom-theme';
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
- // 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
- `;
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
- // Remove any existing custom style
432
- const existingStyle = document.querySelector(`style[data-custom-theme="${formContainerId}"]`);
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
- hexToRgbA(hex, alpha) {
440
- let c;
441
- if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
442
- c = hex.substring(1).split('');
443
- if (c.length === 3) {
444
- c = [c[0], c[0], c[1], c[1], c[2], c[2]];
445
- }
446
- c = '0x' + c.join('');
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 ${this.activeTheme}">${message}</div>
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 = `formique-error ${this.activeTheme}`;
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
- // A complete function to replace your old one
583
- // Use this function wherever you are currently using the fetch().then()... structure
584
- // This is the complete, final version of the class method.
585
- // It is an async arrow function, so 'this' is automatically correct.
586
- handleEmailSubmission = async (formId) => {
587
- try {
588
- const form = document.getElementById(formId);
589
- if (!form) throw new Error(`Form with ID ${formId} not found`);
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
- });
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
- payload.metadata.subject = formSubject || this.formSettings.subject || 'Message From Contact Form';
615
- if (senderEmail) {
616
- payload.metadata.sender = senderEmail;
617
- payload.metadata.replyTo = senderEmail;
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
- // --- End of Payload and Method Logic ---
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
- // Show spinner when request starts
625
- document.getElementById("formiqueSpinner").style.display = "flex";
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
- // The core fix: Read the response body as text first.
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 errorMsg = data.error || `HTTP error! status: ${response.status}`;
653
- throw new Error(errorMsg);
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 successMessage = this.formSettings.successMessage || data.message || "Your message has been sent successfully!";
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("Email submission failed:", error);
661
- const errorMessage = this.formSettings.errorMessage || error.message || "Failed to send message. Please try again later.";
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
- //formContainer.appendChild(errorMessageDiv);
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
- let bindingDirective = '';
1426
- if (attributes.binding === 'bind:value' && name) {
1427
- bindingDirective = `bind:value="${name}"\n`;
1428
- }
1429
- if (attributes.binding.startsWith('::') && name) {
1430
- bindingDirective = `bind:value="${name}"\n`;
1431
- }
1432
- if (attributes.binding && !name) {
1433
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1434
- return;
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.binding === 'bind:value' && name) {
1657
+ if (attributes?.binding === 'bind:value' && name) {
1547
1658
  bindingDirective = `bind:value="${name}"\n`;
1548
1659
  }
1549
- if (attributes.binding.startsWith('::') && name) {
1660
+ if (attributes?.binding?.startsWith('::') && name) {
1550
1661
  bindingDirective = `bind:value="${name}"\n`;
1551
1662
  }
1552
- if (attributes.binding && !name) {
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.binding === 'bind:value' && name) {
1780
+ if (attributes?.binding === 'bind:value' && name) {
1669
1781
  bindingDirective = `bind:value="${name}"\n`;
1670
1782
  }
1671
- if (attributes.binding.startsWith('::') && name) {
1783
+ if (attributes?.binding?.startsWith('::') && name) {
1672
1784
  bindingDirective = `bind:value="${name}"\n`;
1673
1785
  }
1674
- if (attributes.binding && !name) {
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
- let bindingDirective = '';
1919
- if (attributes.binding === 'bind:value' && name) {
1920
- bindingDirective = `bind:value="${name}"\n`;
1921
- } if (attributes.binding.startsWith('::') && name) {
1922
- bindingDirective = `bind:value="${name}"\n`;
1923
- } if (attributes.binding && !name) {
1924
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1925
- return;
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
- let bindingDirective = '';
2041
- if (attributes.binding === 'bind:value' && name) {
2042
- bindingDirective = `bind:value="${name}"\n`;
2043
- } if (attributes.binding.startsWith('::') && name) {
2044
- bindingDirective = `bind:value="${name}"\n`;
2045
- } if (attributes.binding && !name) {
2046
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2047
- return;
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
- let bindingDirective = '';
2159
- if (attributes.binding === 'bind:value' && name) {
2160
- bindingDirective = `bind:value="${name}"\n`;
2161
- } if (attributes.binding.startsWith('::') && name) {
2162
- bindingDirective = `bind:value="${name}"\n`;
2163
- } if (attributes.binding && !name) {
2164
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2165
- return;
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
- let bindingDirective = '';
2276
- if (attributes.binding === 'bind:value' && name) {
2277
- bindingDirective = `bind:value="${name}"\n`;
2278
- } if (attributes.binding.startsWith('::') && name) {
2279
- bindingDirective = `bind:value="${name}"\n`;
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
- }
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.binding === 'bind:value') {
2509
+ if (attributes?.binding === 'bind:value' && name) {
2387
2510
  bindingDirective = `bind:value="${name}"\n`;
2388
- } else if (attributes.binding.startsWith('::') && name) {
2511
+ }
2512
+ if (attributes?.binding?.startsWith('::') && name) {
2389
2513
  bindingDirective = `bind:value="${name}"\n`;
2390
2514
  }
2391
- if (attributes.binding && !name) {
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
- inputClass = this.inputClass;
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
- let bindingDirective = '';
2497
- if (attributes.binding === 'bind:value') {
2498
- bindingDirective = `bind:value="${name}"\n`;
2499
- } if (attributes.binding.startsWith('::') && name) {
2500
- bindingDirective = `bind:value="${name}"\n`;
2501
- }
2502
- if (attributes.binding && !name) {
2503
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2504
- return;
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.binding === 'bind:value') {
2739
+ if (attributes?.binding === 'bind:value' && name) {
2610
2740
  bindingDirective = `bind:value="${name}"\n`;
2611
- } if (attributes.binding.startsWith('::') && name) {
2741
+ }
2742
+ if (attributes?.binding?.startsWith('::') && name) {
2612
2743
  bindingDirective = `bind:value="${name}"\n`;
2613
2744
  }
2614
- if (attributes.binding && !name) {
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
- <label for="${id}">${label}
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
- let bindingDirective = '';
2718
- if (attributes.binding === 'bind:value') {
2719
- bindingDirective = ` bind:value="${name}"`;
2720
- } else if (attributes.binding.startsWith('::')) {
2721
- bindingDirective = ` bind:value="${name}"`;
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 upload
2794
- const imageUploadValidationAttributes = [
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 (imageUploadValidationAttributes.includes(key)) {
2808
- validationAttrs += `${key}="${value}"\n`;
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
- if (attributes.binding === 'bind:value' || bindingSyntax.startsWith('::')) {
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
- if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
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
- let inputClass;
2844
- if ('class' in attributes) {
2845
- inputClass = attributes.class;
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
- inputClass = this.inputClass;
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
- let formattedHtml = formHTML;
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'); // Remove extra blank lines
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 class="" id="formiqueSpinner">
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.formMarkUp+= '</form>';
3837
- //console.log(this.formMarkUp);
3838
- const formContainer = document.getElementById(this.formContainerId);
3839
- //alert(this.formContainerId);
3840
- if (!formContainer) {
3841
- console.error(`Error: formContainer not found. Please ensure an element with id ${this.formContainerId} exists in the HTML.`);
3842
- } else {
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
-