foundation.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. /* eslint-disable prefer-const, max-len */
  2. import BaseFoundation from '../base/foundation';
  3. import * as ObjectUtil from '../utils/object';
  4. import isPromise from '../utils/isPromise';
  5. import { isValid } from './utils';
  6. import { isUndefined, isFunction, toPath, merge } from 'lodash';
  7. import scrollIntoView, { Options as scrollIntoViewOptions } from 'scroll-into-view-if-needed';
  8. import { BaseFormAdapter, FormState, CallOpts, FieldState, FieldStaff, ComponentProps, setValuesConfig, ArrayFieldStaff } from './interface';
  9. export type { BaseFormAdapter };
  10. export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
  11. data: FormState;
  12. fields: Map<string, FieldStaff>;
  13. registered: Record<string, boolean>;
  14. registeredArrayField: Map<string, ArrayFieldStaff>;
  15. constructor(adapter: BaseFormAdapter) {
  16. super({ ...adapter });
  17. /*
  18. Also need to read initValue here, because the Form level can set the initial value,
  19. and the Field level can also set the initial value.
  20. The field set in the Form does not necessarily have a Field entity,
  21. so you cannot completely rely on the register moment to set the initial value
  22. 这里也需要读一次initValue,因为Form级别可设置初始值,Field级别也可设置初始值.
  23. Form中设置的字段,不一定会存在Field实体,所以不能完全依赖register时刻来设置初始值
  24. */
  25. let { initValues } = this._adapter.getProps();
  26. initValues = this._adapter.cloneDeep(initValues);
  27. this.data = {
  28. values: initValues ? initValues : {},
  29. errors: {},
  30. touched: {},
  31. // invalid: false,
  32. // dirty: false,
  33. };
  34. // Map store all fields
  35. // key: fieldName
  36. // value: { field, fieldApi, keepState, initValue}
  37. this.fields = new Map();
  38. // Record all registered fields
  39. this.registered = {};
  40. // Record all registered ArrayField
  41. this.registeredArrayField = new Map();
  42. this.register = this.register.bind(this);
  43. this.unRegister = this.unRegister.bind(this);
  44. this.registerArrayField = this.registerArrayField.bind(this);
  45. this.unRegisterArrayField = this.unRegisterArrayField.bind(this);
  46. this.getArrayField = this.getArrayField.bind(this);
  47. this.updateArrayField = this.updateArrayField.bind(this);
  48. this.getField = this.getField.bind(this);
  49. this.setValues = this.setValues.bind(this);
  50. this.updateStateValue = this.updateStateValue.bind(this);
  51. this.updateStateError = this.updateStateError.bind(this);
  52. this.updateStateTouched = this.updateStateTouched.bind(this);
  53. this.getFormState = this.getFormState.bind(this);
  54. this.getValue = this.getValue.bind(this);
  55. this.getError = this.getError.bind(this);
  56. this.getTouched = this.getTouched.bind(this);
  57. this.getInitValues = this.getInitValues.bind(this);
  58. this.getInitValue = this.getInitValue.bind(this);
  59. this.getFormProps = this.getFormProps.bind(this);
  60. this.getFieldExist = this.getFieldExist.bind(this);
  61. this.scrollToField = this.scrollToField.bind(this);
  62. }
  63. init() {
  64. this._adapter.initFormId();
  65. }
  66. getField(field: string): FieldStaff | undefined {
  67. const targetField = this.fields.get(field);
  68. return targetField;
  69. }
  70. register(field: string, fieldState: FieldState, fieldStuff: FieldStaff): void {
  71. // determine if this field has been register before
  72. const registered = this.registered[field];
  73. this.registered[field] = true;
  74. this.fields.set(field, fieldStuff);
  75. if (fieldStuff.keepState) {
  76. // TODO support keepState
  77. } else {
  78. const allowEmpty = fieldStuff.allowEmpty || false;
  79. const opts = {
  80. notNotify: true,
  81. notUpdate: false,
  82. allowEmpty,
  83. };
  84. let fieldValue = fieldState.value;
  85. // When allowEmpty is false, 'is equivalent to undefined, and the key of the field does not need to be reflected on values
  86. if (!allowEmpty && fieldValue === '') {
  87. fieldValue = undefined;
  88. }
  89. this.updateStateValue(field, fieldValue, opts);
  90. if (fieldState.error) {
  91. this.updateStateError(field, fieldState.error, opts);
  92. }
  93. }
  94. // this.log(this.fields);
  95. }
  96. unRegister(field: string): void {
  97. const targetField = this.fields.get(field);
  98. // delete data
  99. try {
  100. if (!targetField.keepState) {
  101. ObjectUtil.remove(this.data.values, field);
  102. ObjectUtil.remove(this.data.errors, field);
  103. ObjectUtil.remove(this.data.touched, field);
  104. }
  105. } catch (error) {
  106. console.error(`some thing wrong when unregister field:${field}`);
  107. }
  108. // delete field
  109. this.fields.delete(field);
  110. this._adapter.notifyChange(this.data);
  111. this._adapter.forceUpdate();
  112. }
  113. // in order to slove byted-issue-289
  114. registerArrayField(arrayFieldPath: string, initValue: any): void {
  115. // save initValue of arrayField, will be use when calling rest
  116. // this.updateArrayField(arrayFieldPath, {
  117. // updateKey: new Date().valueOf(),
  118. // initValue: initValue,
  119. // });
  120. this.registeredArrayField.set(arrayFieldPath, { field: arrayFieldPath, initValue: initValue });
  121. }
  122. unRegisterArrayField(arrayField: string): void {
  123. this.registeredArrayField.delete(arrayField);
  124. }
  125. getArrayField(arrayField: string): ArrayFieldStaff {
  126. return this.registeredArrayField.get(arrayField);
  127. }
  128. updateArrayField(arrayField: string, updateStaff?: Omit<ArrayFieldStaff, 'field'>): void {
  129. const arrayFieldStaff = this.getArrayField(arrayField);
  130. const mergeStaff = { ...arrayFieldStaff, ...updateStaff };
  131. this.registeredArrayField.set(arrayField, mergeStaff);
  132. mergeStaff.forceUpdate(mergeStaff?.updateValue);
  133. }
  134. validate(fieldPaths?: Array<string>): Promise<unknown> {
  135. const { validateFields } = this.getProps();
  136. if (validateFields && isFunction(validateFields)) {
  137. return this._formValidate();
  138. } else {
  139. return this._fieldsValidate(fieldPaths);
  140. }
  141. }
  142. // form level validate
  143. _formValidate(): Promise<unknown> {
  144. const { values } = this.data;
  145. const { validateFields } = this.getProps();
  146. return new Promise((resolve, reject) => {
  147. let maybePromisedErrors;
  148. try {
  149. maybePromisedErrors = validateFields(values);
  150. } catch (errors) {
  151. // error throw by sync validate directly
  152. maybePromisedErrors = errors;
  153. }
  154. if (!maybePromisedErrors) {
  155. resolve(values);
  156. this.injectErrorToField({});
  157. } else if (isPromise(maybePromisedErrors)) {
  158. maybePromisedErrors.then(
  159. (result: any) => {
  160. // validate success,clear error
  161. if (!result) {
  162. resolve(values);
  163. this.injectErrorToField({});
  164. } else {
  165. this.data.errors = result;
  166. this._adapter.notifyChange(this.data);
  167. this.injectErrorToField(result);
  168. this._adapter.forceUpdate();
  169. this._autoScroll(100);
  170. reject(result);
  171. }
  172. },
  173. (errors: any) => {
  174. // validate failed
  175. // this._adapter.notifyChange(this.data);
  176. this._autoScroll(100);
  177. reject(errors);
  178. }
  179. );
  180. } else {
  181. // 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?
  182. this.data.errors = maybePromisedErrors;
  183. this.injectErrorToField(maybePromisedErrors);
  184. this._adapter.notifyChange(this.data);
  185. this._adapter.forceUpdate();
  186. this._autoScroll(100);
  187. reject(maybePromisedErrors);
  188. }
  189. });
  190. }
  191. // field level validate
  192. _fieldsValidate(fieldPaths: Array<string>): Promise<unknown> {
  193. const { values } = this.data;
  194. // When there is no custom validation function at Form level, perform validation of each Field
  195. return new Promise((resolve, reject) => {
  196. let promiseSet: Promise<any>[] = [];
  197. const targetFields = this._getOperateFieldMap(fieldPaths);
  198. targetFields.forEach((field, fieldPath) => {
  199. // Call each fieldApi for verification
  200. const fieldValue = this.getValue(fieldPath);
  201. // When centralized verification, no need to trigger forceUpdate and notify
  202. const opts = {
  203. notNotify: true,
  204. notUpdate: true,
  205. };
  206. const validateResult = field.fieldApi.validate(fieldValue, opts);
  207. promiseSet.push(validateResult);
  208. field.fieldApi.setTouched(true, opts);
  209. });
  210. Promise.all(promiseSet).then(() => {
  211. // After the centralized verification is completed, trigger notify and forceUpdate once.
  212. this._adapter.notifyChange(this.data);
  213. this._adapter.forceUpdate();
  214. const errors = this.getError();
  215. if (this._isValid(targetFields)) {
  216. resolve(values);
  217. } else {
  218. this._autoScroll();
  219. reject(errors);
  220. }
  221. });
  222. });
  223. }
  224. submit(): void {
  225. const { values } = this.data;
  226. // validate form
  227. this.validate()
  228. .then((resolveValues: any) => {
  229. // if valid do submit
  230. const _values = this._adapter.cloneDeep(resolveValues);
  231. this._adapter.notifySubmit(_values);
  232. })
  233. .catch(errors => {
  234. const _errors = this._adapter.cloneDeep(errors);
  235. const _values = this._adapter.cloneDeep(values);
  236. this._adapter.notifySubmitFail(_errors, _values);
  237. });
  238. }
  239. /**
  240. * Case A:
  241. * All fields: a[0]、a[1]、b.type、b.name[2]、b.name[0]
  242. * input => output:
  243. * a => a[0]、a[1]
  244. * b => b.type、b.name[0]、b.name[2]
  245. *
  246. * Case B:
  247. * All fields: activity.a[0]、activity.a[1]、activity.c、activity.d、other
  248. * input => output:
  249. * activity.a => activity.a[0]、activity.a[1]
  250. *
  251. */
  252. _getNestedField(path: string): Map<string, FieldStaff> {
  253. const allRegisterField = this.fields;
  254. const allFieldPath = [...allRegisterField].map(item => item[0]);
  255. let nestedFieldPath = new Map();
  256. allFieldPath.forEach(item => {
  257. let itemPath = toPath(item);
  258. let targetPath = toPath(path);
  259. if (targetPath.every((path, i) => (targetPath[i] === itemPath[i]))) {
  260. const realField = allRegisterField.get(item);
  261. nestedFieldPath.set(item, realField);
  262. }
  263. });
  264. return nestedFieldPath;
  265. }
  266. // get all operate fields, called by validate() / reset()
  267. _getOperateFieldMap(fieldPaths?: Array<string>): Map<string, FieldStaff> {
  268. let targetFields = new Map();
  269. if (!isUndefined(fieldPaths)) {
  270. // reset or validate specific fields
  271. fieldPaths.forEach(path => {
  272. const field = this.fields.get(path);
  273. // may be undefined, if exists two fields like 'a[0]'、'a[1]', but user directly call reset(['a']) / validate(['a'])
  274. if (isUndefined(field)) {
  275. const nestedFields = this._getNestedField(path);
  276. targetFields = new Map([...targetFields, ...nestedFields]);
  277. } else {
  278. targetFields.set(path, field);
  279. }
  280. });
  281. } else {
  282. // reset or validate all fields
  283. targetFields = this.fields;
  284. }
  285. return targetFields;
  286. }
  287. // Reset the entire form, reset all fields and remove validation results
  288. reset(fieldPaths?: Array<string>): void {
  289. const targetFields = this._getOperateFieldMap(fieldPaths);
  290. targetFields.forEach(field => {
  291. field.fieldApi.reset();
  292. });
  293. if (this.registeredArrayField.size) {
  294. this._resetArrayField();
  295. }
  296. this._adapter.notifyChange(this.data);
  297. this._adapter.forceUpdate();
  298. this._adapter.notifyReset();
  299. }
  300. _resetArrayField(): void {
  301. /*
  302. When Reset, arrayField needs to be processed separately. Restore the key/value of arrayField in formState according to the initial value
  303. Update the key inside the arrayField to make it actively renderer
  304. Reset时,arrayField需要单独处理, 根据初始值还原 arrayField在formState中的key/value, 更新 arrayField内部的key,使其主动rerender
  305. */
  306. const arrayFieldPaths = [...this.registeredArrayField.keys()];
  307. arrayFieldPaths.forEach(path => {
  308. const arrayFieldState = this.registeredArrayField.get(path);
  309. // clone prevent dom unmounted cause initValue lost
  310. const arrayFieldInitValue = this._adapter.cloneDeep(arrayFieldState.initValue);
  311. this.updateStateValue(path, arrayFieldInitValue, { notNotify: true, notUpdate: true });
  312. this.updateArrayField(path, { updateValue: arrayFieldInitValue });
  313. });
  314. }
  315. // After calling the form's custom validateFields function, reject the returned error to the corresponding field
  316. // 调用了Form的自定义validateFields函数后,将返回的错误展示到对应的field中
  317. injectErrorToField(errors: any): void {
  318. this.fields.forEach(field => {
  319. const fieldError = ObjectUtil.get(errors, field.field);
  320. const opts = {
  321. notNotify: true,
  322. notUpdate: true,
  323. };
  324. field.fieldApi.setError(fieldError, opts);
  325. });
  326. }
  327. getValue(field: string | undefined, opts?: CallOpts): any {
  328. const isAllField = typeof field === 'undefined';
  329. const needClone = opts && opts.needClone;
  330. let result, fieldValue;
  331. switch (true) {
  332. case !isAllField && !needClone:
  333. result = ObjectUtil.get(this.data.values, field);
  334. break;
  335. case !isAllField && needClone:
  336. fieldValue = ObjectUtil.get(this.data.values, field);
  337. result = this._adapter.cloneDeep(fieldValue);
  338. break;
  339. case isAllField && !needClone:
  340. result = { ...this.data.values };
  341. break;
  342. case isAllField && needClone:
  343. result = this._adapter.cloneDeep(this.data.values);
  344. break;
  345. default:
  346. break;
  347. }
  348. return result;
  349. }
  350. setValues(values: any, { isOverride = false }): void {
  351. const _values = this._adapter.cloneDeep(values);
  352. this.fields.forEach(field => {
  353. const value = ObjectUtil.get(_values, field.field);
  354. // When calling setValues to override the values, only need to trigger onValueChange and onChange once, so setNotNotify of setValue to true
  355. // 调用setValues进行值的覆盖时,只需要回调一次onValueChange、onChange即可,所以此处将setValue的notNotify置为true
  356. const opts = {
  357. notNotify: true,
  358. notUpdate: true,
  359. };
  360. field.fieldApi.setValue(value, opts);
  361. });
  362. // if there exists any arrayField component in this form
  363. if (this.registeredArrayField.size) {
  364. const arrayFieldPaths = [...this.registeredArrayField.keys()];
  365. arrayFieldPaths.forEach(path => {
  366. this.updateArrayField(path, {
  367. // updateKey: new Date().valueOf(),
  368. updateValue: ObjectUtil.get(_values, path)
  369. });
  370. });
  371. }
  372. // When isOverride is true, there may be a non-existent field in the values passed in, directly synchronized to formState.values
  373. // 当isOverride为true,传入的values中可能存在不存在的field时,直接将其同步到formState.values中
  374. if (isOverride) {
  375. this.data.values = _values;
  376. }
  377. // After completing the assignment, the unified callback can be done once.
  378. // 在完成赋值后,统一回调一次即可
  379. this._adapter.notifyChange(this.data);
  380. this._adapter.notifyValueChange(this.data.values, { ...values });
  381. this._adapter.forceUpdate();
  382. }
  383. // update formState value
  384. updateStateValue(field: string, value: any, opts: CallOpts, callback?: () => void): void {
  385. const notNotify = opts && opts.notNotify;
  386. const notUpdate = opts && opts.notUpdate;
  387. const fieldAllowEmpty = opts && opts.fieldAllowEmpty;
  388. /**
  389. * 当Form.allowEmpty为true时,所有的field,key都会在formState.values中出现,如果值为空那么就是undefined
  390. * 当Form.allowEmpty为false时,只有有值的field,key才会在formState.values中出现
  391. * 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
  392. * When F orm.allow Empty is false, only fields with values will key appear in the formS tate.values
  393. */
  394. const formAllowEmpty = this.getProp('allowEmpty');
  395. // priority at Field level
  396. const allowEmpty = fieldAllowEmpty ? fieldAllowEmpty : formAllowEmpty;
  397. ObjectUtil.set(this.data.values, field, value, allowEmpty);
  398. /**
  399. * When registering, setValue called when Field initValue is synchronized to FormState should not trigger notify
  400. * but need to trigger forceUpdate, otherwise useFormState, useFieldState initial rendering will have problems
  401. *
  402. * register时,Field中同步initValue到FormState时调用的setValue不应该触发notify
  403. * 但需要触发forceUpdate,否则useFormState、useFieldState初始渲染会有问题
  404. */
  405. if (!notNotify) {
  406. this._adapter.notifyChange(this.data);
  407. this._adapter.notifyValueChange(this.data.values, { [field]: value });
  408. }
  409. if (!notUpdate) {
  410. this._adapter.forceUpdate(callback);
  411. }
  412. }
  413. // get touched from formState
  414. getTouched(field?: string): boolean | Record<string, any> | undefined {
  415. if (typeof field === 'undefined') {
  416. return this.data.touched;
  417. }
  418. return ObjectUtil.get(this.data.touched, field);
  419. }
  420. // update formState touched
  421. updateStateTouched(field: string, isTouched: boolean, opts?: CallOpts, callback?: () => void): void {
  422. const notNotify = opts && opts.notNotify;
  423. const notUpdate = opts && opts.notUpdate;
  424. ObjectUtil.set(this.data.touched, field, isTouched);
  425. if (!notNotify) {
  426. this._adapter.notifyChange(this.data);
  427. }
  428. if (!notUpdate) {
  429. this._adapter.forceUpdate(callback);
  430. }
  431. }
  432. // get error from formState
  433. getError(field?: string): any {
  434. if (typeof field === 'undefined') {
  435. return this.data.errors;
  436. }
  437. return ObjectUtil.get(this.data.errors, field);
  438. }
  439. // update formState error
  440. updateStateError(field: string, error: any, opts: CallOpts, callback?: () => void): void {
  441. const notNotify = opts && opts.notNotify;
  442. const notUpdate = opts && opts.notUpdate;
  443. ObjectUtil.set(this.data.errors, field, error);
  444. // 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
  445. // 集中validate时,引起的setError不需要触发notify,否则会过于频繁调用,有多少个field就调用了多少次
  446. if (!notNotify) {
  447. this._adapter.notifyChange(this.data);
  448. }
  449. if (!notUpdate) {
  450. this._adapter.forceUpdate(callback);
  451. }
  452. }
  453. // For internal use in the FormApi Operating Field
  454. getFieldSetterApi() {
  455. const setValue = (field: string, value: any, opts: CallOpts) => {
  456. const fieldApi = this.fields.get(field) ? this.fields.get(field).fieldApi : undefined;
  457. // 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
  458. const newValue = this._adapter.cloneDeep(value);
  459. if (fieldApi) {
  460. // If there is a corresponding Field entity, call FieldApi to update the value
  461. fieldApi.setValue(newValue, opts);
  462. } else {
  463. // 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)
  464. // At this time, first modify formState directly, then find out the subordinate fields and drive them to update
  465. // Eg: peoples: [0, 2, 3]. Each value of the peoples array corresponds to an Input Field
  466. // When the user directly calls formA pi.set Value ('peoples', [2,3])
  467. this.updateStateValue(field, newValue, opts, () => {
  468. let nestedFields = this._getNestedField(field);
  469. if (nestedFields.size) {
  470. nestedFields.forEach(fieldStaff => {
  471. let fieldPath = fieldStaff.field;
  472. let newFieldVal = ObjectUtil.get(this.data.values, fieldPath);
  473. let nestedBatchUpdateOpts = { notNotify: true, notUpdate: true };
  474. fieldStaff.fieldApi.setValue(newFieldVal, nestedBatchUpdateOpts);
  475. });
  476. }
  477. });
  478. // If the reset happens to be, then update the updateKey corresponding to ArrayField to render it again
  479. if (this.getArrayField(field)) {
  480. this.updateArrayField(field, { updateValue: newValue });
  481. }
  482. }
  483. };
  484. const setError = (field: string, error: any, opts: CallOpts) => {
  485. const fieldApi = this.fields.get(field) ? this.fields.get(field).fieldApi : undefined;
  486. const newError = this._adapter.cloneDeep(error);
  487. if (fieldApi) {
  488. fieldApi.setError(newError, opts);
  489. } else {
  490. this.updateStateError(field, newError, opts, () => {
  491. let nestedFields = this._getNestedField(field);
  492. if (nestedFields.size) {
  493. nestedFields.forEach(fieldStaff => {
  494. let fieldPath = fieldStaff.field;
  495. let newFieldError = ObjectUtil.get(this.data.errors, fieldPath);
  496. let nestedBatchUpdateOpts = { notNotify: true, notUpdate: true };
  497. fieldStaff.fieldApi.setError(newFieldError, nestedBatchUpdateOpts);
  498. });
  499. }
  500. });
  501. if (this.getArrayField(field)) {
  502. // todo check一下
  503. this.updateArrayField(field);
  504. }
  505. }
  506. };
  507. const setTouched = (field: string, isTouched: boolean, opts: CallOpts) => {
  508. const fieldApi = this.fields.get(field) ? this.fields.get(field).fieldApi : undefined;
  509. // touched is boolean variable, no need to exec deepClone like setValue
  510. if (fieldApi) {
  511. fieldApi.setTouched(isTouched, opts);
  512. } else {
  513. this.updateStateTouched(field, isTouched, opts, () => {
  514. let nestedFields = this._getNestedField(field);
  515. if (nestedFields.size) {
  516. nestedFields.forEach(fieldStaff => {
  517. let fieldPath = fieldStaff.field;
  518. let newFieldTouch = ObjectUtil.get(this.data.touched, fieldPath);
  519. let nestedBatchUpdateOpts = { notNotify: true, notUpdate: true };
  520. fieldStaff.fieldApi.setTouched(newFieldTouch, nestedBatchUpdateOpts);
  521. });
  522. }
  523. });
  524. if (this.getArrayField(field)) {
  525. // todo check 一下
  526. this.updateArrayField(field);
  527. }
  528. }
  529. };
  530. return {
  531. setValue,
  532. setError,
  533. setTouched,
  534. };
  535. }
  536. // For Field and ArrayField to read and modify FormState
  537. getModifyFormStateApi() {
  538. return {
  539. register: this.register,
  540. unRegister: this.unRegister,
  541. updateStateValue: this.updateStateValue,
  542. updateStateError: this.updateStateError,
  543. updateStateTouched: this.updateStateTouched,
  544. getValue: this.getValue,
  545. getError: this.getError,
  546. getTouched: this.getTouched,
  547. getInitValues: this.getInitValues,
  548. getInitValue: this.getInitValue,
  549. getFormProps: this.getFormProps,
  550. getField: this.getField,
  551. registerArrayField: this.registerArrayField,
  552. unRegisterArrayField: this.unRegisterArrayField,
  553. getArrayField: this.getArrayField,
  554. updateArrayField: this.updateArrayField,
  555. };
  556. }
  557. // Form APIs for external use, exposed to the user
  558. getFormApi() {
  559. const fieldSetterApi = this.getFieldSetterApi();
  560. return {
  561. ...fieldSetterApi,
  562. reset: (fields?: Array<string>) => this.reset(fields),
  563. validate: (fields?: Array<string>) => this.validate(fields),
  564. getValue: (field?: string) => this.getValue(field, { needClone: true }),
  565. getValues: () => this.getValue(undefined, { needClone: true }),
  566. getFormState: () => this.getFormState(true),
  567. getInitValue: (field: string) => this.getInitValue(field),
  568. getInitValues: () => this.getInitValues(),
  569. getTouched: (field?: string) => this.getTouched(field),
  570. getError: (field?: string) => this.getError(field),
  571. setValues: (values: any, opts?: setValuesConfig) => this.setValues(values, opts || { isOverride: false }),
  572. submitForm: () => this.submit(),
  573. getFieldExist: (field: string) => this.getFieldExist(field),
  574. scrollToField: (field: string, scrollOpts?: ScrollIntoViewOptions) => this.scrollToField(field, scrollOpts),
  575. };
  576. }
  577. getFormState(needClone = false): FormState {
  578. // NOTES:这里如果直接返回this.data,forceUpdate 触发 Form rerender 时,通过context传下去的formState会被认为是同一个对象【应该是浅对比的原因】
  579. // 使用了useFormState相关的component都不会触发重新渲染。所以使用...复制一次
  580. /*
  581. The reason for distinguishing deepClone: When semi is calling getFormState for internal consumption,
  582. the value of formState will not be modified, so deep cloning is not necessary, which can reduce performance loss
  583. But if the users use formApi.getFormState(), the behavior is unpredictable, and invasive modifications may be performed,
  584. so deep clones are used for isolation.
  585. 对deepClone进行区分的原因:semi调用getFormState内部消费时,不会对formState的值进行修改,所以无需深克隆,节约开销
  586. 但如果是业务方用formApi调用getFormState时,行为无法预料,有可能会进行侵入式修改,所以用深克隆进行隔离
  587. */
  588. if (!needClone) {
  589. return {
  590. ...this.data,
  591. };
  592. } else {
  593. return this._adapter.cloneDeep(this.data);
  594. }
  595. }
  596. _isValid(targetFields: Map<string, FieldStaff>): boolean {
  597. let valid = true;
  598. if (!targetFields) {
  599. valid = Boolean(ObjectUtil.empty(this.data.errors));
  600. } else {
  601. // when trigger partial validate
  602. const targetFieldStr = [...targetFields.keys()];
  603. targetFieldStr.forEach(fieldStr => {
  604. const fieldError = ObjectUtil.get(this.data.errors, fieldStr);
  605. if (!isValid(fieldError)) {
  606. valid = false;
  607. }
  608. });
  609. }
  610. return valid;
  611. }
  612. // get form.props.initValues
  613. getInitValues(): any {
  614. return this._adapter.getInitValues();
  615. }
  616. getInitValue(field?: string): any {
  617. if (typeof field === 'undefined') {
  618. return this._adapter.getInitValues();
  619. }
  620. return ObjectUtil.get(this._adapter.getInitValues(), field);
  621. }
  622. getFormProps(keys?: Array<string>): ComponentProps {
  623. return this._adapter.getFormProps(keys);
  624. }
  625. getFieldExist(field: string): boolean {
  626. return Boolean(this.fields.has(field));
  627. }
  628. _autoScroll(timeout?: boolean | number): void {
  629. const { autoScrollToError } = this.getFormProps();
  630. if (!autoScrollToError) {
  631. return;
  632. }
  633. let scrollOpts = { behavior: 'smooth' as const, block: 'start' as const };
  634. typeof autoScrollToError === 'object' ? (scrollOpts = autoScrollToError) : null;
  635. if (timeout) {
  636. setTimeout(() => this._getErrorFieldAndScroll(scrollOpts), 100);
  637. } else {
  638. this._getErrorFieldAndScroll(scrollOpts);
  639. }
  640. }
  641. _getErrorFieldAndScroll(scrollOpts?: scrollIntoViewOptions | boolean): void {
  642. const errorDOM = this._adapter.getAllErrorDOM();
  643. if (errorDOM && errorDOM.length) {
  644. try {
  645. const fieldDom = errorDOM[0].parentNode.parentNode;
  646. scrollIntoView(fieldDom as Element, scrollOpts);
  647. } catch (error) {}
  648. }
  649. }
  650. scrollToField(field: string, scrollOpts = { behavior: 'smooth', block: 'start' } as scrollIntoViewOptions): void {
  651. if (this.getFieldExist(field)) {
  652. const fieldDOM = this._adapter.getFieldDOM(field);
  653. scrollIntoView(fieldDOM as Element, scrollOpts);
  654. }
  655. }
  656. }