关于 umbraco-validation-context
面向 Umbraco 后台表单开发者的验证技能,介绍 UmbValidationContext 和基于 JSON Path 的消息管理。涵盖客户端与服务端错误,以及多标签表单的响应式错误计数。
主要功能
- 01使用 UmbValidationContext 集中管理验证消息
- 02通过 JSON Path 绑定字段
- 03处理客户端和服务端错误
- 04按路径及其后代观察错误
- 05在标签页和区块显示错误数
适用场景
- 01验证多步骤表单中的字段
- 02显示 API 返回的错误
- 03引导用户定位有问题的标签页或区块
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ umbraco/ umbraco-cms-backoffice-skills --skill umbraco-validation-context安装后在 Claude Code 中输入/umbraco-validation-context确认已加载。
详细介绍
Umbraco 验证上下文
这是什么?
UmbValidationContext 为 Umbraco backoffice 中的表单提供了集中式验证系统。它使用 JSON Path 表示法管理验证消息,支持客户端和服务端验证,并可为选项卡和分区提供响应式错误计数。这对于多步骤表单、工作区编辑器,以及任何需要全面验证反馈的 UI 都至关重要。
文档
实现前始终获取最新文档:
- 基础功能:https://docs.umbraco.com/umbraco-cms/extend-your-project/backoffice-extensions/foundation
- 扩展注册表:https://docs.umbraco.com/umbraco-cms/extend-your-project/backoffice-extensions/extending-overview/extension-registry
参考示例
Umbraco 源代码包含可运行的示例:
验证上下文仪表板:/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/validation-context/
此示例演示了带错误计数的多选项卡表单验证。
自定义验证工作区上下文:/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/custom-validation-workspace-context/
此示例展示了工作区级别的验证模式。
相关基础功能技能
-
状态管理:用于观察验证状态变化
- 参考技能:
umbraco-state-management
- 参考技能:
-
上下文 API:用于使用验证上下文
- 参考技能:
umbraco-context-api
- 参考技能:
工作流
- 获取文档——对以上 URL 使用 WebFetch
- 提出问题——有哪些字段?验证规则是什么?是多选项卡表单吗?
- 生成文件——创建包含验证上下文的表单元素
- 说明——展示创建的内容以及验证的工作方式
基本设置
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import {
UMB_VALIDATION_CONTEXT,
umbBindToValidation,
UmbValidationContext,
} from '@umbraco-cms/backoffice/validation';
import type { UmbValidationMessage } from '@umbraco-cms/backoffice/validation';
@customElement('my-validated-form')
export class MyValidatedFormElement extends UmbLitElement {
// Create validation context for this component
readonly validation = new UmbValidationContext(this);
@state()
private _name = '';
@state()
private _email = '';
@state()
private _messages?: UmbValidationMessage[];
constructor() {
super();
// Observe all validation messages
this.consumeContext(UMB_VALIDATION_CONTEXT, (validationContext) => {
this.observe(
validationContext?.messages.messages,
(messages) => {
this._messages = messages;
},
'observeValidationMessages'
);
});
}
override render() {
return html`
<uui-form>
<form>
<div>
<label>Name</label>
<uui-form-validation-message>
<uui-input
type="text"
.value=${this._name}
@input=${(e: InputEvent) => (this._name = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this, '$.form.name', this._name)}
required
></uui-input>
</uui-form-validation-message>
</div>
<div>
<label>Email</label>
<uui-form-validation-message>
<uui-input
type="email"
.value=${this._email}
@input=${(e: InputEvent) => (this._email = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this, '$.form.email', this._email)}
required
></uui-input>
</uui-form-validation-message>
</div>
<uui-button look="primary" @click=${this.#handleSave}>Save</uui-button>
</form>
</uui-form>
<pre>${JSON.stringify(this._messages ?? [], null, 2)}</pre>
`;
}
async #handleSave() {
const isValid = await this.validation.validate();
if (isValid) {
// Form is valid, proceed with save
console.log('Form is valid!');
}
}
}
带错误计数的多选项卡表单
import { html, customElement, state, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbValidationContext, umbBindToValidation } from '@umbraco-cms/backoffice/validation';
@customElement('my-tabbed-form')
export class MyTabbedFormElement extends UmbLitElement {
readonly validation = new UmbValidationContext(this);
@state() private _tab = '1';
@state() private _totalErrors = 0;
@state() private _tab1Errors = 0;
@state() private _tab2Errors = 0;
// Form fields
@state() private _name = '';
@state() private _email = '';
@state() private _city = '';
@state() private _country = '';
constructor() {
super();
// Observe total errors
this.observe(
this.validation.messages.messagesOfPathAndDescendant('$.form'),
(messages) => {
this._totalErrors = [...new Set(messages.map((x) => x.path))].length;
}
);
// Observe Tab 1 errors (using JSON Path prefix)
this.observe(
this.validation.messages.messagesOfPathAndDescendant('$.form.tab1'),
(messages) => {
this._tab1Errors = [...new Set(messages.map((x) => x.path))].length;
}
);
// Observe Tab 2 errors
this.observe(
this.validation.messages.messagesOfPathAndDescendant('$.form.tab2'),
(messages) => {
this._tab2Errors = [...new Set(messages.map((x) => x.path))].length;
}
);
}
override render() {
return html`
<uui-box>
<p>Total errors: ${this._totalErrors}</p>
<uui-tab-group @click=${this.#onTabChange}>
<uui-tab ?active=${this._tab === '1'} data-tab="1">
Tab 1
${when(
this._tab1Errors,
() => html`<uui-badge color="danger">${this._tab1Errors}</uui-badge>`
)}
</uui-tab>
<uui-tab ?active=${this._tab === '2'} data-tab="2">
Tab 2
${when(
this._tab2Errors,
() => html`<uui-badge color="danger">${this._tab2Errors}</uui-badge>`
)}
</uui-tab>
</uui-tab-group>
${when(this._tab === '1', () => this.#renderTab1())}
${when(this._tab === '2', () => this.#renderTab2())}
<uui-button look="primary" @click=${this.#handleSave}>Save</uui-button>
</uui-box>
`;
}
#renderTab1() {
return html`
<uui-form>
<form>
<label>Name</label>
<uui-form-validation-message>
<uui-input
.value=${this._name}
@input=${(e: InputEvent) => (this._name = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this, '$.form.tab1.name', this._name)}
required
></uui-input>
</uui-form-validation-message>
<label>Email</label>
<uui-form-validation-message>
<uui-input
type="email"
.value=${this._email}
@input=${(e: InputEvent) => (this._email = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this, '$.form.tab1.email', this._email)}
required
></uui-input>
</uui-form-validation-message>
</form>
</uui-form>
`;
}
#renderTab2() {
return html`
<uui-form>
<form>
<label>City</label>
<uui-form-validation-message>
<uui-input
.value=${this._city}
@input=${(e: InputEvent) => (this._city = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this, '$.form.tab2.city', this._city)}
required
></uui-input>
</uui-form-validation-message>
<label>Country</label>
<uui-form-validation-message>
<uui-input
.value=${this._country}
@input=${(e: InputEvent) => (this._country = (e.target as HTMLInputElement).value)}
required
></uui-input>
</uui-form-validation-message>
</form>
</uui-form>
`;
}
#onTabChange(e: Event) {
this._tab = (e.target as HTMLElement).getAttribute('data-tab') ?? '1';
}
async #handleSave() {
const isValid = await this.validation.validate();
if (!isValid) {
console.log('Form has validation errors');
}
}
}
服务端验证错误
API 调用后添加服务端验证错误:
async #handleSave() {
// First validate client-side
const isValid = await this.validation.validate();
if (!isValid) return;
try {
// Call API
const response = await this.#saveToServer();
if (!response.ok) {
// Add server validation errors
const errors = await response.json();
for (const error of errors.validationErrors) {
this.validation.messages.addMessage(
'server', // Source
error.path, // JSON Path (e.g., '$.form.name')
error.message, // Error message
crypto.randomUUID() // Unique key
);
}
}
} catch (error) {
console.error('Save failed:', error);
}
}
关键 API
UmbValidationContext
// Create context
const validation = new UmbValidationContext(this);
// Validate all bound fields
const isValid = await validation.validate();
// Access messages manager
validation.messages;
验证消息
// Add a message
validation.messages.addMessage(source, path, message, key);
// Remove messages by source
validation.messages.removeMessagesBySource('server');
// Observe messages for a path and descendants
this.observe(
validation.messages.messagesOfPathAndDescendant('$.form.tab1'),
(messages) => { /* handle messages */ }
);
// Observe all messages
this.observe(
validation.messages.messages,
(messages) => { /* handle all messages */ }
);
umbBindToValidation 指令
// Bind an input to validation
${umbBindToValidation(this, '$.form.fieldName', fieldValue)}
JSON Path 表示法
验证使用 JSON Path 来标识字段:
| 路径 | 说明 |
|---|---|
$.form | 根表单对象 |
$.form.name | 名称字段 |
$.form.tab1.email | tab1 中的电子邮件字段 |
$.form.items[0].value | 第一项的值 |
$.form.items[*].name | 所有项目的名称 |
验证消息接口
interface UmbValidationMessage {
source: string; // 'client' | 'server' | custom
path: string; // JSON Path
message: string; // Error message text
key: string; // Unique identifier
}
最佳实践
- 使用 JSON Path 层级结构——按选项卡/分区组织路径,便于统计错误
- 包装输入框——在输入框外使用
<uui-form-validation-message> - 清除服务端错误——在新一轮验证前移除旧的服务端错误
- 使用唯一键——使用
crypto.randomUUID()生成服务端错误键 - 观察特定路径——使用
messagesOfPathAndDescendant统计指定范围内的错误 - 在选项卡上显示计数——显示错误徽标,引导用户找到问题
就是这样!始终获取最新文档,保持示例精简,并生成完整可运行的代码。
评论
支持 Markdown