170 lines
4.7 KiB
TypeScript
170 lines
4.7 KiB
TypeScript
import React, { ReactNode } from "react";
|
|
|
|
import BaseField, { IProps as IBaseFieldProps } from "./BaseField";
|
|
|
|
export type IBaseField = BaseField<IBaseFieldProps>;
|
|
|
|
export type IFormContext = {
|
|
setField: (name: string, field: IBaseField) => void;
|
|
unSetField: (name: string) => void;
|
|
onFieldChange: (name: string, field: IBaseField) => void;
|
|
onFieldFocusChange: (name: string, field: IBaseField, focused: boolean) => void;
|
|
isInputFocused: (name: string) => boolean;
|
|
hasOneFocusedInput: () => boolean;
|
|
};
|
|
|
|
type IFields = {
|
|
[key: string]: IBaseField;
|
|
};
|
|
|
|
type IState = {
|
|
inputFocused: {
|
|
name: string;
|
|
field: IBaseField;
|
|
focused: boolean;
|
|
};
|
|
};
|
|
|
|
export type IProps = {
|
|
onFieldFocusChange?: (name: string, field: IBaseField, focused: boolean) => void;
|
|
onFieldChange?: (name: string, field: IBaseField) => void;
|
|
onSubmit?: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
|
|
className?: string;
|
|
children?: ReactNode;
|
|
};
|
|
|
|
export const FormContext = React.createContext<IFormContext>({
|
|
setField: () => {},
|
|
unSetField: () => {},
|
|
onFieldChange: () => {},
|
|
onFieldFocusChange: () => {},
|
|
isInputFocused: () => false,
|
|
hasOneFocusedInput: () => false,
|
|
});
|
|
|
|
export default class Form extends React.Component<IProps, IState> {
|
|
protected fields: IFields = {};
|
|
private formRef: React.RefObject<HTMLFormElement>;
|
|
|
|
constructor(props: IProps) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
inputFocused: {
|
|
name: "",
|
|
field: {} as IBaseField,
|
|
focused: false,
|
|
},
|
|
};
|
|
|
|
this.setField = this.setField.bind(this);
|
|
this.unSetField = this.unSetField.bind(this);
|
|
this.onFieldChange = this.onFieldChange.bind(this);
|
|
this.onSubmit = this.onSubmit.bind(this);
|
|
this.formRef = React.createRef();
|
|
this.onFieldFocusChange = this.onFieldFocusChange.bind(this);
|
|
this.isInputFocused = this.isInputFocused.bind(this);
|
|
this.hasOneFocusedInput = this.hasOneFocusedInput.bind(this);
|
|
}
|
|
|
|
public override render() {
|
|
return (
|
|
<FormContext.Provider
|
|
value={{
|
|
setField: this.setField,
|
|
unSetField: this.unSetField,
|
|
onFieldChange: this.onFieldChange,
|
|
onFieldFocusChange: this.onFieldFocusChange,
|
|
isInputFocused: this.isInputFocused,
|
|
hasOneFocusedInput: this.hasOneFocusedInput,
|
|
}}>
|
|
<form className={this.props.className} ref={this.formRef} onSubmit={this.onSubmit}>
|
|
{this.props.children}
|
|
</form>
|
|
</FormContext.Provider>
|
|
);
|
|
}
|
|
|
|
public async onSubmit(e: React.FormEvent<HTMLFormElement> | null) {
|
|
e?.preventDefault();
|
|
const allChildren = this.getAllChildrenFields(e);
|
|
const elementsValues = allChildren
|
|
.filter((e) => {
|
|
return e.getAttribute("type") !== "radio" && e.getAttribute("type") !== "checkbox";
|
|
})
|
|
.reduce((obj, element) => ({ ...obj, [element.getAttribute("name") ?? ""]: (element as any).value }), {});
|
|
|
|
const radioInputs = allChildren.filter((e) => e.getAttribute("type") === "radio").filter((e) => (e as any).checked);
|
|
const radioInputsValues = radioInputs.reduce(
|
|
(obj, element) => ({ ...obj, [element.getAttribute("name") ?? ""]: (element as any).value }),
|
|
{},
|
|
);
|
|
|
|
const checkBoxesInput = allChildren.filter((e) => e.getAttribute("type") === "checkbox").filter((e) => (e as any).checked);
|
|
const checkBoxesValues = checkBoxesInput.reduce((obj, element) => {
|
|
const inputName = element.getAttribute("name") ?? "";
|
|
const inputValue = (element as any).value as string;
|
|
const newValue = ((obj as any)[inputName] as string[]) ?? [];
|
|
newValue.push(inputValue);
|
|
return {
|
|
...obj,
|
|
[inputName]: newValue,
|
|
};
|
|
}, {});
|
|
|
|
const allInputs = {
|
|
...elementsValues,
|
|
...radioInputsValues,
|
|
...checkBoxesValues,
|
|
};
|
|
|
|
// Deleting empty input
|
|
delete (allInputs as any)[""];
|
|
if (this.props.onSubmit) {
|
|
this.props.onSubmit(e, allInputs);
|
|
}
|
|
|
|
return { values: elementsValues };
|
|
}
|
|
|
|
protected setField(name: string, field: IBaseField) {
|
|
this.fields[name] = field;
|
|
}
|
|
|
|
protected unSetField(name: string) {
|
|
delete this.fields[name];
|
|
}
|
|
|
|
protected hasOneFocusedInput() {
|
|
return this.state.inputFocused.focused;
|
|
}
|
|
|
|
protected isInputFocused(name: string) {
|
|
return this.state.inputFocused.name === name && this.state.inputFocused.focused;
|
|
}
|
|
|
|
protected onFieldFocusChange(name: string, field: IBaseField, focused: boolean) {
|
|
this.setState({
|
|
inputFocused: {
|
|
name,
|
|
field,
|
|
focused,
|
|
},
|
|
});
|
|
|
|
if (this.props.onFieldFocusChange) {
|
|
this.props.onFieldFocusChange(name, field, focused);
|
|
}
|
|
}
|
|
|
|
protected onFieldChange(name: string, field: IBaseField) {
|
|
if (this.props.onFieldChange) {
|
|
this.props.onFieldChange(name, field);
|
|
}
|
|
}
|
|
|
|
private getAllChildrenFields(e: React.FormEvent<HTMLFormElement> | null): Element[] {
|
|
return Array.from(((e?.target as HTMLFormElement) ?? this.formRef.current).elements);
|
|
}
|
|
}
|