Using Transformers
Transformers are powerful functions that allow you to modify form elements at runtime. They follow a pipeline pattern where each transformer function receives an object, modifies it, and returns the modified version. Enforma includes multiple transformer types that target different aspects of your forms.
Enforma supports several types of transformers:
- Form Props Transformers - Modify all form properties (schema, context, config) before rendering
- Field Props Transformers - Modify field properties immediately after field controller initialization
- Repeatable Props Transformers - Modify repeatable field properties
- Repeatable Table Props Transformers - Modify repeatable table properties
- Section Props Transformers - Modify section properties
Transformer Function Pattern
All transformers follow a similar pattern:
// Basic transformer pattern
const myTransformer = (inputObject, formController) => {
// Create a copy to avoid mutating the original
const result = { ...inputObject };
// Apply your transformations
// ...
// Return the modified object
return result;
};Each transformer:
- Receives at least one argument (the object to transform)
- Usually receives the form controller as the second argument
- Returns a modified version of the object
- Should not mutate the original object
Form Props Transformers
Form props transformers modify all form properties (schema, context, and config) in a single pipeline before rendering, allowing you to dynamically adjust the entire form.
// A form props transformer that modifies schema, context, and config
const formPropsTransformer = (props, formController) => {
// Create a copy to avoid mutating the original
const result = { ...props };
const userRole = formController?.context?.userRole || 'user';
// Modify schema based on user role
if (result.schema && userRole === 'admin') {
result.schema.adminSettings = {
type: 'field',
label: 'Admin Settings',
component: 'textarea',
};
}
// Add date utilities to context
const now = new Date();
result.context = {
...result.context,
// Add date values and utilities
dates: {
currentDate: now.toISOString().split('T')[0],
currentYear: now.getFullYear(),
formatDate: (date) => new Date(date).toLocaleDateString(),
}
};
// Modify form configuration for mobile
const isMobile = window.innerWidth < 768;
if (result.config && result.config.behavior) {
result.config.behavior = {
...result.config.behavior,
// On mobile, validate on blur instead of input to improve performance
validateOn: isMobile ? 'blur' : result.config.behavior.validateOn,
};
}
return result;
};Use Cases for Form Props Transformers
- Adding or removing fields based on permissions
- Converting API-derived schemas to Enforma format
- Adding computed or derived values to context
- Injecting utility functions for expressions
- Enhancing context with user information
- Creating reactive values available to all fields
- Adapting form behavior to different environments
- Changing validation timing based on form complexity
- Adjusting component configurations for different devices
- Implementing A/B testing for form behavior
Component Props Transformers
All component props transformers follow a similar pattern - they receive the component props and form controller, modify the props, and return the transformed version.
Field Props Transformers
Field props transformers modify field properties immediately after the field controller is initialized and before additional component-specific props are applied.
// A field props transformer that adds CSS classes based on validation
// and sets custom IDs for the field inputs
const fieldPropsTransformer = (fieldOptions, field, formController) => {
// Create a copy to avoid mutating the original
const result = { ...fieldOptions };
// Add custom input props
result.inputProps = {
...result.inputProps,
// Add custom ID (will be preserved and respected in the component)
id: `custom-${field.value.name}-${formController.values().formId}`,
// Add special classes based on validation state
class: field.value.error
? 'invalid-input'
: field.value.isTouched
? 'touched-input'
: ''
};
// Customize input events
result.inputEvents = {
...result.inputEvents,
// Add custom event handlers or modify existing ones
change: (e) => {
// Do something before the default handler
console.log('Field changed:', e);
// Call the original handler
result.inputEvents.change(e);
}
};
// Add accessibility attributes to label props
result.labelProps = {
...result.labelProps,
'aria-required': result.required === true,
};
return result;
};Use Cases for Field Props Transformers
- Setting custom IDs for fields that will be respected by the component
- Adding UI framework specific props
- Enhancing accessibility attributes
- Customizing appearance based on field state
- Implementing custom formatting or display logic
- Modifying input event handlers for custom behavior
- Adding event logging or analytics to input events
Repeatable Props Transformers
Repeatable props transformers modify repeatable field properties before rendering.
// A repeatable props transformer that customizes the repeatable component
const repeatablePropsTransformer = (repeatableOptions, formController) => {
// Create a copy to avoid mutating the original
const result = { ...repeatableOptions };
// Customize based on the field name or other properties
if (result.name === 'experiences') {
// Disable sorting for certain fields
result.allowSort = false;
// Set minimum items based on form state
result.min = formController.values().requireExperience ? 1 : 0;
// Customize component props
result.componentProps = {
...result.componentProps,
customClass: 'experience-repeatable'
};
}
return result;
};Use Cases for Repeatable Props Transformers
- Dynamically controlling add/remove/sort capabilities
- Setting minimum or maximum items based on form state
- Customizing appearance and behavior of repeatable fields
- Adding custom buttons or actions to repeatable components
Repeatable Table Props Transformers
Repeatable table props transformers modify repeatable table properties, extending the repeatable props transformers.
// A repeatable table props transformer that customizes table display
const repeatableTablePropsTransformer = (tableOptions, formController) => {
// Create a copy to avoid mutating the original
const result = { ...tableOptions };
// Modify cell props based on content
if (result.name === 'budgetItems') {
// Add special handling for the budget table
result.subfields = {
...result.subfields,
amount: {
...result.subfields.amount,
inputProps: {
...result.subfields.amount.inputProps,
class: 'currency-input',
format: 'currency'
}
}
};
}
return result;
};Use Cases for Repeatable Table Props Transformers
- Customizing table layout and styling
- Adding special cell formatting based on content type
- Creating responsive tables for different screen sizes
- Implementing column-specific behaviors
Section Props Transformers
Section props transformers modify section properties before rendering.
// A section props transformer that adds toggle functionality
const sectionPropsTransformer = (sectionOptions, formController) => {
// Create a copy to avoid mutating the original
const result = { ...sectionOptions };
// Add collapsible behavior to sections
result.titleProps = {
...result.titleProps,
class: 'collapsible-section-title',
onClick: 'toggleSection',
'aria-expanded': 'true',
'aria-controls': `section-${result.name}-content`
};
// Make certain sections collapsed by default based on form state
if (formController.values().sectionPreferences?.[result.name] === 'collapsed') {
result.collapsed = true;
}
return result;
};Use Cases for Section Props Transformers
- Creating collapsible/expandable sections
- Implementing section visibility logic based on user roles
- Customizing section styling based on content
- Adding accessibility features to sections
- Implementing conditional section rendering
Registering Transformers
Transformers are registered in the Enforma configuration:
import { EnformaPlugin } from 'encolajs-enforma';
const app = createApp(App);
// Create Enforma instance with transformers
app.use(EnformaPlugin, {
transformers: {
form_props: [userRolePropsTransformer, responsivePropsTransformer],
field_props: [validationClassTransformer, ui5Transformer],
repeatable_props: [repeatablePropsTransformer],
repeatable_table_props: [responsiveTableTransformer],
section_props: [conditionalSectionsTransformer],
}
});Or locally at the form level:
<template>
<Enforma
:data="formData"
:schema="schema"
:config="formConfig"
:submit-handler="handleSubmit"
/>
</template>
<script setup>
import { ref } from 'vue';
const formConfig = {
transformers: {
form_props: [dynamicFormPropsTransformer],
repeatable_props: [dynamicRepeatableTransformer],
section_props: [accordionSectionTransformer],
}
};
</script>Transformer Order and Composition
Multiple transformers of the same type are applied in sequence, with each transformer receiving the output of the previous one:
input → transformer1 → transformer2 → transformer3 → outputThis allows you to:
- Compose complex transformations from simple ones
- Create reusable transformer functions for common patterns
- Apply transformers conditionally for different scenarios
Best Practices
Keep transformers pure and focused: Each transformer should do one thing well.
Create copies, don't mutate: Always create a copy of the input object before modifying it.
Handle errors gracefully: Use try/catch blocks to prevent transformer errors from breaking your form.
Add comments and meaningful names: Transformers can become complex, so document their purpose.
Consider performance: Transformers run during the render cycle, so keep them efficient.
Advanced Patterns
Conditional Transformers
Create transformers that only apply changes under certain conditions:
const conditionalTransformer = (schema, formController) => {
// Only transform if a condition is met
if (!formController || !formController.getFieldValue('enableFeature')) {
return schema; // Return unchanged
}
// Apply transformations...
return modifiedSchema;
};Creating Transformer Factories
Create functions that generate transformers with custom configurations:
// Factory function that creates a transformer
const createFieldAdderTransformer = (fieldKey, fieldConfig) => {
// Return a transformer function
return (schema, formController) => {
return {
...schema,
[fieldKey]: fieldConfig,
};
};
};
// Usage
const addEmailField = createFieldAdderTransformer('email', {
type: 'field',
label: 'Email',
component: 'input',
inputProps: { type: 'email' },
});Chaining Transformers Programmatically
Create a utility function to chain transformers:
const chainTransformers = (...transformers) => {
return (input, ...args) => {
return transformers.reduce((result, transformer) => {
return transformer(result, ...args);
}, input);
};
};
// Usage
const combinedTransformer = chainTransformers(
addRequiredTransformer,
addValidationTransformer,
translateLabelsTransformer
);Examples
Comprehensive Form Props Transformer
This example combines user role-based fields, form validation mode, and utility functions in a single transformer:
const comprehensivePropsTransformer = (props, formController) => {
const result = { ...props };
const userRole = formController?.context?.userRole || 'user';
// 1. Modify schema based on user role
if (result.schema) {
const roleFieldMap = {
admin: ['adminSettings', 'userManagement', 'systemConfig'],
manager: ['teamSettings', 'reportAccess'],
user: []
};
// Add fields based on user role
(roleFieldMap[userRole] || []).forEach(fieldKey => {
if (!result.schema[fieldKey] && fieldDefinitions[fieldKey]) {
result.schema[fieldKey] = fieldDefinitions[fieldKey];
}
});
}
// 2. Modify form config based on complexity
if (result.config) {
// Count the number of fields in the form
const fieldCount = Object.keys(formController?.values() || {}).length;
if (!result.config.behavior) {
result.config.behavior = {};
}
// For complex forms with many fields, validate on submit to improve performance
result.config.behavior.validateOn = fieldCount > 10 ? 'submit' : 'blur';
}
// 3. Add utility functions to context
result.context = {
...result.context,
utils: {
formatCurrency: (value) => `$${parseFloat(value).toFixed(2)}`,
formatPhone: (phone) => {
if (!phone) return '';
const cleaned = phone.replace(/\D/g, '');
return `(${cleaned.slice(0,3)}) ${cleaned.slice(3,6)}-${cleaned.slice(6,10)}`;
},
calculateAge: (birthdate) => {
const birth = new Date(birthdate);
const now = new Date();
let age = now.getFullYear() - birth.getFullYear();
if (now.getMonth() < birth.getMonth() ||
(now.getMonth() === birth.getMonth() && now.getDate() < birth.getDate())) {
age--;
}
return age;
}
}
};
return result;
};Performance Considerations
Transformers are applied efficiently using Vue's reactivity system:
Computed Transformations: Transformers are applied inside computed properties, so they only re-execute when their dependencies change.
Copy-on-Write: Transformers create copies of the objects they modify rather than mutating the originals.
Sequential Execution: Multiple transformers are applied in sequence, allowing for optimizations in how they compose.
To ensure good performance:
- Keep transformer functions simple and focused
- Avoid expensive operations like deep cloning large objects
- Consider memoizing transformers that perform complex calculations
- Use conditional logic to skip unnecessary transformations
