Skip to content

Commit

Permalink
feat(cdk:forms): useFormGroup support nested objects (#1021)
Browse files Browse the repository at this point in the history
  • Loading branch information
danranVm committed Jul 15, 2022
1 parent 33b43bb commit bdb96df
Show file tree
Hide file tree
Showing 24 changed files with 323 additions and 339 deletions.
6 changes: 3 additions & 3 deletions packages/cdk/forms/__tests__/formArray.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const basicValue = { control: '', array: ['', ''], group: { control: '' } }

describe('formArray.ts', () => {
describe('basic work', () => {
let array: FormArray<BasicGroup[]>
let array: FormArray<BasicGroup>

beforeEach(() => {
array = new FormArray([newFormGroup()])
Expand Down Expand Up @@ -192,7 +192,7 @@ describe('formArray.ts', () => {
})

describe('trigger work', () => {
let array: FormArray<BasicGroup[]>
let array: FormArray<BasicGroup>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _validator = (value: any) => {
return value[0].control === 'test' ? undefined : ({ test: {} } as ValidateErrors)
Expand Down Expand Up @@ -305,7 +305,7 @@ describe('formArray.ts', () => {
})

describe('disabled work', () => {
let array: FormArray<BasicGroup[]>
let array: FormArray<BasicGroup>

test('default disabled work', async () => {
array = new FormArray(
Expand Down
6 changes: 3 additions & 3 deletions packages/cdk/forms/__tests__/formGroup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ describe('formGroup.ts', () => {
expect(group.get('control')).toEqual(control)
expect(group.get('array')).toEqual(array)
expect(group.get('group')).toEqual(groupChild)
expect(group.get('group.control')).toEqual((groupChild as FormGroup<{ control: string }>).controls.value.control)
expect(group.get(['array', 0])).toEqual((array as FormArray<string[]>).controls.value[0])
expect(group.get('array')!.get(0)).toEqual((array as FormArray<string[]>).controls.value[0])
expect(group.get('group.control')).toEqual(groupChild.controls.value.control)
expect(group.get(['array', 0])).toEqual(array.controls.value[0])
expect(group.get('array')!.get(0)).toEqual(array.controls.value[0])

expect(group.get(undefined as never)).toBeUndefined()
expect(group.get('')).toBeUndefined()
Expand Down
25 changes: 20 additions & 5 deletions packages/cdk/forms/__tests__/useForms.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,36 @@ import { Validators } from '../src/validators'
interface BasicGroup {
control1: string
control2?: number
array: (string | number)[]
group: {
array1: (string | number)[]
array2: { control: string }[]
group1: {
control: string | number
}
group2: {
control: string | number
}
}

const basicValue: BasicGroup = { control1: '', control2: undefined, array: ['', 1], group: { control: '' } }
const basicValue: BasicGroup = {
control1: '',
control2: undefined,
array1: ['', 1],
array2: [{ control: '0' }, { control: '1' }],
group1: { control: 1 },
group2: { control: '2' },
}

describe('useForms.ts', () => {
test('basic work', async () => {
const group = useFormGroup<BasicGroup>({
control1: ['', Validators.required],
control2: [undefined, { trigger: 'blur', validators: Validators.required }],
array: useFormArray([[''], [1]]),
group: useFormGroup({ control: useFormControl<string | number>('') }),
array1: useFormArray<string | number>([[''], [1]]),
array2: useFormArray([{ control: ['0'] }, { control: ['1'] }]),
group1: useFormGroup({ control: useFormControl<string | number>(1) }),
group2: {
control: ['2'],
},
})

expect(group.getValue()).toEqual(basicValue)
Expand Down
14 changes: 6 additions & 8 deletions packages/cdk/forms/demo/Basic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,17 @@ import CustomInput from './CustomInput.vue'
const { required, min, max, email } = Validators
const address = useFormGroup({
city: ['', required],
street: ['', required],
zip: [''],
})
const remarks = useFormArray<string[]>([['remark0'], ['remark1'], ['remark2']])
const remarks = useFormArray([['remark0'], ['remark1'], ['remark2']])
const formGroup = useFormGroup({
name: ['tom', required],
age: [18, [required, min(1), max(30)]],
email: ['', [email]],
address: address,
address: {
city: ['', required],
street: ['', required],
zip: [''],
},
remarks: remarks,
})
Expand Down
12 changes: 6 additions & 6 deletions packages/cdk/forms/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@ export function useFormGroup<T>(
| 名称 | 说明 | 类型 | 默认值 | 备注 |
| --- | --- | --- | --- | --- |
| `config` | 控件组配置项 | `GroupConfig<T>` | - | 每个子控件的 `key` 就是配置项的 `key` |
| `validatorOptions` | 控件组验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) |
| `validators` | 一个同步验证器函数或数组 | `ValidatorFn \| ValidatorFn[]` | - | 只针对当前控件组的值进行验证 |
| `asyncValidators` | 一个异步验证器函数或数组 | `AsyncValidatorFn \| AsyncValidatorFn[]` | - | 只针对当前控件组的值进行验证 |
| `validatorOptions` | 控件组验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) |

```ts
type ControlConfig<T> =
| [T]
| [T, ValidatorOptions]
| [T, ValidatorFn | ValidatorFn[]]
| [T, ValidatorFn | ValidatorFn[], AsyncValidatorFn | AsyncValidatorFn[]]
| [T, ValidatorOptions]
type GroupConfig<T> = {
[K in keyof T]: ControlConfig<T[K]> | AbstractControl<T[K]> | FormGroup<T[K]>
[K in keyof T]: ControlConfig<T[K]> | GroupConfig<T[K]> | AbstractControl<T[K]>
}
```

Expand All @@ -54,12 +54,12 @@ export function useFormArray<T>(
| 名称 | 说明 | 类型 | 默认值 | 备注 |
| --- | --- | --- | --- | --- |
| `config` | 控件数组配置项 | `ArrayConfig<T>` | - | 每个子控件的 `key` 就是配置项的 `index` |
| `validatorOptions` | 控件数组验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) |
| `validators` | 一个同步验证器函数或数组 | `ValidatorFn \| ValidatorFn[]` | - | 只针对当前控件数组的值进行验证 |
| `asyncValidators` | 一个异步验证器函数或数组 | `AsyncValidatorFn \| AsyncValidatorFn[]` | - | 只针对当前控件数组的值进行验证 |
| `validatorOptions` | 控件数组验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) |

```ts
type ArrayConfig<T> = Array<AbstractControl<ArrayElement<T>> | ControlConfig<ArrayElement<T>> | ArrayElement<T>>
type ArrayConfig<T> = Array<ControlConfig<T> | GroupConfig<T> | AbstractControl<T>>
```

### useFormControl
Expand All @@ -78,9 +78,9 @@ export function useFormControl<T>(
| 名称 | 说明 | 类型 | 默认值 | 备注 |
| --- | --- | --- | --- | --- |
| `initValue` | 控件初始值 | `any` | - | - |
| `validatorOptions` | 控件验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) |
| `validators` | 一个同步验证器函数或数组 | `ValidatorFn \| ValidatorFn[]` | - | - |
| `asyncValidators` | 一个异步验证器函数或数组 | `AsyncValidatorFn \| AsyncValidatorFn[]` | - | - |
| `validatorOptions` | 控件验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) |

### ValidatorOptions

Expand Down
2 changes: 1 addition & 1 deletion packages/cdk/forms/docs/Overview.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const address = useFormGroup({
zip: [''],
})
const remarks = useFormArray<string[]>([['remark0'], ['remark1'], ['remark2']])
const remarks = useFormArray([['remark0'], ['remark1'], ['remark2']])
const formGroup = useFormGroup({
name: ['tom', required],
Expand Down
38 changes: 20 additions & 18 deletions packages/cdk/forms/src/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { hasOwnProperty } from '@idux/cdk/utils'

import {
type AsyncValidatorFn,
type TriggerType,
type ValidateError,
type ValidateErrors,
type ValidateStatus,
Expand Down Expand Up @@ -151,7 +150,7 @@ export abstract class AbstractControl<T = any> {
* Possible values: `'change'` | `'blur'` | `'submit'`
* Default value: `'change'`
*/
get trigger(): TriggerType {
get trigger(): 'change' | 'blur' | 'submit' {
return this._trigger ?? this._parent?.trigger ?? 'change'
}

Expand Down Expand Up @@ -181,7 +180,7 @@ export abstract class AbstractControl<T = any> {
private _validators: ValidatorFn | undefined
private _asyncValidators: AsyncValidatorFn | undefined
private _parent: AbstractControl<T> | undefined
private _trigger?: TriggerType
private _trigger?: 'change' | 'blur' | 'submit'
private _trim?: boolean

constructor(
Expand All @@ -205,7 +204,10 @@ export abstract class AbstractControl<T = any> {
* * `dirty`: Marks it dirty, default is false.
* * `blur`: Marks it blurred, default is false.
*/
abstract setValue(value: T | Partial<T>, options?: { dirty?: boolean; blur?: boolean }): void
abstract setValue(
value: T | Partial<T> | Partial<ArrayElement<T>>[],
options?: { dirty?: boolean; blur?: boolean },
): void

/**
* The aggregate value of the control.
Expand Down Expand Up @@ -756,9 +758,9 @@ export class FormGroup<T extends object = object> extends AbstractControl<T> {
}
}

export class FormArray<T extends any[] = any[]> extends AbstractControl<T> {
readonly controls!: ComputedRef<AbstractControl<ArrayElement<T>>[]>
protected _controls!: ShallowRef<AbstractControl<ArrayElement<T>>[]>
export class FormArray<T = any> extends AbstractControl<T[]> {
readonly controls!: ComputedRef<AbstractControl<T>[]>
protected _controls!: ShallowRef<AbstractControl<T>[]>

/**
* Length of the control array.
Expand All @@ -769,7 +771,7 @@ export class FormArray<T extends any[] = any[]> extends AbstractControl<T> {
/**
* An array of child controls. Each child control is given an index where it is registered.
*/
controls: AbstractControl<ArrayElement<T>>[],
controls: AbstractControl<T>[],
validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions,
asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[],
) {
Expand All @@ -784,27 +786,27 @@ export class FormArray<T extends any[] = any[]> extends AbstractControl<T> {
this._watchDirty()
}

setValue(value: Partial<ArrayElement<T>>[], options?: { dirty?: boolean; blur?: boolean }): void {
setValue(value: T extends object ? Partial<T>[] : T[], options?: { dirty?: boolean; blur?: boolean }): void {
value.forEach((item, index) => {
const control = this.at(index)
if (control) {
control.setValue(item, options)
control.setValue(item!, options)
}
})
}

getValue(options: { skipDisabled?: boolean } = {}): T {
getValue(options: { skipDisabled?: boolean } = {}): T[] {
const { skipDisabled } = options
return this._controls.value
.filter(control => !skipDisabled || !control.disabled.value)
.map(control => control.getValue(options)) as T
.map(control => control.getValue(options))
}

protected _calculateInitValue(): T {
protected _calculateInitValue(): T[] {
return this.getValue()
}

protected _forEachControls(cb: (v: AbstractControl, k: keyof T) => void): void {
protected _forEachControls(cb: (v: AbstractControl, k: number) => void): void {
this._controls.value.forEach(cb)
}

Expand All @@ -813,7 +815,7 @@ export class FormArray<T extends any[] = any[]> extends AbstractControl<T> {
*
* @param index Index in the array to retrieve the control
*/
at(index: number): AbstractControl<ArrayElement<T>> | undefined {
at(index: number): AbstractControl<T> | undefined {
return this._controls.value[index]
}

Expand All @@ -822,7 +824,7 @@ export class FormArray<T extends any[] = any[]> extends AbstractControl<T> {
*
* @param control Form control to be inserted
*/
push(control: AbstractControl<ArrayElement<T>>): void {
push(control: AbstractControl<T>): void {
control.setParent(this as AbstractControl)
this._controls.value = [...this._controls.value, control]
}
Expand All @@ -833,7 +835,7 @@ export class FormArray<T extends any[] = any[]> extends AbstractControl<T> {
* @param index Index in the array to insert the control
* @param control Form control to be inserted
*/
insert(index: number, control: AbstractControl<ArrayElement<T>>): void {
insert(index: number, control: AbstractControl<T>): void {
control.setParent(this as AbstractControl)
const controls = [...this._controls.value]
controls.splice(index, 0, control)
Expand All @@ -857,7 +859,7 @@ export class FormArray<T extends any[] = any[]> extends AbstractControl<T> {
* @param index Index in the array to replace the control
* @param control The `AbstractControl` control to replace the existing control
*/
setControl(index: number, control: AbstractControl<ArrayElement<T>>): void {
setControl(index: number, control: AbstractControl<T>): void {
control.setParent(this as AbstractControl)
const controls = [...this._controls.value]
controls.splice(index, 1, control)
Expand Down
4 changes: 1 addition & 3 deletions packages/cdk/forms/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@ export interface AsyncValidatorFn {
(value: any, control: AbstractControl): Promise<ValidateErrors | undefined>
}

export type TriggerType = 'change' | 'blur' | 'submit'

export interface ValidatorOptions {
disabled?: boolean
name?: string
example?: string
trigger?: TriggerType
trigger?: 'change' | 'blur' | 'submit'
validators?: ValidatorFn | ValidatorFn[]
asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[]
/**
Expand Down
40 changes: 20 additions & 20 deletions packages/cdk/forms/src/useForms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,32 @@

/* eslint-disable @typescript-eslint/no-explicit-any */

import type { AbstractControl, ArrayElement, GroupControls } from './controls'
import type { AsyncValidatorFn, ValidatorFn, ValidatorOptions } from './types'

import { FormArray, FormControl, FormGroup } from './controls'
import { type AbstractControl, FormArray, FormControl, FormGroup, type GroupControls } from './controls'
import { isAbstractControl } from './typeof'
import { type AsyncValidatorFn, type ValidatorFn, type ValidatorOptions } from './types'

type ControlConfig<T> =
| [T]
| [T, ValidatorOptions]
| [T, ValidatorFn | ValidatorFn[]]
| [T, ValidatorFn | ValidatorFn[], AsyncValidatorFn | AsyncValidatorFn[]]
| [T, ValidatorOptions]

type GroupConfig<T> = {
[K in keyof T]: ControlConfig<T[K]> | AbstractControl<T[K]> | FormGroup<T[K]>
[K in keyof T]: ControlConfig<T[K]> | GroupConfig<T[K]> | AbstractControl<T[K]>
}

export function useFormGroup<T extends Record<string, any> = Record<string, any>>(
type ArrayConfig<T> = Array<ControlConfig<T> | GroupConfig<T> | AbstractControl<T>>

export function useFormGroup<T extends object = object>(
config: GroupConfig<T>,
validatorOptions?: ValidatorOptions,
): FormGroup<T>
export function useFormGroup<T extends Record<string, any> = Record<string, any>>(
export function useFormGroup<T extends object = object>(
config: GroupConfig<T>,
validators?: ValidatorFn | ValidatorFn[],
asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[],
): FormGroup<T>
export function useFormGroup<T extends Record<string, any> = Record<string, any>>(
export function useFormGroup<T extends object = object>(
config: GroupConfig<T>,
validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions,
asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[],
Expand All @@ -41,18 +41,13 @@ export function useFormGroup<T extends Record<string, any> = Record<string, any>
return new FormGroup(controls, validatorOrOptions, asyncValidators)
}

type ArrayConfig<T> = Array<AbstractControl<ArrayElement<T>> | ControlConfig<ArrayElement<T>>>

export function useFormArray<T extends any[] = any[]>(
config: ArrayConfig<T>,
validatorOptions?: ValidatorOptions,
): FormArray<T>
export function useFormArray<T extends any[] = any[]>(
export function useFormArray<T = any>(config: ArrayConfig<T>, validatorOptions?: ValidatorOptions): FormArray<T>
export function useFormArray<T = any>(
config: ArrayConfig<T>,
validators?: ValidatorFn | ValidatorFn[],
asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[],
): FormArray<T>
export function useFormArray<T extends any[] = any[]>(
export function useFormArray<T = any>(
config: ArrayConfig<T>,
validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions,
asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[],
Expand Down Expand Up @@ -83,11 +78,16 @@ function reduceControls<T>(config: GroupConfig<T>): GroupControls<T> {
return controls
}

function createControl<T>(config: AbstractControl<T> | ControlConfig<T>): AbstractControl<T> {
function createControl<T>(config: AbstractControl<T> | ControlConfig<T> | GroupConfig<T>): AbstractControl<T> {
if (isAbstractControl(config)) {
return config
} else {
const [initValue, validatorOrOptions, asyncValidator] = config as ControlConfig<T>
}

if (Array.isArray(config)) {
const [initValue, validatorOrOptions, asyncValidator] = config
return new FormControl(initValue, validatorOrOptions, asyncValidator)
}

const controls = reduceControls(config as GroupConfig<T>)
return new FormGroup(controls) as unknown as AbstractControl<T>
}
Loading

0 comments on commit bdb96df

Please sign in to comment.