Merge pull request #2444 from enkelmedia/example-validation-context-server-error

Fix for issue with multiple server-validation errors
This commit is contained in:
Niels Lyngsø
2024-11-05 10:24:36 +01:00
committed by GitHub
3 changed files with 245 additions and 3 deletions

View File

@@ -0,0 +1,23 @@
import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard';
const dashboard : ManifestDashboard = {
type: 'dashboard',
alias: 'Demo.Dashboard',
name: 'Demo Dashboard Validation Context',
weight: 1000,
element: () => import('./validation-context-dashboard.js'),
meta: {
label: 'Validation Context Demo',
pathname: 'demo'
},
conditions : [
{
alias : "Umb.Condition.SectionAlias",
match : "Umb.Section.Content"
}
]
}
export const manifests = [
dashboard
];

View File

@@ -0,0 +1,219 @@
import { html, customElement, css, state, when } 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';
@customElement('umb-example-validation-context-dashboard')
export class UmbExampleValidationContextDashboard extends UmbLitElement {
readonly validation = new UmbValidationContext(this);
@state()
name = '';
@state()
email = '';
@state()
city = '';
@state()
country = '';
@state()
messages? : any[]
@state()
totalErrorCount = 0;
@state()
tab1ErrorCount = 0;
@state()
tab2ErrorCount = 0;
@state()
tab = "1";
constructor() {
super();
this.consumeContext(UMB_VALIDATION_CONTEXT,(validationContext)=>{
this.observe(validationContext.messages.messages,(messages)=>{
this.messages = messages;
},'observeValidationMessages')
// Observe all errors
this.validation.messages.messagesOfPathAndDescendant('$.form').subscribe((value)=>{
this.totalErrorCount = [...new Set(value.map(x=>x.path))].length;
});
// Observe errors for tab1, note that we only use part of the full JSONPath
this.validation.messages.messagesOfPathAndDescendant('$.form.tab1').subscribe((value)=>{
this.tab1ErrorCount = [...new Set(value.map(x=>x.path))].length;
});
// Observe errors for tab2, note that we only use part of the full JSONPath
this.validation.messages.messagesOfPathAndDescendant('$.form.tab2').subscribe((value)=>{
this.tab2ErrorCount = [...new Set(value.map(x=>x.path))].length;
});
});
}
#onTabChange(e:Event) {
this.tab = (e.target as HTMLElement).getAttribute('data-tab') as string;
}
#handleSave() {
// fake server validation-errors for all fields
if(this.name == '')
this.validation.messages.addMessage('server','$.form.tab1.name','Name server-error message','4875e113-cd0c-4c57-ac92-53d677ba31ec');
if(this.email == '')
this.validation.messages.addMessage('server','$.form.tab1.email','Email server-error message','a47e287b-4ce6-4e8b-8e05-614e7cec1a2a');
if(this.city == '')
this.validation.messages.addMessage('server','$.form.tab2.city','City server-error message','8dfc2f15-fb9a-463b-bcec-2c5d3ba2d07d');
if(this.country == '')
this.validation.messages.addMessage('server','$.form.tab2.country','Country server-error message','d98624f6-82a2-4e94-822a-776b44b01495');
}
override render() {
return html`
<uui-box>
This is a demo of how the Validation Context can be used to validate a form with multiple steps. Start typing in the form or press Save to trigger validation.
<hr/>
Total errors: ${this.totalErrorCount}
<hr/>
<uui-tab-group @click=${this.#onTabChange}>
<uui-tab ?active=${this.tab == '1'} data-tab="1">
Tab 1
${when(this.tab1ErrorCount,()=>html`
<uui-badge color="danger">${this.tab1ErrorCount}</uui-badge>
`)}
</uui-tab>
<uui-tab ?active=${this.tab == '2'} data-tab="2">
Tab 2
${when(this.tab2ErrorCount,()=>html`
<uui-badge color="danger">${this.tab2ErrorCount}</uui-badge>
`)}
</uui-tab>
</uui-tab-group>
${when(this.tab=='1',()=>html`
${this.#renderTab1()}
`)}
${when(this.tab=='2',()=>html`
${this.#renderTab2()}
`)}
<uui-button look="primary" color="positive" @click=${this.#handleSave}>Save</uui-button>
<hr/>
<h3>Validation Context Messages</h3>
<pre>${JSON.stringify(this.messages ?? [],null,3)}</pre>
</uui-box>
`
}
#renderTab1() {
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.tab1.name',this.name)}
required></uui-input>
</uui-form-validation-message>
</div>
<label>E-mail</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>
<div>
<label>City</label>
<uui-form-validation-message>
<uui-input
type="text"
.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>
</div>
<label>Country</label>
<uui-form-validation-message>
<uui-input
type="text"
.value=${this.country}
@input=${(e: InputEvent)=>this.country = (e.target as HTMLInputElement).value}
${umbBindToValidation(this,'$.form.tab2.country',this.country)}
required></uui-input>
</uui-form-validation-message>
</form>
</uui-form>
`
}
static override styles = [css`
uui-badge {
top:0;
right:0;
font-size:10px;
min-width:17px;
min-height:17px;
}
label {
display:block;
}
uui-box {
margin:20px;
}
uui-button {
margin-top:1rem;
}
pre {
text-align:left;
padding:10px;
border:1px dotted #6f6f6f;
background: #f2f2f2;
font-size: 11px;
line-height: 1.3em;
}
`]
}
export default UmbExampleValidationContextDashboard;
declare global {
interface HTMLElementTagNameMap {
'umb-example-validation-context-dashboard': UmbExampleValidationContextDashboard;
}
}

View File

@@ -1,11 +1,10 @@
import type { UmbValidationMessage } from '../context/validation-messages.manager.js';
import { UMB_VALIDATION_CONTEXT } from '../context/validation.context-token.js';
import type { UmbFormControlMixinInterface } from '../mixins/form-control.mixin.js';
import { defaultMemoization } from '@umbraco-cms/backoffice/observable-api';
import { defaultMemoization, simpleHashCode } from '@umbraco-cms/backoffice/observable-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
const ctrlSymbol = Symbol();
const observeSymbol = Symbol();
/**
@@ -13,6 +12,7 @@ const observeSymbol = Symbol();
* This controller will add a custom error to the form control if the validation context has any messages for the specified data path.
*/
export class UmbBindServerValidationToFormControl extends UmbControllerBase {
#context?: typeof UMB_VALIDATION_CONTEXT.TYPE;
#control: UmbFormControlMixinInterface<unknown>;
@@ -41,7 +41,7 @@ export class UmbBindServerValidationToFormControl extends UmbControllerBase {
}
constructor(host: UmbControllerHost, formControl: UmbFormControlMixinInterface<unknown>, dataPath: string) {
super(host, ctrlSymbol);
super(host,'umbFormControlValidation_'+simpleHashCode(dataPath));
this.#control = formControl;
this.consumeContext(UMB_VALIDATION_CONTEXT, (context) => {
this.#context = context;