2023-02-27 15:04:51 +01:00

155 lines
4.4 KiB
TypeScript

import { ChangeEvent, Component, createRef } from "react";
import { FormContext, IFormContext } from "..";
// elements
import Validators, { IValidationTypes } from "../Validators/Validators";
export type IError = {
message: string;
validator: string;
value: string | number | readonly string[];
args: any[];
isErrored?: (hasError: boolean) => void;
};
export type INewBasefieldProps = {
onChange?: (event: ChangeEvent<HTMLInputElement & HTMLSelectElement & HTMLTextAreaElement>) => void;
name: string;
regex?: RegExp;
onCancel?: () => void;
disableValidation?: boolean;
onErrors?: (errors: IError[]) => void;
fieldRef?: React.RefObject<any>;
};
export type IProps = IValidationTypes & React.InputHTMLAttributes<HTMLInputElement> & INewBasefieldProps;
type IState = {
value?: string | number | readonly string[];
errors: IError[];
};
export default abstract class BaseField<P extends IProps> extends Component<P, IState> {
public static override contextType = FormContext;
public override context: IFormContext | null = null;
public fieldRef: React.RefObject<any> = createRef();
static defaultProps: Partial<IProps> = {
disableValidation: false,
};
constructor(props: P) {
super(props);
this.onChange = this.onChange.bind(this);
this.validate = this.validate.bind(this);
this.state = {
value: this.props.value ?? this.props.defaultValue ?? "",
errors: [],
};
}
public override componentDidMount() {
this.context?.setField(this.props.name, this);
}
public override componentDidUpdate(prevProps: P) {
if (prevProps.value !== this.props.value || prevProps.defaultValue !== this.props.defaultValue) {
this.setState({ value: this.props.value ?? this.props.defaultValue ?? "" });
}
}
public override componentWillUnmount() {
this.context?.unSetField(this.props.name);
}
public async onBlur(event: React.FocusEvent<HTMLInputElement, Element>) {
// this.validate();
// if (this.props.onBlur) {
// this.props.onBlur(event);
// }
}
public async validate(isOnSubmit?: boolean) {
if (this.props.disableValidation) return;
if (this.props.readOnly) return;
const errorArray: IError[] = [];
const props: { [key: string]: any } = this.props;
const validators = Object.entries(Validators).filter(([key]) => props[key]);
const isValidable = isOnSubmit
? this.props.required || (this.state.value && this.state.value !== "")
: this.state.value && this.state.value !== "";
if (isValidable) {
const validations = await Promise.all(
validators.map(async ([key, validator]) => {
const validation = await (validator.validate as any)(this.state.value, ...(props[key].args ?? []));
if (props[key].isErrored) {
props[key].isErrored(!validation);
}
return [key, validator, validation];
}),
);
const unValidateds = validations.filter(([key, validator, validation]) => !validation);
const errors: IError[] = unValidateds.map(([key, unValidated]) => {
let message = unValidated.message;
if (typeof props[key] === "object" && props[key].message) message = props[key].message;
return { message, validator: key, value: this.state.value!, args: props[key].args ?? [] };
});
errorArray.push(...errors);
} else {
validators.forEach(async ([key]) => {
if (props[key].isErrored) {
props[key].isErrored(false);
}
});
}
this.setState({ errors: errorArray });
this.onErrors(errorArray);
return errorArray;
}
public setErrors(errors: IError[]) {
this.setState({ ...this.state, errors });
}
/**
* It is automatically called by the parent form when the user cancelled the
* form and all of its changes.
*
* Override the method for custom cancelling logic, or pass a custom onCancel
* callback.
*/
public cancel() {
if (this.props.onCancel) {
this.props.onCancel();
}
}
public onErrors(errors: IError[]) {
if (this.props.onErrors) {
this.props.onErrors(errors);
}
}
protected onChange(event: ChangeEvent<HTMLInputElement & HTMLSelectElement & HTMLTextAreaElement>) {
if (this.props.regex) {
if (!this.props.regex.test(event.currentTarget.value)) {
event.currentTarget.value = event.currentTarget.value.substring(0, event.currentTarget.value.length - 1);
}
}
this.setState({ value: event.currentTarget.value }, () => {
this.validate();
this.context?.onFieldChange(this.props.name, this);
});
if (this.props.onChange) {
this.props.onChange(event);
}
}
}