Advanced Features & Patterns
Deep dive into advanced Page Designer features including workflow configuration, calculated fields, complex data binding, and production patterns.
Workflow Configuration
Workflow Triggers
Workflows are triggered by field changes, form submission, or scheduled events.
export interface WorkflowConfig {
id: string;
name: string;
trigger:
| "onChange"
| "onSubmit"
| "onFocus"
| "onBlur"
| "schedule"
| "manual";
// Conditional execution
conditions?: ConditionalLogic;
// Actions
actions: WorkflowAction[];
// Execution settings
async?: boolean; // Non-blocking
retryCount?: number;
timeout?: number; // ms
stopOnError?: boolean;
// Notifications
showNotification?: boolean;
notificationMessage?: string;
}
Workflow Actions
Email Action
Send email notifications
{
"type": "email",
"to": "user@example.com",
"toField": "emailField",
"cc": ["admin@example.com"],
"subject": "Form Submission: {form_name}",
"template": "form_submitted",
"variables": {
"userName": "{firstName}",
"orderAmount": "{total}",
"timestamp": "{{now}}"
}
}
Data Update Action
Update records in the system
{
"type": "dataUpdate",
"entity": "users",
"operation": "insert|update|delete",
"condition": {
"when": "status",
"operator": "equals",
"value": "premium"
},
"mappings": {
"name": "{firstName} {lastName}",
"email": "{email}",
"phone": "{phone}",
"preferences": {
"newsletter": "{subscribeNewsletter}",
"notifications": "{enableNotifications}"
}
}
}
API Call Action
Invoke external APIs
{
"type": "apiCall",
"method": "POST|GET|PUT|DELETE|PATCH",
"url": "https://api.example.com/submit",
"headers": {
"Authorization": "Bearer {authToken}",
"Content-Type": "application/json"
},
"payload": {
"orderId": "{orderNumber}",
"amount": "{total}",
"currency": "USD"
},
"responseMapping": {
"confirmationId": {
"path": "data.transactionId",
"targetField": "transactionId"
},
"status": {
"path": "data.status",
"targetField": "orderStatus"
}
}
}
Webhook Action
Trigger external webhooks
{
"type": "webhook",
"url": "https://webhook.example.com/forms",
"authentication": "bearer_token|api_key|basic",
"token": "{webhookToken}",
"payload": {
"event": "form_submitted",
"formId": "{form_id}",
"data": "{formData}",
"metadata": {
"timestamp": "{{now}}",
"userId": "{currentUserId}"
}
}
}
File Upload Action
Process uploaded files
{
"type": "fileUpload",
"targetField": "documentFile",
"destination": "s3|azure|local",
"bucket": "form-uploads",
"prefix": "documents/{year}/{month}/",
"encryption": true,
"maxSize": 10485760,
"allowedTypes": ["pdf", "doc", "docx"]
}
Conditional Branch Action
Execute different actions based on conditions
{
"type": "branch",
"conditions": [
{
"when": "amount",
"operator": "greaterThan",
"value": 10000,
"thenActions": [
{
"type": "email",
"to": "compliance@example.com",
"subject": "Large transaction approval required"
}
]
}
],
"elseActions": [
{
"type": "email",
"to": "user@example.com",
"subject": "Transaction approved"
}
]
}
Notification Action
Display in-app notifications
{
"type": "notification",
"style": "success|info|warning|error",
"title": "Success!",
"message": "Your application has been submitted",
"duration": 5000,
"action": {
"label": "View Status",
"url": "/applications/{applicationId}"
}
}
Workflow Example: Multi-Step Approval
{
"id": "expense_approval",
"name": "Expense Approval Workflow",
"trigger": "onSubmit",
"actions": [
{
"type": "dataUpdate",
"entity": "expenses",
"operation": "insert",
"mappings": {
"amount": "{expenseAmount}",
"category": "{category}",
"description": "{description}",
"submittedBy": "{currentUserId}",
"status": "pending"
}
},
{
"type": "branch",
"conditions": [
{
"when": "expenseAmount",
"operator": "greaterThan",
"value": 5000,
"thenActions": [
{
"type": "email",
"to": "director@example.com",
"subject": "Large expense approval required: ${expenseAmount}",
"template": "large_expense_approval"
}
]
}
],
"elseActions": [
{
"type": "email",
"to": "manager@example.com",
"subject": "Expense submitted: ${expenseAmount}",
"template": "standard_expense_notification"
}
]
},
{
"type": "notification",
"style": "success",
"message": "Your expense has been submitted for approval"
}
]
}
Calculated Fields
Formula-Based Calculation
Use mathematical expressions for numeric calculations:
export interface CalculatedValue {
type: "formula" | "javascript" | "transform" | "aggregate";
trigger: "onChange" | "onLoad" | "onSave";
// Formula-specific
formula?: string;
rounding?: number;
// JavaScript-specific
code?: string;
dependencies?: string[]; // Watched field IDs
// Transform-specific
transformType?: "uppercase" | "lowercase" | "capitalize" | "trim" | "reverse";
// Aggregate-specific
aggregationType?: "sum" | "avg" | "count" | "min" | "max";
sourceField?: string; // Field ID to aggregate
}
Formula Examples
[
{
"type": "formula",
"name": "Tax Calculation",
"formula": "subtotal * taxRate / 100",
"rounding": 2
},
{
"type": "formula",
"name": "Total Amount",
"formula": "subtotal + tax + shipping - discount",
"rounding": 2
},
{
"type": "formula",
"name": "Percentage Complete",
"formula": "(completedTasks / totalTasks) * 100",
"rounding": 1
},
{
"type": "formula",
"name": "Compound Interest",
"formula": "principal * (1 + rate/100)^years",
"rounding": 2
}
]
JavaScript Calculations
Complex logic using JavaScript:
{
"type": "javascript",
"code": `
const today = new Date();
const birthDate = new Date(fields.dateOfBirth);
const age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
return age - 1;
}
return age;
`,
"dependencies": ["dateOfBirth"]
}
Aggregate Calculations
Sum, average, or count across field collections:
{
"type": "aggregate",
"aggregationType": "sum",
"sourceField": "lineItems",
"operation": "items.reduce((sum, item) => sum + item.quantity * item.price, 0)"
}
Transform Functions
String manipulation and formatting:
[
{
"type": "transform",
"transformType": "uppercase",
"sourceName": "productName",
"targetName": "displayName"
},
{
"type": "formula",
"formula": "firstName + ' ' + lastName",
"targetName": "fullName"
},
{
"type": "javascript",
"code": "phone.replace(/^(\\d{3})(\\d{3})(\\d{4})$/, '($1) $2-$3')",
"targetName": "formattedPhone"
}
]
Advanced Data Binding
Two-Way Binding
Synchronize form state with external data source:
export interface DataBinding {
type: "oneWay" | "twoWay";
sourceField: string; // External data path
targetField: string; // Form field ID
transformer?: (value: any) => any; // Transform on read
reverseTransformer?: (value: any) => any; // Transform on write
debounce?: number; // ms
}
Example: Real-Time Product Details
{
"fields": [
{
"id": "productCode",
"type": "autocomplete",
"label": "Product Code",
"dataSource": {
"type": "api",
"url": "/api/products/search?q={query}"
}
},
{
"id": "productName",
"type": "text",
"label": "Product Name",
"readOnly": true,
"dataBinding": {
"type": "twoWay",
"sourceField": "selectedProduct.name"
}
},
{
"id": "unitPrice",
"type": "currency",
"label": "Unit Price",
"readOnly": true,
"dataBinding": {
"type": "twoWay",
"sourceField": "selectedProduct.price"
}
},
{
"id": "quantity",
"type": "number",
"label": "Quantity"
},
{
"id": "totalAmount",
"type": "currency",
"label": "Total",
"readOnly": true,
"calculated": {
"type": "javascript",
"code": "return parseFloat(unitPrice || 0) * parseFloat(quantity || 0);",
"dependencies": ["unitPrice", "quantity"]
}
}
]
}
Master-Detail Pattern
Parent-child form relationships:
{
"id": "order_form",
"fields": [
{
"id": "orderId",
"type": "text",
"label": "Order ID",
"readOnly": true
},
{
"id": "customerName",
"type": "autocomplete",
"label": "Customer",
"dataSource": {
"type": "api",
"url": "/api/customers"
}
},
{
"id": "orderItems",
"type": "repeater",
"label": "Order Items",
"children": [
{
"id": "product",
"type": "autocomplete",
"label": "Product",
"dataSource": {
"type": "api",
"url": "/api/products",
"params": {
"category": "{parentScope.category}"
}
}
},
{
"id": "quantity",
"type": "number",
"label": "Quantity"
},
{
"id": "price",
"type": "currency",
"label": "Unit Price",
"readOnly": true,
"dataBinding": {
"sourceField": "product.price"
}
},
{
"id": "lineTotal",
"type": "currency",
"label": "Total",
"readOnly": true,
"calculated": {
"formula": "quantity * price"
}
}
]
},
{
"id": "grandTotal",
"type": "currency",
"label": "Grand Total",
"readOnly": true,
"calculated": {
"type": "javascript",
"code": "return orderItems.reduce((sum, item) => sum + (item.lineTotal || 0), 0);",
"dependencies": ["orderItems"]
}
}
]
}
Cascading Dropdowns
Chain dropdown selections based on parent values:
{
"fields": [
{
"id": "country",
"type": "select",
"label": "Country",
"dataSource": {
"type": "api",
"url": "/api/countries"
}
},
{
"id": "state",
"type": "select",
"label": "State",
"dataSource": {
"type": "api",
"url": "/api/states",
"params": {
"countryId": "{country}"
}
},
"conditional": {
"rules": [
{
"when": "country",
"operator": "isNotEmpty",
"action": "show"
}
]
}
},
{
"id": "city",
"type": "select",
"label": "City",
"dataSource": {
"type": "api",
"url": "/api/cities",
"params": {
"stateId": "{state}"
}
},
"conditional": {
"rules": [
{
"when": "state",
"operator": "isNotEmpty",
"action": "show"
}
]
}
}
]
}
Dynamic Form Generation
Programmatically create forms based on data:
export const generateFormFromData = (data: any): FormSchema => {
const fields = Object.entries(data).map(([key, value]) => {
const fieldType = inferFieldType(value);
return {
id: key,
name: key,
type: fieldType,
label: humanize(key),
placeholder: `Enter ${humanize(key)}`,
required: value !== null && value !== undefined,
defaultValue: value,
};
});
return {
id: "generated-form",
name: "Generated Form",
fields,
};
};
const inferFieldType = (value: any): FieldType => {
if (typeof value === "boolean") return "switch";
if (typeof value === "number") return "number";
if (value instanceof Date) return "date";
if (Array.isArray(value)) return "repeater";
if (typeof value === "object") return "panel";
return "text";
};
Custom Validation Patterns
Cross-Field Validation
Validate one field based on another:
{
"id": "matchPassword",
"type": "password",
"label": "Confirm Password",
"validationRules": [
{
"type": "comparison",
"compareField": "password",
"operator": "equals",
"message": "Passwords do not match"
}
]
}
Custom Async Validation
{
"id": "email",
"type": "email",
"label": "Email",
"validationRules": [
{
"type": "async",
"asyncValidationUrl": "/api/email/check",
"asyncMethod": "POST",
"debounceMs": 500,
"message": "This email is already registered"
}
]
}
Complex Validation Logic
{
"id": "rentalEndDate",
"type": "date",
"label": "Rental End Date",
"validationRules": [
{
"type": "comparison",
"compareField": "rentalStartDate",
"operator": "greaterThan",
"message": "End date must be after start date"
},
{
"type": "custom",
"customValidation": "
const startDate = new Date(fields.rentalStartDate);
const endDate = new Date(value);
const maxDays = 365;
const daysDiff = Math.floor((endDate - startDate) / (1000 * 60 * 60 * 24));
if (daysDiff > maxDays) {
return 'Rental period cannot exceed ' + maxDays + ' days';
}
return true;
",
"message": "Invalid rental period"
}
]
}
Accessibility (a11y)
WCAG Compliance
{
"id": "formExample",
"name": "Accessible Form",
"fields": [
{
"id": "firstName",
"type": "text",
"label": "First Name",
"required": true,
"description": "Your legal first name as shown on official documents",
"ariaLabel": "First Name (required)",
"ariaDescribedBy": "firstName-help",
"inputAttributes": {
"autoComplete": "given-name",
"role": "textbox"
}
},
{
"id": "email",
"type": "email",
"label": "Email Address",
"required": true,
"ariaLabel": "Email Address (required)",
"inputAttributes": {
"autoComplete": "email",
"spellcheck": "false"
}
}
]
}
Keyboard Navigation
- Tab through fields in logical order
- Enter to submit forms
- Space/Enter to toggle checkboxes and radio buttons
- Escape to close dialogs
Screen Reader Support
- All fields must have labels
- Error messages associated with fields
- Form validation feedback announced
- Loading states indicated
Performance Optimization
Lazy Loading Fields
For forms with many fields, lazy load below the fold:
const LazyFieldGroup = React.lazy(() =>
import('./advanced-fields')
);
export const FormRenderer = ({ schema, data }) => {
return (
<div>
{/* Render visible fields immediately */}
{schema.fields.slice(0, 10).map(field =>
renderField(field, data)
)}
{/* Lazy load remaining fields */}
<Suspense fallback={<div>Loading...</div>}>
<LazyFieldGroup
fields={schema.fields.slice(10)}
data={data}
/>
</Suspense>
</div>
);
};
Debounced Validation
Reduce validation API calls:
const debouncedValidate = useMemo(
() =>
debounce((fieldId: string, value: any) => {
validateFieldAsync(fieldId, value);
}, 500),
[],
);
Memoized Data Sources
Cache data source results:
const dataSourceCache = new Map();
const getDataSourceData = async (dataSource: DataSource) => {
const cacheKey = JSON.stringify(dataSource);
if (dataSourceCache.has(cacheKey)) {
return dataSourceCache.get(cacheKey);
}
const data = await fetchDataSource(dataSource);
dataSourceCache.set(cacheKey, data);
return data;
};
Security Considerations
Input Sanitization
export const sanitizeInput = (input: string): string => {
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: [], // No HTML tags allowed
ALLOWED_ATTR: [],
});
};
// In field renderer
const handleChange = (value: string) => {
const sanitized = sanitizeInput(value);
onChange(sanitized);
};
CSRF Protection
Include CSRF tokens in API calls:
const createApiCall = (action: WorkflowAction) => {
return {
...action,
headers: {
...action.headers,
"X-CSRF-Token": getCsrfToken(),
},
};
};
XSS Prevention
Never use innerHTML directly:
// ❌ WRONG
<div dangerouslySetInnerHTML={{ __html: field.description }} />
// ✅ CORRECT
<div>{field.description}</div>
// If HTML is required, use DOMPurify
<div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(field.description)
}} />
Monitoring & Debugging
Form Submission Tracking
const trackFormSubmission = (formId: string, data: any) => {
analytics.track("form_submitted", {
formId,
fieldCount: Object.keys(data).length,
timestamp: new Date(),
userAgent: navigator.userAgent,
});
};
Validation Error Tracking
const trackValidationError = (fieldId: string, error: ValidationError) => {
analytics.track("validation_error", {
fieldId,
errorType: error.type,
errorMessage: error.message,
timestamp: new Date(),
});
};
Performance Monitoring
const measureRenderTime = (formId: string) => {
const startTime = performance.now();
return () => {
const endTime = performance.now();
const duration = endTime - startTime;
if (duration > 1000) {
console.warn(`Form ${formId} rendered slowly: ${duration}ms`);
}
};
};