From e4b1c7e4445c7e6cfd2d76474f7edc3d423aa9b0 Mon Sep 17 00:00:00 2001 From: Md Afiq Iskandar Date: Tue, 29 Jul 2025 13:09:27 +0800 Subject: [PATCH] Enhance Workflow Execution with Iframe Support and Error Notification - Introduced iframe mode detection to streamline the user experience when embedded in other applications. - Added functionality to update the URL upon process completion, allowing for better integration with parent applications. - Implemented error notification to the parent iframe, ensuring that any issues during workflow execution are communicated effectively. - Enhanced UI responsiveness by adjusting styles based on iframe mode, improving overall usability and visual consistency. - Updated process completion and error handling logic to support seamless multi-process workflows. --- .../documentation/08-iframe-integration.md | 725 ++++++++++++++++++ pages/process-builder/index.vue | 2 +- pages/workflow/[id].vue | 272 +++++-- 3 files changed, 946 insertions(+), 53 deletions(-) create mode 100644 content/documentation/08-iframe-integration.md diff --git a/content/documentation/08-iframe-integration.md b/content/documentation/08-iframe-integration.md new file mode 100644 index 0000000..5bd22bc --- /dev/null +++ b/content/documentation/08-iframe-integration.md @@ -0,0 +1,725 @@ +# Iframe Integration Guide + +This guide explains how to integrate Corrad workflow forms into external applications using iframes. This enables seamless embedding of business processes into customer portals, external websites, or any web application. + +## Table of Contents + +- [Overview](#overview) +- [Basic Integration](#basic-integration) +- [Advanced Configuration](#advanced-configuration) +- [Multi-Process Workflows](#multi-process-workflows) +- [Error Handling](#error-handling) +- [Security Considerations](#security-considerations) +- [Examples](#examples) +- [Troubleshooting](#troubleshooting) + +## Overview + +Corrad provides a powerful iframe integration system that allows you to embed workflow forms into external applications. The system supports: + +- **Clean UI Mode**: Hide all debug information and UI chrome +- **Seamless Multi-Process**: Chain multiple workflows together +- **Real-time Communication**: Parent-child message passing +- **Error Handling**: Graceful error management +- **Progress Tracking**: Monitor workflow completion + +## Basic Integration + +### Simple Iframe Embedding + +```html + + +``` + +### URL Parameters + +| Parameter | Description | Example | +|-----------|-------------|---------| +| `debug=false` | Enable iframe mode (hide UI chrome) | `?debug=false` | +| `hideComplete=true` | Hide completion message | `?hideComplete=true` | +| `theme=dark` | Apply custom theme (if supported) | `?theme=dark` | + +### Complete Example + +```html + + + + Workflow Integration + + + +
+

Complete Your Application

+ +
+ + + + +``` + +## Advanced Configuration + +### Message Event Structure + +The workflow sends the following message types to the parent iframe: + +#### Completion Message +```javascript +{ + type: 'workflow-complete', + processId: 'application-form', + processName: 'Application Form', + hideCompletionMessage: true, + timestamp: '2024-01-15T10:30:00.000Z' +} +``` + +#### Error Message +```javascript +{ + type: 'workflow-error', + processId: 'application-form', + processName: 'Application Form', + error: 'API call failed: Network error', + timestamp: '2024-01-15T10:30:00.000Z' +} +``` + +### Advanced Integration Example + +```html + + + + Advanced Workflow Integration + + + +
+
+

Employee Onboarding

+
+
+
+
Starting process...
+
+ + +
+ + + + +``` + +## Multi-Process Workflows + +### Seamless Multi-Step Process + +For complex workflows with multiple steps, you can chain processes together seamlessly: + +```javascript +// Multi-process workflow configuration +const processes = [ + { id: 'personal-info', name: 'Personal Information' }, + { id: 'employment', name: 'Employment Details' }, + { id: 'documents', name: 'Document Upload' }, + { id: 'references', name: 'References' }, + { id: 'background', name: 'Background Check' }, + { id: 'medical', name: 'Medical Information' }, + { id: 'emergency', name: 'Emergency Contacts' }, + { id: 'benefits', name: 'Benefits Selection' }, + { id: 'payroll', name: 'Payroll Information' }, + { id: 'final', name: 'Final Review' } +]; + +let currentProcessIndex = 0; + +function loadProcess(processIndex) { + if (processIndex >= processes.length) { + // All processes complete + showFinalSuccess(); + return; + } + + const process = processes[processIndex]; + const iframe = document.getElementById('workflow-iframe'); + + // Load process with hidden completion message + iframe.src = `/workflow/${process.id}?debug=false&hideComplete=true`; + currentProcessIndex = processIndex; + + // Update progress indicator + updateProgress(processIndex + 1, processes.length); +} + +// Listen for completion and auto-advance +window.addEventListener('message', (event) => { + if (event.data.type === 'workflow-complete') { + console.log(`Process ${event.data.processName} completed`); + + if (event.data.hideCompletionMessage) { + // Seamlessly move to next process + setTimeout(() => { + loadProcess(currentProcessIndex + 1); + }, 100); + } + } else if (event.data.type === 'workflow-error') { + console.error('Process failed:', event.data.error); + showErrorMessage(event.data.error); + } +}); + +// Start the first process +loadProcess(0); +``` + +## Error Handling + +### Comprehensive Error Management + +```javascript +// Enhanced error handling +window.addEventListener('message', (event) => { + if (event.data.type === 'workflow-complete') { + handleWorkflowComplete(event.data); + } else if (event.data.type === 'workflow-error') { + handleWorkflowError(event.data); + } +}); + +function handleWorkflowComplete(data) { + console.log('Workflow completed:', data); + + // Store completion data + localStorage.setItem('workflow-completion', JSON.stringify(data)); + + // Update UI + updateCompletionUI(data); + + // Auto-advance if configured + if (data.hideCompletionMessage) { + setTimeout(() => { + loadNextProcess(); + }, 100); + } +} + +function handleWorkflowError(data) { + console.error('Workflow error:', data); + + // Log error for debugging + console.error('Error details:', { + processId: data.processId, + processName: data.processName, + error: data.error, + timestamp: data.timestamp + }); + + // Show user-friendly error message + showErrorMessage(data.error); + + // Optionally retry or allow manual restart + showRetryOptions(); +} + +function showErrorMessage(error) { + // Create error notification + const errorDiv = document.createElement('div'); + errorDiv.className = 'error-notification'; + errorDiv.innerHTML = ` +
+

⚠️ An error occurred

+

${error}

+ + +
+ `; + + document.body.appendChild(errorDiv); +} + +function retryCurrentProcess() { + const iframe = document.getElementById('workflow-iframe'); + iframe.src = iframe.src; // Reload current process +} + +function skipCurrentProcess() { + // Move to next process + loadNextProcess(); +} +``` + +## Security Considerations + +### Cross-Origin Communication + +When integrating iframes, consider these security aspects: + +1. **Origin Validation**: Verify message origin +2. **Content Security Policy**: Configure appropriate CSP headers +3. **Sandbox Attributes**: Use iframe sandbox for additional security + +```html + + +``` + +### Message Validation + +```javascript +// Validate message origin +window.addEventListener('message', (event) => { + // Verify origin (replace with your domain) + if (event.origin !== 'https://your-workflow-domain.com') { + console.warn('Message from unauthorized origin:', event.origin); + return; + } + + // Validate message structure + if (!event.data || typeof event.data.type !== 'string') { + console.warn('Invalid message structure:', event.data); + return; + } + + // Process valid message + handleWorkflowMessage(event.data); +}); +``` + +## Examples + +### Customer Portal Integration + +```html + +
+
+

Welcome, {{ customer.name }}

+ +
+ +
+
+

Complete Your Application

+
+ Step 1 of 5 +
+
+
+
+ + +
+
+
+ + +``` + +### Employee Onboarding System + +```html + +
+
+

Employee Onboarding

+
+
Personal Info
+
Employment
+
Documents
+
Benefits
+
Final
+
+
+ +
+ +
+
+ + +``` + +## Troubleshooting + +### Common Issues and Solutions + +#### 1. Iframe Not Loading + +**Problem**: Iframe shows blank or doesn't load +**Solution**: Check URL parameters and CORS settings + +```javascript +// Debug iframe loading +const iframe = document.getElementById('workflow-iframe'); +iframe.onload = () => { + console.log('Iframe loaded successfully'); +}; +iframe.onerror = () => { + console.error('Iframe failed to load'); +}; +``` + +#### 2. Messages Not Received + +**Problem**: Parent iframe not receiving completion messages +**Solution**: Verify event listener and origin settings + +```javascript +// Debug message reception +window.addEventListener('message', (event) => { + console.log('Message received:', event.data); + // Your message handling logic +}); +``` + +#### 3. Cross-Origin Issues + +**Problem**: CORS errors when loading iframe +**Solution**: Configure proper CORS headers on the workflow server + +```nginx +# Nginx configuration example +add_header Access-Control-Allow-Origin "*"; +add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; +add_header Access-Control-Allow-Headers "Content-Type"; +``` + +#### 4. URL Parameter Issues + +**Problem**: URL parameters not working as expected +**Solution**: Verify parameter encoding + +```javascript +// Proper URL construction +const baseUrl = '/workflow/process-id'; +const params = new URLSearchParams({ + debug: 'false', + hideComplete: 'true' +}); +const fullUrl = `${baseUrl}?${params.toString()}`; +``` + +### Debug Checklist + +- [ ] Iframe source URL is correct +- [ ] URL parameters are properly encoded +- [ ] Event listener is attached to window +- [ ] Origin validation is configured +- [ ] CORS headers are set on server +- [ ] Console shows no JavaScript errors +- [ ] Network tab shows successful iframe load + +### Support + +For additional support with iframe integration: + +1. Check the browser console for error messages +2. Verify network connectivity to the workflow server +3. Test with different browsers +4. Review server logs for any backend issues +5. Contact support with specific error details + +--- + +This documentation provides comprehensive guidance for integrating Corrad workflows into external applications. The iframe system is designed to be flexible, secure, and easy to implement while providing a seamless user experience. \ No newline at end of file diff --git a/pages/process-builder/index.vue b/pages/process-builder/index.vue index 3a4c6c8..9f11333 100644 --- a/pages/process-builder/index.vue +++ b/pages/process-builder/index.vue @@ -3973,7 +3973,7 @@ const sendToBack = () => { } /* Ensure all text elements in nodes inherit the custom text color */ -:deep(.custom-node *:not(.icon)) { +:deep(.custom-node *:not(.icon):not([class^="validation-"])) { color: inherit !important; } diff --git a/pages/workflow/[id].vue b/pages/workflow/[id].vue index 51bb5a7..4fd067c 100644 --- a/pages/workflow/[id].vue +++ b/pages/workflow/[id].vue @@ -12,8 +12,6 @@ definePageMeta({ title: "Process Execution", description: "Execute and run through a business process workflow", layout: "empty", - middleware: ["auth"], - requiresAuth: true, }); // Get route and router @@ -23,6 +21,16 @@ const router = useRouter(); // Get form builder store for ComponentPreview data sharing const formStore = useFormBuilderStore(); +// Check if we're in iframe/embed mode +const isIframeMode = computed(() => { + return route.query.debug === 'false'; +}); + +// Check if we should hide completion message (for seamless multi-process workflows) +const hideCompletionMessage = computed(() => { + return route.query.hideComplete === 'true'; +}); + // State const loading = ref(true); const process = ref(null); @@ -68,6 +76,13 @@ const isProcessComplete = computed(() => { return currentNode.value?.type === 'end' || currentStep.value >= (workflowData.value?.nodes?.length || 0); }); +// Watch for process completion and update URL +watch(isProcessComplete, (newValue) => { + if (newValue) { + updateUrlOnComplete(); + } +}); + // Load process data const loadProcess = async () => { try { @@ -96,10 +111,12 @@ const loadProcess = async () => { } else { error.value = response.message || 'Failed to load process'; + notifyParentOfError(error.value); } } catch (err) { console.error('[Workflow] Error loading process:', err); error.value = 'Failed to load process data'; + notifyParentOfError(error.value); } finally { loading.value = false; } @@ -128,6 +145,7 @@ const startProcessExecution = async () => { } catch (err) { console.error('[Workflow] Error starting process execution:', err); error.value = 'Failed to start process execution'; + notifyParentOfError(error.value); } }; @@ -637,6 +655,7 @@ const executeCurrentStep = async () => { } } else { error.value = 'API call failed: ' + (err.message || err); + notifyParentOfError(error.value); } } } else { @@ -675,6 +694,7 @@ const executeCurrentStep = async () => { } } else { error.value = 'API call failed: ' + (err.message || err); + notifyParentOfError(error.value); } } } @@ -690,6 +710,7 @@ const executeCurrentStep = async () => { } catch (err) { console.error('[Workflow] Error executing script node:', err); error.value = 'Script execution failed: ' + (err.message || err); + notifyParentOfError(error.value); } } // Only auto-progress if there's a single outgoing edge @@ -811,6 +832,7 @@ const executeCurrentStep = async () => { } } else { error.value = 'Notification failed: ' + (err.message || err); + notifyParentOfError(error.value); } } } else if (['decision', 'gateway'].includes(currentNode.value?.type)) { @@ -823,6 +845,7 @@ const executeCurrentStep = async () => { } catch (err) { console.error('[Workflow] Error executing step:', err); error.value = 'Failed to execute step'; + notifyParentOfError(error.value); } finally { stepLoading.value = false; } @@ -893,6 +916,139 @@ const goHome = () => { router.push('/'); }; +// Update URL when process completes +const updateUrlOnComplete = () => { + if (isIframeMode.value) { + const currentUrl = new URL(window.location.href); + currentUrl.searchParams.set('complete', 'true'); + // Use replaceState to avoid adding to browser history + window.history.replaceState({}, '', currentUrl.toString()); + console.log('[Workflow] URL updated with complete=true'); + + // Also notify parent iframe if we're in an iframe + try { + if (window.parent !== window) { + window.parent.postMessage({ + type: 'workflow-complete', + processId: processId.value, + processName: process.value?.processName, + hideCompletionMessage: hideCompletionMessage.value, + timestamp: new Date().toISOString() + }, '*'); + console.log('[Workflow] Message sent to parent iframe'); + } + } catch (error) { + console.log('[Workflow] Could not send message to parent:', error); + } + } +}; + +// Notify parent iframe of errors +const notifyParentOfError = (errorMessage) => { + if (isIframeMode.value) { + try { + if (window.parent !== window) { + window.parent.postMessage({ + type: 'workflow-error', + processId: processId.value, + processName: process.value?.processName, + error: errorMessage, + timestamp: new Date().toISOString() + }, '*'); + console.log('[Workflow] Error message sent to parent iframe'); + } + } catch (error) { + console.log('[Workflow] Could not send error message to parent:', error); + } + } +}; + +/* + * PARENT IFRAME INTEGRATION GUIDE: + * + * The workflow will send messages to the parent iframe when in iframe mode (?debug=false): + * + * 1. Process Completion: + * - URL will be updated with ?complete=true + * - Message sent: { type: 'workflow-complete', processId, processName, hideCompletionMessage, timestamp } + * + * 2. Process Error: + * - Message sent: { type: 'workflow-error', processId, processName, error, timestamp } + * + * URL Parameters: + * - ?debug=false: Enable iframe mode (hide UI chrome) + * - ?hideComplete=true: Hide completion message (for seamless multi-process workflows) + * + * Example parent iframe listener: + * + * window.addEventListener('message', (event) => { + * if (event.data.type === 'workflow-complete') { + * console.log('Workflow completed:', event.data); + * // Handle completion - redirect to next process if needed + * if (event.data.hideCompletionMessage) { + * // User won't see completion message, redirect immediately + * redirectToNextProcess(); + * } + * } else if (event.data.type === 'workflow-error') { + * console.log('Workflow error:', event.data); + * // Handle error + * } + * }); + * + * To check URL for completion: + * const urlParams = new URLSearchParams(window.location.search); + * if (urlParams.get('complete') === 'true') { + * // Process is complete + * } + * + * MULTI-PROCESS WORKFLOW EXAMPLE: + * + * // Parent system with multiple processes + * const processes = [ + * { id: 'process1', name: 'Personal Information' }, + * { id: 'process2', name: 'Employment Details' }, + * { id: 'process3', name: 'Document Upload' }, + * { id: 'process4', name: 'Final Review' } + * ]; + * + * let currentProcessIndex = 0; + * + * function loadProcess(processIndex) { + * if (processIndex >= processes.length) { + * // All processes complete + * showFinalSuccess(); + * return; + * } + * + * const process = processes[processIndex]; + * const iframe = document.getElementById('workflow-iframe'); + * + * // Load process with hidden completion message + * iframe.src = `/workflow/${process.id}?debug=false&hideComplete=true`; + * currentProcessIndex = processIndex; + * } + * + * // Listen for completion and auto-advance + * window.addEventListener('message', (event) => { + * if (event.data.type === 'workflow-complete') { + * console.log(`Process ${event.data.processName} completed`); + * + * if (event.data.hideCompletionMessage) { + * // Seamlessly move to next process + * setTimeout(() => { + * loadProcess(currentProcessIndex + 1); + * }, 100); // Small delay for smooth transition + * } + * } else if (event.data.type === 'workflow-error') { + * console.error('Process failed:', event.data.error); + * showErrorMessage(event.data.error); + * } + * }); + * + * // Start the first process + * loadProcess(0); + */ + // Load process on mount onMounted(() => { loadProcess(); @@ -1219,9 +1375,9 @@ function getConditionGroupResult(conditionGroup, variables) { -
+ +
- {{ stepLoading ? 'Processing...' : 'Submit & Continue' }} + {{ stepLoading ? 'Processing...' : 'Submit' }}
- -
+ +
Form Logic Available (Development)
@@ -1425,10 +1591,11 @@ function getConditionGroupResult(conditionGroup, variables) {
- -
- -
+ +
+ +

{{ @@ -1442,7 +1609,7 @@ function getConditionGroupResult(conditionGroup, variables) {
-
+

{{ @@ -1455,16 +1622,16 @@ function getConditionGroupResult(conditionGroup, variables) {

-
-

Choose your next step:

+
+

Choose your next step:

@@ -1474,8 +1641,8 @@ function getConditionGroupResult(conditionGroup, variables) {
-
-
+
+

{{ currentNode.data?.title || currentNode.data?.label || 'Content Display' }}

@@ -1485,30 +1652,31 @@ function getConditionGroupResult(conditionGroup, variables) {
-
+
-
+

No HTML content provided

-
+
-
+