diff --git a/Client-Side Components/Catalog Client Script/Form Field Dependencies Manager/README.md b/Client-Side Components/Catalog Client Script/Form Field Dependencies Manager/README.md new file mode 100644 index 0000000000..6e020497c1 --- /dev/null +++ b/Client-Side Components/Catalog Client Script/Form Field Dependencies Manager/README.md @@ -0,0 +1,184 @@ +# Form Field Dependencies Manager + +A powerful utility for managing complex field relationships and dependencies in ServiceNow Catalog Items. + +## Features + +- Dynamic field relationships +- Cascading field updates +- Conditional visibility rules +- Dynamic mandatory field rules +- Value mapping between fields +- Dependency chain tracking +- Error handling and logging + +## Types of Dependencies + +1. **Cascading Changes** + - Update dependent fields automatically + - Complex value calculations + - Multi-field dependencies + +2. **Visibility Rules** + - Show/hide fields based on conditions + - Multiple condition support + - Complex visibility logic + +3. **Mandatory Rules** + - Dynamic required field handling + - Condition-based mandatory flags + - Multiple rule support + +4. **Value Mappings** + - Automatic value population + - Complex value transformations + - Multi-field mapping support + +## Implementation + +### Basic Setup + +1. Create a new Catalog Client Script: + ```javascript + Table: Catalog Client Script [catalog_script_client] + Type: Both onLoad and onChange + Active: true + ``` + +2. Copy the content of `script.js` into your script + +### Configuration Examples + +1. **Cascading Update** +```javascript +var manager = new FieldDependenciesManager(); +manager.addDependency({ + type: 'cascade', + sourceField: 'department', + targetField: 'assignment_group', + rule: function(value, cache) { + // Return assignment group based on department + return departmentToGroupMapping[value] || ''; + } +}); +``` + +2. **Visibility Rule** +```javascript +manager.addDependency({ + type: 'visibility', + sourceField: 'category', + targetField: 'subcategory', + rule: function(value, cache) { + return value === 'hardware' || value === 'software'; + } +}); +``` + +3. **Mandatory Field Rule** +```javascript +manager.addDependency({ + type: 'mandatory', + sourceField: 'impact', + targetField: 'priority', + rule: function(value, cache) { + return value === 'high'; + } +}); +``` + +4. **Value Mapping** +```javascript +manager.addDependency({ + type: 'valueMap', + sourceField: 'country', + targetField: 'currency', + rule: function(value, cache) { + return countryCurrencyMap[value] || 'USD'; + } +}); +``` + +## Technical Details + +### Dependencies +- ServiceNow Platform UI Framework +- GlideForm API + +### Performance Considerations +- Efficient caching mechanism +- Optimized dependency processing +- Circular dependency prevention + +### Browser Support +- Works with all modern browsers +- ES5+ compatible + +## Best Practices + +1. **Performance** + - Group related dependencies + - Use efficient rules + - Avoid complex calculations + +2. **Maintenance** + - Document dependencies + - Use meaningful rule names + - Keep rules simple + +3. **User Experience** + - Clear field relationships + - Immediate feedback + - Logical dependencies + +## Troubleshooting + +Common issues and solutions: + +1. **Dependencies not triggering** + - Check rule configuration + - Verify field names + - Check console for errors + +2. **Circular Dependencies** + - Review dependency chain + - Check log output + - Simplify relationships + +3. **Performance Issues** + - Optimize rule calculations + - Reduce dependency complexity + - Check browser console + +## Example Use Cases + +1. **IT Request Form** +```javascript +// Hardware request dependencies +manager.addDependency({ + type: 'cascade', + sourceField: 'hardware_type', + targetField: 'model', + rule: function(value, cache) { + return getModelsForHardwareType(value); + } +}); +``` + +2. **HR Service Catalog** +```javascript +// Leave request dependencies +manager.addDependency({ + type: 'mandatory', + sourceField: 'leave_type', + targetField: 'return_date', + rule: function(value, cache) { + return value === 'extended_leave'; + } +}); +``` + +## Version Information + +- Compatible with ServiceNow: Rome and later +- Last Updated: October 2025 \ No newline at end of file diff --git a/Client-Side Components/Catalog Client Script/Form Field Dependencies Manager/script.js b/Client-Side Components/Catalog Client Script/Form Field Dependencies Manager/script.js new file mode 100644 index 0000000000..4734168028 --- /dev/null +++ b/Client-Side Components/Catalog Client Script/Form Field Dependencies Manager/script.js @@ -0,0 +1,244 @@ +/** + * Form Field Dependencies Manager + * Manages complex field relationships, cascading changes, and conditional visibility + */ + +function onLoad() { + var manager = new FieldDependenciesManager(); + manager.initialize(); +} + +function onChange(control, oldValue, newValue, isLoading) { + if (isLoading) return; + + var manager = new FieldDependenciesManager(); + manager.handleFieldChange(control, oldValue, newValue); +} + +var FieldDependenciesManager = Class.create(); +FieldDependenciesManager.prototype = { + initialize: function() { + // Dependencies configuration + this.dependencies = { + // Field relationships + relationships: {}, + // Cascading rules + cascades: {}, + // Visibility conditions + visibility: {}, + // Mandatory field rules + mandatory: {}, + // Field value mappings + valueMaps: {} + }; + + // Cache for field values + this.fieldCache = {}; + + // Load and setup dependencies + this._loadDependencies(); + this._setupInitialState(); + }, + + handleFieldChange: function(changedField, oldValue, newValue) { + try { + // Update cache + this.fieldCache[changedField] = newValue; + + // Process different types of dependencies + this._processCascadingChanges(changedField, newValue); + this._processVisibilityRules(changedField, newValue); + this._processMandatoryRules(changedField, newValue); + this._processValueMappings(changedField, newValue); + + // Log dependency chain for debugging + this._logDependencyChain(changedField); + } catch (e) { + console.error('Error processing dependencies:', e); + g_form.addErrorMessage('Error processing field dependencies'); + } + }, + + addDependency: function(config) { + try { + const { type, sourceField, targetField, rule } = config; + + switch(type) { + case 'cascade': + this.dependencies.cascades[sourceField] = + this.dependencies.cascades[sourceField] || []; + this.dependencies.cascades[sourceField].push({ + target: targetField, + rule: rule + }); + break; + + case 'visibility': + this.dependencies.visibility[targetField] = + this.dependencies.visibility[targetField] || []; + this.dependencies.visibility[targetField].push({ + source: sourceField, + condition: rule + }); + break; + + case 'mandatory': + this.dependencies.mandatory[targetField] = + this.dependencies.mandatory[targetField] || []; + this.dependencies.mandatory[targetField].push({ + source: sourceField, + condition: rule + }); + break; + + case 'valueMap': + this.dependencies.valueMaps[sourceField] = + this.dependencies.valueMaps[sourceField] || {}; + this.dependencies.valueMaps[sourceField][targetField] = rule; + break; + } + } catch (e) { + console.error('Error adding dependency:', e); + } + }, + + _loadDependencies: function() { + // Load dependencies from catalog item variables + var fields = g_form.getFields(); + fields.forEach(function(field) { + var fieldName = field.getName(); + + // Check for dependency configurations + var dependencyConfig = g_form.getValue(fieldName + '_dependencies'); + if (dependencyConfig) { + try { + var config = JSON.parse(dependencyConfig); + this.addDependency(config); + } catch (e) { + console.error('Error loading dependency config for ' + fieldName, e); + } + } + }, this); + }, + + _setupInitialState: function() { + // Initialize field cache + var fields = g_form.getFields(); + fields.forEach(function(field) { + var fieldName = field.getName(); + this.fieldCache[fieldName] = g_form.getValue(fieldName); + }, this); + + // Process initial states + Object.keys(this.dependencies.visibility).forEach(function(field) { + this._processVisibilityRules(field, this.fieldCache[field]); + }, this); + + Object.keys(this.dependencies.mandatory).forEach(function(field) { + this._processMandatoryRules(field, this.fieldCache[field]); + }, this); + }, + + _processCascadingChanges: function(sourceField, newValue) { + var cascades = this.dependencies.cascades[sourceField]; + if (!cascades) return; + + cascades.forEach(function(cascade) { + try { + var targetValue = cascade.rule(newValue, this.fieldCache); + g_form.setValue(cascade.target, targetValue); + } catch (e) { + console.error('Error in cascade rule:', e); + } + }, this); + }, + + _processVisibilityRules: function(sourceField, newValue) { + Object.keys(this.dependencies.visibility).forEach(function(targetField) { + var rules = this.dependencies.visibility[targetField]; + var shouldBeVisible = rules.every(function(rule) { + return rule.source === sourceField ? + rule.condition(newValue, this.fieldCache) : + rule.condition(this.fieldCache[rule.source], this.fieldCache); + }, this); + + if (shouldBeVisible) { + g_form.showFieldMsg(targetField, '', 'info'); + g_form.setVisible(targetField, true); + } else { + g_form.hideFieldMsg(targetField); + g_form.setVisible(targetField, false); + } + }, this); + }, + + _processMandatoryRules: function(sourceField, newValue) { + Object.keys(this.dependencies.mandatory).forEach(function(targetField) { + var rules = this.dependencies.mandatory[targetField]; + var shouldBeMandatory = rules.some(function(rule) { + return rule.source === sourceField ? + rule.condition(newValue, this.fieldCache) : + rule.condition(this.fieldCache[rule.source], this.fieldCache); + }, this); + + g_form.setMandatory(targetField, shouldBeMandatory); + }, this); + }, + + _processValueMappings: function(sourceField, newValue) { + var mappings = this.dependencies.valueMaps[sourceField]; + if (!mappings) return; + + Object.keys(mappings).forEach(function(targetField) { + var mapRule = mappings[targetField]; + try { + var mappedValue = mapRule(newValue, this.fieldCache); + if (mappedValue !== undefined) { + g_form.setValue(targetField, mappedValue); + } + } catch (e) { + console.error('Error in value mapping:', e); + } + }, this); + }, + + _logDependencyChain: function(sourceField) { + var chain = this._buildDependencyChain(sourceField); + console.log('Dependency chain for ' + sourceField + ':', chain); + }, + + _buildDependencyChain: function(field, visited = new Set()) { + if (visited.has(field)) return []; + visited.add(field); + + var chain = []; + + // Check cascading dependencies + var cascades = this.dependencies.cascades[field] || []; + cascades.forEach(function(cascade) { + chain.push({ + type: 'cascade', + from: field, + to: cascade.target + }); + chain = chain.concat(this._buildDependencyChain(cascade.target, visited)); + }, this); + + // Check visibility dependencies + Object.keys(this.dependencies.visibility).forEach(function(targetField) { + var rules = this.dependencies.visibility[targetField]; + if (rules.some(rule => rule.source === field)) { + chain.push({ + type: 'visibility', + from: field, + to: targetField + }); + chain = chain.concat(this._buildDependencyChain(targetField, visited)); + } + }, this); + + return chain; + }, + + type: 'FieldDependenciesManager' +}; \ No newline at end of file