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