Skip to content

FieldController

A FieldController wraps a single form field (a [data-form-field] wrapper containing an input, select, or textarea). It implements the FormField interface. Get one via initField() or interact with fields through the parent form controller.

new FieldController(wrapper: HTMLElement, options?: FieldControllerOptions)

Usually created via initField() rather than directly.

PropertyTypeDescription
namestringThe data-form-field attribute value
elementHTMLElementThe wrapper element
inputElementHTMLInputElement | HTMLSelectElement | HTMLTextAreaElementThe active input
fieldTypestring | nullThe data-field-type attribute, or null
enabledbooleanWhether the field is enabled

Returns a snapshot of the current field state.

const state: FieldState = ctrl.getState();
// { name, value, isValid, isDirty, isTouched, errors }

Sets the input value, marks as dirty/touched, validates, and notifies listeners.

Runs validation and returns a FieldValidationResult.

const result = ctrl.validate();
// { isValid: boolean, errors: string[] }

Applies server-side validation errors. Errors persist until the value changes.

ctrl.setServerErrors(['This email is already registered']);

Shows/hides the field, enables/disables the input, and excludes from validation when disabled.

Restores the input to its default DOM value (defaultValue, defaultChecked, defaultSelected) and clears validation state.

Aborts listeners and restores native validation attributes.

Wires field state changes into a parent callback (used internally by FormController). For standalone use, prefer on('change', ...).

Swaps the active input element (e.g., a custom field type hides the select and uses a text input).

on(event, handler) / once(event, handler) / off(event, handler)

Section titled “on(event, handler) / once(event, handler) / off(event, handler)”

Field-level events.

ctrl.on('change', (state: FieldState) => { ... });
ctrl.on('valid', (state: FieldState) => { ... });
ctrl.on('invalid', (state: FieldState) => { ... });
interface FieldErrorRenderContext {
message: string;
index: number;
errors: string[];
}
interface FieldControllerOptions {
validate?(
value: string,
rules: ValidatorRule[],
defaultValidate: () => FieldValidationResult
): FieldValidationResult;
onServerErrors?(errors: string[], fieldName: string): string[];
/** CSS selector scoped to the field wrapper. Used after id-based lookups. */
errorsSelector?: string;
/** Resolve the errors container. Takes precedence over errorsSelector and built-in lookups. */
findErrorsElement?(field: FieldController): HTMLElement | null;
/** Render a single error message as HTML. Ignored when renderErrors is set. */
renderError?(ctx: FieldErrorRenderContext, field: FieldController): string;
/** Join rendered error fragments. Default: `'<br/>'`. Ignored when renderErrors is set. */
errorsSeparator?: string;
/** Replace the entire error rendering step. When set, renderError and errorsSeparator are ignored. */
renderErrors?(errors: string[], ctx: FieldController): void;
}

See Error Rendering for examples (custom containers, inline icons, form summaries).

Override the validation pipeline. Receives the current value, parsed rules, and a defaultValidate function you can call to run the standard validators. Return a FieldValidationResult.

Transform or filter server errors before they are applied. Return the modified array.

Control which element receives error messages. Built-in lookup tries #${input.id}-errors, then group ids, then errorsSelector, then .invalid-feedback.

Customize per-message HTML while keeping the default container resolution. renderError must return safe HTML if messages can contain user input — escape text yourself.

Replace the default error rendering entirely. Called with the error array and the field controller instance.

interface FieldValidationResult {
isValid: boolean;
errors: string[];
}