@uplink-protocol/form-controller
Version:
Reactive multi-step form controller with dynamic validation and state management
335 lines (261 loc) • 10.7 kB
Markdown
# Form Controller Features Guide
This document provides a comprehensive overview of all features and capabilities available in the @uplink-protocol/form-controller package.
## Table of Contents
1. [Core Features](#core-features)
2. [Form Configuration](#form-configuration)
3. [Field Types and Configuration](#field-types-and-configuration)
4. [Validation Capabilities](#validation-capabilities)
5. [Form Navigation](#form-navigation)
6. [State Management](#state-management)
7. [Dynamic Form Modification](#dynamic-form-modification)
8. [Advanced Use Cases](#advanced-use-cases)
## Core Features
The form-controller provides a complete solution for managing form state, validation, and user interactions with the following core capabilities:
- **Flexible Form Structure**: Build single-page forms or multi-step wizards with a consistent API
- **Reactive State Management**: All form state is reactive and observable through a bindings system
- **Built-in Validation**: Comprehensive validation rules that can be configured declaratively
- **Dynamic Validation**: Context-aware validators that can adapt based on other field values
- **Progressive Form Building**: Add or remove steps on-the-fly for truly dynamic forms
- **Framework Agnostic**: Works with any UI library (React, Vue, Angular) or vanilla JavaScript
- **Centralized Form State**: Maintain form state separate from UI components
- **Immutable Updates**: Safe, immutable state updates that maintain data integrity
- **Comprehensive Error Handling**: Field-level and form-level error management
## Form Configuration
### FormConfig Interface
```typescript
interface FormConfig {
steps: FormStep[];
defaultValues?: Record<string, any>;
}
```
A FormConfig is the top-level configuration object passed to the FormController constructor. It contains:
- **steps**: An array of FormStep objects defining each step in your form
- **defaultValues**: (Optional) Initial values for form fields accessible by field ID
### FormStep Interface
```typescript
interface FormStep {
id: string;
title: string;
description?: string;
fields: Record<string, Field>;
validation?: (formData: Record<string, any>) => boolean | string;
}
```
Each step in a form is configured with:
- **id**: Unique identifier for the step
- **title**: Display title for the step
- **description**: (Optional) Descriptive text for the step
- **fields**: Map of field IDs to field configuration objects
- **validation**: (Optional) Custom step-level validation function
## Field Types and Configuration
### Field Interface
```typescript
interface Field {
id: string;
type: 'text' | 'email' | 'password' | 'number' | 'tel' | 'checkbox' |
'radio' | 'select' | 'textarea' | 'date' | 'file' | 'custom';
label: string;
value?: any;
placeholder?: string;
helperText?: string;
required?: boolean;
disabled?: boolean;
hidden?: boolean;
validation?: Validation;
options?: Array<{
label: string;
value: any;
disabled?: boolean;
}>;
props?: Record<string, any>;
}
```
The Field interface supports a wide range of input types:
- **Basic inputs**: text, email, password, number, tel, date
- **Selection inputs**: checkbox, radio, select
- **Multi-line input**: textarea
- **Advanced inputs**: file, custom
Field properties include:
- **id**: Unique identifier for the field
- **type**: The type of field (determines input component)
- **label**: Display label for the field
- **value**: (Optional) Initial value
- **placeholder**: (Optional) Placeholder text
- **helperText**: (Optional) Additional guidance text
- **required**: (Optional) Whether the field is required
- **disabled**: (Optional) Whether the field is disabled
- **hidden**: (Optional) Whether the field is hidden
- **validation**: (Optional) Validation rules
- **options**: (Optional) Available options for select, radio, checkbox fields
- **props**: (Optional) Additional properties for custom field types
## Validation Capabilities
### Validation Interface
```typescript
interface Validation {
required?: boolean;
pattern?: RegExp | string;
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
custom?: (value: any) => boolean | string;
dynamicValidator?: string;
dynamicValidatorParams?: Record<string, any>;
errorMessage?: string;
}
```
The form-controller provides a powerful validation system with:
### Built-in Validators
- **required**: Ensures field has a value
- **pattern**: Validates against a regular expression
- **minLength/maxLength**: Validates text length
- **min/max**: Validates numeric range
- **custom**: Custom validation function
### Dynamic Validators
Dynamic validators can access the entire form state and react to other field values:
- **requiredIf**: Makes a field required based on conditions:
- `equals`: Required when a specified field equals a specific value
- `notEquals`: Required when a specified field doesn't equal a specific value
- `notEmpty`: Required when specified fields have values
- **equals**: Validates that a field equals a specific value or another field's value
### Custom Dynamic Validators
You can register your own dynamic validators using the `registerValidator` method:
```javascript
form.methods.registerValidator('matchesPassword', (value, context) => {
if (value !== context.formData.password) {
return 'Passwords do not match';
}
return true;
});
```
The validator context provides access to:
- The current field configuration
- The entire form data
- The step ID and field ID
## Form Navigation
The form-controller provides methods for navigating through multi-step forms:
- **nextStep()**: Advances to the next step (with validation)
- **prevStep()**: Returns to the previous step (without validation)
- **goToStep(index)**: Jumps to a specific step by index (with validation for skipped steps)
Navigation bindings provide information about the current state:
- **isFirstStep**: Whether the current step is the first
- **isLastStep**: Whether the current step is the last
- **currentStepIndex**: The index of the current step
- **currentStep**: The configuration object for the current step
- **totalSteps**: The total number of steps in the form
## State Management
The form-controller uses a reactive bindings system to maintain and expose form state:
### Data Access Bindings
- **formData**: The complete form data organized by step ID and field ID
- **getAllData()**: Method to get all form data (nested by step)
- **getFlatData()**: Method to get form data flattened into a single object
- **getStepData(stepId)**: Method to get data for a specific step
### Validation Bindings
- **fieldErrors**: Current validation errors by step ID and field ID
- **stepsValidity**: Validation state for each step
- **isCurrentStepValid**: Whether the current step is valid
- **isFormValid**: Whether the entire form is valid
### Subscription System
All bindings provide a subscription mechanism:
```javascript
form.bindings.formData.subscribe((data) => {
console.log('Form data changed:', data);
});
form.bindings.fieldErrors.subscribe((errors) => {
console.log('Validation errors changed:', errors);
});
```
## Dynamic Form Modification
The form-controller supports dynamic modification of the form structure at runtime:
- **updateConfig(newConfig)**: Replace the entire form configuration
- **addStep(step, index?)**: Add a new step at the specified index (or end)
- **removeStep(stepId)**: Remove a step by ID
- **updateField(stepId, fieldId, value)**: Update a field's value
## Advanced Use Cases
### Conditional Fields and Steps
You can implement conditional fields that appear based on other field values:
```javascript
// Show/hide a field based on another field's value
form.bindings.formData.subscribe((data) => {
const showExtraField = data.step1.optIn === true;
document.getElementById('extraFieldContainer').style.display =
showExtraField ? 'block' : 'none';
});
```
### Dynamic Step Creation
Create form steps dynamically based on user input:
```javascript
// Add a new step dynamically
const addNewSection = () => {
const stepCount = form.bindings.totalSteps.current;
const newStepId = `dynamic-step-${stepCount}`;
form.methods.addStep({
id: newStepId,
title: `Dynamic Section ${stepCount}`,
fields: {
dynamicField: {
id: 'dynamicField',
type: 'text',
label: 'Dynamic Field',
required: true
}
}
});
};
```
### Saving and Restoring Form State
Implement form state persistence:
```javascript
// Save form state to localStorage
const saveFormState = () => {
const formData = form.methods.getAllData();
localStorage.setItem('savedForm', JSON.stringify(formData));
};
// Restore form state
const restoreFormState = () => {
const savedData = JSON.parse(localStorage.getItem('savedForm') || '{}');
// Update each field with saved values
Object.entries(savedData).forEach(([stepId, stepData]) => {
Object.entries(stepData).forEach(([fieldId, value]) => {
form.methods.updateField(stepId, fieldId, value);
});
});
};
```
### Multi-page Form with Validation
Create a wizard-style form with validation between steps:
```javascript
// Next button with validation
nextButton.addEventListener('click', () => {
if (form.methods.validateCurrentStep(true)) {
form.methods.nextStep();
renderCurrentStep();
}
});
// Back button without validation
backButton.addEventListener('click', () => {
form.methods.prevStep();
renderCurrentStep();
});
// Submit button with full form validation
submitButton.addEventListener('click', () => {
const result = form.methods.submitForm();
if (result.success) {
// Handle successful submission
console.log('Form submitted successfully:', result.data);
} else {
// Handle validation errors
console.error('Form validation failed:', result.errors);
}
});
```
### Form Reset Functionality
Provide a way to clear form data:
```javascript
// Reset button
resetButton.addEventListener('click', () => {
form.methods.resetForm();
renderCurrentStep();
});
```
This features guide provides a comprehensive overview of all capabilities available in the form-controller package. For implementation details and code examples, refer to the [Usage Guide](./usage-guide.md).