Skip to content

Commit a86f62e

Browse files
authored
Merge pull request #2444 from enkelmedia/example-validation-context-server-error
Fix for issue with multiple server-validation errors
2 parents f0ad1c6 + 14de3e3 commit a86f62e

File tree

3 files changed

+245
-3
lines changed

3 files changed

+245
-3
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard';
2+
3+
const dashboard : ManifestDashboard = {
4+
type: 'dashboard',
5+
alias: 'Demo.Dashboard',
6+
name: 'Demo Dashboard Validation Context',
7+
weight: 1000,
8+
element: () => import('./validation-context-dashboard.js'),
9+
meta: {
10+
label: 'Validation Context Demo',
11+
pathname: 'demo'
12+
},
13+
conditions : [
14+
{
15+
alias : "Umb.Condition.SectionAlias",
16+
match : "Umb.Section.Content"
17+
}
18+
]
19+
}
20+
21+
export const manifests = [
22+
dashboard
23+
];
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import { html, customElement, css, state, when } from '@umbraco-cms/backoffice/external/lit';
2+
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
3+
import { UMB_VALIDATION_CONTEXT, umbBindToValidation, UmbValidationContext } from '@umbraco-cms/backoffice/validation';
4+
5+
@customElement('umb-example-validation-context-dashboard')
6+
export class UmbExampleValidationContextDashboard extends UmbLitElement {
7+
8+
readonly validation = new UmbValidationContext(this);
9+
10+
@state()
11+
name = '';
12+
13+
@state()
14+
email = '';
15+
16+
@state()
17+
city = '';
18+
19+
@state()
20+
country = '';
21+
22+
@state()
23+
messages? : any[]
24+
25+
@state()
26+
totalErrorCount = 0;
27+
28+
@state()
29+
tab1ErrorCount = 0;
30+
31+
@state()
32+
tab2ErrorCount = 0;
33+
34+
@state()
35+
tab = "1";
36+
37+
constructor() {
38+
super();
39+
40+
this.consumeContext(UMB_VALIDATION_CONTEXT,(validationContext)=>{
41+
42+
this.observe(validationContext.messages.messages,(messages)=>{
43+
this.messages = messages;
44+
},'observeValidationMessages')
45+
46+
// Observe all errors
47+
this.validation.messages.messagesOfPathAndDescendant('$.form').subscribe((value)=>{
48+
this.totalErrorCount = [...new Set(value.map(x=>x.path))].length;
49+
});
50+
51+
// Observe errors for tab1, note that we only use part of the full JSONPath
52+
this.validation.messages.messagesOfPathAndDescendant('$.form.tab1').subscribe((value)=>{
53+
this.tab1ErrorCount = [...new Set(value.map(x=>x.path))].length;
54+
});
55+
56+
// Observe errors for tab2, note that we only use part of the full JSONPath
57+
this.validation.messages.messagesOfPathAndDescendant('$.form.tab2').subscribe((value)=>{
58+
this.tab2ErrorCount = [...new Set(value.map(x=>x.path))].length;
59+
});
60+
61+
});
62+
}
63+
64+
#onTabChange(e:Event) {
65+
this.tab = (e.target as HTMLElement).getAttribute('data-tab') as string;
66+
}
67+
68+
#handleSave() {
69+
70+
// fake server validation-errors for all fields
71+
if(this.name == '')
72+
this.validation.messages.addMessage('server','$.form.tab1.name','Name server-error message','4875e113-cd0c-4c57-ac92-53d677ba31ec');
73+
if(this.email == '')
74+
this.validation.messages.addMessage('server','$.form.tab1.email','Email server-error message','a47e287b-4ce6-4e8b-8e05-614e7cec1a2a');
75+
if(this.city == '')
76+
this.validation.messages.addMessage('server','$.form.tab2.city','City server-error message','8dfc2f15-fb9a-463b-bcec-2c5d3ba2d07d');
77+
if(this.country == '')
78+
this.validation.messages.addMessage('server','$.form.tab2.country','Country server-error message','d98624f6-82a2-4e94-822a-776b44b01495');
79+
}
80+
81+
override render() {
82+
return html`
83+
<uui-box>
84+
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.
85+
<hr/>
86+
Total errors: ${this.totalErrorCount}
87+
<hr/>
88+
<uui-tab-group @click=${this.#onTabChange}>
89+
<uui-tab ?active=${this.tab == '1'} data-tab="1">
90+
Tab 1
91+
${when(this.tab1ErrorCount,()=>html`
92+
<uui-badge color="danger">${this.tab1ErrorCount}</uui-badge>
93+
`)}
94+
</uui-tab>
95+
<uui-tab ?active=${this.tab == '2'} data-tab="2">
96+
Tab 2
97+
${when(this.tab2ErrorCount,()=>html`
98+
<uui-badge color="danger">${this.tab2ErrorCount}</uui-badge>
99+
`)}
100+
</uui-tab>
101+
</uui-tab-group>
102+
103+
${when(this.tab=='1',()=>html`
104+
${this.#renderTab1()}
105+
`)}
106+
${when(this.tab=='2',()=>html`
107+
${this.#renderTab2()}
108+
`)}
109+
110+
<uui-button look="primary" color="positive" @click=${this.#handleSave}>Save</uui-button>
111+
<hr/>
112+
<h3>Validation Context Messages</h3>
113+
<pre>${JSON.stringify(this.messages ?? [],null,3)}</pre>
114+
</uui-box>
115+
`
116+
}
117+
118+
#renderTab1() {
119+
return html`
120+
<uui-form>
121+
<form>
122+
<div>
123+
<label>Name</label>
124+
<uui-form-validation-message>
125+
<uui-input
126+
type="text"
127+
.value=${this.name}
128+
@input=${(e: InputEvent)=>this.name = (e.target as HTMLInputElement).value}
129+
${umbBindToValidation(this,'$.form.tab1.name',this.name)}
130+
required></uui-input>
131+
</uui-form-validation-message>
132+
</div>
133+
<label>E-mail</label>
134+
<uui-form-validation-message>
135+
<uui-input
136+
type="email"
137+
.value=${this.email}
138+
@input=${(e: InputEvent)=>this.email = (e.target as HTMLInputElement).value}
139+
${umbBindToValidation(this,'$.form.tab1.email',this.email)}
140+
required></uui-input>
141+
</uui-form-validation-message>
142+
</form>
143+
</uui-form>
144+
`
145+
}
146+
147+
#renderTab2() {
148+
return html`
149+
<uui-form>
150+
<form>
151+
<div>
152+
<label>City</label>
153+
<uui-form-validation-message>
154+
<uui-input
155+
type="text"
156+
.value=${this.city}
157+
@input=${(e: InputEvent)=>this.city = (e.target as HTMLInputElement).value}
158+
${umbBindToValidation(this,'$.form.tab2.city',this.city)}
159+
required></uui-input>
160+
</uui-form-validation-message>
161+
</div>
162+
<label>Country</label>
163+
<uui-form-validation-message>
164+
<uui-input
165+
type="text"
166+
.value=${this.country}
167+
@input=${(e: InputEvent)=>this.country = (e.target as HTMLInputElement).value}
168+
${umbBindToValidation(this,'$.form.tab2.country',this.country)}
169+
required></uui-input>
170+
</uui-form-validation-message>
171+
</form>
172+
</uui-form>
173+
`
174+
}
175+
176+
177+
178+
static override styles = [css`
179+
180+
uui-badge {
181+
top:0;
182+
right:0;
183+
font-size:10px;
184+
min-width:17px;
185+
min-height:17px;
186+
187+
}
188+
189+
label {
190+
display:block;
191+
}
192+
193+
uui-box {
194+
margin:20px;
195+
}
196+
197+
uui-button {
198+
margin-top:1rem;
199+
}
200+
201+
pre {
202+
text-align:left;
203+
padding:10px;
204+
border:1px dotted #6f6f6f;
205+
background: #f2f2f2;
206+
font-size: 11px;
207+
line-height: 1.3em;
208+
}
209+
210+
`]
211+
}
212+
213+
export default UmbExampleValidationContextDashboard;
214+
215+
declare global {
216+
interface HTMLElementTagNameMap {
217+
'umb-example-validation-context-dashboard': UmbExampleValidationContextDashboard;
218+
}
219+
}

src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import type { UmbValidationMessage } from '../context/validation-messages.manager.js';
22
import { UMB_VALIDATION_CONTEXT } from '../context/validation.context-token.js';
33
import type { UmbFormControlMixinInterface } from '../mixins/form-control.mixin.js';
4-
import { defaultMemoization } from '@umbraco-cms/backoffice/observable-api';
4+
import { defaultMemoization, simpleHashCode } from '@umbraco-cms/backoffice/observable-api';
55
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
66
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
77

8-
const ctrlSymbol = Symbol();
98
const observeSymbol = Symbol();
109

1110
/**
1211
* Binds server validation to a form control.
1312
* This controller will add a custom error to the form control if the validation context has any messages for the specified data path.
1413
*/
1514
export class UmbBindServerValidationToFormControl extends UmbControllerBase {
15+
1616
#context?: typeof UMB_VALIDATION_CONTEXT.TYPE;
1717

1818
#control: UmbFormControlMixinInterface<unknown>;
@@ -41,7 +41,7 @@ export class UmbBindServerValidationToFormControl extends UmbControllerBase {
4141
}
4242

4343
constructor(host: UmbControllerHost, formControl: UmbFormControlMixinInterface<unknown>, dataPath: string) {
44-
super(host, ctrlSymbol);
44+
super(host,'umbFormControlValidation_'+simpleHashCode(dataPath));
4545
this.#control = formControl;
4646
this.consumeContext(UMB_VALIDATION_CONTEXT, (context) => {
4747
this.#context = context;

0 commit comments

Comments
 (0)