@uplink-protocol/form-controller
Version:
Reactive multi-step form controller with dynamic validation and state management
256 lines (185 loc) • 7.64 kB
Markdown
# Form Controller Extensibility Guide
This document provides a guide on how to extend the FormControllerClass to create custom form controllers with specialized behavior.
## Overview
Thanks to the class-based architecture refactoring, the `FormControllerClass` has been designed to be highly extensible. You can create custom form controllers by extending this class and overriding specific methods to customize behavior without duplicating code.
## Extensibility Features
The `FormControllerClass` provides several extension points:
- **Protected properties** for access in subclasses
- **Protected methods** that can be overridden
- **Lifecycle hooks** at various stages of initialization
- **Modular service architecture** for targeted customization
## Creating a Custom Form Controller
To create a custom form controller, extend the `FormControllerClass`:
```typescript
import { FormControllerClass, FormConfig } from '@uplink-protocol/form-controller';
export class CustomFormController extends FormControllerClass {
constructor(config: FormConfig) {
super(config);
// Additional initialization if needed
}
// Override methods as needed
}
```
## Extension Points
### Protected Properties
All service instances and state objects are accessible in subclasses:
```typescript
class CustomFormController extends FormControllerClass {
customMethod() {
// Access services directly
const fieldData = this.fieldService.getFieldData(stepId, fieldId);
// Access manager services
this.formManagerService.customOperation();
// Access state
const data = this.initialFormData;
}
}
```
### Initialization Hooks
You can customize the initialization process by overriding these methods:
#### 1. `setupInitialFormData`
Override to customize how initial form data is set up:
```typescript
protected setupInitialFormData(config: FormConfig): void {
// Call the parent implementation first
super.setupInitialFormData(config);
// Add custom logic
config.steps.forEach(step => {
// Add default values for custom fields
this.initialFormData[step.id]['customField'] = 'default value';
});
}
```
#### 2. `initializeServices`
Override to customize service initialization:
```typescript
protected initializeServices(config: FormConfig): void {
// Call the parent implementation
super.initializeServices(config);
// Replace a service with a custom implementation
this.validatorService = new EnhancedValidatorService();
// Add additional services
this.myCustomService = new MyCustomService(config);
}
```
#### 3. `initializeManagerServices`
Override to customize manager service initialization:
```typescript
protected initializeManagerServices(): void {
// Call the parent implementation
super.initializeManagerServices();
// Replace a manager with a custom implementation
this.navigationManager = new CustomNavigationManager(
this.stepperService,
this.configService,
this.fieldService,
this.interactionService
);
}
```
#### 4. `initializeMethods`
Override to customize or extend available controller methods:
```typescript
protected initializeMethods(): void {
// Call the parent implementation to get all standard methods
super.initializeMethods();
// Add custom methods
this.methods.customSubmit = () => {
// Custom submission logic
this.methods.submitForm();
// Additional processing after submission
this.sendDataToServer();
};
// Override existing methods
const originalSubmit = this.methods.submitForm;
this.methods.submitForm = () => {
// Pre-processing
this.beforeSubmit();
// Call original method
const result = originalSubmit();
// Post-processing
this.afterSubmit();
return result;
};
}
```
#### 5. `initializeStepValidation`
Override to customize the initial step validation behavior:
```typescript
protected initializeStepValidation(): void {
// Call the parent implementation
super.initializeStepValidation();
// Add custom validation logic
this.validateCustomRules();
}
```
## Examples
### Example 1: Creating a Form Controller with Analytics
```typescript
import { FormControllerClass, FormConfig } from '@uplink-protocol/form-controller';
export class AnalyticsFormController extends FormControllerClass {
private analyticsService: AnalyticsService;
constructor(config: FormConfig) {
super(config);
this.analyticsService = new AnalyticsService();
}
protected initializeMethods(): void {
super.initializeMethods();
// Wrap navigation methods with analytics
const originalNextStep = this.methods.nextStep;
this.methods.nextStep = () => {
const previousStep = this.bindings.currentStep.current.id;
const result = originalNextStep();
const currentStep = this.bindings.currentStep.current.id;
this.analyticsService.trackNavigation(previousStep, currentStep);
return result;
};
// Wrap submission with analytics
const originalSubmit = this.methods.submitForm;
this.methods.submitForm = () => {
const result = originalSubmit();
if (result.success) {
this.analyticsService.trackCompletion(result.data);
}
return result;
};
}
}
```
### Example 2: Custom Validation Form Controller
```typescript
import { FormControllerClass, FormConfig } from '@uplink-protocol/form-controller';
export class EnhancedValidationController extends FormControllerClass {
constructor(config: FormConfig) {
super(config);
}
protected initializeServices(config: FormConfig): void {
super.initializeServices(config);
// Replace the validator service with an enhanced one
this.validatorService = new EnhancedValidatorService();
}
protected initializeMethods(): void {
super.initializeMethods();
// Add advanced validation methods
this.methods.validateWithRules = (rules) => {
return this.validatorService.validateWithCustomRules(
this.formService.getAllData(),
rules
);
};
}
protected initializeStepValidation(): void {
super.initializeStepValidation();
// Add cross-field validation
this.methods.validateCrossFieldRules();
}
}
```
## Best Practices
1. **Call super methods first**: When overriding methods, call the parent implementation first unless you want to completely replace the behavior.
2. **Avoid modifying base services directly**: Instead of modifying the base services, create enhanced services and replace the references in your subclass.
3. **Maintain the services architecture**: If adding new services, follow the existing pattern of keeping core services separate from manager services.
4. **Use composition for complex extensions**: For very complex extensions, consider composing multiple controllers rather than deeply nesting inheritance.
5. **Document your extensions**: Make sure to document your custom controller's features and how they differ from the base implementation.
## Conclusion
The refactored class-based architecture of the FormControllerClass makes it highly extensible, allowing for customization at various levels. By extending the base class and overriding specific methods, you can create specialized form controllers tailored to specific needs without duplicating code.