@formique/semantq 1.0.3 → 1.0.5

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