README
Updated to RxJS 7.
JQuery extension for creating JavaScript object representation of forms. Intended for use in server-side web frameworks (.NET MVC, PHP, Django, etc.).
Instead of manually selecting and attaching JavaScript code to form's elements,
a more explicit access to form model is provided through objects called FormControl
and FormGroup
. This library, using observables and static type checking, offers a modern workflow for all types of projects,
without even requiring a build process (TypeScript or bundlers).
Demo usage:
Prerequisites:
- Basic knowledge of RxJS.
Table of Contents
Functionality & Usage
Form Control
This is one of the two fundamental building blocks of Observable Forms, along with
FormGroup
. It tracks the value and validation status of an individual form control
(a single text input, a set of radio inputs with the same name, etc.).
Creating and using a form control is pretty simple:
// Module imports
import ...
// FormControl created
let firstName = $('#firstName').asFormControl().enableValidation();
firstName.valueChanges.subscribe(value => console.log('My new value is: ' + value));
// ... let's try something a bit more complicated
// Either copy the delivery address into payment address field,
// or track payment address status, and alert the user if invalid (validation logic not shown)
let deliveryAddress = $('#delivery-address').asFormControl();
let paymentAddress = $('#payment-address').asFormControl().enableValidation();
// Observable<boolean>
let isPaymentDifferentFromDelivery$ = $('#different-checkbox').asFormControl().valueChanges
.pipe(map(value => value === 'true'), startWith(false));
isPaymentDifferentFromDelivery$.pipe(switchMap(isDifferent => isDifferent
? paymentAddress.statusChanges.pipe(tap(status => status === FormControlStatus.INVALID && alert('Entered address is not valid')))
: deliveryAddress.valueChanges.pipe(tap(value => paymentAddress.setValue(value)))
)).subscribe();
// RxJS produces a more concise code (no removeEventListener())
Form Group
Form group aggregates controls found in the subtree of the selected element(s) into one object,
with each control's name as the key. Name is either control's name
attribute or one manually provided.
Class of this object accepts a type parameter representing the model of the form group,
which provides static type checking when working with the controls and values.
Type checking is also available in plain JavaScript no-build projects, as demonstrated in the Demos.
Author's note: The best and easiest way to have type checking is to find a tool
that will generate TypeScript versions of your backend classes, and use those as type parameters of form groups.
Here's the one I use for .NET MVC, link.
Some features of the FormGroup objects are:
- The value is a JSON object of child controls' names and values.
- Controls can be added and removed from the group.
- Validation
- Custom controls as child controls
- Web Components support
class MyForm {
fullName: string;
isSubscriber: boolean;
addresses: {street: string; city: string}[];
}
// Create a form group (TS version)
let form = $('form').asFormGroup<MyForm>();
// Accessing child controls and value, with editor providing type information
form.controls.fullName.valueChanges.subscribe(_ => '...')
form.controls.addresses[0].city.valueChanges.subscribe(_ => '...');
console.log(form.value.isSubscriber);
Some of the properties, observables and methods of FormControl
and FormGroup
are:
- value, valueChanges - string or JSON
- status, statusChanges - valid, invalid or disabled
- touched - has the user interacted with the element(s) at all
- dirty - has the user changed element(s) value
- setValue()
- reset()
Despite some inconsistencies, Angular docs can be used as more detailed API reference: AbstractControl, FormControl, FormGroup.
Demos
These demos will try to cover as many scenarios as possible, such as:
- disabling / enabling form controls
- adding / removing controls from the DOM
- changing element's types
- creating controls from non-input elements
- handling Web Components
- changing form's data / resetting
- handling arrays
Note: These demos are hosted on codesandbox, and code behind the forms can be accessed using the "Open Sandbox" button.
Fullscreen view is preferable, considering the style of validation messages.
Styling can be changed, as shown in the comments of Demo 1.
Demo 1 - "A standard form"
A JavaScript project covering a lot of library's functionalities, and showing how to integrate type checking into JavaScript code.
Demo 1
Note: that CodeSandbox has some built-in js bundler that allows non-standard imports in .js files.
Below those imports are comments on how they should be used plain .js files.
Demo 2 - "Custom made"
A TypeScript project covering custom form controls. Also demonstrates support for Web Components.
Demo 2
Demo 3 - "Form update"
A JavaScript project showing how to change form's data and how to reset it.
Demo 3
Installation
ES6 via npm
npm i observable-forms
Inside a html script tag, or in javascript:
<script type="module">
import {} from "./node_modules/observable-forms/dist/index.js";
// Library self initializes when module is loaded.
let $formControl = $('input').asFormControl().valueChanges.subscribe(val => console.log(val));
...
</script>
or, in Typescript:
import {ConfigService, Validators} from "observable-forms";
// Declaratively adding validation to controls using html attributes
ConfigService.registerAttributeValidators({
'data-val-required': Validators.required,
'data-val-email': Validators.email,
'data-val-url': $e => $e.val() === '' || URL_REGEXP.test($e.val()) ? null : {url: true}
});
CDN
For CDN, you can use unpkg:
https://unpkg.com/observable-forms/dist/index.js
<script type="module">
import {} from "https://unpkg.com/observable-forms/dist/index.js";
...
</script>