umbraco-validation-context

安装量: 57
排名: #13026

安装

npx skills add https://github.com/umbraco/umbraco-cms-backoffice-skills --skill umbraco-validation-context
Umbraco Validation Context
What is it?
UmbValidationContext provides a centralized validation system for forms in the Umbraco backoffice. It manages validation messages using JSON Path notation, supports both client-side and server-side validation, and enables reactive error counting for tabs and sections. This is essential for multi-step forms, workspace editors, and any UI that requires comprehensive validation feedback.
Documentation
Always fetch the latest docs before implementing:
Foundation
:
https://docs.umbraco.com/umbraco-cms/customizing/foundation
Extension Registry
:
https://docs.umbraco.com/umbraco-cms/customizing/extending-overview/extension-registry
Reference Examples
The Umbraco source includes working examples:
Validation Context Dashboard
:
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/validation-context/
This example demonstrates multi-tab form validation with error counting.
Custom Validation Workspace Context
:
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/custom-validation-workspace-context/
This example shows workspace-level validation patterns.
Related Foundation Skills
State Management
For observing validation state changes
Reference skill:
umbraco-state-management
Context API
For consuming validation context Reference skill: umbraco-context-api Workflow Fetch docs - Use WebFetch on the URLs above Ask questions - What fields? What validation rules? Multi-tab form? Generate files - Create form element with validation context Explain - Show what was created and how validation works Basic Setup 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 `
( this . _name = ( e . target as HTMLInputElement ) . value ) } ${ umbBindToValidation ( this , '$.form.name' , this . _name ) } required >
( this . _email = ( e . target as HTMLInputElement ) . value ) } ${ umbBindToValidation ( this , '$.form.email' , this . _email ) } required >
Save

${
JSON
.
stringify
(
this
.
_messages
??
[
]
,
null
,
2
)
}

` ; } async

handleSave

( ) { const isValid = await this . validation . validate ( ) ; if ( isValid ) { // Form is valid, proceed with save console . log ( 'Form is valid!' ) ; } } } Multi-Tab Form with Error Counting 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 `

Total errors: ${ this . _totalErrors }

<uui-tab-group @click= ${ this .

onTabChange

}

Tab 1 ${ when ( this . _tab1Errors , ( ) => html <uui-badge color="danger"> ${ this . _tab1Errors } </uui-badge> ) } Tab 2 ${ when ( this . _tab2Errors , ( ) => html <uui-badge color="danger"> ${ this . _tab2Errors } </uui-badge> ) } ${ when ( this . _tab === '1' , ( ) => this .

renderTab1

( ) ) } ${ when ( this . _tab === '2' , ( ) => this .

renderTab2

( ) ) } <uui-button look="primary" @click= ${ this .

handleSave

}

Save ` ; }

renderTab1

( ) { return html `

( this . _name = ( e . target as HTMLInputElement ) . value ) } ${ umbBindToValidation ( this , '$.form.tab1.name' , this . _name ) } required > ( this . _email = ( e . target as HTMLInputElement ) . value ) } ${ umbBindToValidation ( this , '$.form.tab1.email' , this . _email ) } required >

` ; }

renderTab2

( ) { return html `

( this . _city = ( e . target as HTMLInputElement ) . value ) } ${ umbBindToValidation ( this , '$.form.tab2.city' , this . _city ) } required > ( this . _country = ( e . target as HTMLInputElement ) . value ) } required >

` ; }

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' ) ; } } } Server-Side Validation Errors Add server validation errors after an API call: 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 ) ; } } Key APIs UmbValidationContext // Create context const validation = new UmbValidationContext ( this ) ; // Validate all bound fields const isValid = await validation . validate ( ) ; // Access messages manager validation . messages ; 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 Directive // Bind an input to validation $ { umbBindToValidation ( this , '$.form.fieldName' , fieldValue ) } JSON Path Notation Validation uses JSON Path to identify fields: Path Description $.form Root form object $.form.name Name field $.form.tab1.email Email field in tab1 $.form.items[0].value First item's value $.form.items[*].name All item names Validation Message Interface interface UmbValidationMessage { source : string ; // 'client' | 'server' | custom path : string ; // JSON Path message : string ; // Error message text key : string ; // Unique identifier } Best Practices Use JSON Path hierarchy - Organize paths by tab/section for easy error counting Wrap inputs - Use around inputs Clear server errors - Remove old server errors before new validation Unique keys - Use crypto.randomUUID() for server error keys Observe specific paths - Use messagesOfPathAndDescendant for scoped error counts Show counts on tabs - Display error badges to guide users to problems That's it! Always fetch fresh docs, keep examples minimal, generate complete working code.

返回排行榜