3.0.0 • Published 4 months ago

forms-and-fields v3.0.0

Weekly downloads
2
License
MIT
Repository
github
Last release
4 months ago

Forms and Fields

NPM Version GitHub Workflow Status

A library for field validation and form submission.

Field elements are elements like input (of any type), select and textarea. Each field is defined by validation rules — validators.

You can use the required validator, any validator from the validator library (e.g. isEmail), the FileValidator or your own custom validators (including AJAX-based validation).

Usage

HTML:

<form class="js-form">
    <div>
        <label for="email">Email:</label>
        <br />
        <input id="email" type="text" name="email" class="js-email" />
    </div>
    <div>
        <label for="password">Password:</label>
        <br />
        <input id="password" type="password" name="password" class="js-password" />
    </div>
    <br />
    <button type="submit">Login</button>
</form>

JS:

import {
    Form,
    Field,
    FieldStatus,
    FormStatus,
    SubmissionResult
} from "forms-and-fields";

const form = new Form(
    ".js-form",
    [
        new Field(".js-email", ".js-form", ["required", "isEmail"]),
        new Field(".js-password", ".js-form", ["required"])
    ],
    (formData) => {
        const isOk =
            formData.get("email") === "test@example.com" &&
            formData.get("password") === "123456";
        return SubmissionResult.make(isOk, "Incorrect credentials");
    },
    {
        invalidMessages: {
            required: "This field is required",
            isEmail: "Invalid email"
        },
        onFieldStatusChange: (status, validationResult, fieldElement) => {
            if (status === FieldStatus.No) {
                fieldElement.parentNode.querySelector(".error")?.remove();
            }

            if (status === FieldStatus.Invalid) {
                fieldElement.insertAdjacentHTML(
                    "afterend",
                    `<div class="error">${validationResult.getMessage()}</div>`
                );
            }
        },
        onStatusChange: (
            status,
            validationResult,
            submissionResult,
            formElement
        ) => {
            if (status === FormStatus.No) {
                formElement.querySelector(".form-error")?.remove();
            }

            if (status === FormStatus.NegativeSubmit) {
                formElement.insertAdjacentHTML(
                    "beforeend",
                    `<div class="form-error"><br />${submissionResult.getMessage()}</div>`
                );
            }

            if (status === FormStatus.PositiveSubmit) {
                formElement.replaceWith("You are logged in");
            }
        }
    }
);

form.listen();

You can experiment with the code above in CodePen.

More examples:

Find more examples in the sandbox.

Installation

npm install forms-and-fields

Or without installation and build tools:

<script type="module">
    import { Field } from 'https://esm.sh/forms-and-fields';
</script>

Documentation

Common

To validate and submit a form, you need to create a Form object with Field objects (associated with the field elements), a submit function, and call its listen method.

Form validation involves validating all fields. A form is considered valid when all its fields are valid.

To validate an individual field (outside a form) you need to create a Field object and call its listen method.

When a user submits a form, validation is performed first. If the form passes validation, submission is then executed.

Validation and Submission results

Any custom validator must return a ValidationResult object. The internal validators return ValidationResult also.

ValidationResult class

MethodDescription
valid(resultDetails: object \| string = {})Static method. Creates a new valid result. The resultDetails object is expected to have the keys message and/or data. The resultDetails string is a message.
invalid(resultDetails: object \| string = {})Static method. Creates a new invalid result. The resultDetails object is expected to have the keys message and/or data. The resultDetails string is a message.
make(result: boolean, resultDetailsForInvalidCase: object \| string = {}, resultDetailsForValidCase: object \| string = {})Static method. Creates a new valid or invalid result depending on the result.
isValid(): boolReturns true if valid.
isInvalid(): boolReturns true if invalid.
areValid(validationResults: ValidationResult[]): boolStatic method. Returns true if all the specified validation results are valid.
hasMessage(): boolReturns true if a message exists.
getMessage(): stringReturns the message.
getData(): objectReturns the data.

A form submit function must return a SubmissionResult object.

SubmissionResult class

MethodDescription
positive(resultDetails: object \| string = {})Static method. Creates a new positive result. The resultDetails object is expected to have the keys message and/or data. The resultDetails string is a message.
invalid(invalidData: object, data: object = {})Static method. Creates a new negative result with invalid data. The invalidData is an object in the following format: { [invalidFieldName]: message }, for groups: { [invalidFieldName.index]: message }, and for the entire form: {"*": message}. In this case, the form will set statuses and messages for the corresponding fields .
negative(resultDetails: object \| string = {})Static method. Creates a new negative result. The resultDetails object is expected to have the keys message and/or data. The resultDetails string is a message.
make(result: boolean, resultDetailsForNegativeCase: object \| string = {}, resultDetailsForPositiveCase: object \| string = {})Static method. Creates a new positive or negative result depending on the result.
isPositive(): booleanReturns true if positive.
isNegative(): booleanReturns true if negative.
isValid(): booleanReturns true if valid (i.e., it was not created by the invalid method).
isInvalid(): booleanReturns true if invalid (i.e., it was created by the invalid method).
hasMessage(): booleanReturns true if a message exists.
getMessage(): booleanReturns the message.
getData(): objectReturns the data.

Fields

Field class

MethodDescription
constructor (fieldSelector: string, containerSelector: string, validators: array = [], options: object = {})-fieldSelector: A selector to find the field element. -containerSelector: A selector to find the container element that contains the field element. Typically, this is the form selector.-validators: See the Field validators.-options: See the Field options.
listen(): () => voidStarts listening to events on the field element. This method must be called if the field is used outside of a form. Returns a function that stops listening.

You can specify a CSS class for the field element and use it in the fieldSelector parameter. For elements with the same name (e.g., radio buttons or a group of text input elements), use a single Field object and specify a selector to find the first element. You can find examples in the sandbox.

You can also specify a CSS class for the container element containing the field element and use it in the containerSelector parameter. If the field element is inside a form, the container element should be the form itself.

Field validators

An array of validators. Each validator can be:

  • A string: The name of the validator (e.g., required or any validator from the validator library, such as isEmail).
  • A function: A custom validator function that must return a ValidationResult object. Can be promised.
  • An object with a validate method (that must return a ValidationResult object), such as the FileValidator (see Validation of files) or another custom object. Can be promised.

Field options

OptionTypeDefaultDescription
invalidMessagesobject{}Messages for invalid values for each validator. For example: {isEmail: 'Invalid Email'}.
defaultInvalidMessagestring'Invalid value.'A default message for invalid values if no specific message is provided in the invalidMessages.
trimEnabledbooleantrueSpecifies whether to trim the input value before validation.
typingDelaynumber1000The delay (in milliseconds) to consider a user as "typing" when the time between keystrokes is less than this value.
validateOnChangebooleanfalseSpecifies whether to validate the field when its value changes.
validateOnTypebooleanfalseSpecifies whether to validate the field during typing.
validatorParamsobject{}Parameters for validator. For example: { isEmail: [{ allow_utf8_local_part: false }] }.
validationCacheEnabledbooleantrueSpecifies whether to cache the validation result until the value changes.
cssStatusEnabledbooleantrueSpecifies whether to add CSS classes when the status changes.
onChangefunctionNOOPTriggered when the field value changes. Passed as arguments: value: any, fieldElement: HTMLElement, containerElement: Element.
onTypingStartfunctionNOOPTriggered when the user starts typing. Passed as arguments: value: any, fieldElement: HTMLElement, containerElement: Element.
onTypingEndfunctionNOOPTriggered when the user stops typing. Passed as arguments: value: any, fieldElement: HTMLElement, containerElement: Element.
onBeforeValidatefunctionNOOPTriggered before field validation. Passed as arguments: fieldElement: HTMLElement, containerElement: Element.
onAfterValidatefunctionNOOPTriggered after field validation. Passed as arguments: validationResult: ValidationResult, fieldElement: HTMLElement, containerElement: Element.
onValidationErrorfunctionNOOPTriggered when an error occurs during validation. Passed as arguments: error: any, fieldElement: HTMLElement, containerElement: Element.
onStatusChangefunctionNOOPTriggered when the field status changes. Passed as arguments: status: string, validationResult: ValidationResult \| null, fieldElement: HTMLElement, containerElement: Element, wrapperElement: HTMLElement \| null.
onStatusResetfunctionNOOPTriggered when the field status resets. Passed as arguments: fieldElement: HTMLElement, containerElement: Element, wrapperElement: HTMLElement \| null.

Forms

Form class

MethodDescription
constructor(formSelector: string, fields: array, submit: function, options: object = {})-formSelector: A selector to find the form element. -fields: An array of Field objects. - submit: A submit function, (a FormData object is passed as argument, must return a SubmissionResult object), - options: See the Form options
listen(): () => voidExecutes the listen method for all fields specified in the Form constructor and starts listening for the form submission event. This method must be called. Returns a function that stops listening.

You can specify a CSS class for the form element and use it in the formSelector parameter.

Form options

The Form class supports all Field options and applies them to each field specified in the Form constructor. Field-specific options always take priority over form-level options.

Additionally, the Form class supports the following options:

OptionTypeDefaultDescription
cssStatusEnabledbooleantrueSpecifies whether to add CSS classes when the status changes.
onBeforeValidatefunctionNOOPTriggered before form validation. Passed as argument: formElement: HTMLFormElement.
validatefunctionNOOPAn additional custom validation function, which must return a ValidationResult object. Passed as argument: formData: FormData. This is executed after field validation when all fields are valid. Can be promised.
onAfterValidatefunctionNOOPTriggered after form validation. Passed as arguments: validationResult: { [fieldName]: ValidationResult, "*": ValidationResult }, formElement: HTMLFormElement. validationResults contains the results for each field and the form itself.
onValidationErrorfunctionNOOPTriggered when an error occurs during validation. Passed as arguments: error: any, formElement: HTMLFormElement.
onBeforeSubmitfunctionNOOPTriggered before form submission. Passed as arguments: formData: FormData, formElement: HTMLFormElement.
onAfterSubmitfunctionNOOPTriggered after form submission. Passed as arguments: submissionResult: SubmissionResult, formElement: HTMLFormElement.
onSubmissionErrorfunctionNOOPTriggered when an error occurs during submission. Passed as arguments: error: any, formElement: HTMLFormElement.
onStatusChangefunctionNOOPTriggered when the form status changes. Passed as arguments: status: string, validationResult: { [fieldName]: ValidationResult, "*": ValidationResult } \| null, submissionResult: SubmissionResult \| null, formElement: HTMLFormElement.
onStatusResetfunctionNOOPTriggered when the form status resets. Passed as arguments: formElement: HTMLFormElement.
onBeforeValidateFieldfunctionNOOPTriggered before validating any field specified in the Form constructor. Passed as arguments: fieldElement: HTMLElement, containerElement: Element.
onAfterValidateFieldfunctionNOOPTriggered after validating any field specified in the Form constructor. Passed as arguments: validationResult: ValidationResult, fieldElement: HTMLElement, containerElement: Element.
onFieldValidationErrorfunctionNOOPTriggered when an error occurs while validating any field specified in the Form constructor. Passed as arguments: error: any, fieldElement: HTMLElement, containerElement: Element.
onFieldStatusChangefunctionNOOPTriggered when a status of any field specified in the Form constructor changes. Passed as arguments: status: string, validationResult: ValidationResult \| null, fieldElement: HTMLElement, containerElement: Element, wrapperElement: HTMLElement \| null.
onFieldStatusResetfunctionNOOPTriggered when a status of any field specified in the Form constructor resets. Passed as arguments: fieldElement: HTMLElement, containerElement: Element, wrapperElement: HTMLElement \| null.

Important

The next options are intended for the entire form:

  • onBeforeValidate
  • onAfterValidate
  • onValidationError
  • onStatusChange
  • onStatusReset

If you want to apply them to each field specified in the Form constructor use:

  • onBeforeValidateField
  • onAfterValidateField
  • onFieldValidationError
  • onFieldStatusChange
  • onFieldStatusReset

Validation of files

Use the FileValidator for file validation. See an example of usage in the sandbox.

The FileValidator constructor accepts two objects: rules and invalidMessages.

RuleExampleRelated option for invalid messageAllowed placeholders
maxSize (in bytes)1073741824maxSize{file}, {fileSize}, {fileSizeInKib}, {fileSizeInMiB}, {fileSizeInGiB}, {maxSize}, {maxSizeInKiB}, {maxSizeInMiB}, {maxSizeInGiB}
allowedMimeTypes['image/jpg', 'image/png']allowedMimeTypes{file}, {fileMimeType}, {allowedMimeTypes}
allowedExtensions['.jpg', '.png']allowedExtensions{file}, {allowedExtensions}
maxCount3maxCount{maxCount}

Example:

new Field('.js-file', '.js-container', [
    new FileValidator({ maxSize: 536870912, allowedMimeTypes: ['image/jpg'] }, { maxSize: 'The size of {file} must be less than {maxSizeInMiB} MiB.' });
])

Radio buttons

Use a single Field object for radio buttons with the same name:

HTML:

<form class="js-form">
    <input id="sex-man" type="radio" name="sex" class="js-sex" value="man" />
    <label for="sex-man">Man</label>
    <input id="sex-woman" type="radio" name="sex" class="js-sex" value="woman" />
    <label for="sex-woman">Woman</label>
    <button type="submit">Submit</button>
</form>

JS:

import { Form, Field, SubmissionResult } from "forms-and-fields";

const form = new Form(
    ".js-form",
    [
        new Field(".js-sex", ".js-form", ["required"]),
    ],
    (formData) => SubmissionResult.positive()},
);

form.listen();

Groups

Sometimes it's necessary to add fields with the same name, for example, a group of addresses. In this case, use the data-group-field attribute and a single Field object:

HTML:

<form class="js-form">
    <div>
        <label for="address-1">Address 1:</label>
        <br />
        <input id="address-1" type="text" name="address" class="js-address" data-group-field="" />
    </div>
    <div>
        <label for="address-2">Address 2:</label>
        <br />
        <input id="address-2" type="text" name="address" class="js-address" data-group-field="" />
    </div>
    <br />
    <button type="button">Add an address</button>
    <br />
    <button type="submit">Submit</button>
</form>

JS:

import { Form, Field, SubmissionResult } from "forms-and-fields"

const form = new Form(
    ".js-form",
    [
        new Field(".js-address", ".js-form", ["required"]),
    ],
    (formData) => SubmissionResult.positive()},
)

form.listen()

Important! Don't use the data-group-field attribute for radio buttons.

Statuses

The statuses of forms and fields change during validation and submission. You can subscribe to these events via onStatusChange.

List of statuses:

  • FieldStatus.Validating ("ff-field-validating")
  • FieldStatus.Valid ("ff-field-valid")
  • FieldStatus.Invalid ("ff-field-invalid")
  • FormStatus.Validating ("ff-form-validating")
  • FormStatus.Valid ("ff-form-valid")
  • FormStatus.Invalid ("ff-form-invalid")
  • FormStatus.Submitting ("ff-form-submitting")
  • FormStatus.PositiveSubmit ("ff-form-positive-submit")
  • FormStatus.NegativeSubmit ("ff-form-negative-submit")

Also, by default, statuses are dynamically added as CSS classes to form and field elements.

You can wrap any field element in a container with the data-field-wrapper attribute then the classes will then be applied to this wrapper instead of the field element itself. This is particularly useful for checkboxes and radio buttons. To disable adding CSS classes, set cssStatusEnabled to false.

The onResetStatus callback can be useful for clearing form or field errors that were set using the onAfterValidate or onAfterSubmit callbacks, for example.

License

This library is released under the MIT license.

3.0.0

4 months ago

3.0.0-rc.2

5 months ago

3.0.0-rc.1

5 months ago

3.0.0-rc.0

5 months ago

3.0.0-rc.6

5 months ago

3.0.0-rc.5

5 months ago

3.0.0-rc.4

5 months ago

3.0.0-rc.3

5 months ago

3.0.0-rc.9

5 months ago

3.0.0-rc.8

5 months ago

3.0.0-rc.7

5 months ago

2.1.0-rc.0

5 months ago

2.1.1

5 months ago

2.1.0

5 months ago

2.0.0-rc.3

6 months ago

2.0.0-rc.4

6 months ago

2.0.0-rc.5

6 months ago

2.0.0-rc.6

6 months ago

2.0.1

6 months ago

2.0.0

6 months ago

2.0.0-rc.2

6 months ago

2.0.0-rc.0

6 months ago

2.0.0-rc.1

6 months ago

1.1.1

4 years ago

1.1.2

4 years ago

1.0.0

5 years ago

0.8.0

5 years ago

0.7.0

5 years ago

0.6.0

5 years ago

0.5.0

5 years ago

0.4.0

5 years ago

0.3.0

5 years ago

0.2.0

5 years ago

0.1.0

5 years ago