Form.io Choices.js 下拉框为何点击后才显示选中值?
应先准备好选项、字段 schema 和 submission,再创建 <formio>。如果表单必须提前创建,就在选项到达后刷新对应的 Select 实例,再应用当前值。对于 wizard 中跨标签重复出现的字段,还需要检查它们是否绑定到同一数据路径。
现有代码在给 nebulaWizardData 赋值前已经等待了选项,因此不能仅凭这些代码认定缺少 await。还需要确认 <formio> 是否已经提前创建,以及 angularComponent 是否确实指向持有选项的对象。缺少完整 schema 和依赖版本,也无法直接认定这是某个已知缺陷。
为什么模型有值,界面仍然为空?
2 是保存的 ID。显示文字需要从选项中找到对应对象,再通过 {{ item.text }} 生成。模型赋值成功时,组件可能还没有取得对应标签。
原配置中的:
values = angularComponent.treatmentStagesValues
会在 custom 数据源求值时读取属性。此后即使替换 Angular 对象中的数组,之前创建的 Select 实例也不一定会立即重新求值。
所查阅的 Select 实现 在 setItems() 中包含重新应用当前值的逻辑,因此不能笼统地认为 Form.io 不支持加载后回显。点击后恢复显示,与选项刷新时机有关的推测相符,但还不足以确定根因。
首次加载:数据准备完成后再创建表单
对于由 Angular 服务提供的选项,可以先请求数据,将数组放入 schema 的 JSON 数据源,再一次性提供 schema 和 submission。这样可以减少对 angularComponent 自定义求值上下文的依赖。Form.io 的 ListComponent 实现 会从 data.json 读取 JSON 数据源的选项。
下面的示例假设选项可以完整加载,且 wizardSchema 是可按 JSON 克隆的表单定义。将方法放入现有 Angular 组件,在取得 wizardFormData 和 schema 后调用:
wizardView: {
form: any;
submission: { data: any };
} | null = null;
async prepareWizard(wizardFormData: any, wizardSchema: any) {
const values =
await this._commonDynamicWizard.getTreatmentStagesValues();
const form = JSON.parse(JSON.stringify(wizardSchema));
const configureSelects = (node: any): void => {
if (!node || typeof node !== 'object') {
return;
}
if (Array.isArray(node)) {
node.forEach(configureSelects);
return;
}
if (
node.type === 'select' &&
node.key === 'yporAssessment.treatmentStageId'
) {
node.input = true;
node.dataSrc = 'json';
node.data = { json: values };
node.valueProperty = 'TreatmentStageId';
node.template = '{{ item.text }}';
node.lazyLoad = false;
return;
}
Object.keys(node).forEach(key => configureSelects(node[key]));
};
configureSelects(form);
this.wizardView = {
form,
submission: {
data: JSON.parse(JSON.stringify(wizardFormData))
}
};
}
模板通过条件控制首次创建:
<formio
*ngIf="wizardView as view"
[form]="view.form"
[submission]="view.submission">
</formio>
wizardView 初始为 null,因此请求完成前不会创建 <formio>。这样就明确了等待数据和创建组件的先后关系。单独延迟 [submission] 赋值,不能达到相同效果。
该示例用于首次打开记录。请求失败时应显示加载错误;不要在每次字段变化时重新创建整个表单,否则可能丢失用户正在编辑的内容。如果 schema 包含函数等非 JSON 数据,应使用适合项目的克隆方式。
表单已经存在:刷新 Select 实例
如果必须保留原来的 dataSrc: 'custom',可以在选项到达后调用 Select 的 updateItems(null, true),等待 itemsLoaded,必要时再调用 setValue()。不要把 await updateItems() 当作等待加载完成的通用写法;所查阅的实现没有保证它返回代表加载完成的 Promise。相关实现
先通过 Angular 的 (ready) 保存渲染器:
<formio
[form]="wizardSchema"
[submission]="nebulaWizardData"
(ready)="onFormReady($event)">
</formio>
private renderer: any;
onFormReady(event: any): void {
this.renderer = event.formio || event;
}
async refreshTreatmentStage(): Promise<void> {
const values =
await this._commonDynamicWizard.getTreatmentStagesValues();
const form = this.renderer;
if (!form) {
throw new Error('Form.io renderer is not ready');
}
await form.ready;
const select = form.getComponent(
'yporAssessment.treatmentStageId'
);
if (!select || select.component.type !== 'select') {
throw new Error('Treatment Stage select was not found');
}
const currentValue = select.dataValue;
// 前提:custom 表达式读取的就是这个对象。
this._commonDynamicWizard.treatmentStagesValues = values;
select.updateItems(null, true);
await select.itemsLoaded;
select.setValue(currentValue);
}
在表单 ready 后,需要重新加载选项时调用这个方法。(ready) 的事件参数可能因封装版本而异;所查阅的 Angular 源码 发送的是带有 .formio 属性的 Angular 组件,因此示例兼容了这两种取法。
这里保留刷新前的当前值,避免用最初的 submission 覆盖用户已经修改的值。相关实例方法需要对照项目实际安装的版本确认,也不应直接操作 Form.io 内部管理的 Choices.js 实例。
wizard 跨标签重复字段要单独检查
不能把保持各标签的 key 一致作为通用修复方式。需要先确认这些控件是否代表同一个业务值。
- 如果代表不同值,应使用不同 key,或放入提供独立数据作用域的 Container。普通标签或布局分组不能直接视为独立数据作用域。
- 如果代表同一个值,优先保留一个可编辑输入,在其他位置展示其值,可以减少多个输入同时写入同一路径的情况。
- 如果必须保留多个输入,需要明确同步规则,并定位当前页的目标实例。存在重复 key 时,不能假设一次
getComponent()就能取得当前可见的控件。
首次加载示例会给所有匹配字段配置选项,但不会消除它们共享数据路径的问题。运行时刷新示例则以能够唯一定位目标 Select 为前提。对于切页后才挂载的控件,应根据实际版本的页面渲染生命周期处理,避免缓存已经卸载的实例。
最后核对数据路径和选项内容
字段 key 为 yporAssessment.treatmentStageId 时,通常应检查 submission 是否具有对应的嵌套结构:
{
"data": {
"yporAssessment": {
"treatmentStageId": 2
}
}
}
还要确认选项数组中包含对应的 TreatmentStageId,且该对象的 text 非空。检查 2 与 "2" 的类型是否符合当前组件配置,避免依赖隐式转换。
如果继续使用原来的 custom 表达式,应确认 angularComponent 已通过项目的集成方式注入,并确认它与 _commonDynamicWizard 的对象关系正确。Angular 服务中有数据,不能证明表达式读取的对象也有数据。
lazyLoad: false 不能替代完整的加载时序控制,setTimeout 也不能保证请求和组件挂载都已完成。应以数据请求完成、渲染器 ready 和选项加载完成作为同步条件,再验证首次打开及往返切页时的回显。
备注:内容仅供参考。
