UNPKG

@uplink-protocol/form-controller

Version:

Reactive multi-step form controller with dynamic validation and state management

335 lines (261 loc) 10.7 kB
# 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).