From cd6fadf3a4c80aea18cb9fae163f2e0ff229d9ad Mon Sep 17 00:00:00 2001 From: Janry Date: Thu, 18 Nov 2021 21:36:20 +0800 Subject: [PATCH] feat(core): add selfModified to field model (#2453) --- packages/core/docs/api/models/Field.md | 93 ++++++++++---------- packages/core/docs/api/models/Field.zh-CN.md | 3 +- packages/core/src/__tests__/field.spec.ts | 29 ++++++ packages/core/src/models/Field.ts | 14 +-- packages/core/src/shared/internals.ts | 22 ++++- 5 files changed, 108 insertions(+), 53 deletions(-) diff --git a/packages/core/docs/api/models/Field.md b/packages/core/docs/api/models/Field.md index 28d4e9ee7db..70dc58ea82c 100644 --- a/packages/core/docs/api/models/Field.md +++ b/packages/core/docs/api/models/Field.md @@ -10,52 +10,53 @@ All model attributes are listed below. If the attribute is writable, then we can ## Attributes -| Property | Description | Type | Read-only or not | Default value | -| -------------- | -------------------------------------------- | -------------------------------------------------- | ---------------- | ------------- | -| initialized | Has the field been initialized | Boolean | No | `false` | -| mounted | Is the field mounted | Boolean | No | `false` | -| unmounted | Is the field unmounted | Boolean | No | `false` | -| address | Field node path | [FormPath](/api/entry/form-path) | Yes | | -| path | Field data path | [FormPath](/api/entry/form-path) | Yes | | -| title | Field Title | [FieldMessage](#fieldmessage) | No | `""` | -| description | Field description | [FieldMessage](#fieldmessage) | No | `""` | -| loading | Field loading status | Boolean | No | `false` | -| validating | Is the field being validated | Boolean | No | `false` | -| modified | Whether the field has been manually modified | Boolean | No | `false` | -| active | Is the field active | Boolean | No | `false` | -| visited | Whether the field has been visited | Boolean | No | `false` | -| inputValue | Field input value | Any | No | `null` | -| inputValues | Field input value collection | Array | No | `[]` | -| dataSource | Field data source | Array | No | `[]` | -| validator | Field validator | [FieldValidator](#fieldvalidator) | No | `null` | -| decorator | field decorator | Any[] | No | `null` | -| component | Field component | Any[] | No | `null` | -| feedbacks | Field feedback information | [IFieldFeedback](#ifieldfeedback)[] | No | `[]` | -| parent | Parent field | [GeneralField](#generalfield) | yes | `null` | -| errors | Field all error message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | -| warnings | Field all warning message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | -| successes | Field all success message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | -| valid | Is the all field valid(include children) | Boolean | Yes | `true` | -| invalid | Is the all field illegal(include children) | Boolean | Yes | `false` | -| value | Field value | Any | No | | -| initialValue | Field default value | Any | No | | -| display | Field display status | [FieldDisplayTypes](#fielddisplaytypes) | No | `"visible"` | -| pattern | Field interaction mode | [FieldPatternTypes](#fieldpatterntypes) | No | `"editable"` | -| required | Is the field required | Boolean | No | `false` | -| hidden | Whether the field is hidden | Boolean | No | `false` | -| visible | Whether the field is displayed | Boolean | No | `true` | -| disabled | Whether the field is disabled | Boolean | No | `false` | -| readOnly | Is the field read-only | Boolean | No | `false` | -| readPretty | Whether the field is in the reading state | Boolean | No | `false` | -| editable | Field is editable | Boolean | No | `true` | -| validateStatus | Field validation status | [FieldValidateStatus](#fieldvalidatestatus) | yes | `null` | -| content | Field content, usually as a child node | any | No | `null` | -| data | Field extends properties | Object | No | `null` | -| selfErrors | Field own error message | [FieldMessage](#fieldmessage)[] | No | `[]` | -| selfWarnings | Field own warning message | [FieldMessage](#fieldmessage)[] | No | `[]` | -| selfSuccesses | Success message of the field itself | [FieldMessage](#fieldmessage)[] | No | `[]` | -| selfValid | Is the field valid | Boolean | Yes | `true` | -| selfInvalid | Is the field itself illegal | Boolean | Yes | `false` | +| Property | Description | Type | Read-only or not | Default value | +| -------------- | ------------------------------------------------- | -------------------------------------------------- | ---------------- | ------------- | +| initialized | Has the field been initialized | Boolean | No | `false` | +| mounted | Is the field mounted | Boolean | No | `false` | +| unmounted | Is the field unmounted | Boolean | No | `false` | +| address | Field node path | [FormPath](/api/entry/form-path) | Yes | | +| path | Field data path | [FormPath](/api/entry/form-path) | Yes | | +| title | Field Title | [FieldMessage](#fieldmessage) | No | `""` | +| description | Field description | [FieldMessage](#fieldmessage) | No | `""` | +| loading | Field loading status | Boolean | No | `false` | +| validating | Is the field being validated | Boolean | No | `false` | +| modified | Whether the field tree has been manually modified | Boolean | No | `false` | +| selfModified | Whether the field has been manually modified | Boolean | No | `false` | +| active | Is the field active | Boolean | No | `false` | +| visited | Whether the field has been visited | Boolean | No | `false` | +| inputValue | Field input value | Any | No | `null` | +| inputValues | Field input value collection | Array | No | `[]` | +| dataSource | Field data source | Array | No | `[]` | +| validator | Field validator | [FieldValidator](#fieldvalidator) | No | `null` | +| decorator | field decorator | Any[] | No | `null` | +| component | Field component | Any[] | No | `null` | +| feedbacks | Field feedback information | [IFieldFeedback](#ifieldfeedback)[] | No | `[]` | +| parent | Parent field | [GeneralField](#generalfield) | yes | `null` | +| errors | Field all error message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | +| warnings | Field all warning message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | +| successes | Field all success message(include children) | [IFormFeedback](/api/models/form/#iformfeedback)[] | Yes | `[]` | +| valid | Is the all field valid(include children) | Boolean | Yes | `true` | +| invalid | Is the all field illegal(include children) | Boolean | Yes | `false` | +| value | Field value | Any | No | | +| initialValue | Field default value | Any | No | | +| display | Field display status | [FieldDisplayTypes](#fielddisplaytypes) | No | `"visible"` | +| pattern | Field interaction mode | [FieldPatternTypes](#fieldpatterntypes) | No | `"editable"` | +| required | Is the field required | Boolean | No | `false` | +| hidden | Whether the field is hidden | Boolean | No | `false` | +| visible | Whether the field is displayed | Boolean | No | `true` | +| disabled | Whether the field is disabled | Boolean | No | `false` | +| readOnly | Is the field read-only | Boolean | No | `false` | +| readPretty | Whether the field is in the reading state | Boolean | No | `false` | +| editable | Field is editable | Boolean | No | `true` | +| validateStatus | Field validation status | [FieldValidateStatus](#fieldvalidatestatus) | yes | `null` | +| content | Field content, usually as a child node | any | No | `null` | +| data | Field extends properties | Object | No | `null` | +| selfErrors | Field own error message | [FieldMessage](#fieldmessage)[] | No | `[]` | +| selfWarnings | Field own warning message | [FieldMessage](#fieldmessage)[] | No | `[]` | +| selfSuccesses | Success message of the field itself | [FieldMessage](#fieldmessage)[] | No | `[]` | +| selfValid | Is the field valid | Boolean | Yes | `true` | +| selfInvalid | Is the field itself illegal | Boolean | Yes | `false` | #### explain in detail diff --git a/packages/core/docs/api/models/Field.zh-CN.md b/packages/core/docs/api/models/Field.zh-CN.md index b0993c0c16d..472f397daa0 100644 --- a/packages/core/docs/api/models/Field.zh-CN.md +++ b/packages/core/docs/api/models/Field.zh-CN.md @@ -21,7 +21,8 @@ order: 1 | description | 字段描述 | [FieldMessage](#fieldmessage) | 否 | `""` | | loading | 字段加载状态 | Boolean | 否 | `false` | | validating | 字段是否正在校验 | Boolean | 否 | `false` | -| modified | 字段是否被手动修改过 | Boolean | 否 | `false` | +| modified | 字段子树是否被手动修改过 | Boolean | 否 | `false` | +| selfModified | 字段自身是否被手动修改过 | Boolean | 否 | `false` | | active | 字段是否处于激活态 | Boolean | 否 | `false` | | visited | 字段是否被浏览过 | Boolean | 否 | `false` | | inputValue | 字段输入值 | Any | 否 | `null` | diff --git a/packages/core/src/__tests__/field.spec.ts b/packages/core/src/__tests__/field.spec.ts index 70a07996779..6974efd3fe2 100644 --- a/packages/core/src/__tests__/field.spec.ts +++ b/packages/core/src/__tests__/field.spec.ts @@ -1660,3 +1660,32 @@ test('field name is length in dynamic assign', () => { field.initialValue = 123 expect(field.value).toEqual(123) }) + +test('nested field modified', async () => { + const form = attach(createForm()) + const obj = attach( + form.createObjectField({ + name: 'object', + }) + ) + const child = attach( + form.createField({ + name: 'child', + basePath: 'object', + }) + ) + await child.onInput() + expect(child.modified).toBeTruthy() + expect(child.selfModified).toBeTruthy() + expect(obj.modified).toBeTruthy() + expect(obj.selfModified).toBeFalsy() + expect(form.modified).toBeTruthy() + await obj.reset() + expect(child.modified).toBeFalsy() + expect(child.selfModified).toBeFalsy() + expect(obj.modified).toBeFalsy() + expect(obj.selfModified).toBeFalsy() + expect(form.modified).toBeTruthy() + await form.reset() + expect(form.modified).toBeFalsy() +}) diff --git a/packages/core/src/models/Field.ts b/packages/core/src/models/Field.ts index bb0003cef0b..e938aec6655 100644 --- a/packages/core/src/models/Field.ts +++ b/packages/core/src/models/Field.ts @@ -45,6 +45,7 @@ import { setSubmitting, setLoading, validateSelf, + modifySelf, getValidFieldDefaultValue, initializeStart, initializeEnd, @@ -65,9 +66,10 @@ export class Field< loading: boolean validating: boolean submitting: boolean - modified: boolean active: boolean visited: boolean + selfModified: boolean + modified: boolean inputValue: ValueType inputValues: any[] dataSource: FieldDataSource @@ -100,7 +102,7 @@ export class Field< this.loading = false this.validating = false this.submitting = false - this.modified = false + this.selfModified = false this.active = false this.visited = false this.mounted = false @@ -143,6 +145,7 @@ export class Field< loading: observable.ref, validating: observable.ref, submitting: observable.ref, + selfModified: observable.ref, modified: observable.ref, active: observable.ref, visited: observable.ref, @@ -220,7 +223,7 @@ export class Field< () => this.value, (value) => { this.notify(LifeCycleTypes.ON_FIELD_VALUE_CHANGE) - if (isValid(value) && this.modified && !this.caches.inputting) { + if (isValid(value) && this.selfModified && !this.caches.inputting) { validateSelf(this) } } @@ -456,8 +459,7 @@ export class Field< this.inputValue = value this.inputValues = values this.value = value - this.modified = true - this.form.modified = true + this.modify() this.notify(LifeCycleTypes.ON_FIELD_INPUT_VALUE_CHANGE) this.notify(LifeCycleTypes.ON_FORM_INPUT_CHANGE, this.form) await validateSelf(this, 'onInput') @@ -496,4 +498,6 @@ export class Field< queryFeedbacks = (search?: ISearchFeedback): IFieldFeedback[] => { return queryFeedbacks(this, search) } + + modify = () => modifySelf(this) } diff --git a/packages/core/src/shared/internals.ts b/packages/core/src/shared/internals.ts index 63b625d5a3a..3f9c778745e 100644 --- a/packages/core/src/shared/internals.ts +++ b/packages/core/src/shared/internals.ts @@ -42,6 +42,7 @@ import { isArrayField, isObjectField, isGeneralField, + isDataField, isForm, isQuery, isVoidField, @@ -189,7 +190,7 @@ export const patchFormValues = ( }) } else { if (targetField) { - if (!isVoidField(targetField) && !targetField.modified) { + if (!isVoidField(targetField) && !targetField.selfModified) { update(path, source) } } else if (form.initialized) { @@ -923,6 +924,9 @@ export const batchReset = async ( tasks.push(resetSelf(field, options, target === field)) } }) + if (isForm(target)) { + target.modified = false + } notify(target, LifeCycleTypes.ON_FORM_RESET, LifeCycleTypes.ON_FIELD_RESET) await Promise.all(tasks) } @@ -968,6 +972,7 @@ export const validateSelf = batch.bound( export const resetSelf = batch.bound( async (target: Field, options?: IFieldResetOptions, noEmit = false) => { target.modified = false + target.selfModified = false target.visited = false target.feedbacks = [] target.inputValue = undefined @@ -993,6 +998,21 @@ export const resetSelf = batch.bound( } ) +export const modifySelf = (target: Field) => { + if (target.selfModified) return + target.selfModified = true + target.modified = true + let parent = target.parent + while (parent) { + if (isDataField(parent)) { + if (parent.modified) return + parent.modified = true + } + parent = parent.parent + } + target.form.modified = true +} + export const getValidFormValues = (values: any) => { if (isObservable(values)) return values return clone(values || {})