umbraco-validation-context について
Umbraco バックオフィスのフォーム検証を実装する開発者向けのスキルです。UmbValidationContext と JSON Path によるメッセージ管理を扱い、クライアント側・サーバー側のエラーや複数タブのエラー数にも対応します。
主な機能
- 01UmbValidationContext で検証メッセージを一元管理
- 02JSON Path でフィールドをバインド
- 03クライアント側とサーバー側のエラーを処理
- 04パスと子孫のエラーを監視
- 05タブやセクションにエラー数を表示
活用シーン
- 01複数ステップフォームのフィールドを検証
- 02API が返すエラーを表示
- 03問題のあるタブやセクションへユーザーを誘導
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ umbraco/ umbraco-cms-backoffice-skills --skill umbraco-validation-contextインストール後、Claude Code で/umbraco-validation-contextを入力して読み込まれたか確認します。
詳細
Umbraco Validation Context
これは何ですか?
UmbValidationContext は、Umbraco バックオフィス内のフォームに集中型の検証システムを提供します。JSON Path 記法を使用して検証メッセージを管理し、クライアント側とサーバー側の両方の検証をサポートし、タブやセクションのリアクティブなエラーカウントを可能にします。これは、マルチステップフォーム、ワークスペースエディター、および包括的な検証フィードバックを必要とするあらゆる UI に不可欠です。
ドキュメント
実装する前に、常に最新のドキュメントを取得してください:
- Foundation: https://docs.umbraco.com/umbraco-cms/extend-your-project/backoffice-extensions/foundation
- Extension Registry: https://docs.umbraco.com/umbraco-cms/extend-your-project/backoffice-extensions/extending-overview/extension-registry
参考例
Umbraco のソースには、動作する例が含まれています:
Validation Context Dashboard: /Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/validation-context/
この例では、エラーカウントを使用したマルチタブフォームの検証を実演しています。
Custom Validation Workspace Context: /Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/custom-validation-workspace-context/
この例では、ワークスペースレベルの検証パターンを示しています。
関連する Foundation スキル
-
State Management: 検証状態の変化を監視するために
- 参考スキル:
umbraco-state-management
- 参考スキル:
-
Context 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 を使用します:
| Path | Description |
|---|---|
$.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を使用します - タブにカウントを表示する - エラーバッジを表示して、ユーザーを問題へ誘導します
以上です!常に最新のドキュメントを取得し、例は最小限に保ち、完全に動作するコードを生成してください。