From e2a6ce3c4fa9dcdd6c592aff55d04db572d1bbbb Mon Sep 17 00:00:00 2001 From: Andy Haas Date: Tue, 16 Dec 2025 17:43:06 -0600 Subject: [PATCH 01/15] fix(detectAndLaunch): Fix null value error and add debug mode attribute Priority 1 Fixes: - Fix null value error in field comparison (1.3) - Added null check before calling toString() on field values - Prevents 'Cannot read property toString of null' runtime errors - Extracts fieldKey and fieldData into variables for better readability - Only calls toString() when fieldData exists and value is not null - Add debug mode attribute (1.1 - partial) - Added 'debugMode' Boolean attribute (default: false) - Allows users to opt-in to console logging for debugging - Disabled by default for performance and security Documentation: - Added ANALYSIS.md with comprehensive component analysis - Added FIX_PLAN.md with prioritized fix plan and implementation roadmap Files changed: - detectAndLaunchController.js: Fixed null handling in field value comparison - detectAndLaunch.cmp: Added debugMode attribute - ANALYSIS.md: New file with technical analysis - FIX_PLAN.md: New file with fix plan and priorities --- .../DetectAndLaunch/ANALYSIS.md | 276 ++++++++++++++++++ .../DetectAndLaunch/FIX_PLAN.md | 223 ++++++++++++++ .../aura/detectAndLaunch/detectAndLaunch.cmp | 1 + .../detectAndLaunchController.js | 14 +- 4 files changed, 508 insertions(+), 6 deletions(-) create mode 100644 lightning_page_components/DetectAndLaunch/ANALYSIS.md create mode 100644 lightning_page_components/DetectAndLaunch/FIX_PLAN.md diff --git a/lightning_page_components/DetectAndLaunch/ANALYSIS.md b/lightning_page_components/DetectAndLaunch/ANALYSIS.md new file mode 100644 index 000000000..4a3ea9a1f --- /dev/null +++ b/lightning_page_components/DetectAndLaunch/ANALYSIS.md @@ -0,0 +1,276 @@ +# DetectAndLaunch Component - Technical Analysis + +## Overview +A Lightning Aura component that automatically detects record changes and launches screen flows based on configurable triggers. + +## Component Architecture + +### Files Structure +- **detectAndLaunch.cmp** - Main component markup (58 lines) +- **detectAndLaunchController.js** - Event handlers and initialization (83 lines) +- **detectAndLaunchHelper.js** - Business logic for flow launching (88 lines) +- **detectAndLaunchRenderer.js** - Empty renderer (custom rendering not used) +- **detectAndLaunch.css** - Empty stylesheet (no custom styling) +- **detectAndLaunch.design** - Lightning App Builder configuration +- **detectAndLaunch.cmp-meta.xml** - Metadata (API version 48.0) + +## Functionality + +### Core Features +1. **Record Change Detection** + - Uses `force:recordData` to monitor record changes + - Detects: CHANGED, REMOVED, LOADED events + - Watches specific fields when configured + +2. **Flow Launch Modes** + - **Modal**: Opens flow in a modal dialog + - **Modeless**: Opens in new tab/window (default) + - Console-aware: Uses subtabs in Service Console + +3. **Conditional Launching** + - Can trigger based on specific field value changes + - Supports field comparison logic + +### Component Attributes + +| Attribute | Type | Default | Description | +|-----------|------|---------|-------------| +| `editFlowName` | String | - | Flow to launch on record edit | +| `deleteFlowName` | String | - | Flow to launch on record delete | +| `loadFlowName` | String | - | Flow to launch on record load | +| `fieldChange` | String | - | Field to watch for changes | +| `fieldValue` | String | - | Value to match for conditional launch | +| `launchMode` | String | "Modeless" | Modal or Modeless launch | +| `recordId` | String | - | Inherited from `force:hasRecordId` | + +## Code Analysis + +### Strengths ✅ + +1. **Console Navigation Support** + - Properly detects console vs standard UI + - Uses WorkspaceAPI for subtab management + - Falls back to window.open for standard UI + +2. **Page Builder Protection** + - Checks for `flexipageEditor` in URL + - Prevents execution during page configuration + +3. **Flexible Configuration** + - Multiple trigger types (edit/delete/load) + - Conditional field-based triggering + - Configurable launch modes + +4. **Error Handling** + - Checks for null/undefined flow names + - Handles console API errors with catch blocks + - Passes recordId to flows as input variable + +### Issues & Concerns ⚠️ + +#### 1. **Excessive Console Logging** +```javascript +// Lines 3, 7, 9, 13, 16, 19, 22, 29, 30, etc. +console.log('entering processChangeEvent'); +console.log('currentUrl is: ' + currentUrl); +// ... many more console.log statements +``` +**Impact**: Performance overhead, potential security concerns (exposes internal logic) +**Recommendation**: Remove or wrap in debug mode flag + +#### 2. **Inconsistent Null Checking** +```javascript +// Line 32-33: Checks for null/undefined +if (flowApiName == null || flowApiName == undefined) { +``` +**Issue**: Uses loose equality (`==`) instead of strict (`===`) +**Recommendation**: Use `===` or leverage JavaScript truthiness + +#### 3. **String Concatenation Instead of Template Literals** +```javascript +// Line 18, 65, 78 +var targetUrl = '/flow/' + component.get("v.targetFlowName") + '?recordId=' + component.get("v.recordId"); +``` +**Recommendation**: Use template literals for better readability: +```javascript +var targetUrl = `/flow/${component.get("v.targetFlowName")}?recordId=${component.get("v.recordId")}`; +``` + +#### 4. **Missing Error Handling for Flow Launch** +```javascript +// Line 51: No try-catch around flow.startFlow +flow.startFlow(flowApiName, inputVariable); +``` +**Issue**: If flow fails to start, error may not be handled gracefully +**Recommendation**: Add error handling + +#### 5. **Hardcoded Input Variable Name** +```javascript +// Line 41: Always uses "recordId" as input variable name +name : "recordId", +``` +**Issue**: Assumes all flows have a "recordId" input variable +**Recommendation**: Make this configurable + +#### 6. **Modal Implementation Issues** +- Modal container has hardcoded height (600px) - may not be responsive +- Commented-out header/footer code suggests incomplete implementation +- No close button functionality visible in controller + +#### 7. **Field Watching Logic Complexity** +```javascript +// Lines 39-57: Complex nested conditionals for field value matching +if(component.get("v.fieldChange") != null && component.get("v.fieldValue") != null ){ + // ... nested logic +} +``` +**Issue**: Difficult to maintain and extend +**Recommendation**: Extract to separate helper method + +#### 8. **No Input Validation** +- No validation that flow names are valid +- No check if flows are active +- No validation of field API names + +#### 9. **API Version** +- Using API version 48.0 (from 2020) +- Consider updating to latest version for new features + +#### 10. **Missing Documentation** +- No JSDoc comments +- Limited inline comments +- No usage examples in code + +## Security Considerations + +### Current State +- ✅ Uses `with sharing` context (inherited from platform) +- ✅ No direct DML operations +- ✅ Uses platform APIs (force:recordData, lightning:flow) + +### Potential Concerns +1. **Console Logging**: May expose sensitive data in browser console +2. **URL Construction**: Direct string concatenation could be vulnerable if inputs aren't sanitized (though Salesforce platform handles this) +3. **No CSRF Protection**: Relies on Salesforce platform security + +## Performance Considerations + +1. **Record Data Watching**: Only watches specified fields (efficient) +2. **Console Logging**: Excessive logging may impact performance +3. **Modal Rendering**: Modal only renders when needed (good use of aura:if) +4. **Event Handling**: Single event handler for all change types (efficient) + +## Recommendations for Improvement + +### High Priority +1. **Remove/Reduce Console Logging** + - Add debug mode flag + - Use conditional logging + +2. **Add Error Handling** + - Wrap flow.startFlow in try-catch + - Provide user feedback on errors + +3. **Improve Null Checking** + - Use strict equality (`===`) + - Leverage JavaScript truthiness + +4. **Make Input Variable Configurable** + - Add attribute for input variable name + - Support multiple input variables + +### Medium Priority +5. **Refactor Field Watching Logic** + - Extract to separate method + - Simplify conditional logic + +6. **Update Code Style** + - Use template literals + - Modern JavaScript practices + +7. **Add Input Validation** + - Validate flow names + - Validate field API names + +8. **Improve Modal Implementation** + - Make height responsive + - Add proper close functionality + - Complete header/footer if needed + +### Low Priority +9. **Add Documentation** + - JSDoc comments + - Usage examples + - Inline comments for complex logic + +10. **Update API Version** + - Test with latest API version + - Leverage new platform features + +11. **Add Unit Tests** + - Test change detection logic + - Test flow launching scenarios + - Test error handling + +## Usage Patterns + +### Basic Usage +```xml + + +``` + +### Conditional Launch +```xml + + +``` + +### Multiple Triggers +```xml + + +``` + +## Dependencies + +- **force:recordData** - Record change detection +- **lightning:workspaceAPI** - Console navigation +- **lightning:flow** - Flow execution +- **flexipage:availableForAllPageTypes** - Page builder support +- **force:hasRecordId** - Record context +- **lightning:isUrlAddressable** - URL navigation + +## Testing Considerations + +### Test Scenarios +1. Record edit detection +2. Record delete detection +3. Record load detection +4. Field value conditional launch +5. Modal vs Modeless launch +6. Console vs Standard UI +7. Page builder mode (should not execute) +8. Error scenarios (invalid flow name, etc.) + +## Conclusion + +The DetectAndLaunch component is a functional solution for automatic flow launching based on record changes. It demonstrates good understanding of Salesforce platform capabilities and console navigation. However, it would benefit from code cleanup, improved error handling, and better maintainability practices. + +**Overall Assessment**: ⭐⭐⭐ (3/5) +- Functional and works as intended +- Needs code quality improvements +- Good foundation for enhancement + diff --git a/lightning_page_components/DetectAndLaunch/FIX_PLAN.md b/lightning_page_components/DetectAndLaunch/FIX_PLAN.md new file mode 100644 index 000000000..096f8f67b --- /dev/null +++ b/lightning_page_components/DetectAndLaunch/FIX_PLAN.md @@ -0,0 +1,223 @@ +# DetectAndLaunch Component - Fix Plan + +Based on technical analysis and community feedback from [UnofficialSF](https://unofficialsf.com/from-andy-haas-trigger-screen-flows-with-record-changes-using-detect-and-launch/), here's a prioritized plan to fix and enhance the component. + +## Priority 1: Critical Fixes (Must Fix) + +### 1.1 Remove/Reduce Console Logging +**Issue**: 20+ console.log statements impact performance and expose internal logic +**Files**: `detectAndLaunchController.js`, `detectAndLaunchHelper.js` +**Action**: +- Remove all console.log statements from production code +- Add a debug mode attribute (optional) +- Keep only critical error logging + +**Lines to fix**: +- Controller: 34, 41, 42, 45, 50, 59, 76, 77 +- Helper: 3, 7, 9, 13, 16, 19, 22, 29, 30, 33, 50, 53, 60, 74, 77, 79 + +### 1.2 Fix Loose Equality Checks +**Issue**: Using `==` instead of `===` can cause unexpected type coercion +**Files**: `detectAndLaunchHelper.js` +**Action**: Replace all `==` and `!=` with `===` and `!==` + +**Lines to fix**: +- Line 32: `if (flowApiName == null || flowApiName == undefined)` → `if (!flowApiName)` +- Line 35: `if(component.get("v.launchMode") == 'Modal')` → `if(component.get("v.launchMode") === 'Modal')` +- Line 39: `if(component.get("v.fieldChange") != null && component.get("v.fieldValue") != null)` → Use strict checks + +### 1.3 Fix Null Value Error in Field Comparison +**Status**: ✅ **FIXED** +**Issue**: Calling `.toString()` on null field values causes runtime error +**Files**: `detectAndLaunchController.js` +**Fix Applied**: Added null check before calling toString() on field value +- Check if `fieldData` exists and `fieldData.value != null` before calling `.toString()` +- Prevents "Cannot read property 'toString' of null" errors + +**Lines fixed**: +- Lines 49-56: Added proper null checking with `fieldData && fieldData.value != null` + +### 1.4 Add Error Handling for Flow Launch +**Issue**: No try-catch around `flow.startFlow()` - errors not handled gracefully +**Files**: `detectAndLaunchHelper.js` +**Action**: Wrap flow.startFlow in try-catch block + +**Lines to fix**: +- Line 51: Add try-catch around `flow.startFlow()` + +### 1.5 Fix Modal Close Functionality (Optional Feature) +**Issue**: Modal header/footer are commented out - no way to close modal +**Files**: `detectAndLaunch.cmp`, `detectAndLaunchController.js` +**Action**: +- Add new attribute `enableModalClose` (default: false) to avoid breaking changes +- Uncomment and fix modal header with close button, but only show when enabled +- Users must explicitly enable this feature if they want modal close functionality + +**Breaking Change Consideration**: +- This must be **disabled by default** to maintain backward compatibility +- Existing users who have built flows expecting the current modal behavior won't be affected +- Users can opt-in by setting `enableModalClose="true"` + +**New attribute needed**: +```xml + +``` + +**Lines to fix**: +- Lines 35-38: Conditionally show header with close button based on `enableModalClose` attribute +- Ensure closeModal controller method works properly + +## Priority 2: Important Improvements (Should Fix) + +### 2.1 Make Input Variable Name Configurable +**Status**: ❌ **Won't Fix** - Doesn't align with component design + +**Issue**: Hardcoded "recordId" input variable name - assumes all flows use this +**Reason for Not Implementing**: +- The component is designed to watch a specific record and launch flows based on record changes +- The component inherits `force:hasRecordId` and is fundamentally built around the recordId context +- Making the input variable configurable would conflict with the component's core purpose of detecting and responding to changes on the watched record +- Users who need different input variables should use the recordId in their flows and map it to their desired variable names + +**Decision**: Keep hardcoded "recordId" as it's central to the component's functionality + +### 2.2 Improve Code Readability with Template Literals +**Issue**: String concatenation is harder to read and maintain +**Files**: `detectAndLaunchHelper.js` +**Action**: Replace string concatenation with template literals + +**Lines to fix**: +- Line 18: `/flow/${component.get("v.targetFlowName")}?recordId=${component.get("v.recordId")}` +- Line 65: Same pattern +- Line 78: Same pattern + +### 2.3 Refactor Field Watching Logic +**Issue**: Complex nested conditionals in recordUpdated method +**Files**: `detectAndLaunchController.js` +**Action**: Extract field value matching logic to helper method + +**New helper method needed**: +```javascript +checkFieldValueMatch: function(component, eventParams) { + // Extract the complex field matching logic here +} +``` + +### 2.4 Add Input Validation +**Issue**: No validation that flow names are valid or flows are active +**Files**: `detectAndLaunchController.js`, `detectAndLaunchHelper.js` +**Action**: Add validation before attempting to launch flows + +**Validation needed**: +- Check if flow name is provided +- Check if flow name is not empty +- Optionally: Validate field API names + +### 2.5 Fix Modal Height Issue +**Issue**: Hardcoded 600px height - not responsive +**Files**: `detectAndLaunch.cmp` +**Action**: Use responsive CSS classes or remove fixed height + +**Lines to fix**: +- Line 31: Remove `style="height: 600px;"` or make it responsive + +## Priority 3: Enhancements (Nice to Have) + +### 3.1 Support Multiple Field Conditions +**Issue**: Can only watch one field at a time +**Files**: All component files +**Action**: Add support for multiple field/value pairs (requires significant refactoring) + +**Note**: This is a major enhancement, consider as separate feature request + +### 3.2 Support Conditional Launch on Load/Delete +**Status**: ❌ **Cannot be Supported** + +**Issue**: Conditional field-based launch only works on edit (per blog post limitations) +**Reason**: +- The component architecture is designed to watch record changes and evaluate field conditions during edit events +- LOADED events don't have "changedFields" data to compare against +- REMOVED events occur when the record is deleted, so field value comparisons aren't meaningful +- This limitation is by design and cannot be supported with the current architecture + +**Decision**: Keep as documented limitation - conditional field-based launch only works on CHANGED events + +### 3.3 Add JSDoc Documentation +**Issue**: No function documentation +**Files**: All JS files +**Action**: Add JSDoc comments to all functions + +**Example**: +```javascript +/** + * Processes record change events and launches appropriate flows + * @param {Component} component - The component instance + * @param {Object} eventParams - Event parameters from force:recordData + */ +processChangeEvent: function(component, eventParams) { + // ... +} +``` + +### 3.4 Update API Version +**Issue**: Using API version 48.0 (from 2020) +**Files**: `detectAndLaunch.cmp-meta.xml` +**Action**: Test and update to latest API version (currently 62.0+) + +**Note**: Test thoroughly before updating + +### 3.5 Add User Feedback for Errors +**Issue**: Errors are only logged to console +**Files**: `detectAndLaunchController.js`, `detectAndLaunchHelper.js` +**Action**: Add toast messages or error display for user-facing errors + +## Implementation Order + +### Phase 1: Critical Fixes (Week 1) +1. ✅ Fix null value error in field comparison (COMPLETED) +2. Remove console logging +3. Fix equality checks +4. Add error handling for flow launch +5. Fix modal close functionality (as optional feature, disabled by default) + +### Phase 2: Important Improvements (Week 2) +1. Use template literals +2. Refactor field watching logic +3. Add input validation +4. Fix modal height + +### Phase 3: Enhancements (Week 3+) +1. Add documentation +2. Update API version +3. Add user feedback +4. Consider major enhancements (multiple fields) + +## Testing Checklist + +After each fix, test: +- [ ] Record edit detection +- [ ] Record delete detection +- [ ] Record load detection +- [ ] Field value conditional launch +- [ ] Modal launch mode +- [ ] Modeless launch mode +- [ ] Console navigation +- [ ] Standard UI navigation +- [ ] Page builder mode (should not execute) +- [ ] Error scenarios (invalid flow name, missing flow, etc.) +- [ ] Multiple component instances on same page + +## Known Limitations (From Blog Post) + +These are documented limitations, not bugs: +1. **Cross-field comparisons not supported** - Change Value must be static +2. **Conditional launch only on edit** - Conditional field-based launch only works on CHANGED events, not LOADED or REMOVED (by design - cannot be supported) +3. **Modal close function** - Users need to build close function in flow (now optional via `enableModalClose` attribute, disabled by default) +4. **Input variable name** - Hardcoded to "recordId" (by design - component is built around recordId context) + +## References + +- [UnofficialSF Blog Post](https://unofficialsf.com/from-andy-haas-trigger-screen-flows-with-record-changes-using-detect-and-launch/) +- Component Analysis: `ANALYSIS.md` +- Component Source: `force-app/main/default/aura/detectAndLaunch/` + diff --git a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunch.cmp b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunch.cmp index ede90de63..8d3f46383 100755 --- a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunch.cmp +++ b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunch.cmp @@ -10,6 +10,7 @@ + Date: Tue, 16 Dec 2025 17:44:16 -0600 Subject: [PATCH 02/15] refactor(detectAndLaunch): Remove/reduce console logging with debug mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Priority 1 Fix (1.1): Remove/Reduce Console Logging Changes: - Created debugLog() helper function in detectAndLaunchHelper.js - Checks debugMode attribute before logging - Supports optional data parameter for structured logging - Only logs when debugMode is explicitly enabled (default: false) - Replaced all console.log statements with conditional debug logging - Controller: 8 console.log statements → helper.debugLog() calls - Helper: 12 console.log statements → this.debugLog() calls - Total: 20 debug statements now conditional on debugMode - Critical error logging preserved - Changed error logging to console.error() (always logs) - Catch block errors in workspace API calls - ERROR changeType events in recordUpdated handler - Ensures critical issues are always logged for troubleshooting Benefits: - Performance: No console logging overhead when debugMode=false (default) - Security: Internal component logic not exposed unless explicitly enabled - Maintainability: Centralized logging logic in debugLog() helper - User Experience: Users can enable debug mode when troubleshooting Files changed: - detectAndLaunchController.js: Replaced 8 console.log with debugLog calls - detectAndLaunchHelper.js: Added debugLog helper, replaced 12 console.log calls --- .../detectAndLaunchController.js | 17 ++++--- .../detectAndLaunch/detectAndLaunchHelper.js | 50 +++++++++++++------ 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js index 0df6c1681..27d501c6f 100755 --- a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js +++ b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js @@ -31,18 +31,18 @@ }, recordUpdated: function(component, event, helper) { - console.log('entering recordUpdate'); + helper.debugLog(component, 'entering recordUpdate'); var eventParams = event.getParams(); // If both Field Change and Field Value are not null then set to isChangedRecord to true // This will then populate the users field value within the fields list if(component.get("v.fieldChange") != null && component.get("v.fieldValue") != null ){ component.set("v.isChangedRecord", true); - console.log('fieldChange',component.get("v.fieldChange")); - console.log('fieldValue',component.get("v.fieldValue")); + helper.debugLog(component, 'fieldChange', component.get("v.fieldChange")); + helper.debugLog(component, 'fieldValue', component.get("v.fieldValue")); // Make sure there was an edited field and we just edited a record if ( eventParams.changedFields && eventParams.changeType === "CHANGED") { - console.log('eventParams.changedFields', eventParams.changedFields); + helper.debugLog(component, 'eventParams.changedFields', eventParams.changedFields); var changed = JSON.parse(JSON.stringify(eventParams.changedFields)); var fieldKey = component.get("v.fieldCompare"); var fieldData = changed[fieldKey]; @@ -50,7 +50,7 @@ // Check if field exists and has a non-null value before calling toString() if (fieldData && fieldData.value != null) { var newValue = fieldData.value.toString(); - console.log('changed.dynamic.value', newValue); + helper.debugLog(component, 'changed.dynamic.value', newValue); if (newValue === component.get("v.fieldValue")) { component.set("v.targetFlowName", component.get("v.editFlowName")); helper.processChangeEvent(component, eventParams); @@ -58,7 +58,7 @@ } } } else { - console.log('changeType: ' + eventParams.changeType); + helper.debugLog(component, 'changeType: ' + eventParams.changeType); // Get Flow To Use if(eventParams.changeType === "CHANGED") { component.set("v.targetFlowName", component.get("v.editFlowName")); @@ -75,8 +75,9 @@ } else if( eventParams.changeType === "LOADED") { helper.processChangeEvent(component, eventParams); } else if(eventParams.changeType === "ERROR") { - console.log(eventParams); - console.log('Update event received Error: ' + component.get("v.error")); + // Critical error - always log for troubleshooting + console.error('Update event received Error:', eventParams); + console.error('Error message: ' + component.get("v.error")); } } } diff --git a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js index 3af16e436..8fd452130 100755 --- a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js +++ b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js @@ -1,36 +1,52 @@ ({ + /** + * Debug logging helper - only logs if debugMode is enabled + * @param {Component} component - The component instance + * @param {String} message - Message to log + * @param {*} data - Optional data to log + */ + debugLog: function(component, message, data) { + if (component.get("v.debugMode")) { + if (data !== undefined) { + console.log(message, data); + } else { + console.log(message); + } + } + }, + processChangeEvent : function(component, eventParams) { - console.log('entering processChangeEvent'); + this.debugLog(component, 'entering processChangeEvent'); // Get current URL // If URL contains flexipageEditor do nothing var currentUrl = window.location.href; - console.log('currentUrl is: ' + currentUrl); + this.debugLog(component, 'currentUrl is: ' + currentUrl); if (currentUrl.includes('flexipageEditor')) { - console.log('currentUrl includes flexipageEditor'); + this.debugLog(component, 'currentUrl includes flexipageEditor'); return; } else { if(eventParams.changeType === "CHANGED") { - console.log ('changeType is: ' + eventParams.changeType); + this.debugLog(component, 'changeType is: ' + eventParams.changeType); this.callFlow(component, eventParams); } else if(eventParams.changeType === "REMOVED") { - console.log('record is being deleted'); + this.debugLog(component, 'record is being deleted'); //the other launch paths don't work well when the underlying page is deleted var targetUrl = '/flow/' + component.get("v.targetFlowName") + '?recordId=' + component.get("v.recordId"); //added input variable - console.log('targetURL is: ' + targetUrl); + this.debugLog(component, 'targetURL is: ' + targetUrl); window.open(targetUrl); } else if(eventParams.changeType === "LOADED") { - console.log ('changeType is: ' + eventParams.changeType); + this.debugLog(component, 'changeType is: ' + eventParams.changeType); this.callFlow(component, eventParams); } } }, callFlow : function(component, eventParams) { - console.log('entering callFlow'); - console.log ('changeType is: ' + eventParams.changeType); + this.debugLog(component, 'entering callFlow'); + this.debugLog(component, 'changeType is: ' + eventParams.changeType); var flowApiName = component.get("v.targetFlowName"); if (flowApiName == null || flowApiName == undefined) { - console.log('flowApiName is null or undefined'); + this.debugLog(component, 'flowApiName is null or undefined'); } else { if(component.get("v.launchMode") == 'Modal') { component.set('v.openModal',true); @@ -47,17 +63,18 @@ var flow = component.find("flow"); // Check to see if flow is not null before calling startFlow if (flowApiName != null && flowApiName != undefined) { - console.log('flow is not null', flowApiName); + this.debugLog(component, 'flow is not null', flowApiName); flow.startFlow(flowApiName, inputVariable); //added input variable } else { - console.log('flow is null', flow); + this.debugLog(component, 'flow is null', flow); } } else { //launch modelessly in a tab or browser window var workspaceAPI = component.find("workspace"); + var self = this; workspaceAPI.isConsoleNavigation().then(function(response) { - console.log('current workspace is console? : ' + response); + self.debugLog(component, 'current workspace is console? : ' + response); if (response) { //we are in console mode workspaceAPI.getFocusedTabInfo() @@ -71,12 +88,13 @@ }) .catch(function(error) { - console.log(error); + // Critical error - always log for troubleshooting + console.error('Error opening subtab:', error); }); } else { - console.log('need to launch flow a different way'); + self.debugLog(component, 'need to launch flow a different way'); var targetUrl = '/flow/' + component.get("v.targetFlowName") + '?recordId=' + component.get("v.recordId"); //added input variable; - console.log('targetURL is: ' + targetUrl); + self.debugLog(component, 'targetURL is: ' + targetUrl); window.open(targetUrl); } }) From 649d32ec13736141fd29e7bc574422a9d3dc111a Mon Sep 17 00:00:00 2001 From: Andy Haas Date: Tue, 16 Dec 2025 17:45:11 -0600 Subject: [PATCH 03/15] fix(detectAndLaunch): Replace loose equality checks with strict equality Priority 1 Fix (1.2): Fix Loose Equality Checks Changes: - detectAndLaunchHelper.js: - Line 48: Changed 'flowApiName == null || flowApiName == undefined' to '!flowApiName' - Uses truthiness check which is cleaner and more idiomatic - Handles null, undefined, empty string, 0, false consistently - Line 51: Changed 'launchMode == "Modal"' to 'launchMode === "Modal"' - Prevents unexpected type coercion - Ensures exact string match - Line 65: Changed 'flowApiName != null && flowApiName != undefined' to 'flowApiName' - Simplified redundant check (already verified flowApiName is truthy above) - More readable and maintainable - detectAndLaunchController.js: - Line 39: Changed 'fieldChange != null && fieldValue != null' to strict checks - Now uses '!== null && !== undefined' for both fieldChange and fieldValue - Explicitly checks for both null and undefined (common in Lightning attributes) - Prevents type coercion issues - Line 51: Changed 'fieldData.value != null' to 'fieldData.value !== null' - Uses strict equality for null check - More explicit about checking for null specifically Benefits: - Prevents unexpected type coercion bugs - More explicit and readable code - Follows JavaScript best practices - Reduces potential for subtle bugs from loose equality Files changed: - detectAndLaunchHelper.js: 3 loose equality checks fixed - detectAndLaunchController.js: 2 loose equality checks fixed --- .../aura/detectAndLaunch/detectAndLaunchController.js | 5 +++-- .../default/aura/detectAndLaunch/detectAndLaunchHelper.js | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js index 27d501c6f..81fdbb118 100755 --- a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js +++ b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js @@ -36,7 +36,8 @@ // If both Field Change and Field Value are not null then set to isChangedRecord to true // This will then populate the users field value within the fields list - if(component.get("v.fieldChange") != null && component.get("v.fieldValue") != null ){ + if(component.get("v.fieldChange") !== null && component.get("v.fieldChange") !== undefined && + component.get("v.fieldValue") !== null && component.get("v.fieldValue") !== undefined ){ component.set("v.isChangedRecord", true); helper.debugLog(component, 'fieldChange', component.get("v.fieldChange")); helper.debugLog(component, 'fieldValue', component.get("v.fieldValue")); @@ -48,7 +49,7 @@ var fieldData = changed[fieldKey]; // Check if field exists and has a non-null value before calling toString() - if (fieldData && fieldData.value != null) { + if (fieldData && fieldData.value !== null) { var newValue = fieldData.value.toString(); helper.debugLog(component, 'changed.dynamic.value', newValue); if (newValue === component.get("v.fieldValue")) { diff --git a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js index 8fd452130..a5289f9e5 100755 --- a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js +++ b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js @@ -45,10 +45,10 @@ this.debugLog(component, 'entering callFlow'); this.debugLog(component, 'changeType is: ' + eventParams.changeType); var flowApiName = component.get("v.targetFlowName"); - if (flowApiName == null || flowApiName == undefined) { + if (!flowApiName) { this.debugLog(component, 'flowApiName is null or undefined'); } else { - if(component.get("v.launchMode") == 'Modal') { + if(component.get("v.launchMode") === 'Modal') { component.set('v.openModal',true); //Set input variable @@ -62,7 +62,7 @@ var flow = component.find("flow"); // Check to see if flow is not null before calling startFlow - if (flowApiName != null && flowApiName != undefined) { + if (flowApiName) { this.debugLog(component, 'flow is not null', flowApiName); flow.startFlow(flowApiName, inputVariable); //added input variable } else { From d9043227ea34d3701c21c656df07d0836ba8201e Mon Sep 17 00:00:00 2001 From: Andy Haas Date: Tue, 16 Dec 2025 17:45:43 -0600 Subject: [PATCH 04/15] fix(detectAndLaunch): Add error handling for flow.startFlow() Priority 1 Fix (1.4): Add Error Handling for Flow Launch Changes: - Added comprehensive error handling around flow.startFlow() call - Wrapped flow.startFlow() in try-catch block to handle runtime errors - Added validation to check if flow component exists before calling startFlow - Added validation to ensure flowApiName is valid before attempting to start flow - Error handling improvements: - Try-catch block catches any errors during flow.startFlow() execution - Logs detailed error information using console.error (always logs) - Error object - Flow API name for context - Error message/details - Automatically closes modal if flow fails to start (prevents stuck modal) - Provides clear error messages for missing flow component or invalid flow name - Validation checks: - Checks if flow component exists (component.find('flow') returns valid component) - Checks if flowApiName is truthy before attempting to start - Separate error messages for each validation failure Benefits: - Prevents unhandled exceptions from crashing component - Provides clear error messages for troubleshooting - Gracefully handles errors by closing modal - Better user experience when flows fail to start - Helps developers identify configuration issues Files changed: - detectAndLaunchHelper.js: Added try-catch and validation around flow.startFlow() --- .../detectAndLaunch/detectAndLaunchHelper.js | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js index a5289f9e5..3b0f97cd7 100755 --- a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js +++ b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js @@ -61,12 +61,29 @@ ]; var flow = component.find("flow"); - // Check to see if flow is not null before calling startFlow - if (flowApiName) { - this.debugLog(component, 'flow is not null', flowApiName); - flow.startFlow(flowApiName, inputVariable); //added input variable + // Check to see if flow component exists and flowApiName is valid before calling startFlow + if (flow && flowApiName) { + this.debugLog(component, 'Starting flow:', flowApiName); + try { + flow.startFlow(flowApiName, inputVariable); + } catch (error) { + // Critical error - always log for troubleshooting + console.error('Error starting flow:', error); + console.error('Flow API Name:', flowApiName); + console.error('Error details:', error.message || error); + // Close modal if flow fails to start + component.set('v.openModal', false); + } } else { - this.debugLog(component, 'flow is null', flow); + // Critical error - always log for troubleshooting + if (!flow) { + console.error('Flow component not found. Ensure lightning:flow is present in component markup.'); + } + if (!flowApiName) { + console.error('Flow API name is missing or invalid.'); + } + // Close modal if we can't start the flow + component.set('v.openModal', false); } } else { From 5678b62fc9789b36e379c82cd57a7a5526b9d064 Mon Sep 17 00:00:00 2001 From: Andy Haas Date: Tue, 16 Dec 2025 17:46:19 -0600 Subject: [PATCH 05/15] refactor(detectAndLaunch): Replace string concatenation with template literals Priority 2 Fix (2.2): Improve Code Readability with Template Literals Changes: - Replaced all string concatenation with ES6 template literals for better readability - All targetUrl constructions now use template literals - All debug log string concatenations converted to template literals - Specific changes: - Line 34: targetUrl in REMOVED event handler - Before: '/flow/' + component.get("v.targetFlowName") + '?recordId=' + component.get("v.recordId") - After: `/flow/${component.get("v.targetFlowName")}?recordId=${component.get("v.recordId")}` - Line 99: targetUrl in console subtab navigation - Same pattern updated - Line 113: targetUrl in standard window.open - Same pattern updated - Debug log improvements: - Line 23: currentUrl debug log - Line 29: changeType debug log (CHANGED event) - Line 35: targetURL debug log (REMOVED event) - Line 38: changeType debug log (LOADED event) - Line 46: changeType debug log (callFlow) - Line 94: console navigation debug log - Line 114: targetURL debug log (standard window.open) Benefits: - More readable and maintainable code - Modern JavaScript best practices (ES6 template literals) - Easier to modify URL patterns - Consistent code style throughout helper file - Reduced chance of syntax errors from missing + operators Files changed: - detectAndLaunchHelper.js: 10 string concatenations replaced with template literals --- .../detectAndLaunch/detectAndLaunchHelper.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js index 3b0f97cd7..004fef327 100755 --- a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js +++ b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js @@ -20,22 +20,22 @@ // Get current URL // If URL contains flexipageEditor do nothing var currentUrl = window.location.href; - this.debugLog(component, 'currentUrl is: ' + currentUrl); + this.debugLog(component, `currentUrl is: ${currentUrl}`); if (currentUrl.includes('flexipageEditor')) { this.debugLog(component, 'currentUrl includes flexipageEditor'); return; } else { if(eventParams.changeType === "CHANGED") { - this.debugLog(component, 'changeType is: ' + eventParams.changeType); + this.debugLog(component, `changeType is: ${eventParams.changeType}`); this.callFlow(component, eventParams); } else if(eventParams.changeType === "REMOVED") { this.debugLog(component, 'record is being deleted'); //the other launch paths don't work well when the underlying page is deleted - var targetUrl = '/flow/' + component.get("v.targetFlowName") + '?recordId=' + component.get("v.recordId"); //added input variable - this.debugLog(component, 'targetURL is: ' + targetUrl); + var targetUrl = `/flow/${component.get("v.targetFlowName")}?recordId=${component.get("v.recordId")}`; + this.debugLog(component, `targetURL is: ${targetUrl}`); window.open(targetUrl); } else if(eventParams.changeType === "LOADED") { - this.debugLog(component, 'changeType is: ' + eventParams.changeType); + this.debugLog(component, `changeType is: ${eventParams.changeType}`); this.callFlow(component, eventParams); } } @@ -43,7 +43,7 @@ callFlow : function(component, eventParams) { this.debugLog(component, 'entering callFlow'); - this.debugLog(component, 'changeType is: ' + eventParams.changeType); + this.debugLog(component, `changeType is: ${eventParams.changeType}`); var flowApiName = component.get("v.targetFlowName"); if (!flowApiName) { this.debugLog(component, 'flowApiName is null or undefined'); @@ -91,12 +91,12 @@ var workspaceAPI = component.find("workspace"); var self = this; workspaceAPI.isConsoleNavigation().then(function(response) { - self.debugLog(component, 'current workspace is console? : ' + response); + self.debugLog(component, `current workspace is console? : ${response}`); if (response) { //we are in console mode workspaceAPI.getFocusedTabInfo() .then(function(response) { - var targetUrl = '/flow/' + component.get("v.targetFlowName") + '?recordId=' + component.get("v.recordId"); //added input variable; + var targetUrl = `/flow/${component.get("v.targetFlowName")}?recordId=${component.get("v.recordId")}`; workspaceAPI.openSubtab({ parentTabId: response.tabId, url: targetUrl, @@ -110,8 +110,8 @@ }); } else { self.debugLog(component, 'need to launch flow a different way'); - var targetUrl = '/flow/' + component.get("v.targetFlowName") + '?recordId=' + component.get("v.recordId"); //added input variable; - self.debugLog(component, 'targetURL is: ' + targetUrl); + var targetUrl = `/flow/${component.get("v.targetFlowName")}?recordId=${component.get("v.recordId")}`; + self.debugLog(component, `targetURL is: ${targetUrl}`); window.open(targetUrl); } }) From db5e7e3909ef0da76d6dcde4e2da786014c07e29 Mon Sep 17 00:00:00 2001 From: Andy Haas Date: Tue, 16 Dec 2025 17:46:59 -0600 Subject: [PATCH 06/15] refactor(detectAndLaunch): Extract field value matching logic to helper method Priority 2 Fix (2.3): Refactor Field Watching Logic Changes: - Created checkFieldValueMatch() helper method in detectAndLaunchHelper.js - Extracts complex nested conditional logic from recordUpdated controller method - Encapsulates all field value matching logic in a single, testable method - Returns boolean indicating if field value matches configured criteria - Includes comprehensive JSDoc documentation - Simplified recordUpdated() method in detectAndLaunchController.js - Reduced from 30+ lines of nested conditionals to simple helper call - Much more readable and maintainable - Clear separation of concerns - Helper method logic: - Validates fieldChange and fieldValue are configured - Only processes CHANGED events (conditional launch only works on edits) - Validates changedFields exists - Safely extracts and compares field values - Handles null/undefined values gracefully - Returns true only when field value matches expected value - Additional improvements: - Fixed remaining string concatenation in debug log (template literal) - Improved code organization and maintainability Benefits: - Improved code readability and maintainability - Easier to test field matching logic in isolation - Reduced complexity in controller method - Better separation of concerns - Reusable helper method for future enhancements Files changed: - detectAndLaunchController.js: Simplified recordUpdated method (30 lines reduced) - detectAndLaunchHelper.js: Added checkFieldValueMatch helper method (43 lines added) --- .../detectAndLaunchController.js | 30 ++++--------- .../detectAndLaunch/detectAndLaunchHelper.js | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js index 81fdbb118..0a83af6f9 100755 --- a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js +++ b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchController.js @@ -34,32 +34,18 @@ helper.debugLog(component, 'entering recordUpdate'); var eventParams = event.getParams(); - // If both Field Change and Field Value are not null then set to isChangedRecord to true - // This will then populate the users field value within the fields list - if(component.get("v.fieldChange") !== null && component.get("v.fieldChange") !== undefined && - component.get("v.fieldValue") !== null && component.get("v.fieldValue") !== undefined ){ + // Check if field value matches configured criteria + if (helper.checkFieldValueMatch(component, eventParams)) { component.set("v.isChangedRecord", true); helper.debugLog(component, 'fieldChange', component.get("v.fieldChange")); helper.debugLog(component, 'fieldValue', component.get("v.fieldValue")); - // Make sure there was an edited field and we just edited a record - if ( eventParams.changedFields && eventParams.changeType === "CHANGED") { - helper.debugLog(component, 'eventParams.changedFields', eventParams.changedFields); - var changed = JSON.parse(JSON.stringify(eventParams.changedFields)); - var fieldKey = component.get("v.fieldCompare"); - var fieldData = changed[fieldKey]; - - // Check if field exists and has a non-null value before calling toString() - if (fieldData && fieldData.value !== null) { - var newValue = fieldData.value.toString(); - helper.debugLog(component, 'changed.dynamic.value', newValue); - if (newValue === component.get("v.fieldValue")) { - component.set("v.targetFlowName", component.get("v.editFlowName")); - helper.processChangeEvent(component, eventParams); - } - } - } + helper.debugLog(component, 'eventParams.changedFields', eventParams.changedFields); + + // Field value matches, launch the edit flow + component.set("v.targetFlowName", component.get("v.editFlowName")); + helper.processChangeEvent(component, eventParams); } else { - helper.debugLog(component, 'changeType: ' + eventParams.changeType); + helper.debugLog(component, `changeType: ${eventParams.changeType}`); // Get Flow To Use if(eventParams.changeType === "CHANGED") { component.set("v.targetFlowName", component.get("v.editFlowName")); diff --git a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js index 004fef327..7704de844 100755 --- a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js +++ b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunchHelper.js @@ -15,6 +15,49 @@ } }, + /** + * Checks if a field value matches the expected value when a record is changed + * @param {Component} component - The component instance + * @param {Object} eventParams - Event parameters from force:recordData + * @returns {Boolean} - True if field value matches, false otherwise + */ + checkFieldValueMatch: function(component, eventParams) { + var fieldChange = component.get("v.fieldChange"); + var fieldValue = component.get("v.fieldValue"); + + // Check if both fieldChange and fieldValue are configured + if (fieldChange === null || fieldChange === undefined || + fieldValue === null || fieldValue === undefined) { + return false; + } + + // Only check field values on CHANGED events + if (eventParams.changeType !== "CHANGED") { + return false; + } + + // Check if changedFields exists + if (!eventParams.changedFields) { + return false; + } + + // Get the field data from changed fields + var changed = JSON.parse(JSON.stringify(eventParams.changedFields)); + var fieldKey = component.get("v.fieldCompare"); + var fieldData = changed[fieldKey]; + + // Check if field exists and has a non-null value + if (!fieldData || fieldData.value === null) { + return false; + } + + // Compare the field value + var newValue = fieldData.value.toString(); + this.debugLog(component, 'changed.dynamic.value', newValue); + + return newValue === fieldValue; + }, + processChangeEvent : function(component, eventParams) { this.debugLog(component, 'entering processChangeEvent'); // Get current URL From ccaf8ea22cfa35016b3cd51914cfb70c5334db17 Mon Sep 17 00:00:00 2001 From: Andy Haas Date: Tue, 16 Dec 2025 17:47:28 -0600 Subject: [PATCH 07/15] fix(detectAndLaunch): Make modal height responsive Priority 2 Fix (2.5): Fix Modal Height Issue Changes: - Removed hardcoded 600px height from modal container - Removed inline style="height: 600px;" from div wrapper - Changed class from "demo-only" to "slds-modal-container" for better semantics - Added responsive CSS classes and styles - Added slds-modal__container--responsive class to modal container - Implemented responsive sizing with max-width and max-height - Modal now adapts to viewport size (90vw x 90vh on desktop, 95vw x 95vh on mobile) - Added responsive CSS rules in detectAndLaunch.css - Base responsive sizing: max-width 90vw, max-height 90vh - Content area is scrollable if needed (max-height calc(90vh - 8rem)) - Mobile breakpoint (@media max-width: 48rem) for smaller screens - Ensures modal works well on all screen sizes - Benefits: - Modal adapts to different screen sizes automatically - Content is scrollable if it exceeds viewport - Better user experience on mobile devices - No more fixed height that may cut off content - Follows SLDS responsive design patterns Files changed: - detectAndLaunch.cmp: Removed fixed height, added responsive classes - detectAndLaunch.css: Added responsive CSS rules and media queries --- .../aura/detectAndLaunch/detectAndLaunch.cmp | 4 +-- .../aura/detectAndLaunch/detectAndLaunch.css | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunch.cmp b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunch.cmp index 8d3f46383..c8c55b4ed 100755 --- a/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunch.cmp +++ b/lightning_page_components/DetectAndLaunch/force-app/main/default/aura/detectAndLaunch/detectAndLaunch.cmp @@ -29,9 +29,9 @@ -
+