Pattern 15: Smart Dependencies
Part II: Interaction Patterns - Relationship Patterns
Opening Scenario: The Form That Knew What You Needed
Rachel was ordering custom business cards online. The form started simple:
Card Type: [Business ▼]
When she selected "Business," new fields appeared:
Card Type: [Business ▼]
Material: [Standard ▼]
Finish: [Matte ▼]
Quantity: [500 ▼]
She selected "Premium" for material. Instantly, the form adapted:
Material: [Premium ▼] ← Changed
Finish: [Matte ▼] [Glossy] [Embossed ▼] [Foil] ← New options enabled
(Premium materials support embossing and foil)
Quantity: [500 ▼]
"Embossed" option wasn't available with standard materials. The form understood the dependency and only showed valid choices.
She selected "Embossed" finish:
Finish: [Embossed ▼]
Embossing Color: [Gold ▼] [Silver] [Black] ← New field appeared
(Required for embossed finish)
Rush Delivery: [ ] Not available for embossed cards ← Disabled with explanation
The form knew: - Rush delivery wasn't possible with embossing (requires extra processing time) - Embossing requires selecting a color - Only certain quantities work with embossing (minimums apply)
Meanwhile, across town, Derek was using a different card printing service. Their form didn't understand dependencies:
Material: [Standard ▼]
Finish: [Embossed ▼] ← Can select this
Rush Delivery: [✓] ← Can check this
Quantity: [100] ← Can enter this
Derek could select any combination. He chose: - Standard material - Embossed finish - Rush delivery - 100 cards
He submitted the order. Two days later, he got an email:
Your order cannot be processed:
- Embossing requires Premium material (not Standard)
- Rush delivery not available for embossed orders
- Embossing requires minimum 250 cards (you ordered 100)
Please reorder with correct options.
Derek was frustrated. "Why did the form let me select these options if they're not compatible?"
Rachel's experience was different. Her form guided her toward valid choices:
When she selected Standard material: - Embossing option was grayed out with tooltip: "Requires Premium material" - Foil option was grayed out with tooltip: "Requires Premium material" - Clear visual feedback about what was possible
When she wanted rush delivery: - Tooltip appeared: "Rush delivery available for standard finishes only" - Form suggested: "Choose Matte or Glossy finish to enable rush delivery"
When she selected a quantity: - Minimum quantities were enforced based on finish - Tooltip showed: "Embossed cards require minimum 250 quantity" - Price updated in real-time based on all selections
The form didn't just validate - it educated and guided. Rachel completed her order in 3 minutes with zero errors. Derek spent 30 minutes across two attempts and still wasn't sure if his new configuration would work.
Context
Smart Dependencies applies when:
Complex product configurations: Options depend on other options (materials, features, quantities)
Technical requirements: Some combinations are technically impossible
Business rules: Policies restrict certain combinations
Sequential logic: Later choices depend on earlier ones
Resource constraints: Availability affects what's possible
User expertise varies: Some understand dependencies, others don't
Error prevention critical: Invalid combinations cause real problems
Problem Statement
Most forms treat dependencies poorly, allowing invalid combinations or providing late feedback:
No dependency awareness:
<!-- Bad: All options always available -->
<select name="material">
<option>Standard</option>
<option>Premium</option>
</select>
<select name="finish">
<option>Matte</option>
<option>Glossy</option>
<option>Embossed</option> <!-- Requires Premium! -->
<option>Foil</option> <!-- Requires Premium! -->
</select>
<!-- Form allows selecting Standard + Embossed -->
Late error discovery:
// Bad: Validation at submission
function validateOrder(order) {
if (order.material === 'Standard' && order.finish === 'Embossed') {
return "Error: Embossing requires Premium material";
}
}
// User discovers after filling entire form
No guidance:
<!-- Bad: Options disabled with no explanation -->
<select name="finish">
<option>Matte</option>
<option>Glossy</option>
<option disabled>Embossed</option> <!-- Why disabled? -->
</select>
<!-- User doesn't know what's needed to enable it -->
Hidden relationships:
// Bad: Dependency logic buried in code
if (material === 'Premium' && finish === 'Embossed') {
// Can't do rush delivery
// But this isn't visible to user until they try
}
No progressive disclosure:
<!-- Bad: Show all fields even when irrelevant -->
Embossing Color: [ ] <!-- Shown even for non-embossed -->
Foil Type: [ ] <!-- Shown even for non-foil -->
<!-- Clutters form with irrelevant options -->
We need forms that understand dependencies, guide users toward valid combinations, and explain constraints clearly.
Forces
Simplicity vs Power
- Simple forms are easy to understand
- But complex products need many options
- Balance configurability with clarity
Freedom vs Guidance
- Users want flexibility
- But need guardrails against errors
- Balance choice with direction
Immediate vs Progressive
- Show all options upfront?
- Or reveal them progressively?
- Balance discoverability with focus
Explicit vs Implicit
- Explain every dependency?
- Or just prevent invalid states?
- Balance education with simplicity
Strict vs Flexible
- Enforce all dependencies rigidly?
- Or allow overrides in edge cases?
- Balance correctness with exceptions
Solution
Create intelligent dependency graphs that enable/disable options dynamically, provide clear explanations for constraints, guide users through valid configuration paths, and learn from common patterns to suggest optimal choices.
The pattern has five key strategies:
1. Dependency Graph Definition
Define what depends on what:
class DependencyGraph {
constructor() {
this.nodes = new Map(); // Field definitions
this.edges = new Map(); // Dependencies between fields
}
defineField(fieldName, config) {
this.nodes.set(fieldName, {
name: fieldName,
label: config.label,
type: config.type,
options: config.options || [],
defaultValue: config.defaultValue,
alwaysVisible: config.alwaysVisible || false
});
}
// Field B depends on Field A having specific value(s)
dependsOn(dependentField, sourceField, condition) {
const key = `${dependentField}->${sourceField}`;
this.edges.set(key, {
dependent: dependentField,
source: sourceField,
condition: condition,
type: 'enables'
});
}
// Field B requires Field A to have specific value(s)
requires(field, requiredField, values) {
this.dependsOn(field, requiredField, {
type: 'value-match',
values: Array.isArray(values) ? values : [values],
explanation: `Requires ${requiredField} to be ${values}`
});
}
// Field B is incompatible with Field A having specific value(s)
incompatibleWith(field, conflictField, values) {
const key = `${field}-!->${conflictField}`;
this.edges.set(key, {
dependent: field,
source: conflictField,
condition: {
type: 'value-mismatch',
values: Array.isArray(values) ? values : [values],
explanation: `Not compatible with ${conflictField} = ${values}`
},
type: 'disables'
});
}
// Example: Business card dependencies
defineBusinessCardDependencies() {
// Define fields
this.defineField('cardType', {
label: 'Card Type',
type: 'select',
options: ['Business', 'Personal', 'Event'],
alwaysVisible: true
});
this.defineField('material', {
label: 'Material',
type: 'select',
options: ['Standard', 'Premium', 'Luxury'],
alwaysVisible: true
});
this.defineField('finish', {
label: 'Finish',
type: 'select',
options: ['Matte', 'Glossy', 'Embossed', 'Foil'],
alwaysVisible: true
});
this.defineField('embossingColor', {
label: 'Embossing Color',
type: 'select',
options: ['Gold', 'Silver', 'Black', 'White']
});
this.defineField('foilType', {
label: 'Foil Type',
type: 'select',
options: ['Gold Foil', 'Silver Foil', 'Holographic']
});
this.defineField('rushDelivery', {
label: 'Rush Delivery',
type: 'checkbox'
});
// Define dependencies
// Embossing requires Premium or Luxury material
this.requires('finish', 'material', ['Premium', 'Luxury']);
// Foil requires Premium or Luxury material
this.requires('finish', 'material', ['Premium', 'Luxury']);
// Embossing color only relevant when finish is Embossed
this.requires('embossingColor', 'finish', 'Embossed');
// Foil type only relevant when finish is Foil
this.requires('foilType', 'finish', 'Foil');
// Rush delivery incompatible with Embossed or Foil finishes
this.incompatibleWith('rushDelivery', 'finish', ['Embossed', 'Foil']);
}
}
2. Dependency Evaluator
Determine what should be enabled/disabled:
class DependencyEvaluator {
constructor(graph) {
this.graph = graph;
this.currentState = new Map();
}
evaluate(formData) {
const evaluations = new Map();
// Evaluate each field's availability
this.graph.nodes.forEach((config, fieldName) => {
const evaluation = this.evaluateField(fieldName, formData);
evaluations.set(fieldName, evaluation);
});
return evaluations;
}
evaluateField(fieldName, formData) {
const field = this.graph.nodes.get(fieldName);
if (field.alwaysVisible) {
return {
visible: true,
enabled: true,
options: this.evaluateOptions(fieldName, formData)
};
}
// Check dependencies
const dependencies = this.getDependencies(fieldName);
if (dependencies.length === 0) {
return {
visible: true,
enabled: true,
options: field.options
};
}
// Evaluate all dependencies
let visible = true;
let enabled = true;
const reasons = [];
dependencies.forEach(dep => {
const result = this.evaluateDependency(dep, formData);
if (!result.satisfied) {
if (dep.type === 'enables') {
visible = false;
reasons.push(result.reason);
} else if (dep.type === 'disables') {
enabled = false;
reasons.push(result.reason);
}
}
});
return {
visible,
enabled,
reasons,
options: this.evaluateOptions(fieldName, formData)
};
}
evaluateDependency(dependency, formData) {
const sourceValue = formData[dependency.source];
const condition = dependency.condition;
switch(condition.type) {
case 'value-match':
if (condition.values.includes(sourceValue)) {
return { satisfied: true };
}
return {
satisfied: false,
reason: condition.explanation
};
case 'value-mismatch':
if (!condition.values.includes(sourceValue)) {
return { satisfied: true };
}
return {
satisfied: false,
reason: condition.explanation
};
default:
return { satisfied: true };
}
}
evaluateOptions(fieldName, formData) {
const field = this.graph.nodes.get(fieldName);
const availableOptions = [];
field.options.forEach(option => {
const available = this.isOptionAvailable(
fieldName,
option,
formData
);
availableOptions.push({
value: option,
available: available.available,
reason: available.reason
});
});
return availableOptions;
}
isOptionAvailable(fieldName, optionValue, formData) {
// Check if this specific option value is valid
// given current form state
// For example: "Embossed" option only available
// when material is "Premium" or "Luxury"
const testData = { ...formData, [fieldName]: optionValue };
// Check all dependencies
const dependencies = this.graph.edges;
let available = true;
let reason = null;
dependencies.forEach((dep, key) => {
if (dep.dependent === fieldName) {
const result = this.evaluateDependency(dep, testData);
if (!result.satisfied) {
available = false;
reason = result.reason;
}
}
});
return { available, reason };
}
getDependencies(fieldName) {
const deps = [];
this.graph.edges.forEach((edge, key) => {
if (edge.dependent === fieldName) {
deps.push(edge);
}
});
return deps;
}
}
3. Smart UI Updates
Update interface based on dependencies:
class SmartDependencyUI {
constructor(evaluator) {
this.evaluator = evaluator;
}
update(formData) {
const evaluations = this.evaluator.evaluate(formData);
evaluations.forEach((evaluation, fieldName) => {
this.updateField(fieldName, evaluation);
});
}
updateField(fieldName, evaluation) {
const fieldElement = document.getElementById(fieldName);
const container = fieldElement?.closest('.field-container');
if (!container) return;
// Update visibility
if (evaluation.visible) {
container.style.display = 'block';
container.classList.add('fade-in');
} else {
container.style.display = 'none';
}
// Update enabled state
if (evaluation.enabled) {
fieldElement.disabled = false;
container.classList.remove('disabled');
this.removeDisabledHint(container);
} else {
fieldElement.disabled = true;
container.classList.add('disabled');
this.showDisabledHint(container, evaluation.reasons);
}
// Update options (for select/radio)
if (evaluation.options && fieldElement.tagName === 'SELECT') {
this.updateSelectOptions(fieldElement, evaluation.options);
}
}
updateSelectOptions(selectElement, options) {
// Clear existing options
selectElement.innerHTML = '';
// Add placeholder
const placeholder = document.createElement('option');
placeholder.value = '';
placeholder.textContent = 'Select...';
selectElement.appendChild(placeholder);
// Add options with availability info
options.forEach(opt => {
const option = document.createElement('option');
option.value = opt.value;
option.textContent = opt.value;
if (!opt.available) {
option.disabled = true;
option.textContent += ` (${opt.reason})`;
option.classList.add('unavailable-option');
}
selectElement.appendChild(option);
});
}
showDisabledHint(container, reasons) {
const existing = container.querySelector('.disabled-hint');
if (existing) existing.remove();
const hint = document.createElement('div');
hint.className = 'disabled-hint';
hint.innerHTML = `
<span class="icon">ℹ️</span>
<span class="message">${reasons.join('. ')}</span>
`;
container.appendChild(hint);
}
removeDisabledHint(container) {
const hint = container.querySelector('.disabled-hint');
if (hint) hint.remove();
}
// Show suggestion to enable a field
showEnablementSuggestion(fieldName, requiredChanges) {
const container = document.getElementById(fieldName)
?.closest('.field-container');
if (!container) return;
const suggestion = document.createElement('div');
suggestion.className = 'enablement-suggestion';
suggestion.innerHTML = `
<div class="suggestion-header">
<span class="icon">💡</span>
<span class="title">Want to enable this option?</span>
</div>
<div class="suggestion-body">
${requiredChanges.map(change => `
<button
class="suggestion-action"
onclick="applyChange('${change.field}', '${change.value}')"
>
Set ${change.field} to ${change.value}
</button>
`).join('')}
</div>
`;
container.appendChild(suggestion);
}
}
4. Guided Configuration
Help users navigate complex dependencies:
class ConfigurationGuide {
constructor(graph, evaluator) {
this.graph = graph;
this.evaluator = evaluator;
}
suggestNextStep(formData) {
// Determine what user should configure next
const incomplete = this.getIncompleteFields(formData);
if (incomplete.length === 0) {
return {
message: 'Configuration complete!',
action: 'submit'
};
}
// Find next logical field to fill
const next = this.findNextLogicalField(incomplete, formData);
return {
message: `Next: Configure ${next.label}`,
field: next.name,
suggestions: this.getSuggestions(next.name, formData)
};
}
findNextLogicalField(incompleteFields, formData) {
// Prioritize fields with fewest dependencies
// (easier to fill first)
return incompleteFields.sort((a, b) => {
const aDeps = this.evaluator.getDependencies(a).length;
const bDeps = this.evaluator.getDependencies(b).length;
return aDeps - bDeps;
})[0];
}
getSuggestions(fieldName, formData) {
const field = this.graph.nodes.get(fieldName);
const options = this.evaluator.evaluateOptions(fieldName, formData);
// Filter to available options
const available = options.filter(opt => opt.available);
if (available.length === 0) {
return {
type: 'prerequisites',
message: 'Complete previous fields first',
required: this.getPrerequisites(fieldName, formData)
};
}
// Suggest most popular or default
return {
type: 'options',
message: `Choose from ${available.length} options`,
options: available.map(opt => opt.value),
recommended: this.getRecommendedOption(fieldName, formData)
};
}
getPrerequisites(fieldName, formData) {
const deps = this.evaluator.getDependencies(fieldName);
const missing = [];
deps.forEach(dep => {
const result = this.evaluator.evaluateDependency(dep, formData);
if (!result.satisfied) {
missing.push({
field: dep.source,
required: dep.condition.values,
reason: result.reason
});
}
});
return missing;
}
getRecommendedOption(fieldName, formData) {
// Use historical data, popular choices, or defaults
// to recommend an option
const field = this.graph.nodes.get(fieldName);
// Simple: return default value
if (field.defaultValue) {
return field.defaultValue;
}
// Could enhance with:
// - Most popular choice
// - Choice that enables most other options
// - Machine learning prediction
return null;
}
getConfigurationPath(formData) {
// Generate step-by-step guide through configuration
const steps = [];
const fields = Array.from(this.graph.nodes.keys());
// Sort by dependency order
const sorted = this.topologicalSort(fields);
sorted.forEach((fieldName, index) => {
const evaluation = this.evaluator.evaluateField(fieldName, formData);
steps.push({
step: index + 1,
field: fieldName,
label: this.graph.nodes.get(fieldName).label,
status: formData[fieldName] ? 'complete' : 'pending',
available: evaluation.enabled,
prerequisites: evaluation.reasons || []
});
});
return steps;
}
topologicalSort(fields) {
// Sort fields by dependency order
// (fields with no dependencies first)
const sorted = [];
const visited = new Set();
const visit = (field) => {
if (visited.has(field)) return;
visited.add(field);
// Visit dependencies first
const deps = this.evaluator.getDependencies(field);
deps.forEach(dep => visit(dep.source));
sorted.push(field);
};
fields.forEach(field => visit(field));
return sorted;
}
}
5. Dependency Visualization
Show relationships visually:
class DependencyVisualizer {
constructor(graph) {
this.graph = graph;
}
renderDependencyMap() {
const map = document.createElement('div');
map.className = 'dependency-map';
// Show all fields and their dependencies
this.graph.nodes.forEach((config, fieldName) => {
const fieldCard = this.createFieldCard(fieldName, config);
map.appendChild(fieldCard);
});
// Draw connection lines
this.drawConnections(map);
return map;
}
createFieldCard(fieldName, config) {
const card = document.createElement('div');
card.className = 'dependency-field-card';
card.id = `dep-card-${fieldName}`;
// Get dependencies
const deps = [];
this.graph.edges.forEach((edge, key) => {
if (edge.dependent === fieldName) {
deps.push(edge);
}
});
card.innerHTML = `
<div class="card-header">
<h4>${config.label}</h4>
</div>
<div class="card-body">
${deps.length > 0 ? `
<div class="dependencies">
<strong>Depends on:</strong>
<ul>
${deps.map(dep => `
<li>${dep.source} = ${dep.condition.values?.join(' or ')}</li>
`).join('')}
</ul>
</div>
` : ''}
</div>
`;
return card;
}
drawConnections(container) {
// Use SVG to draw lines between dependent fields
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.classList.add('dependency-connections');
svg.style.position = 'absolute';
svg.style.top = '0';
svg.style.left = '0';
svg.style.width = '100%';
svg.style.height = '100%';
svg.style.pointerEvents = 'none';
this.graph.edges.forEach((edge, key) => {
const fromCard = container.querySelector(`#dep-card-${edge.source}`);
const toCard = container.querySelector(`#dep-card-${edge.dependent}`);
if (fromCard && toCard) {
const line = this.createConnectionLine(fromCard, toCard, edge);
svg.appendChild(line);
}
});
container.appendChild(svg);
}
createConnectionLine(fromElement, toElement, edge) {
const fromRect = fromElement.getBoundingClientRect();
const toRect = toElement.getBoundingClientRect();
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
// Calculate connection points
const x1 = fromRect.right;
const y1 = fromRect.top + fromRect.height / 2;
const x2 = toRect.left;
const y2 = toRect.top + toRect.height / 2;
// Create curved path
const midX = (x1 + x2) / 2;
path.setAttribute('d', `M ${x1},${y1} C ${midX},${y1} ${midX},${y2} ${x2},${y2}`);
path.setAttribute('stroke', edge.type === 'enables' ? '#27ae60' : '#e74c3c');
path.setAttribute('stroke-width', '2');
path.setAttribute('fill', 'none');
path.setAttribute('marker-end', 'url(#arrow)');
return path;
}
showCurrentPath(formData) {
// Highlight the current configuration path
const evaluations = this.evaluator.evaluate(formData);
evaluations.forEach((evaluation, fieldName) => {
const card = document.getElementById(`dep-card-${fieldName}`);
if (!card) return;
if (formData[fieldName]) {
card.classList.add('configured');
} else if (evaluation.enabled) {
card.classList.add('available');
} else {
card.classList.add('locked');
}
});
}
}
Implementation Details
Complete Smart Dependencies System
class SmartDependenciesSystem {
constructor() {
this.graph = new DependencyGraph();
this.evaluator = new DependencyEvaluator(this.graph);
this.ui = new SmartDependencyUI(this.evaluator);
this.guide = new ConfigurationGuide(this.graph, this.evaluator);
this.visualizer = new DependencyVisualizer(this.graph);
}
initialize(config) {
// Define fields
config.fields?.forEach(field => {
this.graph.defineField(field.name, field);
});
// Define dependencies
config.dependencies?.forEach(dep => {
if (dep.type === 'requires') {
this.graph.requires(dep.field, dep.requires, dep.values);
} else if (dep.type === 'incompatible') {
this.graph.incompatibleWith(dep.field, dep.incompatible, dep.values);
}
});
// Set up change listeners
this.setupListeners();
// Initial evaluation
this.update({});
}
setupListeners() {
document.querySelectorAll('input, select').forEach(element => {
element.addEventListener('change', () => {
this.handleChange();
});
});
}
handleChange() {
const formData = this.getFormData();
this.update(formData);
}
update(formData) {
// Update UI based on dependencies
this.ui.update(formData);
// Show next step suggestion
const nextStep = this.guide.suggestNextStep(formData);
this.showNextStepSuggestion(nextStep);
// Update visual map if visible
if (this.isMapVisible()) {
this.visualizer.showCurrentPath(formData);
}
}
showNextStepSuggestion(nextStep) {
const suggestionBox = document.getElementById('next-step-suggestion');
if (!suggestionBox) return;
if (nextStep.action === 'submit') {
suggestionBox.innerHTML = `
<div class="success-message">
✓ ${nextStep.message}
</div>
`;
} else {
suggestionBox.innerHTML = `
<div class="next-step">
<strong>${nextStep.message}</strong>
${nextStep.suggestions ? `
<div class="suggestions">
${this.formatSuggestions(nextStep.suggestions)}
</div>
` : ''}
</div>
`;
}
}
formatSuggestions(suggestions) {
if (suggestions.type === 'prerequisites') {
return `
<p>${suggestions.message}</p>
<ul>
${suggestions.required.map(req => `
<li>Set ${req.field} to ${req.required.join(' or ')}</li>
`).join('')}
</ul>
`;
} else {
return `
<p>${suggestions.message}</p>
${suggestions.recommended ? `
<button onclick="selectOption('${suggestions.recommended}')">
Use recommended: ${suggestions.recommended}
</button>
` : ''}
`;
}
}
getFormData() {
const form = document.querySelector('form');
const data = new FormData(form);
return Object.fromEntries(data.entries());
}
isMapVisible() {
const map = document.querySelector('.dependency-map');
return map && map.style.display !== 'none';
}
}
// Example usage
const system = new SmartDependenciesSystem();
system.initialize({
fields: [
{
name: 'material',
label: 'Material',
type: 'select',
options: ['Standard', 'Premium', 'Luxury'],
alwaysVisible: true
},
{
name: 'finish',
label: 'Finish',
type: 'select',
options: ['Matte', 'Glossy', 'Embossed', 'Foil'],
alwaysVisible: true
},
{
name: 'rushDelivery',
label: 'Rush Delivery',
type: 'checkbox',
alwaysVisible: true
}
],
dependencies: [
{
type: 'requires',
field: 'finish',
requires: 'material',
values: ['Premium', 'Luxury']
},
{
type: 'incompatible',
field: 'rushDelivery',
incompatible: 'finish',
values: ['Embossed', 'Foil']
}
]
});
Consequences
Benefits
Guided Experience: - Users navigate complex options confidently - Clear what's possible at each step - Reduced decision paralysis
Error Prevention: - Invalid combinations impossible - Real-time feedback - No submission errors
Better UX: - Progressive disclosure - Contextual help - Smart suggestions
Improved Completion: - Users understand what's needed - Clear path to completion - Higher success rate
Reduced Support: - Self-explanatory constraints - Visual dependency feedback - Fewer "why can't I" questions
Liabilities
Complexity: - Dependency graphs can be intricate - Testing all paths challenging - Maintenance burden
Performance: - Real-time evaluation expensive - Complex graphs slow - Need optimization
Rigidity: - Enforced dependencies may be too strict - Edge cases not handled - Need override mechanisms
User Learning Curve: - Progressive disclosure can confuse - "Where did this field come from?" - Need clear communication
Over-Guidance: - Too much help feels patronizing - Power users want freedom - Balance guidance with autonomy
Domain Examples
E-commerce: Product Configuration
// Computer configuration dependencies
defineComputerConfig() {
// RAM depends on motherboard
this.graph.requires('ram', 'motherboard', ['DDR5 Board']);
// Graphics card depends on power supply
this.graph.requires('graphicsCard', 'powerSupply', ['750W', '850W']);
// Cooling depends on CPU
this.graph.requires('cooling', 'cpu', ['High-End CPU']);
}
Healthcare: Treatment Planning
// Treatment dependencies
defineTreatmentDeps() {
// Certain medications require lab work
this.graph.requires('medication', 'labResults', ['Complete']);
// Surgery requires pre-op clearance
this.graph.requires('surgery', 'preOpClearance', ['Approved']);
// Physical therapy incompatible with certain conditions
this.graph.incompatibleWith('physicalTherapy', 'condition', ['Acute Injury']);
}
Travel: Booking Flow
// Travel booking dependencies
defineTravelDeps() {
// Hotel requires destination
this.graph.requires('hotel', 'destination', ['Selected']);
// Car rental requires arrival date
this.graph.requires('carRental', 'arrivalDate', ['Set']);
// Activities depend on destination
this.graph.requires('activities', 'destination', ['Selected']);
}
Related Patterns
Prerequisites: - Volume 3, Pattern 7: Calculated Dependencies (computed values) - Volume 3, Pattern 11: Cascading Updates (changes propagate)
Synergies: - Volume 3, Pattern 1: Progressive Disclosure (show fields as relevant) - Volume 3, Pattern 9: Contextual Constraints (context affects availability) - Volume 3, Pattern 13: Conditional Requirements (requirements drive dependencies)
Conflicts: - Simple forms (dependencies add complexity) - Expert users (may find guidance restrictive)
Alternatives: - Wizard workflows (step-by-step instead of smart form) - Static validation (catch errors at submit) - Expert mode (disable all guidance)
Known Uses
PC Part Picker: Compatible components based on selections
Car Configurators: Options depend on model, trim, packages
Insurance Applications: Coverage options based on selections
Product Customization (Dell, Apple): Features depend on base configuration
Travel Booking (Expedia, Kayak): Hotels/cars/activities based on destination
Software Licensing: Features/tiers have dependencies
Cable/Internet Packages: Add-ons depend on base package
Further Reading
Academic Foundations
- Constraint Satisfaction: Russell, S., & Norvig, P. (2020). Artificial Intelligence: A Modern Approach (4th ed.). Pearson. ISBN: 978-0134610993 - Chapter on CSP
- Product Configuration: Felfernig, A., et al. (2014). "Knowledge-based configuration: A survey." IEEE Intelligent Systems 29(2): 16-25.
- Dependency Networks: Heckerman, D., et al. (2000). "Dependency Networks for Inference." ICML '00.
Practical Implementation
- Constraint.js: https://github.com/slightlyoff/cassowary.js/ - Constraint solving in JavaScript
- React Hook Form Dependencies: https://react-hook-form.com/advanced-usage#SmartForm - Field dependencies
- JSON Schema Dependencies: https://json-schema.org/understanding-json-schema/reference/object.html#dependencies
- Yup Conditional Schema: https://github.com/jquense/yup#yupwhenkeys-string--arraystring-builder-object--value-schema-schema-schema
Standards & Patterns
- Configuration Management: ISO/IEC 10007 - Configuration management guidelines
- Product Configuration Standards: https://www.sap.com/products/variant-configuration.html
Related Trilogy Patterns
- Pattern 12: Calculated Dependencies - Calculations follow dependencies
- Pattern 14: Contextual Constraints - Context defines dependencies
- Pattern 16: Cascading Updates - Dependencies trigger cascades
- Pattern 19: Cross-Field Validation - Validate dependencies
- Volume 2, Pattern 7: Multi-Dimensional Risk Assessment - Dependency rules
- Volume 1, Chapter 10: Domain Knowledge Acquisition - Configuration patterns
Tools & Services
- PC Part Picker API: Example of product compatibility checking
- Shopify Product Options: https://help.shopify.com/en/manual/products/details/product-options - Variant dependencies
- Configure Price Quote (CPQ): Salesforce CPQ, Oracle CPQ - Enterprise configuration
Implementation Examples
- Product Configurator Tutorial: https://configit.com/resources/product-configuration-guide/
- Building Smart Forms: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0