- Refactored ConditionalLogicEngine.vue to optimize conditional logic script generation by grouping handlers for watched fields, reducing duplicate event listeners and improving performance. - Added helper functions for generating condition checks and action codes, enhancing code readability and maintainability. - Introduced ConditionalLogicTestDemo.vue to demonstrate the benefits of optimization, showcasing before and after examples of conditional logic handling. - Updated FormBuilderFieldSettingsModal.vue to include notes on optimization when multiple fields watch the same trigger field, improving user awareness of performance enhancements. - Enhanced ComponentPreview.vue and workflow pages to support preview mode for conditional logic, ensuring consistent behavior across the application.
222 lines
6.4 KiB
Vue
222 lines
6.4 KiB
Vue
<template>
|
|
<div class="conditional-logic-engine">
|
|
<!-- This component handles conditional logic execution -->
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, watch, nextTick } from 'vue'
|
|
|
|
const props = defineProps({
|
|
formComponents: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
formData: {
|
|
type: Object,
|
|
required: true
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['script-generated'])
|
|
|
|
// Internal state
|
|
const generatedScript = ref('')
|
|
const isInitialized = ref(false)
|
|
|
|
// Helper function to map operators to valid JavaScript
|
|
const getJavaScriptOperator = (operator) => {
|
|
switch (operator) {
|
|
case 'and':
|
|
case 'AND':
|
|
return '&&'
|
|
case 'or':
|
|
case 'OR':
|
|
return '||'
|
|
default:
|
|
return '&&' // Default to AND if unknown operator
|
|
}
|
|
}
|
|
|
|
// Generate conditional logic script from form components
|
|
const generateConditionalLogicScript = () => {
|
|
const scriptsArray = []
|
|
const fieldWatchers = new Map() // Group by watched field to avoid duplicates
|
|
const initialEvaluations = []
|
|
|
|
// Helper function to generate condition check
|
|
const generateConditionCheck = (condition) => {
|
|
const { field, operator: condOp, value } = condition
|
|
|
|
switch (condOp) {
|
|
case 'equals':
|
|
return `getField('${field}') === '${value}'`
|
|
case 'not_equals':
|
|
return `getField('${field}') !== '${value}'`
|
|
case 'contains':
|
|
return `String(getField('${field}') || '').toLowerCase().includes('${value}'.toLowerCase())`
|
|
case 'not_contains':
|
|
return `!String(getField('${field}') || '').toLowerCase().includes('${value}'.toLowerCase())`
|
|
case 'is_empty':
|
|
return `!getField('${field}') || getField('${field}') === ''`
|
|
case 'is_not_empty':
|
|
return `getField('${field}') && getField('${field}') !== ''`
|
|
case 'greater_than':
|
|
return `Number(getField('${field}')) > ${Number(value) || 0}`
|
|
case 'less_than':
|
|
return `Number(getField('${field}')) < ${Number(value) || 0}`
|
|
default:
|
|
return `getField('${field}') === '${value}'`
|
|
}
|
|
}
|
|
|
|
// Helper function to generate action code
|
|
const generateActionCode = (action, fieldName) => {
|
|
switch (action) {
|
|
case 'show': return `showField('${fieldName}')`
|
|
case 'hide': return `hideField('${fieldName}')`
|
|
case 'enable': return `enableField('${fieldName}')`
|
|
case 'disable': return `disableField('${fieldName}')`
|
|
default: return `showField('${fieldName}')`
|
|
}
|
|
}
|
|
|
|
// Helper function to generate inverse action code
|
|
const generateInverseActionCode = (action, fieldName) => {
|
|
switch (action) {
|
|
case 'show': return `hideField('${fieldName}')`
|
|
case 'hide': return `showField('${fieldName}')`
|
|
case 'enable': return `disableField('${fieldName}')`
|
|
case 'disable': return `enableField('${fieldName}')`
|
|
default: return `hideField('${fieldName}')`
|
|
}
|
|
}
|
|
|
|
// Process each component that has conditional logic enabled
|
|
props.formComponents.forEach(component => {
|
|
const conditionalLogic = component.props.conditionalLogic
|
|
|
|
if (!conditionalLogic || !conditionalLogic.enabled || !conditionalLogic.conditions.length) {
|
|
return
|
|
}
|
|
|
|
const { conditions, action, operator } = conditionalLogic
|
|
const fieldName = component.props.name
|
|
|
|
if (!fieldName) return
|
|
|
|
// Generate condition checks
|
|
const conditionChecks = conditions.map(generateConditionCheck).join(` ${getJavaScriptOperator(operator)} `)
|
|
|
|
// Generate action functions
|
|
const actionCode = generateActionCode(action, fieldName)
|
|
const inverseActionCode = generateInverseActionCode(action, fieldName)
|
|
|
|
// Get all watched fields for this component
|
|
const watchedFields = [...new Set(conditions.map(c => c.field).filter(Boolean))]
|
|
|
|
// Group logic by watched field to avoid duplicates
|
|
watchedFields.forEach(watchField => {
|
|
if (!fieldWatchers.has(watchField)) {
|
|
fieldWatchers.set(watchField, {
|
|
fieldName: watchField,
|
|
logicBlocks: []
|
|
})
|
|
}
|
|
|
|
// Add this component's logic to the watched field
|
|
fieldWatchers.get(watchField).logicBlocks.push({
|
|
targetField: fieldName,
|
|
conditionChecks,
|
|
actionCode,
|
|
inverseActionCode,
|
|
componentType: component.type
|
|
})
|
|
})
|
|
|
|
// Add initial evaluation
|
|
initialEvaluations.push({
|
|
targetField: fieldName,
|
|
conditionChecks,
|
|
actionCode,
|
|
inverseActionCode,
|
|
componentType: component.type
|
|
})
|
|
})
|
|
|
|
// Generate optimized field change handlers (one per watched field)
|
|
fieldWatchers.forEach((watcher, watchField) => {
|
|
const logicBlocks = watcher.logicBlocks.map(block => `
|
|
// Logic for ${block.componentType} field: ${block.targetField}
|
|
if (${block.conditionChecks}) {
|
|
${block.actionCode};
|
|
} else {
|
|
${block.inverseActionCode};
|
|
}`).join('')
|
|
|
|
const script = `
|
|
// Optimized field change handler for: ${watchField}
|
|
// Handles ${watcher.logicBlocks.length} dependent field(s): ${watcher.logicBlocks.map(b => b.targetField).join(', ')}
|
|
onFieldChange('${watchField}', function() {${logicBlocks}
|
|
});`
|
|
|
|
scriptsArray.push(script)
|
|
})
|
|
|
|
// Generate initial evaluations
|
|
if (initialEvaluations.length > 0) {
|
|
const initialLogicBlocks = initialEvaluations.map(block => `
|
|
// Initial evaluation for ${block.componentType} field: ${block.targetField}
|
|
if (${block.conditionChecks}) {
|
|
${block.actionCode};
|
|
} else {
|
|
${block.inverseActionCode};
|
|
}`).join('')
|
|
|
|
const initialScript = `
|
|
// Initial conditional logic evaluation
|
|
(function() {${initialLogicBlocks}
|
|
})();`
|
|
|
|
scriptsArray.push(initialScript)
|
|
}
|
|
|
|
return scriptsArray.join('\n\n')
|
|
}
|
|
|
|
// Update generated script when components change
|
|
watch(() => props.formComponents, () => {
|
|
if (isInitialized.value) {
|
|
updateConditionalLogic()
|
|
}
|
|
}, { deep: true })
|
|
|
|
// Update conditional logic
|
|
const updateConditionalLogic = () => {
|
|
const newScript = generateConditionalLogicScript()
|
|
generatedScript.value = newScript
|
|
|
|
// Emit the generated script to parent component
|
|
emit('script-generated', newScript)
|
|
}
|
|
|
|
// Initialize on mount
|
|
onMounted(() => {
|
|
nextTick(() => {
|
|
updateConditionalLogic()
|
|
isInitialized.value = true
|
|
})
|
|
})
|
|
|
|
// Expose the generated script for debugging
|
|
defineExpose({
|
|
generatedScript,
|
|
updateConditionalLogic
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.conditional-logic-engine {
|
|
display: none; /* This component is purely functional */
|
|
}
|
|
</style> |