mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat(components): [form] add setInitialValues and setInitialValue function (#23013)
* feat: setInitialFields * Remove '@eslint/markdown' dependency Removed '@eslint/markdown' dependency from package.json * Remove '@eslint/markdown' from pnpm-lock.yaml Removed '@eslint/markdown' dependency from lock file. * fix(components): expose setInitialValue alias * fix: test * fix: Nitpick comments * docs(components): add setInitialValues and setInitialValue API docs * fix: docs[form] setInitialValue * fix: typecheck * Update docs/en-US/component/form.md Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com> * fix: test * fix: test * fix: test lg * Update docs/en-US/component/form.md Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com> * fix: update initValue * fix: form.vue * fix: form description * chore: sync comment * chore: format --------- Co-authored-by: lw567 <liwei567@example.com> Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com> Co-authored-by: Dsaquel <291874700n@gmail.com>
This commit is contained in:
@@ -160,15 +160,16 @@ form/accessibility
|
||||
|
||||
### Form Exposes
|
||||
|
||||
| Name | Description | Type |
|
||||
| ------------------ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| validate | Validate the whole form. Receives a callback or returns `Promise`. | ^[Function]`(callback?: FormValidateCallback) => Promise<void>` |
|
||||
| validateField | Validate specified fields. | ^[Function]`(props?: Arrayable<FormItemProp> \| undefined, callback?: FormValidateCallback \| undefined) => FormValidationResult` |
|
||||
| resetFields | Reset specified fields and remove validation result. | ^[Function]`(props?: Arrayable<FormItemProp> \| undefined) => void` |
|
||||
| scrollToField | Scroll to the specified fields. | ^[Function]`(prop: FormItemProp) => void` |
|
||||
| clearValidate | Clear validation messages for all or specified fields. | ^[Function]`(props?: Arrayable<FormItemProp> \| undefined) => void` |
|
||||
| fields ^(2.7.3) | Get all fields context. | ^[array]`FormItemContext[]` |
|
||||
| getField ^(2.10.2) | Get a field context. | ^[Function]`(prop: FormItemProp) => FormItemContext \| undefined` |
|
||||
| Name | Description | Type |
|
||||
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| validate | Validate the whole form. Receives a callback or returns `Promise`. | ^[Function]`(callback?: FormValidateCallback) => Promise<void>` |
|
||||
| validateField | Validate specified fields. | ^[Function]`(props?: Arrayable<FormItemProp> \| undefined, callback?: FormValidateCallback \| undefined) => FormValidationResult` |
|
||||
| resetFields | Reset specified fields and remove validation result. | ^[Function]`(props?: Arrayable<FormItemProp> \| undefined) => void` |
|
||||
| scrollToField | Scroll to the specified fields. | ^[Function]`(prop: FormItemProp) => void` |
|
||||
| clearValidate | Clear validation messages for all or specified fields. | ^[Function]`(props?: Arrayable<FormItemProp> \| undefined) => void` |
|
||||
| fields ^(2.7.3) | Get all fields context. | ^[array]`FormItemContext[]` |
|
||||
| getField ^(2.10.2) | Get a field context. | ^[Function]`(prop: FormItemProp) => FormItemContext \| undefined` |
|
||||
| setInitialValues ^(2.13.1) | Set initial values for form fields. When `resetFields` is called, fields will reset to these values. Only fields present in `initModel` will be updated. | ^[Function]`(initModel: Record<string, any>) => void` |
|
||||
|
||||
## FormItem API
|
||||
|
||||
@@ -211,14 +212,15 @@ If you don't want to trigger the validator based on input events, set the `valid
|
||||
|
||||
### FormItem Exposes
|
||||
|
||||
| Name | Description | Type |
|
||||
| --------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| size | Form item size. | ^[object]`ComputedRef<'' \| 'large' \| 'default' \| 'small'>` |
|
||||
| validateMessage | Validation message. | ^[object]`Ref<string>` |
|
||||
| validateState | Validation state. | ^[object]`Ref<'' \| 'error' \| 'validating' \| 'success'>` |
|
||||
| validate | Validate form item. | ^[Function]`(trigger: string, callback?: FormValidateCallback \| undefined) => FormValidationResult` |
|
||||
| resetField | Reset current field and remove validation result. | ^[Function]`() => void` |
|
||||
| clearValidate | Remove validation status of the field. | ^[Function]`() => void` |
|
||||
| Name | Description | Type |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| size | Form item size. | ^[object]`ComputedRef<'' \| 'large' \| 'default' \| 'small'>` |
|
||||
| validateMessage | Validation message. | ^[object]`Ref<string>` |
|
||||
| validateState | Validation state. | ^[object]`Ref<'' \| 'error' \| 'validating' \| 'success'>` |
|
||||
| validate | Validate form item. | ^[Function]`(trigger: string, callback?: FormValidateCallback \| undefined) => FormValidationResult` |
|
||||
| resetField | Reset current field and remove validation result. | ^[Function]`() => void` |
|
||||
| clearValidate | Remove validation status of the field. | ^[Function]`() => void` |
|
||||
| setInitialValue ^(2.13.1) | Set initial value for this field. When `resetField` is called, the field will reset to this value. | ^[Function]`(value: any) => void` |
|
||||
|
||||
## Type Declarations
|
||||
|
||||
@@ -297,6 +299,7 @@ type FormItemContext = FormItemProps & {
|
||||
) => FormValidationResult
|
||||
resetField(): void
|
||||
clearValidate(): void
|
||||
setInitialValue: (value: any) => void
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -232,4 +232,91 @@ describe('ElFormItem', () => {
|
||||
expect(labelSlot.exists()).toBe(true)
|
||||
expect(labelSlot.text()).toBe(AXIOM)
|
||||
})
|
||||
|
||||
describe('setInitialValue', () => {
|
||||
it('should allow setting custom initial value for reset', async () => {
|
||||
vi.useFakeTimers()
|
||||
const form = reactive({
|
||||
username: 'original',
|
||||
})
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return { form }
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Form model={form}>
|
||||
<FormItem ref="usernameItem" label="Username" prop="username">
|
||||
<Input v-model={form.username} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
const formItemRef = wrapper.findComponent({ ref: 'usernameItem' })
|
||||
.vm as FormItemInstance
|
||||
|
||||
// Set custom initial value
|
||||
formItemRef.setInitialValue('customInitial')
|
||||
await nextTick()
|
||||
|
||||
// Modify field value
|
||||
form.username = 'modified'
|
||||
await nextTick()
|
||||
|
||||
// Reset field
|
||||
formItemRef.resetField()
|
||||
await nextTick()
|
||||
vi.runAllTimers()
|
||||
await nextTick()
|
||||
|
||||
// Should reset to custom initial value
|
||||
expect(form.username).toBe('customInitial')
|
||||
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('should handle undefined initial values', async () => {
|
||||
vi.useFakeTimers()
|
||||
const form = reactive({
|
||||
value: 'original',
|
||||
})
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return { form }
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Form model={form}>
|
||||
<FormItem ref="valueItem" label="Value" prop="value">
|
||||
<Input v-model={form.value} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
const formItemRef = wrapper.findComponent({ ref: 'valueItem' })
|
||||
.vm as FormItemInstance
|
||||
|
||||
// Test undefined
|
||||
formItemRef.setInitialValue(undefined)
|
||||
form.value = 'changed'
|
||||
await nextTick()
|
||||
|
||||
formItemRef.resetField()
|
||||
await nextTick()
|
||||
vi.runAllTimers()
|
||||
await nextTick()
|
||||
|
||||
expect(form.value).toBe(undefined)
|
||||
|
||||
vi.useRealTimers()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -125,14 +125,14 @@ describe('Form', () => {
|
||||
labelWidth="150px"
|
||||
model={form}
|
||||
>
|
||||
<FormItem label="名称">
|
||||
<FormItem label="Name">
|
||||
<Input v-model={form.name} />
|
||||
</FormItem>
|
||||
<FormItem label="活动区域" label-width="auto">
|
||||
<FormItem label="Activity Region" label-width="auto">
|
||||
<Input v-model={form.region} />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="活动形式(我是一个很长很长很长很长的label)"
|
||||
label="Activity Form (I am a very very very very long label)"
|
||||
label-width="auto"
|
||||
>
|
||||
<Input v-model={form.type} />
|
||||
@@ -1020,4 +1020,183 @@ describe('Form', () => {
|
||||
expect(onSubmit).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('setInitialValues', () => {
|
||||
it('should update initial values for all fields', async () => {
|
||||
vi.useFakeTimers()
|
||||
const form = reactive({
|
||||
name: '',
|
||||
age: '',
|
||||
address: '',
|
||||
})
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return { form }
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Form ref="formRef" model={form}>
|
||||
<FormItem label="Name" prop="name">
|
||||
<Input v-model={form.name} />
|
||||
</FormItem>
|
||||
<FormItem label="Age" prop="age">
|
||||
<Input v-model={form.age} />
|
||||
</FormItem>
|
||||
<FormItem label="Address" prop="address">
|
||||
<Input v-model={form.address} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
const formRef = wrapper.findComponent({ ref: 'formRef' })
|
||||
.vm as FormInstance
|
||||
|
||||
// Set new initial values
|
||||
formRef.setInitialValues({
|
||||
name: 'InitName',
|
||||
age: '30',
|
||||
address: 'Los Angeles',
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
// Modify form values
|
||||
form.name = 'Modified'
|
||||
form.age = '40'
|
||||
form.address = 'Chicago'
|
||||
await nextTick()
|
||||
|
||||
// Reset form, should reset to values set by setInitialValues
|
||||
formRef.resetFields()
|
||||
await nextTick()
|
||||
vi.runAllTimers()
|
||||
await nextTick()
|
||||
|
||||
expect(form.name).toBe('InitName')
|
||||
expect(form.age).toBe('30')
|
||||
expect(form.address).toBe('Los Angeles')
|
||||
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('should work with partial field updates', async () => {
|
||||
vi.useFakeTimers()
|
||||
const form = reactive({
|
||||
name: 'DefaultName',
|
||||
age: '20',
|
||||
address: 'DefaultAddress',
|
||||
})
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return { form }
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Form ref="formRef" model={form}>
|
||||
<FormItem label="Name" prop="name">
|
||||
<Input v-model={form.name} />
|
||||
</FormItem>
|
||||
<FormItem label="Age" prop="age">
|
||||
<Input v-model={form.age} />
|
||||
</FormItem>
|
||||
<FormItem label="Address" prop="address">
|
||||
<Input v-model={form.address} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
const formRef = wrapper.findComponent({ ref: 'formRef' })
|
||||
.vm as FormInstance
|
||||
|
||||
// Only update initial values for partial fields
|
||||
formRef.setInitialValues({
|
||||
name: 'NewInitName',
|
||||
age: '25',
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
// Modify all fields
|
||||
form.name = 'Modified'
|
||||
form.age = '30'
|
||||
form.address = 'ModifiedAddress'
|
||||
await nextTick()
|
||||
|
||||
// Reset form
|
||||
formRef.resetFields()
|
||||
await nextTick()
|
||||
vi.runAllTimers()
|
||||
await nextTick()
|
||||
|
||||
// name and age should reset to new initial values
|
||||
expect(form.name).toBe('NewInitName')
|
||||
expect(form.age).toBe('25')
|
||||
// address has no new initial value set, should keep original mounted value
|
||||
expect(form.address).toBe('DefaultAddress')
|
||||
|
||||
vi.useRealTimers()
|
||||
})
|
||||
})
|
||||
|
||||
describe('setInitialValue (FormItem)', () => {
|
||||
it('should update initial value for single field', async () => {
|
||||
vi.useFakeTimers()
|
||||
const form = reactive({
|
||||
name: '',
|
||||
age: '',
|
||||
})
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return { form }
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Form ref="formRef" model={form}>
|
||||
<FormItem ref="nameItem" label="Name" prop="name">
|
||||
<Input v-model={form.name} />
|
||||
</FormItem>
|
||||
<FormItem label="Age" prop="age">
|
||||
<Input v-model={form.age} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
const formRef = wrapper.findComponent({ ref: 'formRef' })
|
||||
.vm as FormInstance
|
||||
const nameItem = wrapper.findComponent({ ref: 'nameItem' })
|
||||
.vm as FormItemInstance
|
||||
|
||||
// Set new initial value only for name field
|
||||
nameItem.setInitialValue('CustomInitName')
|
||||
await nextTick()
|
||||
|
||||
// Modify field values
|
||||
form.name = 'Modified'
|
||||
form.age = '30'
|
||||
await nextTick()
|
||||
|
||||
// Reset form
|
||||
formRef.resetFields()
|
||||
await nextTick()
|
||||
vi.runAllTimers()
|
||||
await nextTick()
|
||||
|
||||
// name should reset to custom initial value
|
||||
expect(form.name).toBe('CustomInitName')
|
||||
// age should reset to original mounted value
|
||||
expect(form.age).toBe('')
|
||||
|
||||
vi.useRealTimers()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -362,6 +362,10 @@ const removeInputId: FormItemContext['removeInputId'] = (id: string) => {
|
||||
inputIds.value = inputIds.value.filter((listId) => listId !== id)
|
||||
}
|
||||
|
||||
const setInitialValue: FormItemContext['setInitialValue'] = (value: any) => {
|
||||
initialValue = clone(value)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.error,
|
||||
(val) => {
|
||||
@@ -393,6 +397,7 @@ const context: FormItemContext = reactive({
|
||||
clearValidate,
|
||||
validate,
|
||||
propString,
|
||||
setInitialValue,
|
||||
})
|
||||
|
||||
provide(formItemContextKey, context)
|
||||
@@ -433,5 +438,9 @@ defineExpose({
|
||||
* @description Reset current field and remove validation result.
|
||||
*/
|
||||
resetField,
|
||||
/**
|
||||
* @description Set initial value for this field. When `resetField` is called, the field will reset to this value.
|
||||
*/
|
||||
setInitialValue,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, provide, reactive, ref, toRefs, watch } from 'vue'
|
||||
import { debugWarn, isFunction } from '@element-plus/utils'
|
||||
import { has } from 'lodash-unified'
|
||||
import { debugWarn, getProp, isFunction } from '@element-plus/utils'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import { useFormSize } from './hooks'
|
||||
import { formContextKey } from './constants'
|
||||
@@ -61,6 +62,32 @@ const removeField: FormContext['removeField'] = (field) => {
|
||||
}
|
||||
}
|
||||
|
||||
const setInitialValues: FormContext['setInitialValues'] = (
|
||||
initModel: Partial<typeof props.model>
|
||||
) => {
|
||||
if (!props.model) {
|
||||
debugWarn(COMPONENT_NAME, 'model is required for setInitialValues to work.')
|
||||
return
|
||||
}
|
||||
if (!initModel) {
|
||||
debugWarn(
|
||||
COMPONENT_NAME,
|
||||
'initModel is required for setInitialValues to work.'
|
||||
)
|
||||
return
|
||||
}
|
||||
fields.forEach((field) => {
|
||||
if (field.prop) {
|
||||
// Check if the property path actually exists in initModel
|
||||
// This allows setting undefined/null values while skipping non-existent properties
|
||||
if (has(initModel, field.prop)) {
|
||||
const initValue = getProp(initModel, field.prop).value
|
||||
field.setInitialValue(initValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const resetFields: FormContext['resetFields'] = (properties = []) => {
|
||||
if (!props.model) {
|
||||
debugWarn(COMPONENT_NAME, 'model is required for resetFields to work.')
|
||||
@@ -181,6 +208,7 @@ provide(
|
||||
getField,
|
||||
addField,
|
||||
removeField,
|
||||
setInitialValues,
|
||||
|
||||
...useFormLabelWidth(),
|
||||
})
|
||||
@@ -215,5 +243,9 @@ defineExpose({
|
||||
* @description All fields context.
|
||||
*/
|
||||
fields,
|
||||
/**
|
||||
* @description Set initial values for form fields. When `resetFields` is called, fields will reset to these values. Only fields present in `initModel` will be updated.
|
||||
*/
|
||||
setInitialValues,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -46,6 +46,7 @@ export type FormContext = FormProps &
|
||||
addField: (field: FormItemContext) => void
|
||||
removeField: (field: FormItemContext) => void
|
||||
resetFields: (props?: Arrayable<FormItemProp>) => void
|
||||
setInitialValues: (initModel: Record<string, any>) => void
|
||||
clearValidate: (props?: Arrayable<FormItemProp>) => void
|
||||
validateField: (
|
||||
props?: Arrayable<FormItemProp>,
|
||||
@@ -72,4 +73,5 @@ export interface FormItemContext extends FormItemProps {
|
||||
) => FormValidationResult
|
||||
resetField(): void
|
||||
clearValidate(): void
|
||||
setInitialValue: (value: any) => void
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user