表单使用
- 如果您需要实现某种逻辑却没有头绪,欢迎提问
本文介绍表单的常用能力:值监听、布局移动、分区禁用、字段分组、验证、级联、懒加载等。
Valibot 类型如何映射为表单控件 / 表单组 / 表单数组,以及默认值与嵌套定义,统一见 类型映射 — 定义即表单,本文不再重复。
valueChange 的 list 属性接受一个路径或者 undefined,通过路径查询到对应控件并监听输入值变更:
import * as v from 'valibot';
import {
actions,
NFCSchema,
setComponent,
valueChange,
} from '@piying/view-angular-core';
import { appendLog } from '../components/log';
export const schema = v.object({
k1: v.pipe(
v.string(),
valueChange((fn) => {
fn({ list: [undefined] }).subscribe(({ list, field }) => {
appendLog(field, `k1 = ${JSON.stringify(list[0])}`);
});
}),
),
__log: v.pipe(
NFCSchema,
setComponent('log'),
actions.inputs.set({ logs: [] }),
),
});
export const model = { k1: '' };更多联动场景(hideWhen / disableWhen / 级联 / 修改其他字段)见 动态字段控制。
表单中往往是数据结构与视图布局不一致,此时可在保证数据结构正常的前提下移动组件。被移动的位置一定要先被解析,权重越大组件越靠后:
import { asVirtualGroup } from '@piying/view-angular';
import * as v from 'valibot';
import {
NFCSchema,
layout,
setAlias,
setComponent,
} from '@piying/view-angular-core';
export const schema = v.pipe(
v.intersect([
v.pipe(v.object({}), setAlias('ly1')),
v.pipe(v.object({}), setAlias('ly2')),
v.object({
input1: v.pipe(v.string(), layout({ keyPath: ['@ly1'] })),
input2: v.pipe(v.string(), layout({ keyPath: ['@ly2'], priority: 3 })),
input3: v.pipe(v.string(), layout({ keyPath: ['@ly2'], priority: 2 })),
input4: v.pipe(v.string(), layout({ keyPath: ['@ly2'], priority: 1 })),
__helper: v.pipe(NFCSchema, setComponent('formHelper')),
}),
]),
asVirtualGroup(),
);
export const model = { input1: '', input2: '', input3: '', input4: '' };详细示例见 asControl / asVirtualGroup。
当表单部分字段在某些场景可以/不可以被填写时,使用 disableWhen:
import * as v from 'valibot';
import {
actions,
disableWhen,
NFCSchema,
setComponent,
} from '@piying/view-angular-core';
import { map } from 'rxjs';
import { appendLog } from '../components/log';
export const schema = v.object({
enable: v.boolean(),
value: v.intersect([
v.pipe(
v.object({
input1: v.string(),
input2: v.string(),
}),
disableWhen({
listen: (fn) => {
return fn({ list: [['#', 'enable']] }).pipe(
map(({ list, field }) => {
appendLog(
field,
`enable = ${list[0]} → input1 / input2 ${list[0] ? '禁用' : '可编辑'}`,
);
return list[0];
}),
);
},
}),
),
v.pipe(
v.object({
input3: v.string(),
input4: v.string(),
}),
disableWhen({
listen: (fn) => {
return fn({ list: [['#', 'enable']] }).pipe(
map(({ list, field }) => {
appendLog(
field,
`enable = ${list[0]} → input3 / input4 ${list[0] ? '可编辑' : '禁用'}`,
);
return !list[0];
}),
);
},
}),
),
]),
__log: v.pipe(
NFCSchema,
setComponent('log'),
actions.inputs.set({ logs: [] }),
),
});
export const model = {
enable: true,
value: { input1: '', input2: '', input3: '', input4: '' },
};任何情况下,都可以将指定类型定义外加入 v.array(),表明为一个数组:
import * as v from 'valibot';
import { actions } from '@piying/view-angular';
import { setComponent } from '@piying/view-angular-core';
export const schema = v.object({
list: v.pipe(
v.array(v.string()),
setComponent('array-rw'),
actions.inputs.set({ minLength: 2 }),
),
});
export const model = { list: ['v1', 'v2'] };数组的增删改、deletionMode、groupMode 等高级用法见 数组高级用法。
v.intersect() + asVirtualGroup 可以进行多级虚拟组,配合 tabs 组件实现标签页分组:
import { asVirtualGroup } from '@piying/view-angular';
import * as v from 'valibot';
import { setComponent, valueChange } from '@piying/view-angular-core';
export const schema = v.pipe(
v.intersect([
v.pipe(
v.object({
k1: v.optional(v.string()),
k2: v.optional(v.string()),
}),
setComponent('fieldset'),
v.title('tab1-fieldset'),
),
v.pipe(
v.object({
k3: v.optional(v.string()),
k4: v.optional(v.string()),
}),
v.title('tab2'),
),
]),
setComponent('tabs'),
asVirtualGroup(),
valueChange((fn) => {
fn().subscribe(({ list }) => {
console.log('tabs', list[0]);
});
}),
);
export const model = { k1: '', k2: '', k3: '', k4: '' };值变更时修改表单其他值
Section titled “值变更时修改表单其他值”使用 valueChange 监听控件并更新其他控件的值:
import * as v from 'valibot';
import {
actions,
NFCSchema,
setComponent,
valueChange,
} from '@piying/view-angular-core';
import { appendLog } from '../components/log';
export const schema = v.object({
list: v.pipe(
v.picklist(['data1', 'data2']),
valueChange((fn) => {
fn().subscribe(({ list: [value], field }) => {
const o1FG = field.get(['#', 'o1'])!.form.control!;
const o2FG = field.get(['#', 'o2'])!.form.control!;
if (value === 'data1') {
o1FG.updateValue({ k1: 'data1-input-k1', k2: 'data1-input-k2' });
o2FG.updateValue({});
appendLog(field, 'list = data1 → 写入 o1,清空 o2');
} else {
o2FG.updateValue({ k3: 'data2-input-k3', k4: 'data2-input-k4' });
o1FG.updateValue({});
appendLog(field, 'list = data2 → 写入 o2,清空 o1');
}
});
}),
),
o1: v.object({ k1: v.optional(v.string()), k2: v.optional(v.string()) }),
o2: v.object({ k3: v.optional(v.string()), k4: v.optional(v.string()) }),
__log: v.pipe(
NFCSchema,
setComponent('log'),
actions.inputs.set({ logs: [] }),
),
});
export const model = {
list: 'data1',
o1: { k1: '', k2: '' },
o2: { k3: '', k4: '' },
};验证和控件组件完全解耦,需要自行实现,只提供相关控件状态。可以使用 wrapper 套一层实现,也可以使用 group 只在专门的组中进行验证:
import * as v from 'valibot';
import { actions } from '@piying/view-angular';
import { setComponent } from '@piying/view-angular-core';
export const schema = v.pipe(
v.object({
k1: v.pipe(
v.string(),
v.check((value) => value === 'k2-value', 'should input k2-value'),
actions.wrappers.set(['validator']),
),
}),
);
export const model = { k1: '' };import * as v from 'valibot';
import { setComponent } from '@piying/view-angular-core';
export const schema = v.pipe(
v.object({
k1: v.string(),
k2: v.pipe(
v.string(),
v.check((value) => value === 'k2-value'),
),
}),
setComponent('validGroup'),
);
export const model = { k1: '', k2: '' };同步/异步验证器、formConfig.validators 等详细用法见 自定义验证。
通过 valueChange 监听指定控件并更新控件值。第一次监听的值默认为 undefined(假如没有设置默认值),所以需要跳过:
import * as v from 'valibot';
import {
actions,
NFCSchema,
setComponent,
valueChange,
} from '@piying/view-angular-core';
import { skip } from 'rxjs';
import { appendLog } from '../components/log';
export const schema = v.object({
k1: v.boolean(),
k2: v.pipe(
v.boolean(),
valueChange((fn) => {
fn({ list: [['#', 'k1']] })
.pipe(skip(1))
.subscribe(({ list: [value], field }) => {
field.form.control?.updateValue(!value);
appendLog(field, `k1 = ${value} → k2 = ${!value}`);
});
}),
),
k3: v.pipe(
v.boolean(),
valueChange((fn) => {
fn({ list: [['#', 'k2']] })
.pipe(skip(1))
.subscribe(({ list: [value], field }) => {
field.form.control?.updateValue(!value);
appendLog(field, `k2 = ${value} → k3 = ${!value}`);
});
}),
),
k4: v.pipe(
v.boolean(),
valueChange((fn) => {
fn({ list: [['#', 'k3']] })
.pipe(skip(1))
.subscribe(({ list: [value], field }) => {
field.form.control?.updateValue(!value);
appendLog(field, `k3 = ${value} → k4 = ${!value}`);
});
}),
),
__log: v.pipe(
NFCSchema,
setComponent('log'),
actions.inputs.set({ logs: [] }),
),
});
export const model = { k1: true, k2: false, k3: true, k4: false };自定义创建表单组控制内部表单的显示(大量字段时可使用 filterGroup 过滤组件):
import * as v from 'valibot';
import { setComponent } from '@piying/view-angular-core';
export const schema = v.pipe(
v.object(
new Array(100)
.fill(undefined)
.map((item, i) => v.pipe(v.string(), v.title(`SearchTitle${i}`)))
.reduce((obj, item, i) => {
obj[`k${i}`] = item;
return obj;
}, Object.create({})),
),
setComponent('filterGroup'),
);
export const model = {};使用 defer 动态创建控件,配合 scrollGroup 组件实现大量字段的滚动懒加载:
import { actions } from '@piying/view-angular';
import * as v from 'valibot';
import { setComponent } from '@piying/view-angular-core';
export const schema = v.pipe(
v.object(
new Array(100)
.fill(undefined)
.map((item, i) => v.pipe(v.string(), v.title(`title${i}`)))
.reduce((obj, item, i) => {
obj[`k${i}`] = item;
return obj;
}, Object.create({})),
),
setComponent('scrollGroup'),
actions.inputs.set({ scrollHeight: 500, placeholerHeight: 20 }),
);
export const model = {};- 类型映射 — 定义即表单 — 类型映射 / 默认值 / 嵌套定义
- 动态字段控制 — valueChange / disableWhen / 级联
- asControl / asVirtualGroup — 布局移动 / 字段分组
- 数组高级用法 — 过滤组 / 滚动组
- 自定义验证 — 验证器与分组验证