Enhance Form Script Engine with New Field Functions and Notification Improvements
- Added new functions to the FormScriptEngine for enhanced field manipulation: setFieldByLabel() for setting fields by their display label, and getFieldOptions() for retrieving available options for select, radio, and checkbox fields. - Improved the setField() function to handle various field types more robustly, including select, radio, checkbox, and range inputs, with better event triggering for reactive updates. - Enhanced notification functions (showSuccess, showError, showInfo) to utilize Vue Toastification for improved user feedback, including customizable options for toast notifications. - Updated documentation in the form builder to reflect new field helper functions and their usage, improving developer guidance and usability.
This commit is contained in:
parent
72a70972fb
commit
4425e912ab
@ -778,7 +778,7 @@
|
||||
|
||||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3">
|
||||
<div class="mb-2 text-xs text-gray-600">
|
||||
<strong>Available functions:</strong> getField(), setField(), showField(), hideField(), enableField(), disableField(), showSuccess(), showError(), showInfo()
|
||||
<strong>Available functions:</strong> getField(), setField(), setFieldByLabel(), getFieldOptions(), showField(), hideField(), enableField(), disableField(), showSuccess(), showError(), showInfo(), showWarning(), showConfirm(), showToast()
|
||||
</div>
|
||||
<textarea
|
||||
v-model="configModel.onClick"
|
||||
|
@ -54,24 +54,226 @@ const createScriptContext = () => {
|
||||
},
|
||||
|
||||
setField: (fieldName, value) => {
|
||||
// Try to find the FormKit input element and update it directly
|
||||
const fieldElement = document.querySelector(`[data-name="${fieldName}"] input, [data-name="${fieldName}"] select, [data-name="${fieldName}"] textarea`);
|
||||
if (fieldElement) {
|
||||
fieldElement.value = value;
|
||||
|
||||
// Trigger input event to notify FormKit of the change
|
||||
const inputEvent = new Event('input', { bubbles: true });
|
||||
fieldElement.dispatchEvent(inputEvent);
|
||||
|
||||
// Also trigger change event
|
||||
const changeEvent = new Event('change', { bubbles: true });
|
||||
fieldElement.dispatchEvent(changeEvent);
|
||||
// Find the FormKit field container
|
||||
const fieldContainer = document.querySelector(`[data-name="${fieldName}"]`);
|
||||
if (!fieldContainer) {
|
||||
console.warn(`[FormScriptEngine] Field "${fieldName}" not found`);
|
||||
// Still emit the event for reactive updates
|
||||
emit('field-change', { fieldName, value });
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the field type from data attributes or class names
|
||||
const fieldType = fieldContainer.getAttribute('data-type') ||
|
||||
(fieldContainer.querySelector('select') ? 'select' :
|
||||
fieldContainer.querySelector('input[type="radio"]') ? 'radio' :
|
||||
fieldContainer.querySelector('input[type="checkbox"]') ? 'checkbox' :
|
||||
fieldContainer.querySelector('input[type="range"]') ? 'range' :
|
||||
fieldContainer.querySelector('textarea') ? 'textarea' : 'input');
|
||||
|
||||
try {
|
||||
if (fieldType === 'select') {
|
||||
// Handle select dropdowns
|
||||
const selectElement = fieldContainer.querySelector('select');
|
||||
if (selectElement) {
|
||||
// Try to set by value first, then by label if value doesn't exist
|
||||
let optionFound = false;
|
||||
|
||||
// First try to match by value
|
||||
for (let option of selectElement.options) {
|
||||
if (option.value === value) {
|
||||
selectElement.value = value;
|
||||
optionFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If not found by value, try to match by label (for user convenience)
|
||||
if (!optionFound) {
|
||||
for (let option of selectElement.options) {
|
||||
if (option.text === value || option.textContent === value) {
|
||||
selectElement.value = option.value;
|
||||
optionFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!optionFound) {
|
||||
console.warn(`[FormScriptEngine] Option "${value}" not found in select field "${fieldName}"`);
|
||||
}
|
||||
|
||||
// Trigger events
|
||||
selectElement.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
selectElement.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
} else if (fieldType === 'radio') {
|
||||
// Handle radio button groups
|
||||
const radioButtons = fieldContainer.querySelectorAll('input[type="radio"]');
|
||||
let radioFound = false;
|
||||
|
||||
radioButtons.forEach(radio => {
|
||||
if (radio.value === value) {
|
||||
radio.checked = true;
|
||||
radioFound = true;
|
||||
// Trigger events
|
||||
radio.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
radio.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
} else {
|
||||
radio.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
// If not found by value, try to match by label
|
||||
if (!radioFound) {
|
||||
radioButtons.forEach(radio => {
|
||||
const label = radio.nextElementSibling?.textContent ||
|
||||
radio.closest('label')?.textContent?.replace(radio.value, '').trim();
|
||||
if (label === value) {
|
||||
radio.checked = true;
|
||||
radioFound = true;
|
||||
radio.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
radio.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
} else {
|
||||
radio.checked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!radioFound) {
|
||||
console.warn(`[FormScriptEngine] Radio option "${value}" not found in field "${fieldName}"`);
|
||||
}
|
||||
} else if (fieldType === 'checkbox') {
|
||||
// Handle checkbox groups
|
||||
const checkboxes = fieldContainer.querySelectorAll('input[type="checkbox"]');
|
||||
|
||||
if (checkboxes.length === 1) {
|
||||
// Single checkbox - treat as boolean
|
||||
const checkbox = checkboxes[0];
|
||||
checkbox.checked = Boolean(value);
|
||||
checkbox.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
} else if (checkboxes.length > 1) {
|
||||
// Multiple checkboxes - handle array of values or single value
|
||||
const valuesToCheck = Array.isArray(value) ? value : [value];
|
||||
|
||||
checkboxes.forEach(checkbox => {
|
||||
const shouldCheck = valuesToCheck.includes(checkbox.value) ||
|
||||
valuesToCheck.includes(checkbox.nextElementSibling?.textContent?.trim());
|
||||
|
||||
if (checkbox.checked !== shouldCheck) {
|
||||
checkbox.checked = shouldCheck;
|
||||
checkbox.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (fieldType === 'range') {
|
||||
// Handle range slider
|
||||
const rangeElement = fieldContainer.querySelector('input[type="range"]');
|
||||
if (rangeElement) {
|
||||
rangeElement.value = value;
|
||||
rangeElement.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
rangeElement.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
} else {
|
||||
// Handle regular input fields (text, number, email, etc.)
|
||||
const inputElement = fieldContainer.querySelector('input, textarea');
|
||||
if (inputElement) {
|
||||
inputElement.value = value;
|
||||
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
inputElement.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[FormScriptEngine] Error setting field "${fieldName}":`, error);
|
||||
}
|
||||
|
||||
// Also emit the event for the parent component
|
||||
// Always emit the event for reactive updates
|
||||
emit('field-change', { fieldName, value });
|
||||
},
|
||||
|
||||
setFieldByLabel: (fieldName, labelValue) => {
|
||||
// Convenience function to set option-based fields by their label instead of value
|
||||
// This is especially useful for select, radio, and checkbox fields
|
||||
const fieldContainer = document.querySelector(`[data-name="${fieldName}"]`);
|
||||
if (!fieldContainer) {
|
||||
console.warn(`[FormScriptEngine] Field "${fieldName}" not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldType = fieldContainer.getAttribute('data-type') ||
|
||||
(fieldContainer.querySelector('select') ? 'select' :
|
||||
fieldContainer.querySelector('input[type="radio"]') ? 'radio' :
|
||||
fieldContainer.querySelector('input[type="checkbox"]') ? 'checkbox' : 'input');
|
||||
|
||||
try {
|
||||
if (fieldType === 'select') {
|
||||
const selectElement = fieldContainer.querySelector('select');
|
||||
if (selectElement) {
|
||||
for (let option of selectElement.options) {
|
||||
if (option.text === labelValue || option.textContent === labelValue) {
|
||||
selectElement.value = option.value;
|
||||
selectElement.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
selectElement.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
emit('field-change', { fieldName, value: option.value });
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.warn(`[FormScriptEngine] Label "${labelValue}" not found in select field "${fieldName}"`);
|
||||
}
|
||||
} else if (fieldType === 'radio') {
|
||||
const radioButtons = fieldContainer.querySelectorAll('input[type="radio"]');
|
||||
let radioFound = false;
|
||||
|
||||
radioButtons.forEach(radio => {
|
||||
const label = radio.nextElementSibling?.textContent ||
|
||||
radio.closest('label')?.textContent?.replace(radio.value, '').trim();
|
||||
if (label === labelValue) {
|
||||
radio.checked = true;
|
||||
radioFound = true;
|
||||
radio.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
radio.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
emit('field-change', { fieldName, value: radio.value });
|
||||
} else {
|
||||
radio.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!radioFound) {
|
||||
console.warn(`[FormScriptEngine] Label "${labelValue}" not found in radio field "${fieldName}"`);
|
||||
}
|
||||
} else if (fieldType === 'checkbox') {
|
||||
const checkboxes = fieldContainer.querySelectorAll('input[type="checkbox"]');
|
||||
const labelsToCheck = Array.isArray(labelValue) ? labelValue : [labelValue];
|
||||
const selectedValues = [];
|
||||
|
||||
checkboxes.forEach(checkbox => {
|
||||
const label = checkbox.nextElementSibling?.textContent?.trim() ||
|
||||
checkbox.closest('label')?.textContent?.replace(checkbox.value, '').trim();
|
||||
const shouldCheck = labelsToCheck.includes(label);
|
||||
|
||||
if (checkbox.checked !== shouldCheck) {
|
||||
checkbox.checked = shouldCheck;
|
||||
checkbox.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
|
||||
if (shouldCheck) {
|
||||
selectedValues.push(checkbox.value);
|
||||
}
|
||||
});
|
||||
|
||||
emit('field-change', { fieldName, value: selectedValues });
|
||||
} else {
|
||||
// For non-option fields, just use setField
|
||||
context.setField(fieldName, labelValue);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[FormScriptEngine] Error setting field "${fieldName}" by label:`, error);
|
||||
}
|
||||
},
|
||||
|
||||
hideField: (fieldName) => {
|
||||
const fieldElement = document.querySelector(`[data-name="${fieldName}"]`);
|
||||
if (fieldElement) {
|
||||
@ -104,6 +306,48 @@ const createScriptContext = () => {
|
||||
}
|
||||
},
|
||||
|
||||
getFieldOptions: (fieldName) => {
|
||||
// Get all available options for a select, radio, or checkbox field
|
||||
// Returns an array of {label, value} objects
|
||||
const fieldContainer = document.querySelector(`[data-name="${fieldName}"]`);
|
||||
if (!fieldContainer) {
|
||||
console.warn(`[FormScriptEngine] Field "${fieldName}" not found`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const fieldType = fieldContainer.getAttribute('data-type') ||
|
||||
(fieldContainer.querySelector('select') ? 'select' :
|
||||
fieldContainer.querySelector('input[type="radio"]') ? 'radio' :
|
||||
fieldContainer.querySelector('input[type="checkbox"]') ? 'checkbox' : 'input');
|
||||
|
||||
try {
|
||||
if (fieldType === 'select') {
|
||||
const selectElement = fieldContainer.querySelector('select');
|
||||
if (selectElement) {
|
||||
return Array.from(selectElement.options).map(option => ({
|
||||
label: option.text || option.textContent,
|
||||
value: option.value
|
||||
}));
|
||||
}
|
||||
} else if (fieldType === 'radio' || fieldType === 'checkbox') {
|
||||
const inputElements = fieldContainer.querySelectorAll(`input[type="${fieldType}"]`);
|
||||
return Array.from(inputElements).map(input => {
|
||||
const label = input.nextElementSibling?.textContent?.trim() ||
|
||||
input.closest('label')?.textContent?.replace(input.value, '').trim() ||
|
||||
input.value;
|
||||
return {
|
||||
label: label,
|
||||
value: input.value
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[FormScriptEngine] Error getting options for field "${fieldName}":`, error);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
|
||||
validateField: (fieldName) => {
|
||||
emit('field-validate', { fieldName });
|
||||
},
|
||||
@ -123,8 +367,26 @@ const createScriptContext = () => {
|
||||
},
|
||||
|
||||
// Add missing helper functions
|
||||
showSuccess: (message) => {
|
||||
// Create a simple success notification
|
||||
showSuccess: (message, options = {}) => {
|
||||
// Enhanced success notification with Vue Toastification
|
||||
if (typeof window !== 'undefined' && window.$nuxt) {
|
||||
try {
|
||||
const toast = window.$nuxt.$toast;
|
||||
if (toast) {
|
||||
const defaultOptions = {
|
||||
timeout: 3000,
|
||||
position: 'bottom-right',
|
||||
...options
|
||||
};
|
||||
toast.success(message, defaultOptions);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[FormScriptEngine] Toast not available:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to simple notification
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded shadow-lg z-50';
|
||||
notification.textContent = message;
|
||||
@ -136,8 +398,26 @@ const createScriptContext = () => {
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
showError: (message) => {
|
||||
// Create a simple error notification
|
||||
showError: (message, options = {}) => {
|
||||
// Enhanced error notification with Vue Toastification
|
||||
if (typeof window !== 'undefined' && window.$nuxt) {
|
||||
try {
|
||||
const toast = window.$nuxt.$toast;
|
||||
if (toast) {
|
||||
const defaultOptions = {
|
||||
timeout: 5000,
|
||||
position: 'bottom-right',
|
||||
...options
|
||||
};
|
||||
toast.error(message, defaultOptions);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[FormScriptEngine] Toast not available:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to simple notification
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed top-4 right-4 bg-red-500 text-white px-4 py-2 rounded shadow-lg z-50';
|
||||
notification.textContent = message;
|
||||
@ -149,8 +429,26 @@ const createScriptContext = () => {
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
showInfo: (message) => {
|
||||
// Create a simple info notification
|
||||
showInfo: (message, options = {}) => {
|
||||
// Enhanced info notification with Vue Toastification
|
||||
if (typeof window !== 'undefined' && window.$nuxt) {
|
||||
try {
|
||||
const toast = window.$nuxt.$toast;
|
||||
if (toast) {
|
||||
const defaultOptions = {
|
||||
timeout: 3000,
|
||||
position: 'bottom-right',
|
||||
...options
|
||||
};
|
||||
toast.info(message, defaultOptions);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[FormScriptEngine] Toast not available:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to simple notification
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed top-4 right-4 bg-blue-500 text-white px-4 py-2 rounded shadow-lg z-50';
|
||||
notification.textContent = message;
|
||||
@ -162,6 +460,201 @@ const createScriptContext = () => {
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
showWarning: (message, options = {}) => {
|
||||
// Warning notification with Vue Toastification
|
||||
if (typeof window !== 'undefined' && window.$nuxt) {
|
||||
try {
|
||||
const toast = window.$nuxt.$toast;
|
||||
if (toast) {
|
||||
const defaultOptions = {
|
||||
timeout: 4000,
|
||||
position: 'bottom-right',
|
||||
...options
|
||||
};
|
||||
toast.warning(message, defaultOptions);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[FormScriptEngine] Toast not available:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to simple notification
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed top-4 right-4 bg-yellow-500 text-white px-4 py-2 rounded shadow-lg z-50';
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(notification)) {
|
||||
document.body.removeChild(notification);
|
||||
}
|
||||
}, 4000);
|
||||
},
|
||||
|
||||
showConfirm: (message, options = {}) => {
|
||||
// Confirmation dialog with customizable buttons
|
||||
if (typeof window !== 'undefined' && window.Swal) {
|
||||
const defaultOptions = {
|
||||
title: 'Confirm Action',
|
||||
text: message,
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'OK',
|
||||
cancelButtonText: 'Cancel',
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
focusConfirm: false
|
||||
};
|
||||
return window.Swal.fire({ ...defaultOptions, ...options });
|
||||
} else {
|
||||
// Fallback to browser confirm
|
||||
return Promise.resolve({
|
||||
isConfirmed: confirm(message),
|
||||
isDenied: false,
|
||||
isDismissed: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
showInputDialog: (message, options = {}) => {
|
||||
// Input dialog with validation
|
||||
if (typeof window !== 'undefined' && window.Swal) {
|
||||
const defaultOptions = {
|
||||
title: 'Input Required',
|
||||
text: message,
|
||||
input: 'text',
|
||||
inputPlaceholder: 'Enter value...',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'OK',
|
||||
cancelButtonText: 'Cancel',
|
||||
inputValidator: (value) => {
|
||||
if (!value) {
|
||||
return 'Please enter a value';
|
||||
}
|
||||
}
|
||||
};
|
||||
return window.Swal.fire({ ...defaultOptions, ...options });
|
||||
} else {
|
||||
// Fallback to browser prompt
|
||||
const result = prompt(message);
|
||||
return Promise.resolve({
|
||||
isConfirmed: result !== null,
|
||||
value: result,
|
||||
isDenied: false,
|
||||
isDismissed: result === null
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
showSelectDialog: (message, options = {}) => {
|
||||
// Select dialog for choosing from options
|
||||
if (typeof window !== 'undefined' && window.Swal) {
|
||||
const defaultOptions = {
|
||||
title: 'Select Option',
|
||||
text: message,
|
||||
input: 'select',
|
||||
inputOptions: {},
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'OK',
|
||||
cancelButtonText: 'Cancel',
|
||||
inputValidator: (value) => {
|
||||
if (!value) {
|
||||
return 'Please select an option';
|
||||
}
|
||||
}
|
||||
};
|
||||
return window.Swal.fire({ ...defaultOptions, ...options });
|
||||
} else {
|
||||
// Fallback to basic prompt
|
||||
const result = prompt(message);
|
||||
return Promise.resolve({
|
||||
isConfirmed: result !== null,
|
||||
value: result,
|
||||
isDenied: false,
|
||||
isDismissed: result === null
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
showToast: (message, options = {}) => {
|
||||
// Toast notification using Vue Toastification
|
||||
if (typeof window !== 'undefined' && window.$nuxt) {
|
||||
try {
|
||||
const toast = window.$nuxt.$toast;
|
||||
if (toast) {
|
||||
const defaultOptions = {
|
||||
timeout: 3000,
|
||||
position: 'bottom-right',
|
||||
...options
|
||||
};
|
||||
// Use info as default type, but allow override via options.type
|
||||
const toastType = options.type || 'info';
|
||||
if (typeof toast[toastType] === 'function') {
|
||||
toast[toastType](message, defaultOptions);
|
||||
} else {
|
||||
toast.info(message, defaultOptions);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[FormScriptEngine] Toast not available:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to simple notification
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed top-4 right-4 bg-blue-500 text-white px-4 py-2 rounded shadow-lg z-50';
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(notification)) {
|
||||
document.body.removeChild(notification);
|
||||
}
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
showProgress: (message, options = {}) => {
|
||||
// Progress dialog
|
||||
if (typeof window !== 'undefined' && window.Swal) {
|
||||
const defaultOptions = {
|
||||
title: 'Please Wait...',
|
||||
text: message,
|
||||
allowOutsideClick: false,
|
||||
allowEscapeKey: false,
|
||||
showConfirmButton: false,
|
||||
didOpen: () => {
|
||||
window.Swal.showLoading();
|
||||
}
|
||||
};
|
||||
return window.Swal.fire({ ...defaultOptions, ...options });
|
||||
} else {
|
||||
console.log('Progress:', message);
|
||||
return Promise.resolve({});
|
||||
}
|
||||
},
|
||||
|
||||
showCustomAlert: (options = {}) => {
|
||||
// Fully customizable alert
|
||||
if (typeof window !== 'undefined' && window.Swal) {
|
||||
return window.Swal.fire(options);
|
||||
} else {
|
||||
// Fallback to basic alert
|
||||
alert(options.text || options.title || 'Alert');
|
||||
return Promise.resolve({
|
||||
isConfirmed: true,
|
||||
isDenied: false,
|
||||
isDismissed: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
closeAlert: () => {
|
||||
// Close any open SweetAlert2 dialog
|
||||
if (typeof window !== 'undefined' && window.Swal) {
|
||||
window.Swal.close();
|
||||
}
|
||||
},
|
||||
|
||||
// Utility functions
|
||||
console: {
|
||||
log: (...args) => console.log('[Form Script]', ...args),
|
||||
@ -195,7 +688,10 @@ const createScriptContext = () => {
|
||||
setTimeout: setTimeout,
|
||||
setInterval: setInterval,
|
||||
clearTimeout: clearTimeout,
|
||||
clearInterval: clearInterval
|
||||
clearInterval: clearInterval,
|
||||
|
||||
// Direct Vue Toastification access for advanced usage
|
||||
$toast: typeof window !== 'undefined' && window.$nuxt ? window.$nuxt.$toast : null
|
||||
};
|
||||
|
||||
return context;
|
||||
|
443
docs/form-javascript-api.md
Normal file
443
docs/form-javascript-api.md
Normal file
@ -0,0 +1,443 @@
|
||||
# Form JavaScript API - Enhanced setField for Option Fields
|
||||
|
||||
## Overview
|
||||
|
||||
The Form JavaScript API provides powerful functions to interact with form fields dynamically. This document focuses on the enhanced `setField` functionality that properly handles option-based components like select dropdowns, radio buttons, and checkboxes.
|
||||
|
||||
## Enhanced setField Function
|
||||
|
||||
The `setField` function has been enhanced to intelligently handle different field types, especially option-based components where users can set values by either the internal value or the display label.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
// Basic text field
|
||||
setField('first_name', 'John');
|
||||
|
||||
// Number field
|
||||
setField('age', 25);
|
||||
|
||||
// Email field
|
||||
setField('email', 'john@example.com');
|
||||
```
|
||||
|
||||
### Option-Based Fields
|
||||
|
||||
#### Select Dropdown
|
||||
|
||||
For select dropdowns, you can set the value using either the internal value or the display label:
|
||||
|
||||
```javascript
|
||||
// Setting by value (recommended)
|
||||
setField('country', 'us');
|
||||
|
||||
// Setting by label (will automatically find the corresponding value)
|
||||
setField('country', 'United States');
|
||||
```
|
||||
|
||||
Example form configuration:
|
||||
```json
|
||||
{
|
||||
"type": "select",
|
||||
"props": {
|
||||
"name": "country",
|
||||
"label": "Country",
|
||||
"options": [
|
||||
{ "label": "United States", "value": "us" },
|
||||
{ "label": "Canada", "value": "ca" },
|
||||
{ "label": "United Kingdom", "value": "uk" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Radio Button Groups
|
||||
|
||||
Radio buttons work similarly - you can set by value or label:
|
||||
|
||||
```javascript
|
||||
// Setting by value
|
||||
setField('gender', 'male');
|
||||
|
||||
// Setting by label
|
||||
setField('gender', 'Male');
|
||||
```
|
||||
|
||||
#### Checkbox Groups
|
||||
|
||||
For checkboxes, you can set single values or arrays:
|
||||
|
||||
```javascript
|
||||
// Single checkbox (boolean)
|
||||
setField('newsletter', true);
|
||||
|
||||
// Multiple checkboxes - by value
|
||||
setField('interests', ['sports', 'technology']);
|
||||
|
||||
// Multiple checkboxes - by label
|
||||
setField('interests', ['Sports', 'Technology']);
|
||||
|
||||
// Single checkbox value
|
||||
setField('interests', 'sports');
|
||||
```
|
||||
|
||||
## Additional Helper Functions
|
||||
|
||||
### setFieldByLabel
|
||||
|
||||
Explicitly set a field by its label (display text) instead of value:
|
||||
|
||||
```javascript
|
||||
// Explicitly set by label
|
||||
setFieldByLabel('priority', 'High Priority');
|
||||
setFieldByLabel('status', 'In Progress');
|
||||
```
|
||||
|
||||
### getFieldOptions
|
||||
|
||||
Get all available options for a field:
|
||||
|
||||
```javascript
|
||||
// Get all options for a select field
|
||||
const countryOptions = getFieldOptions('country');
|
||||
console.log(countryOptions);
|
||||
// Output: [
|
||||
// { label: "United States", value: "us" },
|
||||
// { label: "Canada", value: "ca" },
|
||||
// { label: "United Kingdom", value: "uk" }
|
||||
// ]
|
||||
|
||||
// Use with conditional logic
|
||||
const priorities = getFieldOptions('priority');
|
||||
if (priorities.length > 0) {
|
||||
setField('priority', priorities[0].value); // Set to first option
|
||||
}
|
||||
```
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### Dynamic Field Updates Based on Selection
|
||||
|
||||
```javascript
|
||||
// When country changes, update state/province options
|
||||
onFieldChange('country', function(newValue) {
|
||||
if (newValue === 'us') {
|
||||
// Set to a US state
|
||||
setField('state', 'california');
|
||||
} else if (newValue === 'ca') {
|
||||
// Set to a Canadian province
|
||||
setField('province', 'ontario');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Conditional Option Setting
|
||||
|
||||
```javascript
|
||||
// Set different options based on user type
|
||||
onFieldChange('user_type', function(userType) {
|
||||
if (userType === 'premium') {
|
||||
setField('support_level', 'priority');
|
||||
setFieldByLabel('notification_preference', 'Email + SMS');
|
||||
} else {
|
||||
setField('support_level', 'standard');
|
||||
setFieldByLabel('notification_preference', 'Email Only');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Working with Multiple Selections
|
||||
|
||||
```javascript
|
||||
// Add to existing checkbox selections
|
||||
const currentInterests = getField('interests') || [];
|
||||
const allInterests = [...currentInterests, 'photography'];
|
||||
setField('interests', allInterests);
|
||||
|
||||
// Set multiple checkboxes by label
|
||||
setFieldByLabel('services', ['Consulting', 'Development', 'Support']);
|
||||
```
|
||||
|
||||
### Form Initialization
|
||||
|
||||
```javascript
|
||||
// Set default values when form loads
|
||||
setField('country', 'us');
|
||||
setFieldByLabel('language', 'English');
|
||||
setField('notifications', ['email', 'sms']);
|
||||
|
||||
// Show success message
|
||||
showSuccess('Form initialized with default values');
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The enhanced setField provides helpful warnings when options are not found:
|
||||
|
||||
```javascript
|
||||
// If option doesn't exist, warning is logged to console
|
||||
setField('country', 'invalid_country');
|
||||
// Console: [FormScriptEngine] Option "invalid_country" not found in select field "country"
|
||||
|
||||
// Check if field exists before setting
|
||||
const options = getFieldOptions('priority');
|
||||
if (options.some(opt => opt.value === 'urgent')) {
|
||||
setField('priority', 'urgent');
|
||||
} else {
|
||||
console.log('Urgent priority option not available');
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Values When Possible**: Internal values are more reliable than labels
|
||||
```javascript
|
||||
// Preferred
|
||||
setField('status', 'active');
|
||||
|
||||
// Works but less reliable if labels change
|
||||
setFieldByLabel('status', 'Active Status');
|
||||
```
|
||||
|
||||
2. **Check Options Exist**: Use `getFieldOptions` to verify options exist
|
||||
```javascript
|
||||
const statuses = getFieldOptions('status');
|
||||
if (statuses.some(opt => opt.value === 'pending')) {
|
||||
setField('status', 'pending');
|
||||
}
|
||||
```
|
||||
|
||||
3. **Handle Arrays for Multi-Select**: Always use arrays for checkbox groups
|
||||
```javascript
|
||||
// Correct for multiple checkboxes
|
||||
setField('preferences', ['email', 'sms', 'push']);
|
||||
|
||||
// Also works for single selection
|
||||
setField('preferences', ['email']);
|
||||
```
|
||||
|
||||
4. **Use Descriptive Field Names**: Makes scripts more maintainable
|
||||
```javascript
|
||||
// Good
|
||||
setField('notification_method', 'email');
|
||||
setField('user_subscription_type', 'premium');
|
||||
|
||||
// Less clear
|
||||
setField('field1', 'email');
|
||||
setField('type', 'premium');
|
||||
```
|
||||
|
||||
## Migration from Basic setField
|
||||
|
||||
If you're upgrading from the basic setField implementation:
|
||||
|
||||
### Before (Basic Implementation)
|
||||
```javascript
|
||||
// Only worked reliably with simple text fields
|
||||
setField('name', 'John');
|
||||
|
||||
// Option fields required manual DOM manipulation
|
||||
const select = document.querySelector('[data-name="country"] select');
|
||||
select.value = 'us';
|
||||
select.dispatchEvent(new Event('change'));
|
||||
```
|
||||
|
||||
### After (Enhanced Implementation)
|
||||
```javascript
|
||||
// Works with all field types automatically
|
||||
setField('name', 'John');
|
||||
setField('country', 'us'); // By value
|
||||
setField('country', 'United States'); // By label
|
||||
setFieldByLabel('priority', 'High'); // Explicit label setting
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
The enhanced `setField` function provides:
|
||||
|
||||
- ✅ **Smart option handling** - works with select, radio, and checkbox fields
|
||||
- ✅ **Flexible value setting** - by internal value or display label
|
||||
- ✅ **Array support** - for multiple checkbox selections
|
||||
- ✅ **Error handling** - helpful warnings when options don't exist
|
||||
- ✅ **Additional helpers** - `setFieldByLabel` and `getFieldOptions`
|
||||
- ✅ **Backward compatibility** - existing scripts continue to work
|
||||
|
||||
This makes form scripting much more intuitive and powerful for complex forms with option-based fields.
|
||||
|
||||
---
|
||||
|
||||
## JavaScript API Functions Summary
|
||||
|
||||
All the functions documented above are now available in the Form JavaScript API:
|
||||
|
||||
### Core Form Functions
|
||||
- `getField(fieldName)` - Get field value
|
||||
- `setField(fieldName, value)` - Set field value (enhanced for options)
|
||||
- `setFieldByLabel(fieldName, labelValue)` - Set field by display label
|
||||
- `getFieldOptions(fieldName)` - Get available options for a field
|
||||
- `hideField(fieldName)` / `showField(fieldName)` - Control field visibility
|
||||
- `disableField(fieldName)` / `enableField(fieldName)` - Control field state
|
||||
- `validateField(fieldName)` - Trigger field validation
|
||||
- `getAllFieldValues()` - Get all form data
|
||||
- `onFieldChange(fieldNames, callback)` - Register field change handlers
|
||||
|
||||
### Enhanced Alert Functions (Vue Toastification)
|
||||
- `showSuccess(message, options)` - Success notifications
|
||||
- `showError(message, options)` - Error notifications
|
||||
- `showInfo(message, options)` - Info notifications
|
||||
- `showWarning(message, options)` - Warning notifications
|
||||
- `showToast(message, options)` - Toast notifications with type support
|
||||
- `showConfirm(message, options)` - Confirmation dialogs with custom buttons (SweetAlert2)
|
||||
- `showInputDialog(message, options)` - Input dialogs with validation (SweetAlert2)
|
||||
- `showSelectDialog(message, options)` - Selection dialogs (SweetAlert2)
|
||||
- `showProgress(message, options)` - Progress dialogs (SweetAlert2)
|
||||
- `showCustomAlert(options)` - Fully customizable alerts (SweetAlert2)
|
||||
- `closeAlert()` - Close any open alert (SweetAlert2)
|
||||
- `$toast` - Direct access to Vue Toastification library
|
||||
|
||||
### Usage Examples for showConfirm with Custom Buttons
|
||||
|
||||
```javascript
|
||||
// Basic confirmation with OK button
|
||||
showConfirm('Are you sure you want to save?').then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// User clicked OK
|
||||
console.log('User confirmed');
|
||||
}
|
||||
});
|
||||
|
||||
// Custom button labels
|
||||
showConfirm('Delete this record?', {
|
||||
confirmButtonText: 'Yes, Delete',
|
||||
cancelButtonText: 'Keep It',
|
||||
title: 'Confirm Deletion'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
showSuccess('Record deleted successfully!');
|
||||
}
|
||||
});
|
||||
|
||||
// Custom colors and styling
|
||||
showConfirm('This action cannot be undone', {
|
||||
title: 'Are you absolutely sure?',
|
||||
confirmButtonText: 'Delete Forever',
|
||||
cancelButtonText: 'Cancel',
|
||||
confirmButtonColor: '#d33',
|
||||
cancelButtonColor: '#3085d6',
|
||||
icon: 'warning'
|
||||
});
|
||||
|
||||
// Single OK button (no cancel)
|
||||
showConfirm('Please read the terms and conditions', {
|
||||
title: 'Important Notice',
|
||||
confirmButtonText: 'I Understand',
|
||||
showCancelButton: false,
|
||||
icon: 'info'
|
||||
});
|
||||
```
|
||||
|
||||
All functions support custom options and return promises for easy async handling!
|
||||
|
||||
---
|
||||
|
||||
## Vue Toastification Integration
|
||||
|
||||
The notification functions now use Vue Toastification, which is already integrated in the project, providing beautiful and consistent toast notifications.
|
||||
|
||||
### Basic Toast Usage
|
||||
|
||||
```javascript
|
||||
// Simple notifications
|
||||
showSuccess('Form saved successfully!');
|
||||
showError('Validation failed');
|
||||
showInfo('Please review your entries');
|
||||
showWarning('Some fields are incomplete');
|
||||
|
||||
// With custom options
|
||||
showSuccess('Data updated!', {
|
||||
timeout: 5000,
|
||||
position: 'top-center'
|
||||
});
|
||||
|
||||
// Different toast types
|
||||
showToast('Custom message', { type: 'success' });
|
||||
showToast('Warning message', { type: 'warning' });
|
||||
showToast('Error message', { type: 'error' });
|
||||
```
|
||||
|
||||
### Available Toast Options
|
||||
|
||||
```javascript
|
||||
showSuccess('Message', {
|
||||
timeout: 3000, // Auto-dismiss time (0 for no auto-dismiss)
|
||||
position: 'bottom-right', // Position: top-left, top-center, top-right, bottom-left, bottom-center, bottom-right
|
||||
closeOnClick: true, // Close on click
|
||||
pauseOnFocusLoss: true, // Pause timer when window loses focus
|
||||
pauseOnHover: true, // Pause timer on hover
|
||||
draggable: true, // Allow dragging to dismiss
|
||||
draggablePercent: 0.6, // Percentage of drag needed to dismiss
|
||||
showCloseButtonOnHover: false, // Show close button only on hover
|
||||
hideProgressBar: false, // Hide progress bar
|
||||
closeButton: "button", // Close button type
|
||||
icon: true, // Show icon
|
||||
rtl: false // Right-to-left text direction
|
||||
});
|
||||
```
|
||||
|
||||
### Direct Toast Access
|
||||
|
||||
For advanced usage, you can access the toast instance directly:
|
||||
|
||||
```javascript
|
||||
// Get direct access to Vue Toastification
|
||||
const toast = $toast;
|
||||
|
||||
if (toast) {
|
||||
// Use all Vue Toastification methods
|
||||
toast.success('Success message');
|
||||
toast.error('Error message');
|
||||
toast.warning('Warning message');
|
||||
toast.info('Info message');
|
||||
|
||||
// Clear all toasts
|
||||
toast.clear();
|
||||
|
||||
// Update a toast
|
||||
const toastId = toast.success('Loading...', { timeout: false });
|
||||
// Later update it
|
||||
toast.update(toastId, {
|
||||
content: 'Completed!',
|
||||
options: { timeout: 3000, type: 'success' }
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Examples
|
||||
|
||||
```javascript
|
||||
// Form validation with toasts
|
||||
onFieldChange('email', function(emailValue) {
|
||||
if (emailValue && !emailValue.includes('@')) {
|
||||
showWarning('Please enter a valid email address');
|
||||
}
|
||||
});
|
||||
|
||||
// Success feedback after form actions
|
||||
onFieldChange('save_draft', function(value) {
|
||||
if (value) {
|
||||
showSuccess('Draft saved successfully!', {
|
||||
position: 'top-center',
|
||||
timeout: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Error handling
|
||||
try {
|
||||
// Some form operation
|
||||
setField('complex_field', calculatedValue);
|
||||
showInfo('Field updated automatically');
|
||||
} catch (error) {
|
||||
showError('Failed to update field: ' + error.message);
|
||||
}
|
||||
```
|
@ -920,14 +920,31 @@
|
||||
<details>
|
||||
<summary class="text-sm font-medium text-green-800 cursor-pointer">🎨 UI & Notifications</summary>
|
||||
<div class="mt-2 text-xs text-green-700 space-y-1">
|
||||
<div><code>showSuccess('message')</code> - Display success notification</div>
|
||||
<div><code>showError('message')</code> - Display error notification</div>
|
||||
<div><code>showInfo('message')</code> - Display info notification</div>
|
||||
<div><code>showSuccess('message')</code> - Display success toast notification</div>
|
||||
<div><code>showError('message')</code> - Display error toast notification</div>
|
||||
<div><code>showInfo('message')</code> - Display info toast notification</div>
|
||||
<div><code>showWarning('message')</code> - Display warning toast notification</div>
|
||||
<div><code>showToast('message', options)</code> - Display custom toast with type options</div>
|
||||
<div><code>showConfirm('message')</code> - Show confirmation dialog with custom buttons</div>
|
||||
<div><code>querySelector('selector')</code> - Safe DOM querying within form</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Option Field Helpers -->
|
||||
<div class="mb-4 p-3 bg-indigo-50 rounded border border-indigo-200">
|
||||
<details>
|
||||
<summary class="text-sm font-medium text-indigo-800 cursor-pointer">🎯 Option Field Helpers</summary>
|
||||
<div class="mt-2 text-xs text-indigo-700 space-y-1">
|
||||
<div><code>setFieldByLabel('field', 'Label Text')</code> - Set field by display label</div>
|
||||
<div><code>getFieldOptions('field')</code> - Get all options for select/radio/checkbox</div>
|
||||
<div><strong>Enhanced setField:</strong> Works with select, radio, checkbox by value or label</div>
|
||||
<div>• <code>setField('country', 'us')</code> - Set by value</div>
|
||||
<div>• <code>setField('country', 'United States')</code> - Set by label</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Conditional Logic Optimization -->
|
||||
<div class="mb-4 p-3 bg-amber-50 rounded border border-amber-200">
|
||||
<details>
|
||||
|
Loading…
x
Reference in New Issue
Block a user