react-form-with-constraints
Version:
Simple form validation for React
137 lines (136 loc) • 5.58 kB
JavaScript
import * as React from 'react';
import { instanceOf } from 'prop-types';
import { assert } from './assert';
import { FieldsStore } from './FieldsStore';
import { InputElement } from './InputElement';
import { notUndefined } from './notUndefined';
import { withFieldDidResetEventEmitter } from './withFieldDidResetEventEmitter';
import { withFieldDidValidateEventEmitter } from './withFieldDidValidateEventEmitter';
import { withFieldWillValidateEventEmitter } from './withFieldWillValidateEventEmitter';
import { withValidateFieldEventEmitter } from './withValidateFieldEventEmitter';
class FormWithConstraintsComponent extends React.PureComponent {
}
export class FormWithConstraints extends withFieldDidResetEventEmitter(withFieldWillValidateEventEmitter(withFieldDidValidateEventEmitter(withValidateFieldEventEmitter(FormWithConstraintsComponent)))) {
constructor() {
super(...arguments);
this.form = null;
this.fieldsStore = new FieldsStore();
this.fieldFeedbacksKeyCounter = 0;
}
getChildContext() {
return {
form: this
};
}
computeFieldFeedbacksKey() {
return `${this.fieldFeedbacksKeyCounter++}`;
}
validateFields(...inputsOrNames) {
return this._validateFields(true, ...inputsOrNames);
}
validateForm() {
return this.validateFieldsWithoutFeedback();
}
validateFieldsWithoutFeedback(...inputsOrNames) {
return this._validateFields(false, ...inputsOrNames);
}
async _validateFields(forceValidateFields, ...inputsOrNames) {
const fields = new Array();
const inputs = this.normalizeInputs(...inputsOrNames);
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i];
const field = await this.validateField(forceValidateFields, new InputElement(input), input);
if (field !== undefined)
fields.push(field);
}
return fields;
}
async validateField(forceValidateFields, input, nativeInput) {
const fieldName = input.name;
const field = this.fieldsStore.getField(fieldName);
if (field === undefined) {
}
else if (forceValidateFields || !field.hasFeedbacks()) {
field.element = nativeInput;
field.clearValidations();
this.emitFieldWillValidateEvent(fieldName);
const arrayOfArrays = await this.emitValidateFieldEvent(input);
assert(JSON.stringify(arrayOfArrays.flat(Number.POSITIVE_INFINITY).filter(fieldFeedback => notUndefined(fieldFeedback))) ===
JSON.stringify(field.validations), `FieldsStore does not match emitValidateFieldEvent() result, did the user changed the input rapidly?`);
this.emitFieldDidValidateEvent(field);
}
return field;
}
normalizeInputs(...inputsOrNames) {
let inputs;
if (inputsOrNames.length === 0) {
inputs = Array.from(this.form.querySelectorAll('[name]'));
inputs = inputs.filter(input => input.validity !== undefined);
inputs
.filter(input => input.type !== 'checkbox' && input.type !== 'radio')
.map(input => input.name)
.forEach((name, index, self) => {
if (self.indexOf(name) !== index) {
throw new Error(`Multiple elements matching '[name="${name}"]' inside the form`);
}
});
}
else {
inputs = inputsOrNames.map(input => {
if (typeof input === 'string') {
const query = `[name="${input}"]`;
const elements = Array.from(this.form.querySelectorAll(query));
if (elements.some(el => el.validity === undefined)) {
throw new Error(`'${query}' should match an <input>, <select> or <textarea>`);
}
if (elements.filter(el => el.type !== 'checkbox' && el.type !== 'radio').length > 1) {
throw new Error(`Multiple elements matching '${query}' inside the form`);
}
const element = elements[0];
if (element === undefined) {
throw new Error(`Could not find field '${query}' inside the form`);
}
return element;
}
return input;
});
}
return inputs;
}
isValid() {
return this.fieldsStore.isValid();
}
hasFeedbacks() {
return this.fieldsStore.hasFeedbacks();
}
reset() {
return this.resetFields();
}
resetFields(...inputsOrNames) {
const fields = new Array();
const inputs = this.normalizeInputs(...inputsOrNames);
inputs.forEach(input => {
const field = this.resetField(new InputElement(input));
if (field !== undefined)
fields.push(field);
});
return fields;
}
resetField(input) {
const fieldName = input.name;
const field = this.fieldsStore.getField(fieldName);
if (field === undefined) {
}
else {
field.clearValidations();
this.emitFieldDidResetEvent(field);
}
return field;
}
render() {
return React.createElement("form", { ref: form => (this.form = form), ...this.props });
}
}
FormWithConstraints.childContextTypes = {
form: instanceOf(FormWithConstraints).isRequired
};