@formique/semantq 1.0.2 → 1.0.4

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.
@@ -0,0 +1,3870 @@
1
+ 'use strict';
2
+ /**
3
+ * Formique Semantq Class Library
4
+ *
5
+ * This library provides an extension of the FormBuilder class, allowing for dynamic form rendering, theming,
6
+ * and dependency management. The key functionalities include:
7
+ *
8
+ * - Dynamic form rendering based on a provided schema (`formSchema`).
9
+ * - Theming support with predefined themes that can be applied to the form container.
10
+ * - Dependency management to show/hide fields based on parent field values.
11
+ * - Initialization of event listeners to handle form input changes.
12
+ * - **Dynamic dropdowns**: Automatically populate dropdown fields based on other form inputs.
13
+ * - **ARIA labels and WCAG compliance**: Generates forms with accessibility features, including ARIA labels for improved accessibility and compliance with Web Content Accessibility Guidelines (WCAG).
14
+ *
15
+ * Key Methods:
16
+ * - `constructor(formParams, formSchema, formSettings)`: Initializes the form with the provided parameters, schema, and settings.
17
+ * - `renderForm()`: Renders the form using the schema and appends it to the DOM.
18
+ * - `initDependencyGraph()`: Sets up the dependency graph for managing field visibility based on dependencies.
19
+ * - `attachInputChangeListener(parentField)`: Attaches input change listeners to parent fields for dependency management.
20
+ * - `handleParentFieldChange(parentFieldId, value)`: Handles changes in parent fields and updates dependent fields.
21
+ * - `registerObservers()`: Registers observers for dependent fields to manage their state based on parent field values.
22
+ * - `applyTheme(theme, formContainerId)`: Applies a specified theme to the form container.
23
+ * - `renderFormElement()`: Renders the form element with the necessary attributes and CSRF token if applicable.
24
+ * - `renderField(type, name, label, validate, attributes, options)`: Renders individual form fields based on type and attributes, including dynamic dropdowns and ARIA attributes.
25
+ *
26
+ * Dependencies:
27
+ * - The library depends on a DOM structure to initialize and manipulate form elements.
28
+ * - Requires a CSS stylesheet with theme definitions.- there are plans to internalise css themes within js
29
+ *
30
+ * Example Usage:
31
+ * const form = new Formique(formSchema,formParams,formSettings);
32
+ * - formParams and formSettings parameters are optional
33
+ *
34
+ * This package is suited for Vanilla Js implementations. Formique has different versions
35
+ * applicable to these frameworks: Svelte, Vue JS, React and Angular.
36
+ *
37
+ * Author: Gugulethu Nyoni
38
+ * Version: 1.0.8
39
+ * License: Open-source & MIT licensed.
40
+ */
41
+
42
+
43
+ class FormBuilder
44
+ {
45
+ renderField(type, name, label, validate, attributes, options) {
46
+ throw new Error('Method renderField must be implemented');
47
+ }
48
+
49
+ }
50
+
51
+ // Extended class for specific form rendering methods
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
160
+
161
+
162
+
163
+ if (this.formContainerStyle) {
164
+ const formContainer = document.getElementById(this.formContainerId);
165
+ formContainer.setAttribute("style", this.formContainerStyle);
166
+ }
167
+
168
+
169
+
170
+ // disable wrapper for DOM event listener
171
+ // });
172
+
173
+ // CONSTRUCTOR WRAPPER FOR FORMIQUE CLASS
174
+ }
175
+
176
+
177
+ generateFormId() {
178
+ return `fmq-${Math.random().toString(36).substr(2, 10)}`;
179
+ }
180
+
181
+
182
+
183
+ initDependencyGraph() {
184
+ this.dependencyGraph = {};
185
+
186
+ this.formSchema.forEach((field) => {
187
+ const [type, name, label, validate, attributes = {}] = field;
188
+ const fieldId = attributes.id || name;
189
+
190
+ if (attributes.dependents) {
191
+ // Initialize dependency array for the parent field
192
+ this.dependencyGraph[fieldId] = attributes.dependents.map((dependentName) => {
193
+ const dependentField = this.formSchema.find(
194
+ ([, depName]) => depName === dependentName
195
+ );
196
+
197
+ if (dependentField) {
198
+ const dependentAttributes = dependentField[4] || {};
199
+ const dependentFieldId = dependentAttributes.id || dependentName; // Get dependent field ID
200
+
201
+ return {
202
+ dependent: dependentFieldId,
203
+ condition: dependentAttributes.condition || null,
204
+ };
205
+ } else {
206
+ console.warn(`Dependent field "${dependentName}" not found in schema.`);
207
+ }
208
+ });
209
+
210
+ // Add state tracking for the parent field
211
+ this.dependencyGraph[fieldId].push({ state: null });
212
+
213
+ // console.log("Graph", this.dependencyGraph[fieldId]);
214
+
215
+ // Attach the input change event listener to the parent field
216
+ this.attachInputChangeListener(fieldId);
217
+ }
218
+
219
+ // Hide dependent fields initially
220
+ if (attributes.dependents) {
221
+
222
+ attributes.dependents.forEach((dependentName) => {
223
+ const dependentField = this.formSchema.find(
224
+ ([, depName]) => depName === dependentName
225
+ );
226
+ const dependentAttributes = dependentField ? dependentField[4] || {} : {};
227
+ const dependentFieldId = dependentAttributes.id || dependentName;
228
+
229
+ //alert(dependentFieldId);
230
+
231
+ const inputBlock = document.querySelector(`#${dependentFieldId}-block`);
232
+ //alert(inputBlock);
233
+
234
+
235
+ if (inputBlock) {
236
+ // alert(dependentName);
237
+ inputBlock.style.display = 'none'; // Hide dependent field by default
238
+ }
239
+ });
240
+ }
241
+ });
242
+
243
+ // console.log("Dependency Graph:", this.dependencyGraph);
244
+ }
245
+
246
+
247
+ // Attach Event Listeners
248
+ attachInputChangeListener(parentField) {
249
+ const fieldElement = document.getElementById(parentField);
250
+ //alert(parentField);
251
+
252
+ if (fieldElement) {
253
+ fieldElement.addEventListener('input', (event) => {
254
+ const value = event.target.value;
255
+ this.handleParentFieldChange(parentField, value);
256
+ });
257
+ }
258
+ }
259
+
260
+
261
+ handleParentFieldChange(parentFieldId, value) {
262
+ const dependencies = this.dependencyGraph[parentFieldId];
263
+
264
+ if (dependencies) {
265
+ // Update the state of the parent field
266
+ this.dependencyGraph[parentFieldId].forEach((dep) => {
267
+ if (dep.state !== undefined) {
268
+ dep.state = value; // Set state to the selected value
269
+ }
270
+ });
271
+
272
+ // Log the updated dependency graph for the parent field
273
+ // console.log(`Updated Dependency Graph for ${parentFieldId}:`, this.dependencyGraph[parentFieldId]);
274
+
275
+ // Notify all observers (dependent fields)
276
+ dependencies.forEach((dependency) => {
277
+ if (dependency.dependent) {
278
+ const observerId = dependency.dependent + "-block"; // Ensure we're targeting the wrapper
279
+ const inputBlock = document.getElementById(observerId); // Find the wrapper element
280
+
281
+ if (inputBlock) {
282
+ // Check if the condition for the observer is satisfied
283
+ const conditionMet = typeof dependency.condition === 'function'
284
+ ? dependency.condition(value)
285
+ : value === dependency.condition;
286
+
287
+ // Debug the condition evaluation
288
+ // console.log(`Checking condition for ${observerId}: `, value, "==", dependency.condition, "Result:", conditionMet);
289
+
290
+ // Toggle visibility based on the condition
291
+ inputBlock.style.display = conditionMet ? 'block' : 'none';
292
+
293
+ // Adjust the 'required' attribute for all inputs within the block based on visibility
294
+ const inputs = inputBlock.querySelectorAll('input, select, textarea');
295
+ inputs.forEach((input) => {
296
+ if (conditionMet) {
297
+ input.required = input.getAttribute('data-original-required') === 'true'; // Restore original required state
298
+ } else {
299
+ input.setAttribute('data-original-required', input.required); // Save original required state
300
+ input.required = false; // Remove required attribute when hiding
301
+ }
302
+ });
303
+ } else {
304
+ console.warn(`Wrapper block with ID ${observerId} not found.`);
305
+ }
306
+ }
307
+ });
308
+ }
309
+ }
310
+
311
+ // Register observers for each dependent field
312
+ registerObservers() {
313
+ this.formSchema.forEach((field) => {
314
+ const [type, name, label, validate, attributes = {}] = field;
315
+ const fieldId = attributes.id || name;
316
+
317
+ if (attributes.dependents) {
318
+ attributes.dependents.forEach((dependentName) => {
319
+ // Ensure the dependency graph exists for the parent field
320
+ if (this.dependencyGraph[fieldId]) {
321
+ // Find the dependent field in the form schema
322
+ const dependentField = this.formSchema.find(
323
+ ([, depName]) => depName === dependentName
324
+ );
325
+
326
+ // If the dependent field exists, register it as an observer
327
+ if (dependentField) {
328
+ const dependentFieldId = dependentField[4]?.id || dependentName;
329
+ this.dependencyGraph[fieldId].forEach((dependency) => {
330
+ if (dependency.dependent === dependentName) {
331
+ // Store the dependent as an observer for this parent field
332
+ if (!dependency.observers) {
333
+ dependency.observers = [];
334
+ }
335
+ dependency.observers.push(dependentFieldId);
336
+ }
337
+ });
338
+ }
339
+ }
340
+ });
341
+ }
342
+ });
343
+
344
+ // console.log("Observers Registered:", JSON.stringify(this.dependencyGraph,null,2));
345
+ }
346
+
347
+
348
+ 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
+ }
356
+
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');
361
+
362
+ // If themeColor is provided, use it to create a custom theme
363
+ if (this.themeColor) {
364
+ this.applyCustomTheme(formContainerId);
365
+ 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
+ }
396
+
397
+ applyCustomTheme(formContainerId) {
398
+ const formContainer = document.getElementById(formContainerId);
399
+ const spinnerContainer = document.getElementById('formiqueSpinner');
400
+
401
+ if (!formContainer) return;
402
+
403
+ formContainer.classList.add('custom-theme', 'formique');
404
+ spinnerContainer.classList.add('custom-theme');
405
+
406
+ this.activeTheme = 'custom-theme';
407
+
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
+
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
+ `;
430
+
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
+ }
438
+
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})`;
450
+ }
451
+
452
+
453
+ // renderFormElement method
454
+ renderFormElement() {
455
+ let formHTML = '<form';
456
+
457
+ // Ensure `this.formParams` is being passed in as the source of form attributes
458
+ const paramsToUse = this.formParams || {};
459
+ //console.log(paramsToUse);
460
+
461
+ if (!paramsToUse.id) {
462
+ paramsToUse.id = this.formId;
463
+ }
464
+
465
+
466
+ // Dynamically add attributes if they are present in the parameters
467
+ Object.keys(paramsToUse).forEach(key => {
468
+ const value = paramsToUse[key];
469
+ if (value !== undefined && value !== null) {
470
+ // Handle boolean attributes (without values, just their presence)
471
+ if (typeof value === 'boolean') {
472
+ if (value) {
473
+ formHTML += ` ${key}`; // Simply add the key as the attribute
474
+ }
475
+ } else {
476
+ // Handle other attributes (key-value pairs)
477
+ const formattedKey = key === 'accept_charset' ? 'accept-charset' : key.replace(/_/g, '-');
478
+ formHTML += ` ${formattedKey}="${value}"`;
479
+ //console.log("HERE",formHTML);
480
+ }
481
+ }
482
+ });
483
+
484
+ // Conditionally add CSRF token if 'laravel' is true
485
+ if (paramsToUse.laravel) {
486
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
487
+ if (csrfToken) {
488
+ formHTML += `<input type="hidden" name="_token" value="${csrfToken}">`;
489
+ }
490
+ }
491
+
492
+ // Close the <form> tag
493
+ formHTML += '>\n';
494
+
495
+ // Return the generated form HTML
496
+ return formHTML;
497
+ }
498
+
499
+
500
+
501
+
502
+ // Main renderForm method
503
+ renderForm() {
504
+ // Process each field synchronously
505
+ const formHTML = this.formSchema.map(field => {
506
+ const [type, name, label, validate, attributes = {},options] = field;
507
+ return this.renderField(type, name, label, validate, attributes, options);
508
+ }).join('');
509
+ this.formMarkUp += formHTML;
510
+ }
511
+
512
+
513
+ renderField(type, name, label, validate, attributes, options) {
514
+ const fieldRenderMap = {
515
+ 'text': this.renderTextField,
516
+ 'email': this.renderEmailField,
517
+ 'number': this.renderNumberField,
518
+ 'password': this.renderPasswordField,
519
+ 'textarea': this.renderTextAreaField,
520
+ 'tel': this.renderTelField,
521
+ 'date': this.renderDateField,
522
+ 'time': this.renderTimeField,
523
+ 'datetime-local': this.renderDateTimeField,
524
+ 'month': this.renderMonthField,
525
+ 'week': this.renderWeekField,
526
+ 'url': this.renderUrlField,
527
+ 'search': this.renderSearchField,
528
+ 'color': this.renderColorField,
529
+ 'checkbox': this.renderCheckboxField,
530
+ 'radio': this.renderRadioField,
531
+ 'file': this.renderFileField,
532
+ 'hidden': this.renderHiddenField,
533
+ 'image': this.renderImageField,
534
+ 'textarea': this.renderTextAreaField,
535
+ 'singleSelect': this.renderSingleSelectField,
536
+ 'multipleSelect': this.renderMultipleSelectField,
537
+ 'dynamicSingleSelect': this.renderDynamicSingleSelectField,
538
+ 'range': this.renderRangeField,
539
+ 'submit': this.renderSubmitButton,
540
+ };
541
+
542
+ const renderMethod = fieldRenderMap[type];
543
+
544
+ if (renderMethod) {
545
+ return renderMethod.call(this, type, name, label, validate, attributes, options);
546
+ } else {
547
+ console.warn(`Unsupported field type '${type}' encountered.`);
548
+ return ''; // or handle gracefully
549
+ }
550
+ }
551
+
552
+
553
+
554
+ // Show success/error messages (externalizable)
555
+ showSuccessMessage(message) {
556
+ const container = document.getElementById(this.formContainerId);
557
+ container.innerHTML = `
558
+ <div class="formique-success ${this.activeTheme}">${message}</div>
559
+ ${this.formSettings.redirectURL
560
+ ? `<meta http-equiv="refresh" content="2;url=${this.formSettings.redirectURL}">`
561
+ : ""}
562
+ `;
563
+ }
564
+
565
+ showErrorMessage(message) {
566
+ const container = document.getElementById(this.formContainerId);
567
+ const errorDiv = document.createElement("div");
568
+ errorDiv.className = `formique-error ${this.activeTheme}`;
569
+ errorDiv.textContent = `${message}`;
570
+ container.prepend(errorDiv);
571
+ }
572
+
573
+ // Check if form has file inputs
574
+ hasFileInputs(form) {
575
+ return Boolean(form.querySelector('input[type="file"]'));
576
+ }
577
+
578
+
579
+
580
+
581
+
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
+ });
613
+
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;
618
+ }
619
+ // --- End of Payload and Method Logic ---
620
+
621
+ const endpoint = this.formiqueEndpoint || this.formAction;
622
+ const method = this.method || 'POST';
623
+
624
+ // Show spinner when request starts
625
+ document.getElementById("formiqueSpinner").style.display = "flex";
626
+
627
+ const response = await fetch(endpoint, {
628
+ method: method,
629
+ headers: {
630
+ 'Content-Type': 'application/json',
631
+ 'X-Formique-Version': '1.0'
632
+ },
633
+ body: JSON.stringify(payload)
634
+ });
635
+
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
+ }
650
+
651
+ if (!response.ok) {
652
+ const errorMsg = data.error || `HTTP error! status: ${response.status}`;
653
+ throw new Error(errorMsg);
654
+ }
655
+
656
+ const successMessage = this.formSettings.successMessage || data.message || "Your message has been sent successfully!";
657
+ this.showSuccessMessage(successMessage);
658
+
659
+ } 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.";
662
+ this.showErrorMessage(errorMessage);
663
+ } finally {
664
+ // Ensure spinner is hidden on success or failure
665
+ document.getElementById("formiqueSpinner").style.display = "none";
666
+ }
667
+ };
668
+
669
+ // Email validation helper
670
+ validateEmail(email) {
671
+ const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
672
+ console.log(`Validating email ${email}: ${isValid ? 'valid' : 'invalid'}`); // Debug log
673
+ return isValid;
674
+ }
675
+
676
+
677
+
678
+ // Method to handle on-page form submissions
679
+ handleOnPageFormSubmission(formId) {
680
+ const formElement = document.getElementById(formId);
681
+ //console.warn("handler fired also",formId,this.method,this.formAction);
682
+
683
+ if (formElement) {
684
+ // Gather form data
685
+ const formData = new FormData(formElement);
686
+
687
+ // Submit form data using fetch to a test endpoint
688
+ fetch(this.formAction, {
689
+ method: this.method,
690
+ body: formData
691
+ })
692
+ .then(response => response.json())
693
+ .then(data => {
694
+ console.log('Success:', data);
695
+ // Handle the response data here, e.g., show a success message
696
+
697
+ // Get the form container element
698
+ const formContainer = document.getElementById(this.formContainerId);
699
+
700
+ if (this.redirect && this.redirectURL) {
701
+ window.location.href = this.redirectURL;
702
+ }
703
+
704
+
705
+ if (formContainer) {
706
+ // Create a new div element for the success message
707
+ const successMessageDiv = document.createElement('div');
708
+
709
+ // Add custom classes for styling the success message
710
+ successMessageDiv.classList.add('success-message', 'message-container');
711
+
712
+ // Set the success message text
713
+ successMessageDiv.innerHTML = this.formSettings.successMessage || 'Your details have been successfully submitted!';
714
+
715
+ // Replace the content of the form container with the success message div
716
+ formContainer.innerHTML = ''; // Clear existing content
717
+ formContainer.appendChild(successMessageDiv); // Append the new success message div
718
+ }
719
+
720
+
721
+ })
722
+ .catch(error => {
723
+ console.error('Error:', error);
724
+
725
+ const formContainer = document.getElementById(this.formContainerId);
726
+ if (formContainer) {
727
+ // Check if an error message div already exists and remove it
728
+ let existingErrorDiv = formContainer.querySelector('.error-message');
729
+ if (existingErrorDiv) {
730
+ existingErrorDiv.remove();
731
+ }
732
+
733
+ // Create a new div element for the error message
734
+ const errorMessageDiv = document.createElement('div');
735
+
736
+ // Add custom classes for styling the error message
737
+ errorMessageDiv.classList.add('error-message', 'message-container');
738
+
739
+ // Set the error message text
740
+ let err = this.formSettings.errorMessage || 'An error occurred while submitting the form. Please try again.';
741
+ err = `${err}<br/>Details: ${error.message}`;
742
+ errorMessageDiv.innerHTML = err;
743
+
744
+ // Append the new error message div to the form container
745
+ //formContainer.appendChild(errorMessageDiv);
746
+ }
747
+ });
748
+
749
+ }
750
+ }
751
+
752
+
753
+
754
+
755
+ // text field rendering
756
+ renderTextField(type, name, label, validate, attributes) {
757
+ const textInputValidationAttributes = [
758
+ 'required',
759
+ 'minlength',
760
+ 'maxlength',
761
+ 'pattern',
762
+ ];
763
+
764
+ // Construct validation attributes
765
+ let validationAttrs = '';
766
+ if (validate) {
767
+ Object.entries(validate).forEach(([key, value]) => {
768
+ if (textInputValidationAttributes.includes(key)) {
769
+ if (typeof value === 'boolean' && value) {
770
+ validationAttrs += ` ${key}\n`;
771
+ } else {
772
+ switch (key) {
773
+ case 'pattern':
774
+ case 'minlength':
775
+ case 'maxlength':
776
+ validationAttrs += ` ${key}="${value}"\n`;
777
+ break;
778
+ default:
779
+ if (!textInputValidationAttributes.includes(key)) {
780
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'number'.\x1b[0m`);
781
+ }
782
+ break;
783
+ }
784
+ }
785
+ } else {
786
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'text'.\x1b[0m`);
787
+ }
788
+ });
789
+ }
790
+
791
+ // Handle the binding syntax
792
+ let bindingDirective = '';
793
+ if (attributes.binding) {
794
+ if (attributes.binding === 'bind:value' && name) {
795
+ bindingDirective = `bind:value="${name}"\n`;
796
+ }
797
+ if (attributes.binding.startsWith('::') && name) {
798
+ bindingDirective = `bind:value="${name}"\n`;
799
+ }
800
+ if (attributes.binding && !name) {
801
+ console.log(`\x1b[31m%s\x1b[0m`,`You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
802
+ return;
803
+ }
804
+ }
805
+
806
+
807
+
808
+ // Get the id from attributes or fall back to name
809
+ let id = attributes.id || name;
810
+ // Determine if semanti is true based on formSettings
811
+ const framework = this.formSettings?.framework || false;
812
+
813
+ // Construct additional attributes dynamically
814
+ let additionalAttrs = '';
815
+ for (const [key, value] of Object.entries(attributes)) {
816
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
817
+ // Handle event attributes
818
+ if (framework === 'semantq') {
819
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
820
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
821
+ } else {
822
+ // Add parentheses if not present
823
+ const eventValue = value.endsWith('()') ? value : `${value}()`;
824
+ additionalAttrs += ` ${key}="${eventValue}"\n`;
825
+ }
826
+ } else {
827
+ // Handle boolean attributes
828
+ if (value === true) {
829
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
830
+ } else if (value !== false) {
831
+ // Convert underscores to hyphens and set the attribute
832
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
833
+ }
834
+ }
835
+ }
836
+ }
837
+
838
+
839
+
840
+ let inputClass;
841
+ if ('class' in attributes) {
842
+ inputClass = attributes.class;
843
+ } else {
844
+ inputClass = this.inputClass;
845
+ }
846
+ // Construct the final HTML string
847
+ let formHTML = `
848
+ <div class="${this.divClass}" id="${id + '-block'}">
849
+ <label for="${id}">${label}
850
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
851
+ </label>
852
+ <input
853
+ type="${type}"
854
+ name="${name}"
855
+ ${bindingDirective}
856
+ id="${id}"
857
+ class="${inputClass}"
858
+ ${additionalAttrs}
859
+ ${validationAttrs}
860
+ ${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')} />
861
+ </div>
862
+ `.replace(/^\s*\n/gm, '').trim();
863
+
864
+ let formattedHtml = formHTML;
865
+
866
+ // Apply vertical layout to the <input> element only
867
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
868
+ // Reformat attributes into a vertical layout
869
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
870
+ return `<input\n${attributes}\n/>`;
871
+ });
872
+
873
+
874
+ this.formMarkUp +=formattedHtml;
875
+ //return formattedHtml;
876
+ }
877
+
878
+
879
+
880
+
881
+ // Specific rendering method for rendering the email field
882
+ renderEmailField(type, name, label, validate, attributes) {
883
+ // Define valid attributes for the email input type
884
+
885
+ const emailInputValidationAttributes = [
886
+ 'required',
887
+ 'pattern',
888
+ 'minlength',
889
+ 'maxlength',
890
+ 'multiple'
891
+ ];
892
+
893
+
894
+ // Construct validation attributes
895
+ let validationAttrs = '';
896
+ if (validate) {
897
+ Object.entries(validate).forEach(([key, value]) => {
898
+ if (emailInputValidationAttributes.includes(key)) {
899
+ if (typeof value === 'boolean' && value) {
900
+ validationAttrs += ` ${key}\n`;
901
+ } else {
902
+ switch (key) {
903
+ case 'pattern':
904
+ case 'minlength':
905
+ case 'maxlength':
906
+ validationAttrs += ` ${key}="${value}"\n`;
907
+ break;
908
+ default:
909
+ if (!emailInputValidationAttributes.includes(key)) {
910
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'number'.\x1b[0m`);
911
+ }
912
+ break;
913
+ }
914
+ }
915
+ } else {
916
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'email'.\x1b[0m`);
917
+ }
918
+ });
919
+ }
920
+
921
+ // Handle the binding syntax
922
+ let bindingDirective = '';
923
+ if (attributes.binding) {
924
+ if (attributes.binding === 'bind:value' && name) {
925
+ bindingDirective = `bind:value="${name}"\n`;
926
+ }
927
+ if (attributes.binding.startsWith('::') && name) {
928
+ bindingDirective = `bind:value="${name}"\n`;
929
+ }
930
+ if (attributes.binding && !name) {
931
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
932
+ return;
933
+ }
934
+ }
935
+
936
+
937
+ // Get the id from attributes or fall back to name
938
+ let id = attributes.id || name;
939
+
940
+ // Construct additional attributes dynamically
941
+ let additionalAttrs = '';
942
+ for (const [key, value] of Object.entries(attributes)) {
943
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
944
+ // Handle event attributes
945
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
946
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
947
+ } else {
948
+ // Handle boolean attributes
949
+ if (value === true) {
950
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
951
+ } else if (value !== false) {
952
+ // Convert underscores to hyphens and set the attribute
953
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
954
+ }
955
+ }
956
+ }
957
+ }
958
+
959
+
960
+ let inputClass;
961
+ if ('class' in attributes) {
962
+ inputClass = attributes.class;
963
+ } else {
964
+ inputClass = this.inputClass;
965
+ }
966
+ // Construct the final HTML string
967
+ let formHTML = `
968
+ <div class="${this.divClass}" id="${id + '-block'}">
969
+ <label for="${id}">${label}
970
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
971
+ </label>
972
+ <input
973
+ type="${type}"
974
+ name="${name}"
975
+ ${bindingDirective}
976
+ id="${id}"
977
+ class="${inputClass}"
978
+ ${additionalAttrs}
979
+ ${validationAttrs}
980
+ ${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}
981
+
982
+ />
983
+ </div>
984
+ `.replace(/^\s*\n/gm, '').trim();
985
+
986
+ let formattedHtml = formHTML;
987
+
988
+ // Apply vertical layout to the <input> element only
989
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
990
+ // Reformat attributes into a vertical layout
991
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
992
+ return `<input\n${attributes}\n/>`;
993
+ });
994
+
995
+ // Ensure the <div> block starts on a new line and remove extra blank lines
996
+
997
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
998
+ // Ensure <div> starts on a new line
999
+ return `\n${match}\n`;
1000
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
1001
+
1002
+
1003
+ this.formMarkUp += formattedHtml;
1004
+
1005
+ //return formattedHtml;
1006
+ //return this.formMarkUp;
1007
+ //console.log(this.formMarkUp);
1008
+ }
1009
+
1010
+
1011
+
1012
+ renderNumberField(type, name, label, validate, attributes) {
1013
+ // Define valid attributes for the number input type
1014
+
1015
+ const numberInputValidationAttributes = [
1016
+ 'required',
1017
+ 'min',
1018
+ 'max',
1019
+ 'step',
1020
+ ];
1021
+
1022
+ // Construct validation attributes
1023
+ let validationAttrs = '';
1024
+ if (validate) {
1025
+ Object.entries(validate).forEach(([key, value]) => {
1026
+ if (numberInputValidationAttributes.includes(key)) {
1027
+ if (typeof value === 'boolean' && value) {
1028
+ validationAttrs += ` ${key}\n`;
1029
+ } else {
1030
+ switch (key) {
1031
+ case 'min':
1032
+ case 'max':
1033
+ validationAttrs += ` ${key}="${value}"\n`;
1034
+ break;
1035
+ case 'step':
1036
+ validationAttrs += ` ${key}="${value}"\n`;
1037
+ break;
1038
+ default:
1039
+ if (!numberInputValidationAttributes.includes(key)) {
1040
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'number'.\x1b[0m`);
1041
+ }
1042
+ break;
1043
+ }
1044
+ }
1045
+ } else {
1046
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'number'.\x1b[0m`);
1047
+ }
1048
+ });
1049
+ }
1050
+
1051
+ // Handle the binding syntax
1052
+ let bindingDirective = '';
1053
+ if (attributes.binding) {
1054
+ if (attributes.binding === 'bind:value' && name) {
1055
+ bindingDirective = `bind:value="${name}"\n`;
1056
+ }
1057
+ if (attributes.binding.startsWith('::') && name) {
1058
+ bindingDirective = `bind:value="${name}"\n`;
1059
+ }
1060
+ if (attributes.binding && !name) {
1061
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1062
+ return;
1063
+ }
1064
+ }
1065
+
1066
+
1067
+
1068
+ // Get the id from attributes or fall back to name
1069
+ let id = attributes.id || name;
1070
+
1071
+ // Construct additional attributes dynamically
1072
+ let additionalAttrs = '';
1073
+ for (const [key, value] of Object.entries(attributes)) {
1074
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
1075
+ // Handle event attributes
1076
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
1077
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
1078
+ } else {
1079
+ // Handle boolean attributes
1080
+ if (value === true) {
1081
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
1082
+ } else if (value !== false) {
1083
+ // Convert underscores to hyphens and set the attribute
1084
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
1085
+ }
1086
+ }
1087
+ }
1088
+ }
1089
+
1090
+ let inputClass;
1091
+ if ('class' in attributes) {
1092
+ inputClass = attributes.class;
1093
+ } else {
1094
+ inputClass = this.inputClass;
1095
+ }
1096
+ // Construct the final HTML string
1097
+ let formHTML = `
1098
+ <div class="${this.divClass}" id="${id + '-block'}">
1099
+ <label for="${id}">${label}
1100
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1101
+ </label>
1102
+ <input
1103
+ type="${type}"
1104
+ name="${name}"
1105
+ ${bindingDirective}
1106
+ id="${id}"
1107
+ class="${inputClass}"
1108
+ ${additionalAttrs}
1109
+ ${validationAttrs}
1110
+ />
1111
+ </div>
1112
+ `.replace(/^\s*\n/gm, '').trim();
1113
+
1114
+ let formattedHtml = formHTML;
1115
+
1116
+ // Apply vertical layout to the <input> element only
1117
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
1118
+ // Reformat attributes into a vertical layout
1119
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
1120
+ return `<input\n${attributes}\n/>`;
1121
+ });
1122
+
1123
+ // Ensure the <div> block starts on a new line and remove extra blank lines
1124
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
1125
+ // Ensure <div> starts on a new line
1126
+ return `\n${match}\n`;
1127
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
1128
+
1129
+ //return formattedHtml;
1130
+ this.formMarkUp +=formattedHtml;
1131
+ }
1132
+
1133
+
1134
+
1135
+ // New method for rendering password fields
1136
+ renderPasswordField(type, name, label, validate, attributes) {
1137
+ // Define valid attributes for the password input type
1138
+
1139
+
1140
+ const passwordInputValidationAttributes = [
1141
+ 'required',
1142
+ 'minlength',
1143
+ 'maxlength',
1144
+ 'pattern',
1145
+ ];
1146
+
1147
+ // Construct validation attributes
1148
+ let validationAttrs = '';
1149
+ if (validate) {
1150
+ Object.entries(validate).forEach(([key, value]) => {
1151
+ if (passwordInputValidationAttributes.includes(key)) {
1152
+ if (typeof value === 'boolean' && value) {
1153
+ validationAttrs += ` ${key}\n`;
1154
+ } else {
1155
+ switch (key) {
1156
+ case 'minlength':
1157
+ case 'maxlength':
1158
+ case 'pattern':
1159
+ validationAttrs += ` ${key}="${value}"\n`;
1160
+ break;
1161
+ default:
1162
+ if (!passwordInputValidationAttributes.includes(key)) {
1163
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'password'.\x1b[0m`);
1164
+ }
1165
+ break;
1166
+ }
1167
+ }
1168
+ } else {
1169
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'password'.\x1b[0m`);
1170
+ }
1171
+ });
1172
+ }
1173
+
1174
+ // Handle the binding syntax
1175
+ // Handle the binding syntax
1176
+ let bindingDirective = '';
1177
+ if (attributes.binding) {
1178
+ if (attributes.binding === 'bind:value' && name) {
1179
+ bindingDirective = `bind:value="${name}"\n`;
1180
+ }
1181
+ if (attributes.binding.startsWith('::') && name) {
1182
+ bindingDirective = `bind:value="${name}"\n`;
1183
+ }
1184
+ if (attributes.binding && !name) {
1185
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1186
+ return;
1187
+ }
1188
+ }
1189
+
1190
+
1191
+
1192
+
1193
+ // Get the id from attributes or fall back to name
1194
+ let id = attributes.id || name;
1195
+
1196
+ // Construct additional attributes dynamically
1197
+ let additionalAttrs = '';
1198
+ for (const [key, value] of Object.entries(attributes)) {
1199
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
1200
+ // Handle event attributes
1201
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
1202
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
1203
+ } else {
1204
+ // Handle boolean attributes
1205
+ if (value === true) {
1206
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
1207
+ } else if (value !== false) {
1208
+ // Convert underscores to hyphens and set the attribute
1209
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
1210
+ }
1211
+ }
1212
+ }
1213
+ }
1214
+
1215
+ let inputClass;
1216
+ if ('class' in attributes) {
1217
+ inputClass = attributes.class;
1218
+ } else {
1219
+ inputClass = this.inputClass;
1220
+ }
1221
+ // Construct the final HTML string
1222
+ let formHTML = `
1223
+ <div class="${this.divClass}" id="${id + '-block'}">
1224
+ <label for="${id}">${label}
1225
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1226
+ </label>
1227
+ <input
1228
+ type="${type}"
1229
+ name="${name}"
1230
+ ${bindingDirective}
1231
+ id="${id}"
1232
+ class="${inputClass}"
1233
+ ${additionalAttrs}
1234
+ ${validationAttrs}
1235
+ />
1236
+ </div>
1237
+ `.replace(/^\s*\n/gm, '').trim();
1238
+
1239
+ let formattedHtml = formHTML;
1240
+
1241
+ // Apply vertical layout to the <input> element only
1242
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
1243
+ // Reformat attributes into a vertical layout
1244
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
1245
+ return `<input\n${attributes}\n/>`;
1246
+ });
1247
+
1248
+ // Ensure the <div> block starts on a new line and remove extra blank lines
1249
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
1250
+ // Ensure <div> starts on a new line
1251
+ return `\n${match}\n`;
1252
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
1253
+
1254
+ //return formattedHtml;
1255
+ this.formMarkUp +=formattedHtml;
1256
+ }
1257
+
1258
+
1259
+ // Textarea field rendering
1260
+
1261
+ renderTextAreaField(type, name, label, validate, attributes) {
1262
+ const textInputValidationAttributes = [
1263
+ 'required',
1264
+ 'minlength',
1265
+ 'maxlength',
1266
+ 'pattern',
1267
+ ];
1268
+
1269
+ // Construct validation attributes
1270
+ let validationAttrs = '';
1271
+ if (validate) {
1272
+ Object.entries(validate).forEach(([key, value]) => {
1273
+ if (textInputValidationAttributes.includes(key)) {
1274
+ if (typeof value === 'boolean' && value) {
1275
+ validationAttrs += ` ${key}\n`;
1276
+ } else {
1277
+ switch (key) {
1278
+ case 'pattern':
1279
+ case 'minlength':
1280
+ case 'maxlength':
1281
+ validationAttrs += ` ${key}="${value}"\n`;
1282
+ break;
1283
+ default:
1284
+ if (!textInputValidationAttributes.includes(key)) {
1285
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'number'.\x1b[0m`);
1286
+ }
1287
+ break;
1288
+ }
1289
+ }
1290
+ } else {
1291
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'text'.\x1b[0m`);
1292
+ }
1293
+ });
1294
+ }
1295
+
1296
+
1297
+
1298
+ // Handle the binding syntax
1299
+ let bindingDirective = '';
1300
+ if (attributes.binding) {
1301
+ if (attributes.binding === 'bind:value' && name) {
1302
+ bindingDirective = `bind:value="${name}"\n`;
1303
+ }
1304
+ if (attributes.binding.startsWith('::') && name) {
1305
+ bindingDirective = `bind:value="${name}"\n`;
1306
+ }
1307
+ if (attributes.binding && !name) {
1308
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1309
+ return;
1310
+ }
1311
+ }
1312
+
1313
+
1314
+
1315
+ // Get the id from attributes or fall back to name
1316
+ let id = attributes.id || name;
1317
+ // Determine if semanti is true based on formSettings
1318
+ const framework = this.formSettings?.framework || false;
1319
+
1320
+ // Construct additional attributes dynamically
1321
+ let additionalAttrs = '';
1322
+ for (const [key, value] of Object.entries(attributes)) {
1323
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
1324
+ // Handle event attributes
1325
+ if (framework === 'semantq') {
1326
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
1327
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
1328
+ } else {
1329
+ // Add parentheses if not present
1330
+ const eventValue = value.endsWith('()') ? value : `${value}()`;
1331
+ additionalAttrs += ` ${key}="${eventValue}"\n`;
1332
+ }
1333
+ } else {
1334
+ // Handle boolean attributes
1335
+ if (value === true) {
1336
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
1337
+ } else if (value !== false) {
1338
+ // Convert underscores to hyphens and set the attribute
1339
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
1340
+ }
1341
+ }
1342
+ }
1343
+ }
1344
+
1345
+
1346
+
1347
+ let inputClass;
1348
+ if ('class' in attributes) {
1349
+ inputClass = attributes.class;
1350
+ } else {
1351
+ inputClass = this.inputClass;
1352
+ }
1353
+
1354
+ // Construct the final HTML string for textarea
1355
+ let formHTML = `
1356
+ <div class="${this.divClass}" id="${id + '-block'}">
1357
+ <label for="${id}">${label}
1358
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1359
+ </label>
1360
+ <textarea
1361
+ name="${name}"
1362
+ ${bindingDirective}
1363
+ id="${id}"
1364
+ class="${inputClass}"
1365
+ ${additionalAttrs}
1366
+ ${validationAttrs}
1367
+ ${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>
1368
+ </textarea>
1369
+ </div>
1370
+ `.replace(/^\s*\n/gm, '').trim();
1371
+
1372
+ let formattedHtml = formHTML;
1373
+
1374
+ // Apply vertical layout to the <textarea> element only
1375
+ formattedHtml = formattedHtml.replace(/<textarea\s+([^>]*)>\s*<\/textarea>/, (match, p1) => {
1376
+ // Reformat attributes into a vertical layout
1377
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
1378
+ return `<textarea\n${attributes}\n></textarea>`;
1379
+ });
1380
+
1381
+ this.formMarkUp += formattedHtml;
1382
+
1383
+ }
1384
+
1385
+
1386
+ // New method for rendering tel fields
1387
+ renderTelField(type, name, label, validate, attributes) {
1388
+
1389
+ const telInputValidationAttributes = [
1390
+ 'required',
1391
+ 'pattern',
1392
+ 'minlength',
1393
+ 'maxlength',
1394
+ ];
1395
+
1396
+
1397
+ // Construct validation attributes
1398
+ let validationAttrs = '';
1399
+ if (validate) {
1400
+ Object.entries(validate).forEach(([key, value]) => {
1401
+ if (telInputValidationAttributes.includes(key)) {
1402
+ if (typeof value === 'boolean' && value) {
1403
+ validationAttrs += ` ${key}\n`;
1404
+ } else {
1405
+ switch (key) {
1406
+ case 'pattern':
1407
+ case 'minlength':
1408
+ case 'maxlength':
1409
+ validationAttrs += ` ${key}="${value}"\n`;
1410
+ break;
1411
+ default:
1412
+ if (!telInputValidationAttributes.includes(key)) {
1413
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'tel'.\x1b[0m`);
1414
+ }
1415
+ break;
1416
+ }
1417
+ }
1418
+ } else {
1419
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'tel'.\x1b[0m`);
1420
+ }
1421
+ });
1422
+ }
1423
+
1424
+ // 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
+ }
1436
+
1437
+ // Get the id from attributes or fall back to name
1438
+ let id = attributes.id || name;
1439
+
1440
+ // Construct additional attributes dynamically
1441
+ let additionalAttrs = '';
1442
+ for (const [key, value] of Object.entries(attributes)) {
1443
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
1444
+ // Handle event attributes
1445
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
1446
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
1447
+ } else {
1448
+ // Handle boolean attributes
1449
+ if (value === true) {
1450
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
1451
+ } else if (value !== false) {
1452
+ // Convert underscores to hyphens and set the attribute
1453
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
1454
+ }
1455
+ }
1456
+ }
1457
+ }
1458
+
1459
+ let inputClass;
1460
+ if ('class' in attributes) {
1461
+ inputClass = attributes.class;
1462
+ } else {
1463
+ inputClass = this.inputClass;
1464
+ }
1465
+ // Construct the final HTML string
1466
+ let formHTML = `
1467
+ <div class="${this.divClass}" id="${id + '-block'}">
1468
+ <label for="${id}">${label}
1469
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1470
+ </label>
1471
+ <input
1472
+ type="${type}"
1473
+ name="${name}"
1474
+ ${bindingDirective}
1475
+ id="${id}"
1476
+ class="${inputClass}"
1477
+ ${additionalAttrs}
1478
+ ${validationAttrs}
1479
+ />
1480
+ </div>
1481
+ `.replace(/^\s*\n/gm, '').trim();
1482
+
1483
+ let formattedHtml = formHTML;
1484
+
1485
+ // Apply vertical layout to the <input> element only
1486
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
1487
+ // Reformat attributes into a vertical layout
1488
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
1489
+ return `<input\n${attributes}\n/>`;
1490
+ });
1491
+
1492
+ // Ensure the <div> block starts on a new line and remove extra blank lines
1493
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
1494
+ // Ensure <div> starts on a new line
1495
+ return `\n${match}\n`;
1496
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
1497
+
1498
+ return formattedHtml;
1499
+ }
1500
+
1501
+ renderDateField(type, name, label, validate, attributes) {
1502
+ // Define valid attributes for the date input type
1503
+ const dateInputAttributes = [
1504
+ 'required',
1505
+ 'min',
1506
+ 'max',
1507
+ 'step',
1508
+ 'placeholder',
1509
+ 'readonly',
1510
+ 'disabled',
1511
+ 'autocomplete',
1512
+ 'spellcheck',
1513
+ 'inputmode',
1514
+ 'title',
1515
+ ];
1516
+
1517
+ // Construct validation attributes
1518
+ let validationAttrs = '';
1519
+ if (validate) {
1520
+ Object.entries(validate).forEach(([key, value]) => {
1521
+ if (dateInputAttributes.includes(key)) {
1522
+ if (typeof value === 'boolean' && value) {
1523
+ validationAttrs += ` ${key}\n`;
1524
+ } else {
1525
+ switch (key) {
1526
+ case 'min':
1527
+ case 'max':
1528
+ case 'step':
1529
+ validationAttrs += ` ${key}="${value}"\n`;
1530
+ break;
1531
+ default:
1532
+ if (!dateInputAttributes.includes(key)) {
1533
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'date'.\x1b[0m`);
1534
+ }
1535
+ break;
1536
+ }
1537
+ }
1538
+ } else {
1539
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'date'.\x1b[0m`);
1540
+ }
1541
+ });
1542
+ }
1543
+
1544
+ // Handle the binding syntax
1545
+ let bindingDirective = '';
1546
+ if (attributes.binding === 'bind:value' && name) {
1547
+ bindingDirective = `bind:value="${name}"\n`;
1548
+ }
1549
+ if (attributes.binding.startsWith('::') && name) {
1550
+ bindingDirective = `bind:value="${name}"\n`;
1551
+ }
1552
+ if (attributes.binding && !name) {
1553
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1554
+ return;
1555
+ }
1556
+
1557
+ // Get the id from attributes or fall back to name
1558
+ let id = attributes.id || name;
1559
+
1560
+ // Construct additional attributes dynamically
1561
+ let additionalAttrs = '';
1562
+ for (const [key, value] of Object.entries(attributes)) {
1563
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
1564
+ // Handle event attributes
1565
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
1566
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
1567
+ } else {
1568
+ // Handle boolean attributes
1569
+ if (value === true) {
1570
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
1571
+ } else if (value !== false) {
1572
+ // Convert underscores to hyphens and set the attribute
1573
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
1574
+ }
1575
+ }
1576
+ }
1577
+ }
1578
+
1579
+ let inputClass;
1580
+ if ('class' in attributes) {
1581
+ inputClass = attributes.class;
1582
+ } else {
1583
+ inputClass = this.inputClass;
1584
+ }
1585
+ // Construct the final HTML string
1586
+ let formHTML = `
1587
+ <div class="${this.divClass}" id="${id + '-block'}">
1588
+ <label for="${id}">${label}
1589
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1590
+ </label>
1591
+ <input
1592
+ type="${type}"
1593
+ name="${name}"
1594
+ ${bindingDirective}
1595
+ id="${id}"
1596
+ class="${inputClass}"
1597
+ ${additionalAttrs}
1598
+ ${validationAttrs}
1599
+ />
1600
+ </div>
1601
+ `.replace(/^\s*\n/gm, '').trim();
1602
+
1603
+ let formattedHtml = formHTML;
1604
+
1605
+ // Apply vertical layout to the <input> element only
1606
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
1607
+ // Reformat attributes into a vertical layout
1608
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
1609
+ return `<input\n${attributes}\n/>`;
1610
+ });
1611
+
1612
+ // Ensure the <div> block starts on a new line and remove extra blank lines
1613
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
1614
+ // Ensure <div> starts on a new line
1615
+ return `\n${match}\n`;
1616
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
1617
+
1618
+ //return formattedHtml;
1619
+ this.formMarkUp +=formattedHtml;
1620
+ }
1621
+
1622
+
1623
+
1624
+ renderTimeField(type, name, label, validate, attributes) {
1625
+ // Define valid attributes for the time input type
1626
+ const timeInputAttributes = [
1627
+ 'required',
1628
+ 'min',
1629
+ 'max',
1630
+ 'step',
1631
+ 'readonly',
1632
+ 'disabled',
1633
+ 'autocomplete',
1634
+ 'spellcheck',
1635
+ 'inputmode',
1636
+ 'title',
1637
+ ];
1638
+
1639
+ // Construct validation attributes
1640
+ let validationAttrs = '';
1641
+ if (validate) {
1642
+ Object.entries(validate).forEach(([key, value]) => {
1643
+ if (timeInputAttributes.includes(key)) {
1644
+ if (typeof value === 'boolean' && value) {
1645
+ validationAttrs += ` ${key}\n`;
1646
+ } else {
1647
+ switch (key) {
1648
+ case 'min':
1649
+ case 'max':
1650
+ case 'step':
1651
+ validationAttrs += ` ${key}="${value}"\n`;
1652
+ break;
1653
+ default:
1654
+ if (!timeInputAttributes.includes(key)) {
1655
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
1656
+ }
1657
+ break;
1658
+ }
1659
+ }
1660
+ } else {
1661
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
1662
+ }
1663
+ });
1664
+ }
1665
+
1666
+ // Handle the binding syntax
1667
+ let bindingDirective = '';
1668
+ if (attributes.binding === 'bind:value' && name) {
1669
+ bindingDirective = `bind:value="${name}"\n`;
1670
+ }
1671
+ if (attributes.binding.startsWith('::') && name) {
1672
+ bindingDirective = `bind:value="${name}"\n`;
1673
+ }
1674
+ if (attributes.binding && !name) {
1675
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1676
+ return;
1677
+ }
1678
+
1679
+ // Get the id from attributes or fall back to name
1680
+ let id = attributes.id || name;
1681
+
1682
+ // Construct additional attributes dynamically
1683
+ let additionalAttrs = '';
1684
+ for (const [key, value] of Object.entries(attributes)) {
1685
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
1686
+ // Handle event attributes
1687
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
1688
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
1689
+ } else {
1690
+ // Handle boolean attributes
1691
+ if (value === true) {
1692
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
1693
+ } else if (value !== false) {
1694
+ // Convert underscores to hyphens and set the attribute
1695
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
1696
+ }
1697
+ }
1698
+ }
1699
+ }
1700
+
1701
+ let inputClass;
1702
+ if ('class' in attributes) {
1703
+ inputClass = attributes.class;
1704
+ } else {
1705
+ inputClass = this.inputClass;
1706
+ }
1707
+ // Construct the final HTML string
1708
+ let formHTML = `
1709
+ <div class="${this.divClass}" id="${id + '-block'}">
1710
+ <label for="${id}">${label}
1711
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1712
+ </label>
1713
+ <input
1714
+ type="${type}"
1715
+ name="${name}"
1716
+ ${bindingDirective}
1717
+ id="${id}"
1718
+ class="${inputClass}"
1719
+ ${additionalAttrs}
1720
+ ${validationAttrs}
1721
+ />
1722
+ </div>
1723
+ `.replace(/^\s*\n/gm, '').trim();
1724
+
1725
+ let formattedHtml = formHTML;
1726
+
1727
+ // Apply vertical layout to the <input> element only
1728
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
1729
+ // Reformat attributes into a vertical layout
1730
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
1731
+ return `<input\n${attributes}\n/>`;
1732
+ });
1733
+
1734
+ // Ensure the <div> block starts on a new line and remove extra blank lines
1735
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
1736
+ // Ensure <div> starts on a new line
1737
+ return `\n${match}\n`;
1738
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
1739
+
1740
+ //return formattedHtml;
1741
+ this.formMarkUp +=formattedHtml;
1742
+ }
1743
+
1744
+
1745
+
1746
+
1747
+ renderDateTimeField(type, name, label, validate, attributes) {
1748
+ // Define valid attributes for the datetime input type
1749
+ const dateTimeInputAttributes = [
1750
+ 'required',
1751
+ 'min',
1752
+ 'max',
1753
+ 'step',
1754
+ 'readonly',
1755
+ 'disabled',
1756
+ 'autocomplete',
1757
+ 'spellcheck',
1758
+ 'inputmode',
1759
+ 'title',
1760
+ ];
1761
+
1762
+ // Construct validation attributes
1763
+ let validationAttrs = '';
1764
+ if (validate) {
1765
+ Object.entries(validate).forEach(([key, value]) => {
1766
+ if (dateTimeInputAttributes.includes(key)) {
1767
+ if (typeof value === 'boolean' && value) {
1768
+ validationAttrs += ` ${key}\n`;
1769
+ } else {
1770
+ switch (key) {
1771
+ case 'min':
1772
+ case 'max':
1773
+ case 'step':
1774
+ validationAttrs += ` ${key}="${value}"\n`;
1775
+ break;
1776
+ default:
1777
+ if (!dateTimeInputAttributes.includes(key)) {
1778
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
1779
+ }
1780
+ break;
1781
+ }
1782
+ }
1783
+ } else {
1784
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
1785
+ }
1786
+ });
1787
+ }
1788
+
1789
+ // Handle the binding syntax
1790
+ let bindingDirective = '';
1791
+ if (attributes.binding) {
1792
+ if (attributes.binding === 'bind:value' && name) {
1793
+ bindingDirective = `bind:value="${name}"\n`;
1794
+ }
1795
+ if (attributes.binding.startsWith('::') && name) {
1796
+ bindingDirective = `bind:value="${name}"\n`;
1797
+ }
1798
+ if (attributes.binding && !name) {
1799
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1800
+ return;
1801
+ }
1802
+ }
1803
+
1804
+
1805
+
1806
+
1807
+ // Get the id from attributes or fall back to name
1808
+ let id = attributes.id || name;
1809
+
1810
+ // Construct additional attributes dynamically
1811
+ let additionalAttrs = '';
1812
+ for (const [key, value] of Object.entries(attributes)) {
1813
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
1814
+ // Handle event attributes
1815
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
1816
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
1817
+ } else {
1818
+ // Handle boolean attributes
1819
+ if (value === true) {
1820
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
1821
+ } else if (value !== false) {
1822
+ // Convert underscores to hyphens and set the attribute
1823
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
1824
+ }
1825
+ }
1826
+ }
1827
+ }
1828
+
1829
+ let inputClass;
1830
+ if ('class' in attributes) {
1831
+ inputClass = attributes.class;
1832
+ } else {
1833
+ inputClass = this.inputClass;
1834
+ }
1835
+ // Construct the final HTML string
1836
+ let formHTML = `
1837
+ <div class="${this.divClass}" id="${id + '-block'}">
1838
+ <label for="${id}">${label}
1839
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1840
+ </label>
1841
+ <input
1842
+ type="${type}"
1843
+ name="${name}"
1844
+ ${bindingDirective}
1845
+ id="${id}"
1846
+ class="${inputClass}"
1847
+ ${additionalAttrs}
1848
+ ${validationAttrs}
1849
+ />
1850
+ </div>
1851
+ `.replace(/^\s*\n/gm, '').trim();
1852
+
1853
+ let formattedHtml = formHTML;
1854
+
1855
+ // Apply vertical layout to the <input> element only
1856
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
1857
+ // Reformat attributes into a vertical layout
1858
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
1859
+ return `<input\n${attributes}\n/>`;
1860
+ });
1861
+
1862
+ // Ensure the <div> block starts on a new line and remove extra blank lines
1863
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
1864
+ // Ensure <div> starts on a new line
1865
+ return `\n${match}\n`;
1866
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
1867
+
1868
+ //return formattedHtml;
1869
+ this.formMarkUp +=formattedHtml;
1870
+ }
1871
+
1872
+
1873
+ renderMonthField(type, name, label, validate, attributes) {
1874
+ // Define valid attributes for the month input type
1875
+ const monthInputAttributes = [
1876
+ 'required',
1877
+ 'min',
1878
+ 'max',
1879
+ 'pattern',
1880
+ 'placeholder',
1881
+ 'readonly',
1882
+ 'disabled',
1883
+ 'size',
1884
+ 'autocomplete',
1885
+ 'spellcheck',
1886
+ 'inputmode',
1887
+ 'title',
1888
+ ];
1889
+
1890
+ // Construct validation attributes
1891
+ let validationAttrs = '';
1892
+ if (validate) {
1893
+ Object.entries(validate).forEach(([key, value]) => {
1894
+ if (monthInputAttributes.includes(key)) {
1895
+ if (typeof value === 'boolean' && value) {
1896
+ validationAttrs += ` ${key}\n`;
1897
+ } else {
1898
+ switch (key) {
1899
+ case 'min':
1900
+ case 'max':
1901
+ case 'pattern':
1902
+ validationAttrs += ` ${key}="${value}"\n`;
1903
+ break;
1904
+ default:
1905
+ if (monthInputAttributes.includes(key)) {
1906
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'month'.\x1b[0m`);
1907
+ }
1908
+ break;
1909
+ }
1910
+ }
1911
+ } else {
1912
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'month'.\x1b[0m`);
1913
+ }
1914
+ });
1915
+ }
1916
+
1917
+ // 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
+ }
1927
+
1928
+ // Get the id from attributes or fall back to name
1929
+ let id = attributes.id || name;
1930
+
1931
+ // Construct additional attributes dynamically
1932
+ let additionalAttrs = '';
1933
+ for (const [key, value] of Object.entries(attributes)) {
1934
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
1935
+ // Handle event attributes
1936
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
1937
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
1938
+ } else {
1939
+ // Handle boolean attributes
1940
+ if (value === true) {
1941
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
1942
+ } else if (value !== false) {
1943
+ // Convert underscores to hyphens and set the attribute
1944
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
1945
+ }
1946
+ }
1947
+ }
1948
+ }
1949
+
1950
+ let inputClass;
1951
+ if ('class' in attributes) {
1952
+ inputClass = attributes.class;
1953
+ } else {
1954
+ inputClass = this.inputClass;
1955
+ }
1956
+ // Construct the final HTML string
1957
+ let formHTML = `
1958
+ <div class="${this.divClass}" id="${id + '-block'}">
1959
+ <label for="${id}">${label}
1960
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1961
+ </label>
1962
+ <input
1963
+ type="${type}"
1964
+ name="${name}"
1965
+ ${bindingDirective}
1966
+ id="${id}"
1967
+ class="${inputClass}"
1968
+ ${additionalAttrs}
1969
+ ${validationAttrs}
1970
+ />
1971
+ </div>
1972
+ `.replace(/^\s*\n/gm, '').trim();
1973
+
1974
+ let formattedHtml = formHTML;
1975
+
1976
+ // Apply vertical layout to the <input> element only
1977
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
1978
+ // Reformat attributes into a vertical layout
1979
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
1980
+ return `<input\n${attributes}\n/>`;
1981
+ });
1982
+
1983
+ // Ensure the <div> block starts on a new line and remove extra blank lines
1984
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
1985
+ // Ensure <div> starts on a new line
1986
+ return `\n${match}\n`;
1987
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
1988
+
1989
+ //return formattedHtml;
1990
+ this.formMarkUp +=formattedHtml;
1991
+ }
1992
+
1993
+
1994
+
1995
+ renderWeekField(type, name, label, validate, attributes) {
1996
+ // Define valid attributes for the week input type
1997
+ const weekInputAttributes = [
1998
+ 'required',
1999
+ 'min',
2000
+ 'max',
2001
+ 'pattern',
2002
+ 'placeholder',
2003
+ 'readonly',
2004
+ 'disabled',
2005
+ 'size',
2006
+ 'autocomplete',
2007
+ 'spellcheck',
2008
+ 'inputmode',
2009
+ 'title',
2010
+ ];
2011
+
2012
+ // Construct validation attributes
2013
+ let validationAttrs = '';
2014
+ if (validate) {
2015
+ Object.entries(validate).forEach(([key, value]) => {
2016
+ if (weekInputAttributes.includes(key)) {
2017
+ if (typeof value === 'boolean' && value) {
2018
+ validationAttrs += ` ${key}\n`;
2019
+ } else {
2020
+ switch (key) {
2021
+ case 'min':
2022
+ case 'max':
2023
+ case 'pattern':
2024
+ validationAttrs += ` ${key}="${value}"\n`;
2025
+ break;
2026
+ default:
2027
+ if (weekInputAttributes.includes(key)) {
2028
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'week'.\x1b[0m`);
2029
+ }
2030
+ break;
2031
+ }
2032
+ }
2033
+ } else {
2034
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'week'.\x1b[0m`);
2035
+ }
2036
+ });
2037
+ }
2038
+
2039
+ // 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
+ }
2049
+
2050
+ // Get the id from attributes or fall back to name
2051
+ let id = attributes.id || name;
2052
+
2053
+ // Construct additional attributes dynamically
2054
+ let additionalAttrs = '';
2055
+ for (const [key, value] of Object.entries(attributes)) {
2056
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
2057
+ // Handle event attributes
2058
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
2059
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
2060
+ } else {
2061
+ // Handle boolean attributes
2062
+ if (value === true) {
2063
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
2064
+ } else if (value !== false) {
2065
+ // Convert underscores to hyphens and set the attribute
2066
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
2067
+ }
2068
+ }
2069
+ }
2070
+ }
2071
+
2072
+ let inputClass;
2073
+ if ('class' in attributes) {
2074
+ inputClass = attributes.class;
2075
+ } else {
2076
+ inputClass = this.inputClass;
2077
+ }
2078
+ // Construct the final HTML string
2079
+ let formHTML = `
2080
+ <div class="${this.divClass}" id="${id + '-block'}">
2081
+ <label for="${id}">${label}
2082
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
2083
+ </label>
2084
+ <input
2085
+ type="${type}"
2086
+ name="${name}"
2087
+ ${bindingDirective}
2088
+ id="${id}"
2089
+ class="${inputClass}"
2090
+ ${additionalAttrs}
2091
+ ${validationAttrs}
2092
+ />
2093
+ </div>
2094
+ `.replace(/^\s*\n/gm, '').trim();
2095
+
2096
+ let formattedHtml = formHTML;
2097
+
2098
+ // Apply vertical layout to the <input> element only
2099
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
2100
+ // Reformat attributes into a vertical layout
2101
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
2102
+ return `<input\n${attributes}\n/>`;
2103
+ });
2104
+
2105
+ // Ensure the <div> block starts on a new line and remove extra blank lines
2106
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
2107
+ // Ensure <div> starts on a new line
2108
+ return `\n${match}\n`;
2109
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
2110
+
2111
+ //return formattedHtml;
2112
+ this.formMarkUp +=formattedHtml;
2113
+ }
2114
+
2115
+
2116
+
2117
+ renderUrlField(type, name, label, validate, attributes) {
2118
+ // Define valid attributes for the URL input type
2119
+ const urlInputAttributes = [
2120
+ 'required',
2121
+ 'pattern',
2122
+ 'placeholder',
2123
+ 'readonly',
2124
+ 'disabled',
2125
+ 'size',
2126
+ 'autocomplete',
2127
+ 'spellcheck',
2128
+ 'inputmode',
2129
+ 'title',
2130
+ ];
2131
+
2132
+ // Construct validation attributes
2133
+ let validationAttrs = '';
2134
+ if (validate) {
2135
+ Object.entries(validate).forEach(([key, value]) => {
2136
+ if (urlInputAttributes.includes(key)) {
2137
+ if (typeof value === 'boolean' && value) {
2138
+ validationAttrs += ` ${key}\n`;
2139
+ } else {
2140
+ switch (key) {
2141
+ case 'pattern':
2142
+ validationAttrs += ` ${key}="${value}"\n`;
2143
+ break;
2144
+ default:
2145
+ if (!urlInputAttributes.includes(key)) {
2146
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2147
+ }
2148
+ break;
2149
+ }
2150
+ }
2151
+ } else {
2152
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2153
+ }
2154
+ });
2155
+ }
2156
+
2157
+ // 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
+ }
2167
+
2168
+ // Get the id from attributes or fall back to name
2169
+ let id = attributes.id || name;
2170
+
2171
+ // Construct additional attributes dynamically
2172
+ let additionalAttrs = '';
2173
+ for (const [key, value] of Object.entries(attributes)) {
2174
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
2175
+ // Handle event attributes
2176
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
2177
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
2178
+ } else {
2179
+ // Handle boolean attributes
2180
+ if (value === true) {
2181
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
2182
+ } else if (value !== false) {
2183
+ // Convert underscores to hyphens and set the attribute
2184
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
2185
+ }
2186
+ }
2187
+ }
2188
+ }
2189
+
2190
+ let inputClass;
2191
+ if ('class' in attributes) {
2192
+ inputClass = attributes.class;
2193
+ } else {
2194
+ inputClass = this.inputClass;
2195
+ }
2196
+ // Construct the final HTML string
2197
+ let formHTML = `
2198
+ <div class="${this.divClass}" id="${id + '-block'}">
2199
+ <label for="${id}">${label}
2200
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
2201
+ </label>
2202
+ <input
2203
+ type="${type}"
2204
+ name="${name}"
2205
+ ${bindingDirective}
2206
+ id="${id}"
2207
+ class="${inputClass}"
2208
+ ${additionalAttrs}
2209
+ ${validationAttrs}
2210
+ />
2211
+ </div>
2212
+ `.replace(/^\s*\n/gm, '').trim();
2213
+
2214
+ let formattedHtml = formHTML;
2215
+
2216
+ // Apply vertical layout to the <input> element only
2217
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
2218
+ // Reformat attributes into a vertical layout
2219
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
2220
+ return `<input\n${attributes}\n/>`;
2221
+ });
2222
+
2223
+ // Ensure the <div> block starts on a new line and remove extra blank lines
2224
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
2225
+ // Ensure <div> starts on a new line
2226
+ return `\n${match}\n`;
2227
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
2228
+
2229
+ //return formattedHtml;
2230
+ this.formMarkUp +=formattedHtml;
2231
+ }
2232
+
2233
+
2234
+ renderSearchField(type, name, label, validate, attributes) {
2235
+ // Define valid attributes for the search input type
2236
+ const searchInputAttributes = [
2237
+ 'required',
2238
+ 'pattern',
2239
+ 'placeholder',
2240
+ 'readonly',
2241
+ 'disabled',
2242
+ 'size',
2243
+ 'autocomplete',
2244
+ 'spellcheck',
2245
+ 'inputmode',
2246
+ 'title',
2247
+ ];
2248
+
2249
+ // Construct validation attributes
2250
+ let validationAttrs = '';
2251
+ if (validate) {
2252
+ Object.entries(validate).forEach(([key, value]) => {
2253
+ if (searchInputAttributes.includes(key)) {
2254
+ if (typeof value === 'boolean' && value) {
2255
+ validationAttrs += ` ${key}\n`;
2256
+ } else {
2257
+ switch (key) {
2258
+ case 'pattern':
2259
+ validationAttrs += ` ${key}="${value}"\n`;
2260
+ break;
2261
+ default:
2262
+ if (!searchInputAttributes.includes(key)) {
2263
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2264
+ }
2265
+ break;
2266
+ }
2267
+ }
2268
+ } else {
2269
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2270
+ }
2271
+ });
2272
+ }
2273
+
2274
+ // 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
+ }
2284
+
2285
+ // Get the id from attributes or fall back to name
2286
+ let id = attributes.id || name;
2287
+
2288
+ // Construct additional attributes dynamically
2289
+ let additionalAttrs = '';
2290
+ for (const [key, value] of Object.entries(attributes)) {
2291
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
2292
+ // Handle event attributes
2293
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
2294
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
2295
+ } else {
2296
+ // Handle boolean attributes
2297
+ if (value === true) {
2298
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
2299
+ } else if (value !== false) {
2300
+ // Convert underscores to hyphens and set the attribute
2301
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
2302
+ }
2303
+ }
2304
+ }
2305
+ }
2306
+
2307
+ let inputClass;
2308
+ if ('class' in attributes) {
2309
+ inputClass = attributes.class;
2310
+ } else {
2311
+ inputClass = this.inputClass;
2312
+ }
2313
+ // Construct the final HTML string
2314
+ let formHTML = `
2315
+ <div class="${this.divClass}" id="${id + '-block'}">
2316
+ <label for="${id}">${label}
2317
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
2318
+ </label>
2319
+ <input
2320
+ type="${type}"
2321
+ name="${name}"
2322
+ ${bindingDirective}
2323
+ id="${id}"
2324
+ class="${inputClass}"
2325
+ ${additionalAttrs}
2326
+ ${validationAttrs}
2327
+ />
2328
+ </div>
2329
+ `.replace(/^\s*\n/gm, '').trim();
2330
+
2331
+ let formattedHtml = formHTML;
2332
+
2333
+ // Apply vertical layout to the <input> element only
2334
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
2335
+ // Reformat attributes into a vertical layout
2336
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
2337
+ return `<input\n${attributes}\n/>`;
2338
+ });
2339
+
2340
+ // Ensure the <div> block starts on a new line and remove extra blank lines
2341
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
2342
+ // Ensure <div> starts on a new line
2343
+ return `\n${match}\n`;
2344
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
2345
+
2346
+ //return formattedHtml;
2347
+ this.formMarkUp +=formattedHtml;
2348
+ }
2349
+
2350
+
2351
+ renderColorField(type, name, label, validate, attributes) {
2352
+ // Define valid attributes for the color input type
2353
+ const colorInputAttributes = [
2354
+ 'required',
2355
+ 'readonly',
2356
+ 'disabled',
2357
+ 'autocomplete',
2358
+ 'inputmode',
2359
+ 'title',
2360
+ ];
2361
+
2362
+ // Construct validation attributes
2363
+ let validationAttrs = '';
2364
+ if (validate) {
2365
+ Object.entries(validate).forEach(([key, value]) => {
2366
+ if (colorInputAttributes.includes(key)) {
2367
+ if (typeof value === 'boolean' && value) {
2368
+ validationAttrs += ` ${key}\n`;
2369
+ } else {
2370
+ switch (key) {
2371
+ default:
2372
+ if (!colorInputAttributes.includes(key)) {
2373
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2374
+ }
2375
+ break;
2376
+ }
2377
+ }
2378
+ } else {
2379
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2380
+ }
2381
+ });
2382
+ }
2383
+
2384
+ // Handle the binding syntax
2385
+ let bindingDirective = '';
2386
+ if (attributes.binding === 'bind:value') {
2387
+ bindingDirective = `bind:value="${name}"\n`;
2388
+ } else if (attributes.binding.startsWith('::') && name) {
2389
+ bindingDirective = `bind:value="${name}"\n`;
2390
+ }
2391
+ if (attributes.binding && !name) {
2392
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2393
+ return;
2394
+ }
2395
+
2396
+ // Get the id from attributes or fall back to name
2397
+ let id = attributes.id || name;
2398
+
2399
+ // Construct additional attributes dynamically
2400
+ let additionalAttrs = '';
2401
+ for (const [key, value] of Object.entries(attributes)) {
2402
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
2403
+ // Handle event attributes
2404
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
2405
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
2406
+ } else {
2407
+ // Handle boolean attributes
2408
+ if (value === true) {
2409
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
2410
+ } else if (value !== false) {
2411
+ // Convert underscores to hyphens and set the attribute
2412
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
2413
+ }
2414
+ }
2415
+ }
2416
+ }
2417
+
2418
+ let inputClass;
2419
+ if ('class' in attributes) {
2420
+ inputClass = attributes.class;
2421
+ } else {
2422
+ inputClass = this.inputClass;
2423
+ }
2424
+ // Construct the final HTML string
2425
+ let formHTML = `
2426
+ <div class="${this.divClass}" id="${id + '-block'}">
2427
+ <label for="${id}">${label}
2428
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
2429
+ </label>
2430
+ <input
2431
+ type="${type}"
2432
+ name="${name}"
2433
+ ${bindingDirective}
2434
+ id="${id}"
2435
+ class="${inputClass}"
2436
+ ${additionalAttrs}
2437
+ ${validationAttrs}
2438
+ />
2439
+ </div>
2440
+ `.replace(/^\s*\n/gm, '').trim();
2441
+
2442
+ let formattedHtml = formHTML;
2443
+
2444
+ // Apply vertical layout to the <input> element only
2445
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
2446
+ // Reformat attributes into a vertical layout
2447
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
2448
+ return `<input\n${attributes}\n/>`;
2449
+ });
2450
+
2451
+ // Ensure the <div> block starts on a new line and remove extra blank lines
2452
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
2453
+ // Ensure <div> starts on a new line
2454
+ return `\n${match}\n`;
2455
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
2456
+
2457
+ //return formattedHtml;
2458
+ this.formMarkUp +=formattedHtml;
2459
+ }
2460
+
2461
+
2462
+
2463
+ renderFileField(type, name, label, validate, attributes) {
2464
+ // Define valid attributes for the file input type
2465
+ const fileInputAttributes = [
2466
+ 'required',
2467
+ 'accept',
2468
+ 'multiple',
2469
+ 'disabled',
2470
+ 'title',
2471
+ ];
2472
+
2473
+ // Construct validation attributes
2474
+ let validationAttrs = '';
2475
+ if (validate) {
2476
+ Object.entries(validate).forEach(([key, value]) => {
2477
+ if (fileInputAttributes.includes(key)) {
2478
+ if (typeof value === 'boolean' && value) {
2479
+ validationAttrs += ` ${key}\n`;
2480
+ } else {
2481
+ switch (key) {
2482
+ default:
2483
+ if (!fileInputAttributes.includes(key)) {
2484
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2485
+ }
2486
+ break;
2487
+ }
2488
+ }
2489
+ } else {
2490
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2491
+ }
2492
+ });
2493
+ }
2494
+
2495
+ // 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
+ }
2506
+
2507
+ // Get the id from attributes or fall back to name
2508
+ let id = attributes.id || name;
2509
+
2510
+ // Construct additional attributes dynamically
2511
+ let additionalAttrs = '';
2512
+ for (const [key, value] of Object.entries(attributes)) {
2513
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
2514
+ // Handle event attributes
2515
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
2516
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
2517
+ } else {
2518
+ // Handle boolean attributes
2519
+ if (value === true) {
2520
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
2521
+ } else if (value !== false) {
2522
+ // Convert underscores to hyphens and set the attribute
2523
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
2524
+ }
2525
+ }
2526
+ }
2527
+ }
2528
+
2529
+ let inputClass;
2530
+ if ('class' in attributes) {
2531
+ inputClass = attributes.class;
2532
+ } else {
2533
+ inputClass = this.inputClass;
2534
+ }
2535
+ // Construct the final HTML string
2536
+ let formHTML = `
2537
+ <div class="${this.divClass}" id="${id + '-block'}">
2538
+ <label for="${id}">${label}
2539
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
2540
+ </label>
2541
+ <input
2542
+ type="${type}"
2543
+ name="${name}"
2544
+ ${bindingDirective}
2545
+ id="${id}"
2546
+ class="${inputClass}"
2547
+ ${additionalAttrs}
2548
+ ${validationAttrs}
2549
+ />
2550
+ </div>
2551
+ `.replace(/^\s*\n/gm, '').trim();
2552
+
2553
+ let formattedHtml = formHTML;
2554
+
2555
+ // Apply vertical layout to the <input> element only
2556
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
2557
+ // Reformat attributes into a vertical layout
2558
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
2559
+ return `<input\n${attributes}\n/>`;
2560
+ });
2561
+
2562
+ // Ensure the <div> block starts on a new line and remove extra blank lines
2563
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
2564
+ // Ensure <div> starts on a new line
2565
+ return `\n${match}\n`;
2566
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
2567
+
2568
+ //return formattedHtml;
2569
+ this.formMarkUp +=formattedHtml;
2570
+ }
2571
+
2572
+
2573
+
2574
+
2575
+
2576
+ renderHiddenField(type, name, label, validate, attributes) {
2577
+ // Define valid attributes for the hidden input type
2578
+ const validAttributes = [
2579
+ 'type',
2580
+ 'name',
2581
+ 'value',
2582
+ 'id',
2583
+ 'class',
2584
+ 'style',
2585
+ 'required',
2586
+ 'readonly',
2587
+ 'disabled',
2588
+ 'tabindex',
2589
+ ];
2590
+
2591
+ // Construct validation attributes
2592
+ let validationAttrs = '';
2593
+ if (validate) {
2594
+ Object.entries(validate).forEach(([key, value]) => {
2595
+ if (validAttributes.includes(key)) {
2596
+ if (typeof value === 'boolean' && value) {
2597
+ validationAttrs += ` ${key}\n`;
2598
+ } else {
2599
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2600
+ }
2601
+ } else {
2602
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2603
+ }
2604
+ });
2605
+ }
2606
+
2607
+ // Handle the binding syntax
2608
+ let bindingDirective = '';
2609
+ if (attributes.binding === 'bind:value') {
2610
+ bindingDirective = `bind:value="${name}"\n`;
2611
+ } if (attributes.binding.startsWith('::') && name) {
2612
+ bindingDirective = `bind:value="${name}"\n`;
2613
+ }
2614
+ if (attributes.binding && !name) {
2615
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2616
+ return;
2617
+ }
2618
+
2619
+
2620
+ // Get the id from attributes or fall back to name
2621
+ let id = attributes.id || name;
2622
+
2623
+ // Construct additional attributes dynamically
2624
+ let additionalAttrs = '';
2625
+ for (const [key, value] of Object.entries(attributes)) {
2626
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
2627
+ // Handle event attributes
2628
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
2629
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
2630
+ } else {
2631
+ // Handle boolean attributes
2632
+ if (value === true) {
2633
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
2634
+ } else if (value !== false) {
2635
+ // Convert underscores to hyphens and set the attribute
2636
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
2637
+ }
2638
+ }
2639
+ }
2640
+ }
2641
+
2642
+ let inputClass;
2643
+ if ('class' in attributes) {
2644
+ inputClass = attributes.class;
2645
+ } else {
2646
+ inputClass = this.inputClass;
2647
+ }
2648
+ // Construct the final HTML string
2649
+ let formHTML = `
2650
+ <div class="${this.divClass}" id="${id + '-block'}">
2651
+ <label for="${id}">${label}
2652
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
2653
+ </label>
2654
+ <input
2655
+ type="${type}"
2656
+ name="${name}"
2657
+ ${bindingDirective}
2658
+ id="${id}"
2659
+ class="${inputClass}"
2660
+ ${additionalAttrs}
2661
+ ${validationAttrs}
2662
+ />
2663
+ </div>
2664
+ `.replace(/^\s*\n/gm, '').trim();
2665
+
2666
+ let formattedHtml = formHTML;
2667
+
2668
+ // Apply vertical layout to the <input> element only
2669
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
2670
+ // Reformat attributes into a vertical layout
2671
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
2672
+ return `<input\n${attributes}\n/>`;
2673
+ });
2674
+
2675
+ // Ensure the <div> block starts on a new line and remove extra blank lines
2676
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
2677
+ // Ensure <div> starts on a new line
2678
+ return `\n${match}\n`;
2679
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
2680
+
2681
+ //return formattedHtml;
2682
+ this.formMarkUp +=formattedHtml;
2683
+ }
2684
+
2685
+
2686
+
2687
+ renderImageField(type, name, label, validate, attributes) {
2688
+ // Define valid validation attributes for image upload
2689
+ const imageUploadValidationAttributes = [
2690
+ 'accept',
2691
+ 'required',
2692
+ 'minwidth',
2693
+ 'maxwidth',
2694
+ 'minheight',
2695
+ 'maxheight',
2696
+ ];
2697
+
2698
+ // Construct validation attributes
2699
+ let validationAttrs = '';
2700
+ if (validate) {
2701
+ Object.entries(validate).forEach(([key, value]) => {
2702
+ if (imageUploadValidationAttributes.includes(key)) {
2703
+ if (key === 'accept') {
2704
+ validationAttrs += `accept="${value}"\n`;
2705
+ } else if (['required', 'minwidth', 'maxwidth', 'minheight', 'maxheight'].includes(key)) {
2706
+ validationAttrs += `${key}="${value}"\n`;
2707
+ } else {
2708
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2709
+ }
2710
+ } else {
2711
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2712
+ }
2713
+ });
2714
+ }
2715
+
2716
+ // 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
+ }
2723
+
2724
+ // Get the id from attributes or fall back to name
2725
+ let id = attributes.id || name;
2726
+
2727
+ // Construct additional attributes dynamically
2728
+ let additionalAttrs = '';
2729
+ for (const [key, value] of Object.entries(attributes)) {
2730
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
2731
+ // Handle event attributes
2732
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
2733
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
2734
+ } else {
2735
+ // Handle boolean attributes
2736
+ if (value === true) {
2737
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
2738
+ } else if (value !== false) {
2739
+ // Convert underscores to hyphens and set the attribute
2740
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
2741
+ }
2742
+ }
2743
+ }
2744
+ }
2745
+
2746
+ let inputClass;
2747
+ if ('class' in attributes) {
2748
+ inputClass = attributes.class;
2749
+ } else {
2750
+ inputClass = this.inputClass;
2751
+ }
2752
+ // Construct the final HTML string
2753
+ let formHTML = `
2754
+ <div class="${this.divClass}" id="${id + '-block'}">
2755
+ <label for="${id}">${label}
2756
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
2757
+ </label>
2758
+ <input
2759
+ type="${type}"
2760
+ name="${name}"
2761
+ ${bindingDirective}
2762
+ id="${id}"
2763
+ class="${inputClass}"
2764
+ ${additionalAttrs}
2765
+ ${validationAttrs}
2766
+ />
2767
+ </div>
2768
+ `.replace(/^\s*\n/gm, '').trim();
2769
+
2770
+ let formattedHtml = formHTML;
2771
+
2772
+ // Apply vertical layout to the <input> element only
2773
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
2774
+ // Reformat attributes into a vertical layout
2775
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
2776
+ return `<input\n${attributes}\n/>`;
2777
+ });
2778
+
2779
+ // Ensure the <div> block starts on a new line and remove extra blank lines
2780
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
2781
+ // Ensure <div> starts on a new line
2782
+ return `\n${match}\n`;
2783
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
2784
+
2785
+ //return formattedHtml;
2786
+ this.formMarkUp +=formattedHtml;
2787
+ }
2788
+
2789
+
2790
+
2791
+
2792
+ renderImageField(type, name, label, validate, attributes) {
2793
+ // Define valid validation attributes for image upload
2794
+ const imageUploadValidationAttributes = [
2795
+ 'accept',
2796
+ 'required',
2797
+ 'minwidth',
2798
+ 'maxwidth',
2799
+ 'minheight',
2800
+ 'maxheight',
2801
+ ];
2802
+
2803
+ // Construct validation attributes
2804
+ let validationAttrs = '';
2805
+ if (validate) {
2806
+ Object.entries(validate).forEach(([key, value]) => {
2807
+ if (imageUploadValidationAttributes.includes(key)) {
2808
+ validationAttrs += `${key}="${value}"\n`;
2809
+ } else {
2810
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2811
+ }
2812
+ });
2813
+ }
2814
+
2815
+ // Handle the binding syntax
2816
+ let bindingDirective = '';
2817
+ if (attributes.binding === 'bind:value' || bindingSyntax.startsWith('::')) {
2818
+ bindingDirective = `bind:value="${name}"\n`;
2819
+ }
2820
+
2821
+ // Get the id from attributes or fall back to name
2822
+ let id = attributes.id || name;
2823
+
2824
+ // Construct additional attributes dynamically
2825
+ let additionalAttrs = '';
2826
+ for (const [key, value] of Object.entries(attributes)) {
2827
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
2828
+ // Handle event attributes
2829
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
2830
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
2831
+ } else {
2832
+ // Handle boolean attributes
2833
+ if (value === true) {
2834
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
2835
+ } else if (value !== false) {
2836
+ // Convert underscores to hyphens and set the attribute
2837
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
2838
+ }
2839
+ }
2840
+ }
2841
+ }
2842
+
2843
+ let inputClass;
2844
+ if ('class' in attributes) {
2845
+ inputClass = attributes.class;
2846
+ } 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>
2855
+ <input
2856
+ type="${type}"
2857
+ name="${name}"
2858
+ ${bindingDirective}
2859
+ id="${id}"
2860
+ class="${inputClass}"
2861
+ ${additionalAttrs}
2862
+ ${validationAttrs}
2863
+ />
2864
+ </div>
2865
+ `.replace(/^\s*\n/gm, '').trim();
2866
+
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
2872
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
2873
+ return `<input\n${attributes}\n/>`;
2874
+ });
2875
+
2876
+ // Ensure the <div> block starts on a new line and remove extra blank lines
2877
+ formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
2878
+ // Ensure <div> starts on a new line
2879
+ return `\n${match}\n`;
2880
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
2881
+
2882
+ return formattedHtml;
2883
+ }
2884
+
2885
+
2886
+
2887
+
2888
+
2889
+
2890
+ // Textarea field rendering
2891
+ renderTextAreaField(type, name, label, validate, attributes) {
2892
+ const textAreaValidationAttributes = [
2893
+ 'required',
2894
+ 'minlength',
2895
+ 'maxlength'
2896
+ ];
2897
+
2898
+ // Construct validation attributes
2899
+ let validationAttrs = '';
2900
+ if (validate) {
2901
+ Object.entries(validate).forEach(([key, value]) => {
2902
+ if (textAreaValidationAttributes.includes(key)) {
2903
+ if (typeof value === 'boolean' && value) {
2904
+ validationAttrs += ` ${key}\n`;
2905
+ } else {
2906
+ validationAttrs += ` ${key}="${value}"\n`;
2907
+ }
2908
+ } else {
2909
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2910
+ }
2911
+ });
2912
+ }
2913
+
2914
+ // Handle the binding syntax
2915
+ let bindingDirective = '';
2916
+ if (attributes.binding) {
2917
+ if (attributes.binding === 'bind:value' && name) {
2918
+ bindingDirective = `bind:value="${name}"\n`;
2919
+ }
2920
+ if (attributes.binding.startsWith('::') && name) {
2921
+ bindingDirective = `bind:value="${name}"\n`;
2922
+ }
2923
+ if (attributes.binding && !name) {
2924
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2925
+ return;
2926
+ }
2927
+ }
2928
+
2929
+ // Get the id from attributes or fall back to name
2930
+ let id = attributes.id || name;
2931
+
2932
+ // Construct additional attributes dynamically
2933
+ let additionalAttrs = '';
2934
+ for (const [key, value] of Object.entries(attributes)) {
2935
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
2936
+ if (key.startsWith('on')) {
2937
+ const eventValue = value.endsWith('()') ? value : `${value}()`;
2938
+ additionalAttrs += ` ${key}="${eventValue}"\n`;
2939
+ } else {
2940
+ if (value === true) {
2941
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
2942
+ } else if (value !== false) {
2943
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
2944
+ }
2945
+ }
2946
+ }
2947
+ }
2948
+
2949
+ let inputClass = attributes.class || this.inputClass;
2950
+
2951
+ // Construct the final HTML string
2952
+ let formHTML = `
2953
+ <div class="${this.divClass}" id="${id + '-block'}">
2954
+ <label for="${id}">${label}
2955
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
2956
+ </label>
2957
+ <textarea
2958
+ name="${name}"
2959
+ ${bindingDirective}
2960
+ id="${id}"
2961
+ class="${inputClass}"
2962
+ ${additionalAttrs}
2963
+ ${validationAttrs}
2964
+ ${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>
2965
+ </textarea>
2966
+ </div>
2967
+ `.replace(/^\s*\n/gm, '').trim();
2968
+
2969
+ let formattedHtml = formHTML;
2970
+
2971
+ // Apply vertical layout to the <textarea> element only
2972
+ formattedHtml = formattedHtml.replace(/<textarea\s+([^>]*)>\s*<\/textarea>/, (match, p1) => {
2973
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
2974
+ return `<textarea\n${attributes}\n></textarea>`;
2975
+ });
2976
+
2977
+ this.formMarkUp += formattedHtml;
2978
+ }
2979
+
2980
+
2981
+
2982
+ renderRadioField(type, name, label, validate, attributes, options) {
2983
+ // Define valid validation attributes for radio fields
2984
+ const radioValidationAttributes = ['required'];
2985
+
2986
+ // Construct validation attributes
2987
+ let validationAttrs = '';
2988
+ if (validate) {
2989
+ Object.entries(validate).forEach(([key, value]) => {
2990
+ if (radioValidationAttributes.includes(key)) {
2991
+ if (typeof value === 'boolean' && value) {
2992
+ validationAttrs += ` ${key}\n`;
2993
+ } else {
2994
+ // Handle specific validation attributes
2995
+ switch (key) {
2996
+ case 'required':
2997
+ validationAttrs += ` ${key}\n`;
2998
+ break;
2999
+ default:
3000
+ if (!radioValidationAttributes.includes(key)) {
3001
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'radio'.\x1b[0m`);
3002
+ }
3003
+ break;
3004
+ }
3005
+ }
3006
+ } else {
3007
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'radio'.\x1b[0m`);
3008
+ }
3009
+ });
3010
+ }
3011
+
3012
+ // Handle the binding syntax
3013
+ let bindingDirective = '';
3014
+ if (attributes.binding) {
3015
+ if (attributes.binding === 'bind:value' && name) {
3016
+ bindingDirective = ` bind:value="${name}"\n`;
3017
+ } else if (attributes.binding.startsWith('::') && name) {
3018
+ bindingDirective = ` bind:value="${name}"\n`;
3019
+ } else if (attributes.binding && !name) {
3020
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
3021
+ return;
3022
+ }
3023
+ }
3024
+
3025
+ // Define attributes for the radio inputs
3026
+ let id = attributes.id || name;
3027
+
3028
+ // Construct additional attributes dynamically
3029
+ let additionalAttrs = '';
3030
+ for (const [key, value] of Object.entries(attributes)) {
3031
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
3032
+ // Handle event attributes
3033
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3034
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
3035
+ } else {
3036
+ // Handle boolean attributes
3037
+ if (value === true) {
3038
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3039
+ } else if (value !== false) {
3040
+ // Convert underscores to hyphens and set the attribute
3041
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3042
+ }
3043
+ }
3044
+ }
3045
+ }
3046
+
3047
+ let inputClass = attributes.class || this.inputClass;
3048
+
3049
+ // Construct radio button HTML based on options
3050
+ let optionsHTML = '';
3051
+ if (options && options.length) {
3052
+ optionsHTML = options.map((option) => {
3053
+ return `
3054
+ <div>
3055
+ <input
3056
+ type="${type}"
3057
+ name="${name}"
3058
+ value="${option.value}"
3059
+ ${bindingDirective}
3060
+ ${additionalAttrs}
3061
+ ${attributes.id ? `id="${id}-${option.value}"` : `id="${id}-${option.value}"`}
3062
+ class="${inputClass}"
3063
+ ${validationAttrs}
3064
+ />
3065
+ <label
3066
+ for="${attributes.id ? `${id}-${option.value}` : `${id}-${option.value}`}">
3067
+ ${option.label}
3068
+ </label>
3069
+ </div>
3070
+ `;
3071
+ }).join('');
3072
+ }
3073
+
3074
+ // Construct the final HTML string
3075
+ let formHTML = `
3076
+ <fieldset class="${this.radioGroupClass}" id="${id + '-block'}">
3077
+ <legend>
3078
+ ${label}
3079
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3080
+ </legend>
3081
+ ${optionsHTML}
3082
+ </fieldset>
3083
+ `.replace(/^\s*\n/gm, '').trim();
3084
+
3085
+ // Apply vertical layout to the <input> elements only
3086
+ let formattedHtml = formHTML.replace(/<input\s+([^>]*)\/>/g, (match, p1) => {
3087
+ // Reformat attributes into a vertical layout
3088
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
3089
+ return `<input\n${attributes}\n/>`;
3090
+ });
3091
+
3092
+ // Ensure the <fieldset> block starts on a new line and remove extra blank lines
3093
+ formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
3094
+ // Ensure <fieldset> starts on a new line
3095
+ return `\n${match}\n`;
3096
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
3097
+
3098
+ //return formattedHtml;
3099
+ this.formMarkUp +=formattedHtml;
3100
+ }
3101
+
3102
+
3103
+ renderCheckboxField(type, name, label, validate, attributes, options) {
3104
+ // Define valid validation attributes for checkbox fields
3105
+ const checkboxValidationAttributes = ['required'];
3106
+
3107
+ // Construct validation attributes
3108
+ let validationAttrs = '';
3109
+ if (validate) {
3110
+ Object.entries(validate).forEach(([key, value]) => {
3111
+ if (checkboxValidationAttributes.includes(key)) {
3112
+ if (key === 'required') {
3113
+ validationAttrs += `${key}\n`;
3114
+ }
3115
+ } else {
3116
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
3117
+ }
3118
+ });
3119
+ }
3120
+
3121
+ // Handle the binding syntax
3122
+ let bindingDirective = '';
3123
+ if (attributes.binding) {
3124
+ if (attributes.binding === 'bind:checked') {
3125
+ bindingDirective = ` bind:checked="${name}"\n`;
3126
+ } else if (attributes.binding.startsWith('::')) {
3127
+ bindingDirective = ` bind:checked="${name}"\n`;
3128
+ }
3129
+ }
3130
+
3131
+ // Define attributes for the checkbox inputs
3132
+ let id = attributes.id || name;
3133
+
3134
+ // Handle additional attributes
3135
+ let additionalAttrs = '';
3136
+ for (const [key, value] of Object.entries(attributes)) {
3137
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
3138
+ // Handle event attributes
3139
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3140
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
3141
+ } else {
3142
+ // Handle boolean attributes
3143
+ if (value === true) {
3144
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3145
+ } else if (value !== false) {
3146
+ // Convert underscores to hyphens and set the attribute
3147
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3148
+ }
3149
+ }
3150
+ }
3151
+ }
3152
+
3153
+
3154
+ let inputClass;
3155
+ if ('class' in attributes) {
3156
+ inputClass = attributes.class;
3157
+ } else {
3158
+ inputClass = this.inputClass;
3159
+ }
3160
+
3161
+ // Construct checkbox HTML based on options
3162
+ let optionsHTML = '';
3163
+ if (Array.isArray(options)) {
3164
+ optionsHTML = options.map((option) => {
3165
+ const optionId = `${id}-${option.value}`;
3166
+ return `
3167
+ <div>
3168
+ <input
3169
+ type="checkbox"
3170
+ name="${name}"
3171
+ value="${option.value}"${bindingDirective} ${additionalAttrs}
3172
+ ${attributes.id ? `id="${optionId}"` : `id="${optionId}"`}
3173
+ class="${inputClass}"
3174
+ />
3175
+ <label
3176
+ for="${optionId}">
3177
+ ${option.label}
3178
+ </label>
3179
+ </div>
3180
+ `;
3181
+ }).join('');
3182
+ }
3183
+
3184
+ // Construct the final HTML string
3185
+ let formHTML = `
3186
+ <fieldset class="${this.checkboxGroupClass}" id="${id + '-block'}">
3187
+ <legend>
3188
+ ${label} ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3189
+ </legend>
3190
+ ${optionsHTML}
3191
+ </fieldset>
3192
+ `.replace(/^\s*\n/gm, '').trim();
3193
+
3194
+ let formattedHtml = formHTML;
3195
+
3196
+ // Apply vertical layout to the <input> elements only
3197
+ formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/g, (match, p1) => {
3198
+ // Reformat attributes into a vertical layout
3199
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
3200
+ return `<input\n${attributes}\n/>`;
3201
+ });
3202
+
3203
+ // Ensure the <fieldset> block starts on a new line and remove extra blank lines
3204
+ formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
3205
+ // Ensure <fieldset> starts on a new line
3206
+ return `\n${match}\n`;
3207
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
3208
+
3209
+ //return formattedHtml;
3210
+ this.formMarkUp +=formattedHtml;
3211
+ }
3212
+
3213
+
3214
+
3215
+ /* DYNAMIC SINGLE SELECT BLOCK */
3216
+
3217
+ // Function to render the dynamic select field and update based on user selection
3218
+ renderDynamicSingleSelectField(type, name, label, validate, attributes, options) {
3219
+
3220
+ // Step 1: Transform the data into an array of objects
3221
+ const mainCategoryOptions = options.flat().map(item => {
3222
+ // Check if any option has selected: true
3223
+ const selected = item.options.some(option => option.selected === true);
3224
+
3225
+ // Create a transformed object
3226
+ return {
3227
+ value: item.id,
3228
+ label: item.label,
3229
+ ...(selected && { selected: true }) // Conditionally add selected: true
3230
+ };
3231
+ });
3232
+
3233
+ const subCategoriesOptions=options;
3234
+ const mode='dynamicSingleSelect';
3235
+ this.renderSingleSelectField(type, name, label, validate, attributes, mainCategoryOptions, subCategoriesOptions, mode);
3236
+
3237
+ }
3238
+
3239
+
3240
+ renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
3241
+
3242
+ // Define valid validation attributes for select fields
3243
+ const selectValidationAttributes = ['required'];
3244
+
3245
+ // Construct validation attributes
3246
+ let validationAttrs = '';
3247
+ if (validate) {
3248
+ Object.entries(validate).forEach(([key, value]) => {
3249
+ if (selectValidationAttributes.includes(key)) {
3250
+ if (key === 'required') {
3251
+ validationAttrs += `${key} `;
3252
+ }
3253
+ } else {
3254
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
3255
+ }
3256
+ });
3257
+ }
3258
+
3259
+ // Handle the binding syntax
3260
+ let bindingDirective = '';
3261
+ if (attributes.binding) {
3262
+ if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
3263
+ bindingDirective = ` bind:value="${name}" `;
3264
+ }
3265
+ }
3266
+
3267
+ // Define attributes for the select field
3268
+ let id = attributes.id || name;
3269
+ let dimensionAttrs = ''; // No dimension attributes applicable for select fields
3270
+
3271
+ // Handle additional attributes
3272
+ let additionalAttrs = '';
3273
+ for (const [key, value] of Object.entries(attributes)) {
3274
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
3275
+ // Handle event attributes
3276
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3277
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
3278
+ } else {
3279
+ // Handle boolean attributes
3280
+ if (value === true) {
3281
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3282
+ } else if (value !== false) {
3283
+ // Convert underscores to hyphens and set the attribute
3284
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3285
+ }
3286
+ }
3287
+ }
3288
+ }
3289
+
3290
+ // Construct select options HTML based on options
3291
+ let selectHTML = '';
3292
+ if (Array.isArray(options)) {
3293
+ // Add a default option
3294
+ selectHTML += `
3295
+ <option value="">Choose an option</option>
3296
+ `;
3297
+
3298
+ // Add the provided options
3299
+ selectHTML += options.map((option) => {
3300
+ const isSelected = option.selected ? ' selected' : '';
3301
+ return `
3302
+ <option value="${option.value}"${isSelected}>${option.label}</option>
3303
+ `;
3304
+ }).join('');
3305
+ }
3306
+
3307
+ let inputClass = attributes.class || this.inputClass;
3308
+
3309
+ const onchangeAttr = (mode === 'dynamicSingleSelect' && subCategoriesOptions) ? ' onchange="handleDynamicSingleSelect(this.value,id)"' : '';
3310
+
3311
+ let labelDisplay;
3312
+ let rawLabel;
3313
+
3314
+ if (mode === 'dynamicSingleSelect' && subCategoriesOptions) {
3315
+ if (label.includes('-')) {
3316
+ const [mainCategoryLabel] = label.split('-');
3317
+ labelDisplay = mainCategoryLabel;
3318
+ rawLabel = label;
3319
+ } else {
3320
+ labelDisplay = label;
3321
+ rawLabel = label;
3322
+ }
3323
+ } else {
3324
+ labelDisplay = label;
3325
+ }
3326
+
3327
+
3328
+ // Construct the final HTML string
3329
+ let formHTML = `
3330
+ <fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
3331
+ <legend>${labelDisplay}
3332
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3333
+ </legend>
3334
+ <label for="${id}"> Select ${labelDisplay}
3335
+ <select name="${name}"
3336
+ ${bindingDirective}
3337
+ ${dimensionAttrs}
3338
+ id="${id}"
3339
+ class="${inputClass}"
3340
+ ${additionalAttrs}
3341
+ ${validationAttrs}
3342
+ ${onchangeAttr}
3343
+ >
3344
+ ${selectHTML}
3345
+ </select>
3346
+ </fieldset>
3347
+ `.replace(/^\s*\n/gm, '').trim();
3348
+
3349
+
3350
+ // Apply vertical layout to the <select> element and its children
3351
+ let formattedHtml = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3352
+ // Reformat attributes into a vertical layout
3353
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
3354
+ return `<select\n${attributes}\n>\n${p2.trim()}\n</select>`;
3355
+ });
3356
+
3357
+ // Ensure the <fieldset> block starts on a new line and remove extra blank lines
3358
+ formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
3359
+ // Ensure <fieldset> starts on a new line
3360
+ return `\n${match}\n`;
3361
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
3362
+
3363
+ //console.log(formattedHtml);
3364
+ this.formMarkUp+=formattedHtml;
3365
+ //return formattedHtml;
3366
+
3367
+
3368
+ /* dynamicSingleSelect */
3369
+
3370
+ if (mode && mode ==='dynamicSingleSelect' && subCategoriesOptions) {
3371
+
3372
+
3373
+ // Find the target div with id this.formContainerId
3374
+ const targetDiv = document.getElementById(this.formContainerId);
3375
+
3376
+ let categoryId = attributes.id || name;
3377
+
3378
+
3379
+ if (targetDiv) {
3380
+ // Create a script element
3381
+ const scriptElement = document.createElement('script');
3382
+ scriptElement.textContent = `
3383
+ window.handleDynamicSingleSelect = function(category, fieldsetid) {
3384
+ //console.log("HERE", fieldsetid);
3385
+
3386
+ // Hide all subcategory fields
3387
+ document.querySelectorAll(\`[class*="\${fieldsetid}"]\`).forEach(div => {
3388
+ div.style.display = "none";
3389
+ });
3390
+
3391
+ // Show the selected category
3392
+ const selectedCategoryFieldset = document.getElementById(category + '-options');
3393
+ if (selectedCategoryFieldset) {
3394
+ selectedCategoryFieldset.style.display = "block";
3395
+ }
3396
+ }
3397
+ `;
3398
+
3399
+ // Append the script element to the target div
3400
+ targetDiv.appendChild(scriptElement);
3401
+ } else {
3402
+ console.error(`Target div with id "${this.formContainerId}" not found.`);
3403
+ }
3404
+
3405
+ subCategoriesOptions.forEach(subCategory => {
3406
+ const { id, label, options } = subCategory;
3407
+
3408
+ // Build the select options HTML
3409
+ const selectHTML = options.map(option => {
3410
+ const isSelected = option.selected ? ' selected' : '';
3411
+ return `
3412
+ <option value="${option.value}"${isSelected}>${option.label}</option>
3413
+ `;
3414
+ }).join('');
3415
+
3416
+
3417
+ let subCategoryLabel;
3418
+ console.log('Label:', rawLabel); // Debug log
3419
+
3420
+ if (rawLabel.includes('-')) {
3421
+ subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
3422
+ } else {
3423
+ subCategoryLabel = 'options';
3424
+ }
3425
+
3426
+ let optionsLabel;
3427
+ if (subCategoryLabel !== 'options') {
3428
+ optionsLabel = rawLabel.split('-')?.[1] + ' Option';
3429
+ } else {
3430
+ optionsLabel = subCategoryLabel;
3431
+ }
3432
+
3433
+
3434
+ // Create the HTML for the fieldset and select elements
3435
+ let formHTML = `
3436
+ <fieldset class="${this.selectGroupClass} ${categoryId}" id="${id}-options" style="display: none;">
3437
+ <legend> ${label} ${subCategoryLabel} ${this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3438
+ </legend>
3439
+ <label for="${id}"> Select ${label} ${optionsLabel}
3440
+ </label>
3441
+ <select name="${id}"
3442
+ ${bindingDirective}
3443
+ ${dimensionAttrs}
3444
+ id="${id + '-block'}"
3445
+ class="${inputClass}"
3446
+ ${additionalAttrs}
3447
+ ${validationAttrs}
3448
+ >
3449
+ <option value="">Choose an option</option>
3450
+ ${selectHTML}
3451
+ </select>
3452
+ </fieldset>
3453
+ `.replace(/^\s*\n/gm, '').trim();
3454
+
3455
+ // Apply vertical layout to the <select> element and its children
3456
+ formHTML = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3457
+ // Reformat attributes into a vertical layout
3458
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
3459
+ return `<select\n${attributes}\n>\n${p2.trim()}\n</select>`;
3460
+ });
3461
+
3462
+ // Ensure the <fieldset> block starts on a new line and remove extra blank lines
3463
+ formHTML = formHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
3464
+ // Ensure <fieldset> starts on a new line
3465
+ return `\n${match}\n`;
3466
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
3467
+
3468
+ // Append the generated HTML to formMarkUp
3469
+ this.formMarkUp += formHTML;
3470
+
3471
+ //return formHTML;
3472
+ });
3473
+
3474
+
3475
+ }
3476
+ }
3477
+
3478
+
3479
+
3480
+ renderMultipleSelectField(type, name, label, validate, attributes, options) {
3481
+ // Define valid validation attributes for multiple select fields
3482
+ const selectValidationAttributes = ['required', 'minlength', 'maxlength'];
3483
+
3484
+ // Construct validation attributes
3485
+ let validationAttrs = '';
3486
+ if (validate) {
3487
+ Object.entries(validate).forEach(([key, value]) => {
3488
+ if (selectValidationAttributes.includes(key)) {
3489
+ if (key === 'required') {
3490
+ validationAttrs += `${key} `;
3491
+ } else if (key === 'minlength') {
3492
+ validationAttrs += `minlength="${value}" `;
3493
+ } else if (key === 'maxlength') {
3494
+ validationAttrs += `maxlength="${value}" `;
3495
+ }
3496
+ } else {
3497
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
3498
+ }
3499
+ });
3500
+ }
3501
+
3502
+ // Handle the binding syntax
3503
+ let bindingDirective = '';
3504
+ if (attributes.binding) {
3505
+ if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
3506
+ bindingDirective = ` bind:value="${name}" `;
3507
+ }
3508
+ }
3509
+
3510
+ // Define attributes for the select field
3511
+ let id = attributes.id || name;
3512
+ let dimensionAttrs = ''; // No dimension attributes applicable for select fields
3513
+
3514
+ // Handle additional attributes
3515
+ let additionalAttrs = '';
3516
+ for (const [key, value] of Object.entries(attributes)) {
3517
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
3518
+ // Handle event attributes
3519
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3520
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
3521
+ } else {
3522
+ // Handle boolean attributes
3523
+ if (value === true) {
3524
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3525
+ } else if (value !== false) {
3526
+ // Convert underscores to hyphens and set the attribute
3527
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3528
+ }
3529
+ }
3530
+ }
3531
+ }
3532
+
3533
+ // Construct select options HTML based on options
3534
+ let selectHTML = '';
3535
+ if (Array.isArray(options)) {
3536
+ selectHTML = options.map((option) => {
3537
+ const isSelected = option.selected ? ' selected' : '';
3538
+ return `
3539
+ <option value="${option.value}"${isSelected}>${option.label}</option>
3540
+ `;
3541
+ }).join('');
3542
+ }
3543
+
3544
+ // Define multiple attribute for multi-select
3545
+ const multipleAttr = 'multiple';
3546
+
3547
+ let inputClass;
3548
+ if ('class' in attributes) {
3549
+ inputClass = attributes.class;
3550
+ } else {
3551
+ inputClass = this.inputClass;
3552
+ }
3553
+ // Construct the final HTML string
3554
+ let formHTML = `
3555
+ <fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
3556
+ <label for="${id}">${label}
3557
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3558
+ </label>
3559
+ <select name="${name}"
3560
+ ${bindingDirective}
3561
+ ${dimensionAttrs}
3562
+ id="${id}"
3563
+ class="${inputClass}"
3564
+ ${additionalAttrs}
3565
+ ${validationAttrs}
3566
+ ${multipleAttr}
3567
+ >
3568
+ ${selectHTML}
3569
+ </select>
3570
+ </fieldset>
3571
+ `.replace(/^\s*\n/gm, '').trim();
3572
+
3573
+ let formattedHtml = formHTML;
3574
+
3575
+ // Apply vertical layout to the <select> element and its children
3576
+ formattedHtml = formattedHtml.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3577
+ // Reformat attributes into a vertical layout
3578
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
3579
+ return `<select\n${attributes}\n>\n${p2.trim()}\n</select>`;
3580
+ });
3581
+
3582
+ // Ensure the <fieldset> block starts on a new line and remove extra blank lines
3583
+ formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
3584
+ // Ensure <fieldset> starts on a new line
3585
+ return `\n${match}\n`;
3586
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
3587
+
3588
+ //return formattedHtml;
3589
+ this.formMarkUp +=formattedHtml;
3590
+ }
3591
+
3592
+
3593
+ renderRangeField(type, name, label, validate, attributes) {
3594
+ const rangeValidationAttributes = ['required', 'min', 'max', 'step'];
3595
+
3596
+ // Construct validation attributes
3597
+ let validationAttrs = '';
3598
+ if (validate) {
3599
+ Object.entries(validate).forEach(([key, value]) => {
3600
+ if (rangeValidationAttributes.includes(key)) {
3601
+ if (typeof value === 'boolean' && value) {
3602
+ validationAttrs += ` ${key}\n`;
3603
+ } else {
3604
+ validationAttrs += ` ${key}="${value}"\n`;
3605
+ }
3606
+ } else {
3607
+ console.warn(`Unsupported validation attribute '${key}' for field '${name}' of type 'range'.`);
3608
+ }
3609
+ });
3610
+ }
3611
+
3612
+ // Handle the binding syntax
3613
+ let bindingDirective = '';
3614
+ if (attributes.binding) {
3615
+ if (attributes.binding === 'bind:value' && name) {
3616
+ bindingDirective = `bind:value="${name}"\n`;
3617
+ } else if (attributes.binding.startsWith('::') && name) {
3618
+ bindingDirective = `bind:value="${name}"\n`;
3619
+ } else if (attributes.binding && !name) {
3620
+ console.log(`You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
3621
+ return;
3622
+ }
3623
+ }
3624
+
3625
+ // Get the id from attributes or fall back to name
3626
+ let id = attributes.id || name;
3627
+
3628
+ // Construct additional attributes dynamically
3629
+ let additionalAttrs = '';
3630
+ for (const [key, value] of Object.entries(attributes)) {
3631
+ if (key !== 'id' && key !== 'class' && value !== undefined) {
3632
+ if (key.startsWith('on')) {
3633
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3634
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
3635
+ } else {
3636
+ if (value === true) {
3637
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3638
+ } else if (value !== false) {
3639
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3640
+ }
3641
+ }
3642
+ }
3643
+ }
3644
+
3645
+ // Handle class attribute
3646
+ let inputClass = attributes.class || this.inputClass;
3647
+
3648
+ // Construct the final HTML string
3649
+ let formHTML = `
3650
+ <div class="${this.divClass}" id="${id}-block">
3651
+ <label for="${id}">${label}
3652
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3653
+ </label>
3654
+ <input
3655
+ type="${type}"
3656
+ name="${name}"
3657
+ ${bindingDirective}
3658
+ id="${id}"
3659
+ class="${inputClass}"
3660
+ ${additionalAttrs}
3661
+ ${validationAttrs}
3662
+ ${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}
3663
+ />
3664
+ <span id="${id}-value">50</span> <!-- Displays the range value dynamically -->
3665
+ </div>
3666
+ `.replace(/^\s*\n/gm, '').trim();
3667
+
3668
+ // Apply vertical layout to the <input> element only
3669
+ formHTML = formHTML.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
3670
+ const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
3671
+ return `<input\n${attributes}\n/>`;
3672
+ });
3673
+
3674
+ this.formMarkUp += formHTML;
3675
+ }
3676
+
3677
+
3678
+
3679
+ /*
3680
+ renderRangeField(type, name, label, validate, attributes) {
3681
+ const rangeValidationAttributes = ['required', 'min', 'max', 'step'];
3682
+
3683
+ // Validate required parameters
3684
+ if (!type || !name || !label) {
3685
+ throw new Error('Missing required parameters: type, name, or label.');
3686
+ }
3687
+
3688
+ // Construct validation attributes
3689
+ let validationAttrs = '';
3690
+ if (validate) {
3691
+ Object.entries(validate).forEach(([key, value]) => {
3692
+ if (rangeValidationAttributes.includes(key)) {
3693
+ if (typeof value === 'boolean' && value) {
3694
+ validationAttrs += ` ${key}`;
3695
+ } else {
3696
+ validationAttrs += ` ${key}="${value}"`;
3697
+ }
3698
+ } else {
3699
+ console.warn(`Unsupported validation attribute '${key}' for field '${name}' of type 'range'.`);
3700
+ }
3701
+ });
3702
+ }
3703
+
3704
+ // Handle the binding syntax
3705
+ let bindingDirective = '';
3706
+ if (attributes.binding) {
3707
+ if (attributes.binding === 'bind:value' && name) {
3708
+ bindingDirective = `bind:value="${name}"`;
3709
+ } else if (attributes.binding.startsWith('::') && name) {
3710
+ bindingDirective = `bind:value="${name}"`;
3711
+ } else if (attributes.binding && !name) {
3712
+ console.error(`You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
3713
+ return;
3714
+ }
3715
+ }
3716
+
3717
+ // Get the id from attributes or fall back to name
3718
+ let id = attributes.id || name;
3719
+
3720
+ // Construct additional attributes dynamically
3721
+ let additionalAttrs = '';
3722
+ for (const [key, value] of Object.entries(attributes)) {
3723
+ if (key !== 'id' && key !== 'class' && value !== undefined) {
3724
+ if (key.startsWith('on')) {
3725
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3726
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}`;
3727
+ } else {
3728
+ if (value === true) {
3729
+ additionalAttrs += ` ${key.replace(/_/g, '-')}`;
3730
+ } else if (value !== false) {
3731
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"`;
3732
+ }
3733
+ }
3734
+ }
3735
+ }
3736
+
3737
+ // Handle class attribute
3738
+ let inputClass = attributes.class || this.inputClass;
3739
+
3740
+ // Construct the final HTML string
3741
+ let formHTML = `
3742
+ <div class="${this.divClass}" id="${id}-block">
3743
+ <label for="${id}">${label}
3744
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3745
+ </label>
3746
+ <input
3747
+ type="${type}"
3748
+ name="${name}"
3749
+ ${bindingDirective}
3750
+ id="${id}"
3751
+ class="${inputClass}"
3752
+ ${additionalAttrs}
3753
+ ${validationAttrs}
3754
+ ${this.formSettings.placeholders ? `placeholder="${label}"` : ''}
3755
+ />
3756
+ <span id="${id}-value">50</span> <!-- Displays the range value dynamically -->
3757
+ </div>
3758
+ `;
3759
+
3760
+ this.formMarkUp += formHTML;
3761
+ }
3762
+
3763
+ */
3764
+
3765
+
3766
+ /* DYNAMIC SINGLE SELECT BLOCK */
3767
+
3768
+
3769
+
3770
+
3771
+
3772
+
3773
+ /* END DYNAMIC SINGLE SELECT BLOCK */
3774
+
3775
+
3776
+
3777
+ renderSubmitButton(type, name, label, validate, attributes) {
3778
+ // Define id attribute or fallback to name
3779
+ const id = attributes.id || name;
3780
+
3781
+ // Handle additional attributes§
3782
+ let additionalAttrs = '';
3783
+ for (const [key, value] of Object.entries(attributes)) {
3784
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
3785
+ // Handle event attributes
3786
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3787
+ additionalAttrs += ` ${key}="${eventValue}"`;
3788
+ } else {
3789
+ // Handle boolean attributes
3790
+ if (value === true) {
3791
+ additionalAttrs += ` ${key.replace(/_/g, '-')}`;
3792
+ } else if (value !== false) {
3793
+ // Convert underscores to hyphens and set the attribute
3794
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"`;
3795
+ }
3796
+ }
3797
+ }
3798
+ }
3799
+
3800
+ let submitButtonClass;
3801
+ if ('class' in attributes) {
3802
+ submitButtonClass=attributes.class;
3803
+ } else {
3804
+ submitButtonClass=this.submitButtonClass;
3805
+ }
3806
+
3807
+
3808
+ const spinner = `<div class="" id="formiqueSpinner">
3809
+ <div class="formique-spinner"></div>
3810
+ <p class="message">Hang in tight, we are submitting your details…</p>
3811
+ </div>`;
3812
+ // Construct the final HTML string
3813
+
3814
+ const formHTML = `
3815
+ ${spinner}
3816
+ <input type="${type}"
3817
+ id="${id + '-block'}"
3818
+ class="${submitButtonClass}"
3819
+ value="${label}"
3820
+ ${additionalAttrs}
3821
+ />
3822
+ `.replace(/^\s*\n/gm, '').trim();
3823
+
3824
+ let formattedHtml = formHTML;
3825
+
3826
+ //return formattedHtml;
3827
+ this.formMarkUp +=formattedHtml;
3828
+ }
3829
+
3830
+
3831
+
3832
+
3833
+
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
+ }
3845
+
3846
+ //return this.formMarkUp;
3847
+
3848
+
3849
+ }
3850
+
3851
+
3852
+
3853
+
3854
+ // no renderMethod below here
3855
+ }
3856
+
3857
+
3858
+
3859
+ export default Formique;
3860
+
3861
+
3862
+
3863
+
3864
+
3865
+
3866
+
3867
+
3868
+
3869
+
3870
+