123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713 |
- /* eslint-disable prefer-const, max-len */
- import BaseFoundation from '../base/foundation';
- import * as ObjectUtil from '../utils/object';
- import isPromise from '../utils/isPromise';
- import { isValid } from './utils';
- import { isUndefined, isFunction, toPath } from 'lodash';
- import scrollIntoView, { Options as scrollIntoViewOptions } from 'scroll-into-view-if-needed';
- import { BaseFormAdapter, FormState, CallOpts, FieldState, FieldStaff, ComponentProps, setValuesConfig, ArrayFieldStaff } from './interface';
- export { BaseFormAdapter };
- export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
- data: FormState;
- fields: Map<string, FieldStaff>;
- registered: Record<string, boolean>;
- registeredArrayField: Map<string, ArrayFieldStaff>;
- constructor(adapter: BaseFormAdapter) {
- super({ ...adapter });
- /*
- Also need to read initValue here, because the Form level can set the initial value,
- and the Field level can also set the initial value.
- The field set in the Form does not necessarily have a Field entity,
- so you cannot completely rely on the register moment to set the initial value
- 这里也需要读一次initValue,因为Form级别可设置初始值,Field级别也可设置初始值.
- Form中设置的字段,不一定会存在Field实体,所以不能完全依赖register时刻来设置初始值
- */
- let { initValues } = this._adapter.getProps();
- initValues = this._adapter.cloneDeep(initValues);
- this.data = {
- values: initValues ? initValues : {},
- errors: {},
- touched: {},
- // invalid: false,
- // dirty: false,
- };
- // Map store all fields
- // key: fieldName
- // value: { field, fieldApi, keepState, initValue}
- this.fields = new Map();
- // Record all registered fields
- this.registered = {};
- // Record all registered ArrayField
- this.registeredArrayField = new Map();
- this.register = this.register.bind(this);
- this.unRegister = this.unRegister.bind(this);
- this.registerArrayField = this.registerArrayField.bind(this);
- this.unRegisterArrayField = this.unRegisterArrayField.bind(this);
- this.getArrayField = this.getArrayField.bind(this);
- this.updateArrayField = this.updateArrayField.bind(this);
- this.getField = this.getField.bind(this);
- this.setValues = this.setValues.bind(this);
- this.updateStateValue = this.updateStateValue.bind(this);
- this.updateStateError = this.updateStateError.bind(this);
- this.updateStateTouched = this.updateStateTouched.bind(this);
- this.getFormState = this.getFormState.bind(this);
- this.getValue = this.getValue.bind(this);
- this.getError = this.getError.bind(this);
- this.getTouched = this.getTouched.bind(this);
- this.getInitValues = this.getInitValues.bind(this);
- this.getInitValue = this.getInitValue.bind(this);
- this.getFormProps = this.getFormProps.bind(this);
- this.getFieldExist = this.getFieldExist.bind(this);
- this.scrollToField = this.scrollToField.bind(this);
- }
- init() {
- this._adapter.initFormId();
- }
- getField(field: string): FieldStaff | undefined {
- const targetField = this.fields.get(field);
- return targetField;
- }
- register(field: string, fieldState: FieldState, fieldStuff: FieldStaff): void {
- // determine if this field has been register before
- const registered = this.registered[field];
- this.registered[field] = true;
- this.fields.set(field, fieldStuff);
- if (fieldStuff.keepState) {
- // TODO support keepState
- } else {
- const allowEmpty = fieldStuff.allowEmpty || false;
- const opts = {
- notNotify: true,
- notUpdate: false,
- allowEmpty,
- };
- let fieldValue = fieldState.value;
- // When allowEmpty is false, 'is equivalent to undefined, and the key of the field does not need to be reflected on values
- if (!allowEmpty && fieldValue === '') {
- fieldValue = undefined;
- }
- this.updateStateValue(field, fieldValue, opts);
- if (fieldState.error) {
- this.updateStateError(field, fieldState.error, opts);
- }
- }
- // this.log(this.fields);
- }
- unRegister(field: string): void {
- const targetField = this.fields.get(field);
- // delete data
- try {
- if (!targetField.keepState) {
- ObjectUtil.remove(this.data.values, field);
- ObjectUtil.remove(this.data.errors, field);
- ObjectUtil.remove(this.data.touched, field);
- }
- } catch (error) {
- console.error(`some thing wrong when unregister field:${field}`);
- }
- // delete field
- this.fields.delete(field);
- this._adapter.notifyChange(this.data);
- this._adapter.forceUpdate();
- }
- // in order to slove byted-issue-289
- registerArrayField(arrayFieldPath: string, val: any): void {
- this.updateArrayField(arrayFieldPath, {
- updateKey: new Date().valueOf(),
- initValue: val
- });
- }
- unRegisterArrayField(arrayField: string): void {
- this.registeredArrayField.delete(arrayField);
- }
- getArrayField(arrayField: string): ArrayFieldStaff {
- return this.registeredArrayField.get(arrayField);
- }
- updateArrayField(arrayField: string, updateValue: any): void {
- const mergeVal = { ...this.registeredArrayField.get(arrayField), ...updateValue };
- this.registeredArrayField.set(arrayField, mergeVal);
- }
- validate(fieldPaths?: Array<string>): Promise<unknown> {
- const { validateFields } = this.getProps();
- if (validateFields && isFunction(validateFields)) {
- return this._formValidate();
- } else {
- return this._fieldsValidate(fieldPaths);
- }
- }
- // form level validate
- _formValidate(): Promise<unknown> {
- const { values } = this.data;
- const { validateFields } = this.getProps();
- return new Promise((resolve, reject) => {
- let maybePromisedErrors;
- try {
- maybePromisedErrors = validateFields(values);
- } catch (errors) {
- // error throw by sync validate directly
- maybePromisedErrors = errors;
- }
- if (!maybePromisedErrors) {
- resolve(values);
- this.injectErrorToField({});
- } else if (isPromise(maybePromisedErrors)) {
- maybePromisedErrors.then(
- (result: any) => {
- // validate success,clear error
- if (!result) {
- resolve(values);
- this.injectErrorToField({});
- } else {
- this.data.errors = result;
- this._adapter.notifyChange(this.data);
- this.injectErrorToField(result);
- this._adapter.forceUpdate();
- this._autoScroll(100);
- reject(result);
- }
- },
- (errors: any) => {
- // validate failed
- // this._adapter.notifyChange(this.data);
- this._autoScroll(100);
- reject(errors);
- }
- );
- } else {
- // TODO: current design, returning an empty object will be considered a checksum failure and will be rejected. Only returning an empty string will be considered a success, consider resetting it in 1.0?
- this.data.errors = maybePromisedErrors;
- this.injectErrorToField(maybePromisedErrors);
- this._adapter.notifyChange(this.data);
- this._adapter.forceUpdate();
- this._autoScroll(100);
- reject(maybePromisedErrors);
- }
- });
- }
- // field level validate
- _fieldsValidate(fieldPaths: Array<string>): Promise<unknown> {
- const { values } = this.data;
- // When there is no custom validation function at Form level, perform validation of each Field
- return new Promise((resolve, reject) => {
- let promiseSet: Promise<any>[] = [];
- const targetFields = this._getOperateFieldMap(fieldPaths);
- targetFields.forEach((field, fieldPath) => {
- // Call each fieldApi for verification
- const fieldValue = this.getValue(fieldPath);
- // When centralized verification, no need to trigger forceUpdate and notify
- const opts = {
- notNotify: true,
- notUpdate: true,
- };
- const validateResult = field.fieldApi.validate(fieldValue, opts);
- promiseSet.push(validateResult);
- field.fieldApi.setTouched(true, opts);
- });
- Promise.all(promiseSet).then(() => {
- // After the centralized verification is completed, trigger notify and forceUpdate once.
- this._adapter.notifyChange(this.data);
- this._adapter.forceUpdate();
- const errors = this.getError();
- if (this._isValid(targetFields)) {
- resolve(values);
- } else {
- this._autoScroll();
- reject(errors);
- }
- });
- });
- }
- submit(): void {
- const { values } = this.data;
- // validate form
- this.validate()
- .then((resolveValues: any) => {
- // if valid do submit
- const _values = this._adapter.cloneDeep(resolveValues);
- this._adapter.notifySubmit(_values);
- })
- .catch(errors => {
- const _errors = this._adapter.cloneDeep(errors);
- const _values = this._adapter.cloneDeep(values);
- this._adapter.notifySubmitFail(_errors, _values);
- });
- }
- /**
- * Case A:
- * All fields: a[0]、a[1]、b.type、b.name[2]、b.name[0]
- * input => output:
- * a => a[0]、a[1]
- * b => b.type、b.name[0]、b.name[2]
- *
- * Case B:
- * All fields: activity.a[0]、activity.a[1]、activity.c、activity.d、other
- * input => output:
- * activity.a => activity.a[0]、activity.a[1]
- *
- */
- _getNestedField(path: string): Map<string, FieldStaff> {
- const allRegisterField = this.fields;
- const allFieldPath = [...allRegisterField].map(item => item[0]);
- let nestedFieldPath = new Map();
- allFieldPath.forEach(item => {
- let itemPath = toPath(item);
- let targetPath = toPath(path);
- if (targetPath.every((path, i) => (targetPath[i] === itemPath[i]))) {
- const realField = allRegisterField.get(item);
- nestedFieldPath.set(item, realField);
- }
- });
- return nestedFieldPath;
- }
- // get all operate fields, called by validate() / reset()
- _getOperateFieldMap(fieldPaths?: Array<string>): Map<string, FieldStaff> {
- let targetFields = new Map();
- if (!isUndefined(fieldPaths)) {
- // reset or validate specific fields
- fieldPaths.forEach(path => {
- const field = this.fields.get(path);
- // may be undefined, if exists two fields like 'a[0]'、'a[1]', but user directly call reset(['a']) / validate(['a'])
- if (isUndefined(field)) {
- const nestedFields = this._getNestedField(path);
- targetFields = new Map([...targetFields, ...nestedFields]);
- } else {
- targetFields.set(path, field);
- }
- });
- } else {
- // reset or validate all fields
- targetFields = this.fields;
- }
- return targetFields;
- }
- // Reset the entire form, reset all fields and remove validation results
- reset(fieldPaths?: Array<string>): void {
- const targetFields = this._getOperateFieldMap(fieldPaths);
- targetFields.forEach(field => {
- field.fieldApi.reset();
- });
- if (this.registeredArrayField.size) {
- this._resetArrayField();
- }
- this._adapter.notifyChange(this.data);
- this._adapter.forceUpdate();
- this._adapter.notifyReset();
- }
- _resetArrayField(): void {
- /*
- When Reset, arrayField needs to be processed separately. Restore the key/value of arrayField in formState according to the initial value
- Update the key inside the arrayField to make it actively renderer
- Reset时,arrayField需要单独处理, 根据初始值还原 arrayField在formState中的key/value, 更新 arrayField内部的key,使其主动rerender
- */
- const arrayFieldPaths = [...this.registeredArrayField.keys()];
- arrayFieldPaths.forEach(path => {
- const arrayFieldState = this.registeredArrayField.get(path);
- const arrayFieldInitValue = arrayFieldState.initValue;
- this.updateStateValue(path, arrayFieldInitValue, { notNotify: true, notUpdate: true });
- this.updateArrayField(path, { updateKey: new Date().valueOf() });
- });
- }
- // After calling the form's custom validateFields function, reject the returned error to the corresponding field
- // 调用了Form的自定义validateFields函数后,将返回的错误展示到对应的field中
- injectErrorToField(errors: any): void {
- this.fields.forEach(field => {
- const fieldError = ObjectUtil.get(errors, field.field);
- const opts = {
- notNotify: true,
- notUpdate: true,
- };
- field.fieldApi.setError(fieldError, opts);
- });
- }
- getValue(field: string | undefined, opts?: CallOpts): any {
- const isAllField = typeof field === 'undefined';
- const needClone = opts && opts.needClone;
- let result, fieldValue;
- switch (true) {
- case !isAllField && !needClone:
- result = ObjectUtil.get(this.data.values, field);
- break;
- case !isAllField && needClone:
- fieldValue = ObjectUtil.get(this.data.values, field);
- result = this._adapter.cloneDeep(fieldValue);
- break;
- case isAllField && !needClone:
- result = { ...this.data.values };
- break;
- case isAllField && needClone:
- result = this._adapter.cloneDeep(this.data.values);
- break;
- default:
- break;
- }
- return result;
- }
- setValues(values: any, { isOverride = false }): void {
- const _values = this._adapter.cloneDeep(values);
- this.fields.forEach(field => {
- const value = ObjectUtil.get(_values, field.field);
- // When calling setValues to override the values, only need to trigger onValueChange and onChange once, so setNotNotify of setValue to true
- // 调用setValues进行值的覆盖时,只需要回调一次onValueChange、onChange即可,所以此处将setValue的notNotify置为true
- const opts = {
- notNotify: true,
- notUpdate: true,
- };
- field.fieldApi.setValue(value, opts);
- });
- // if there exists any arrayField component in this form
- if (this.registeredArrayField.size) {
- const arrayFieldPaths = [...this.registeredArrayField.keys()];
- arrayFieldPaths.forEach(path => {
- this.updateArrayField(path, { updateKey: new Date().valueOf() });
- });
- }
- // When isOverride is true, there may be a non-existent field in the values passed in, directly synchronized to formState.values
- // 当isOverride为true,传入的values中可能存在不存在的field时,直接将其同步到formState.values中
- if (isOverride) {
- this.data.values = _values;
- }
- // After completing the assignment, the unified callback can be done once.
- // 在完成赋值后,统一回调一次即可
- this._adapter.notifyChange(this.data);
- this._adapter.notifyValueChange(this.data.values, { ...values });
- this._adapter.forceUpdate();
- }
- // update formState value
- updateStateValue(field: string, value: any, opts: CallOpts, callback?: () => void): void {
- const notNotify = opts && opts.notNotify;
- const notUpdate = opts && opts.notUpdate;
- const fieldAllowEmpty = opts && opts.fieldAllowEmpty;
- /**
- * 当Form.allowEmpty为true时,所有的field,key都会在formState.values中出现,如果值为空那么就是undefined
- * 当Form.allowEmpty为false时,只有有值的field,key才会在formState.values中出现
- * When F orm.allow Empty is true, all fields and keys will appear in the formS tate.values. If the value is empty, it is undefined
- * When F orm.allow Empty is false, only fields with values will key appear in the formS tate.values
- */
- const formAllowEmpty = this.getProp('allowEmpty');
- // priority at Field level
- const allowEmpty = fieldAllowEmpty ? fieldAllowEmpty : formAllowEmpty;
- ObjectUtil.set(this.data.values, field, value, allowEmpty);
- /**
- * When registering, setValue called when Field initValue is synchronized to FormState should not trigger notify
- * but need to trigger forceUpdate, otherwise useFormState, useFieldState initial rendering will have problems
- *
- * register时,Field中同步initValue到FormState时调用的setValue不应该触发notify
- * 但需要触发forceUpdate,否则useFormState、useFieldState初始渲染会有问题
- */
- if (!notNotify) {
- this._adapter.notifyChange(this.data);
- this._adapter.notifyValueChange(this.data.values, { [field]: value });
- }
- if (!notUpdate) {
- this._adapter.forceUpdate(callback);
- }
- }
- // get touched from formState
- getTouched(field?: string): boolean | Record<string, any> | undefined {
- if (typeof field === 'undefined') {
- return this.data.touched;
- }
- return ObjectUtil.get(this.data.touched, field);
- }
- // update formState touched
- updateStateTouched(field: string, isTouched: boolean, opts?: CallOpts, callback?: () => void): void {
- const notNotify = opts && opts.notNotify;
- const notUpdate = opts && opts.notUpdate;
- ObjectUtil.set(this.data.touched, field, isTouched);
- if (!notNotify) {
- this._adapter.notifyChange(this.data);
- }
- if (!notUpdate) {
- this._adapter.forceUpdate(callback);
- }
- }
- // get error from formState
- getError(field?: string): any {
- if (typeof field === 'undefined') {
- return this.data.errors;
- }
- return ObjectUtil.get(this.data.errors, field);
- }
- // update formState error
- updateStateError(field: string, error: any, opts: CallOpts, callback?: () => void): void {
- const notNotify = opts && opts.notNotify;
- const notUpdate = opts && opts.notUpdate;
- ObjectUtil.set(this.data.errors, field, error);
- // The setError caused by centralized validation does not need to trigger notify, otherwise it will be called too frequently, as many times as there are fields
- // 集中validate时,引起的setError不需要触发notify,否则会过于频繁调用,有多少个field就调用了多少次
- if (!notNotify) {
- this._adapter.notifyChange(this.data);
- }
- if (!notUpdate) {
- this._adapter.forceUpdate(callback);
- }
- }
- // For internal use in the FormApi Operating Field
- getFieldSetterApi() {
- const setValue = (field: string, value: any, opts: CallOpts) => {
- const fieldApi = this.fields.get(field) ? this.fields.get(field).fieldApi : undefined;
- // DeepClone the value entered from the outside to avoid unexpected errors caused by not isolating the scope to the greatest extent. This setValue will be called in eg: ArrayField
- const newValue = this._adapter.cloneDeep(value);
- if (fieldApi) {
- // If there is a corresponding Field entity, call FieldApi to update the value
- fieldApi.setValue(newValue, opts);
- } else {
- // If you reset an entire array, such as Array Field, the array as a whole may actually have no Field entities (but each array element corresponds to a Field)
- // At this time, first modify formState directly, then find out the subordinate fields and drive them to update
- // Eg: peoples: [0, 2, 3]. Each value of the peoples array corresponds to an Input Field
- // When the user directly calls formA pi.set Value ('peoples', [2,3])
- this.updateStateValue(field, newValue, opts, () => {
- let nestedFields = this._getNestedField(field);
- if (nestedFields.size) {
- nestedFields.forEach(fieldStaff => {
- let fieldPath = fieldStaff.field;
- let newFieldVal = ObjectUtil.get(this.data.values, fieldPath);
- let nestedBatchUpdateOpts = { notNotify: true, notUpdate: true };
- fieldStaff.fieldApi.setValue(newFieldVal, nestedBatchUpdateOpts);
- });
- }
- });
- // If the reset happens to be, then update the updateKey corresponding to ArrayField to render it again
- if (this.getArrayField(field)) {
- this.updateArrayField(field, { updateKey: new Date().valueOf() });
- }
- }
- };
- const setError = (field: string, error: any, opts: CallOpts) => {
- const fieldApi = this.fields.get(field) ? this.fields.get(field).fieldApi : undefined;
- const newError = this._adapter.cloneDeep(error);
- if (fieldApi) {
- fieldApi.setError(newError, opts);
- } else {
- this.updateStateError(field, newError, opts, () => {
- let nestedFields = this._getNestedField(field);
- if (nestedFields.size) {
- nestedFields.forEach(fieldStaff => {
- let fieldPath = fieldStaff.field;
- let newFieldError = ObjectUtil.get(this.data.errors, fieldPath);
- let nestedBatchUpdateOpts = { notNotify: true, notUpdate: true };
- fieldStaff.fieldApi.setError(newFieldError, nestedBatchUpdateOpts);
- });
- }
- });
- if (this.getArrayField(field)) {
- this.updateArrayField(field, { updateKey: new Date().valueOf() });
- }
- }
- };
- const setTouched = (field: string, isTouched: boolean, opts: CallOpts) => {
- const fieldApi = this.fields.get(field) ? this.fields.get(field).fieldApi : undefined;
- // touched is boolean variable, no need to exec deepClone like setValue
- if (fieldApi) {
- fieldApi.setTouched(isTouched, opts);
- } else {
- this.updateStateTouched(field, isTouched, opts, () => {
- let nestedFields = this._getNestedField(field);
- if (nestedFields.size) {
- nestedFields.forEach(fieldStaff => {
- let fieldPath = fieldStaff.field;
- let newFieldTouch = ObjectUtil.get(this.data.touched, fieldPath);
- let nestedBatchUpdateOpts = { notNotify: true, notUpdate: true };
- fieldStaff.fieldApi.setTouched(newFieldTouch, nestedBatchUpdateOpts);
- });
- }
- });
- if (this.getArrayField(field)) {
- this.updateArrayField(field, { updateKey: new Date().valueOf() });
- }
- }
- };
- return {
- setValue,
- setError,
- setTouched,
- };
- }
- // For Field and ArrayField to read and modify FormState
- getModifyFormStateApi() {
- return {
- register: this.register,
- unRegister: this.unRegister,
- updateStateValue: this.updateStateValue,
- updateStateError: this.updateStateError,
- updateStateTouched: this.updateStateTouched,
- getValue: this.getValue,
- getError: this.getError,
- getTouched: this.getTouched,
- getInitValues: this.getInitValues,
- getInitValue: this.getInitValue,
- getFormProps: this.getFormProps,
- getField: this.getField,
- registerArrayField: this.registerArrayField,
- unRegisterArrayField: this.unRegisterArrayField,
- getArrayField: this.getArrayField,
- updateArrayField: this.updateArrayField,
- };
- }
- // Form APIs for external use, exposed to the user
- getFormApi() {
- const fieldSetterApi = this.getFieldSetterApi();
- return {
- ...fieldSetterApi,
- reset: (fields?: Array<string>) => this.reset(fields),
- validate: (fields?: Array<string>) => this.validate(fields),
- getValue: (field?: string) => this.getValue(field, { needClone: true }),
- getValues: () => this.getValue(undefined, { needClone: true }),
- getFormState: () => this.getFormState(true),
- getInitValue: (field: string) => this.getInitValue(field),
- getInitValues: () => this.getInitValues(),
- getTouched: (field?: string) => this.getTouched(field),
- getError: (field?: string) => this.getError(field),
- setValues: (values: any, opts?: setValuesConfig) => this.setValues(values, opts || { isOverride: false }),
- submitForm: () => this.submit(),
- getFieldExist: (field: string) => this.getFieldExist(field),
- scrollToField: (field: string, scrollOpts?: ScrollIntoViewOptions) => this.scrollToField(field, scrollOpts),
- };
- }
- getFormState(needClone = false): FormState {
- // NOTES:这里如果直接返回this.data,forceUpdate 触发 Form rerender 时,通过context传下去的formState会被认为是同一个对象【应该是浅对比的原因】
- // 使用了useFormState相关的component都不会触发重新渲染。所以使用...复制一次
- /*
- The reason for distinguishing deepClone: When semi is calling getFormState for internal consumption,
- the value of formState will not be modified, so deep cloning is not necessary, which can reduce performance loss
- But if the users use formApi.getFormState(), the behavior is unpredictable, and invasive modifications may be performed,
- so deep clones are used for isolation.
- 对deepClone进行区分的原因:semi调用getFormState内部消费时,不会对formState的值进行修改,所以无需深克隆,节约开销
- 但如果是业务方用formApi调用getFormState时,行为无法预料,有可能会进行侵入式修改,所以用深克隆进行隔离
- */
- if (!needClone) {
- return {
- ...this.data,
- };
- } else {
- return this._adapter.cloneDeep(this.data);
- }
- }
- _isValid(targetFields: Map<string, FieldStaff>): boolean {
- let valid = true;
- if (!targetFields) {
- valid = Boolean(ObjectUtil.empty(this.data.errors));
- } else {
- // when trigger partial validate
- const targetFieldStr = [...targetFields.keys()];
- targetFieldStr.forEach(fieldStr => {
- const fieldError = ObjectUtil.get(this.data.errors, fieldStr);
- if (!isValid(fieldError)) {
- valid = false;
- }
- });
- }
- return valid;
- }
- // get form.props.initValues
- getInitValues(): any {
- return this._adapter.getInitValues();
- }
- getInitValue(field?: string): any {
- if (typeof field === 'undefined') {
- return this._adapter.getInitValues();
- }
- return ObjectUtil.get(this._adapter.getInitValues(), field);
- }
- getFormProps(keys?: Array<string>): ComponentProps {
- return this._adapter.getFormProps(keys);
- }
- getFieldExist(field: string): boolean {
- return Boolean(this.fields.has(field));
- }
- _autoScroll(timeout?: boolean | number): void {
- const { autoScrollToError } = this.getFormProps();
- if (!autoScrollToError) {
- return;
- }
- let scrollOpts = { behavior: 'smooth' as const, block: 'start' as const };
- typeof autoScrollToError === 'object' ? (scrollOpts = autoScrollToError) : null;
- if (timeout) {
- setTimeout(() => this._getErrorFieldAndScroll(scrollOpts), 100);
- } else {
- this._getErrorFieldAndScroll(scrollOpts);
- }
- }
- _getErrorFieldAndScroll(scrollOpts?: scrollIntoViewOptions | boolean): void {
- const errorDOM = this._adapter.getAllErrorDOM();
- if (errorDOM && errorDOM.length) {
- try {
- const fieldDom = errorDOM[0].parentNode.parentNode;
- scrollIntoView(fieldDom as Element, scrollOpts);
- } catch (error) {}
- }
- }
- scrollToField(field: string, scrollOpts = { behavior: 'smooth', block: 'start' } as scrollIntoViewOptions): void {
- if (this.getFieldExist(field)) {
- const fieldDOM = this._adapter.getFieldDOM(field);
- scrollIntoView(fieldDOM as Element, scrollOpts);
- }
- }
- }
|