-
Notifications
You must be signed in to change notification settings - Fork 2
FormField
API / FormField<TValue, TValidationError> class
Represents a form field containing the minimum set of information required to describe a field in a form.
Extends Validatable<TValidationError>.
class FormField<TValue, TValidationError = string>
extends Validatable<TValidationError>
Source reference: src/forms/FormField.ts:169
.
-
TValue - The type of values the field contains.
-
TValidationError - The concrete type for representing validation errors (strings, enums, numbers etc.).
Default value:
string
.
The form fields are designed to have the absolute minimum a form would require and at the same time be easily extensible. It is highly encouraged that applications define their own forms and fields even if there are no extra features, just to make it easy to add them later on.
The initialization follows a config-style approach where an object that contains all the values is provided to the field constructor. This allows for a simple syntax where properties are initialized in a similar way to object initializers.
On top of this, extending the field with this approach is easy. Extend the base config interface with extra properties that are need, pass the same object to the base constructor and extract the newly added ones afterwards.
-
constructor - Initializes a new instance of the
FormField<TValue, TValidationError>
class.
- initialValue
- name
-
readonly
validation - Gets the validation configuration for the current field. - value
-
inherited
error - Gets or sets the error message when the object is invalid. -
inherited
isInvalid - A flag indicating whether the object is invalid. -
inherited
isValid - A flag indicating whether the object is valid. -
inherited
propertiesChanged - An event that is raised when one or more properties may have changed.
- reset - Resets the field. Only the validation configuration is reset, the field retains its current value.
-
protected
onShouldTriggerValidation - Invoked when the current instance's properties change, this is a plugin method to help reduce
-
ViewModel
- ReadOnlyObservableCollection<TItem>
- ReadOnlyObservableSet<TItem>
- ReadOnlyObservableMap<TKey, TItem>
-
Validatable<TValidationError>
- Form<TValidationError>
- FormField<TValue, TValidationError>
One of the common features that a field may use is to track whether it was touched, i.e. if the input it is bound to ever came into focus. This is not something provided implicity by the base implementation, however adding this is easy.
class ExtendedFormField<TValue> extends FormField<TValue> {
private _isTouched: boolean = false;
public get isTouched(): boolean {
return this._isTouched;
}
public set isTouched(value: boolean) {
if (this._isTouched !== value) {
this._isTouched = value;
this.notifyPropertiesChanged('isTouched');
}
}
}
Form fields follow a config-style approach when being initialized, this allows to pass property values through the constructor instead of having to set each after the instance is created. Additionally, required and optional properties can be clearly specified.
Following on the example, an isTouched
initial value can be provided by extending the base config
and requesting it in the constructor.
interface IExtendedFieldConfig<TValue> extends IFormFieldConfig<TValue> {
readonly isTouched?: boolean;
}
class ExtendedFormField<TValue> extends FormField<TValue> {
public constructor({ isTouched = false, ...baseConfig }: IExtendedFieldConfig<TValue>) {
super(baseConfig);
this._isTouched = isTouched;
}
// ...
}
Changes to the field may trigger validation, by default only changes to the FormField.value
does this,
to change the behavior see FormField.onShouldTriggerValidation
.
Generally, binding in MVVM comes along with binding expressions, however this is not really necessary.
Binding, or data binding, refers to linking a view and a view model so one updates based on how the other changes. Usually, the view is the destination and the view model is the source, this gives us a few types of binding.
- One-time: read the value when the component renders.
- One-way: same as one-time, but whenever the value changes the component re-renders.
- Two-way: applies mostly to inputs, when the field value changes the input reflects this, and when the input value changes, the field value is also updated. The two are permanently in sync.
- One-way to source: whenever the value on the view changes, the view model is updated, but the view does not update when the view model changes.
Binding expressions are nice, but not really required. They may add additional complexity to the UI and can be limiting. While it is great to be descriptive about this, being able to do this more or less manually through event handling can provide more options. One should not exclude the other.
The most common types of binding are the one-way and two-way ones. On pages where we only
show data, we usually use one-way binding because there's no interaction with it, we simply display it
when it is ready. We can do this using the useViewModel
hook alone.
Forms, on the other hand, use two-way binding. We want to keep our inputs in sync with whatever is going on with the fields. If changing one field clears another, we want to see this on the UI.
Unavoidably, we end up creating components for specific types of inputs to avoid repetitive code as well as ensure they all behave in the same way. Binding is generally handled inside these components using DOM event handlers/callbacks.
This is the most basic form of two-way binding, there's full control over it. The value coming from the field can be transformed in the component. Similarly, when the input changes its value can be converted back to something the form field may understand. There is full control over how the two link to one another as well as having the ability to create reusable components for specific types of inputs.
interface ITextInputProps {
readonly field: FormField<string>;
}
function TextInput({ field }: ITextInputProps): JSX.Element {
useViewModel(field);
const onInputChangedCallback = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
field.value = event.target.value;
},
[field]
);
return (
<input
value={field.value}
onChange={onInputChangedCallback} />
);
}
Overview
Motivation
Guides and Tutorials - Getting Started
Releases
CodeSandbox
API
Events
IEvent
IEventHandler
EventDispatcher
ViewModels
INotifyPropertiesChanged
ViewModel
Forms
Form
IFormFieldConfig
FormField
ReadOnlyFormCollection
FormCollection
IConfigurableFormCollection
FormSetupCallback
Validation
IValidator
ValidatorCallback
IObjectValidator
IValidatable
Validation / Triggers
WellKnownValidationTrigger
ValidationTrigger
Observable Collection
ReadOnlyObservableCollection
ObservableCollection
INotifyCollectionChanged
CollectionChangeOperation
INotifyCollectionReordered
CollectionReorderOperation
Observable Map
ReadOnlyObservableMap
ObservableMap
INotifyMapChanged
MapChangeOperation
Observable Set
ReadOnlyObservableSet
ObservableSet
INotifySetChanged
SetChangeOperation
Dependency Handling
IDependencyResolver
IDependencyContainer
DependencyContainer
useDependency
useViewModelDependency
useDependencyResolver
React Hooks
useViewModel
useViewModelMemo
useObservableCollection
useObservableMap
useObservableSet