155 lines
4.4 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|