Skip to content

Commit 131c9cd

Browse files
hotfix #18735 (#18750)
* fix multiple text string validation * notify about messages * cherry picked fix * protection again unnecessary calls * json path cherry pick + tests * validation message change lock * cherry pick from control lifecycle * optimization * propagate errors * cherry picked sync * query umb-input-multiple-text-string * remove unused import * remove optional chain expression * use ! * outcomment the error handling * outcomment more promise rejection error * Fixed issue with multi URL picker. * remove unesecary warning --------- Co-authored-by: Andy Butland <[email protected]>
1 parent f7854b8 commit 131c9cd

File tree

14 files changed

+202
-147
lines changed

14 files changed

+202
-147
lines changed

src/Umbraco.Web.UI.Client/src/assets/lang/en.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,6 @@ export default {
374374
fileSecurityValidationFailure: 'One or more file security validations have failed',
375375
moveToSameFolderFailed: 'Parent and destination folders cannot be the same',
376376
uploadNotAllowed: 'Upload is not allowed in this location.',
377-
noticeExtensionsServerOverride:
378-
'Regardless of the allowed file types, the following limitations apply system-wide due to the server configuration:',
379377
},
380378
member: {
381379
'2fa': 'Two-Factor Authentication',
@@ -899,7 +897,6 @@ export default {
899897
retrieve: 'Retrieve',
900898
retry: 'Retry',
901899
rights: 'Permissions',
902-
serverConfiguration: 'Server Configuration',
903900
scheduledPublishing: 'Scheduled Publishing',
904901
umbracoInfo: 'Umbraco info',
905902
search: 'Search',

src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ export class UmbContextProviderController<
4848

4949
public override destroy(): void {
5050
if (this.#host) {
51-
this.#host.removeUmbController(this);
51+
const host = this.#host;
5252
(this.#host as unknown) = undefined;
53+
host.removeUmbController(this);
5354
}
5455
super.destroy();
5556
}

src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,13 @@ export class UmbPropertyEditorUIBlockListElement
173173
this.observe(
174174
this.#managerContext.layouts,
175175
(layouts) => {
176+
const validationMessagesToRemove: string[] = [];
176177
const contentKeys = layouts.map((x) => x.contentKey);
177178
this.#validationContext.messages.getMessagesOfPathAndDescendant('$.contentData').forEach((message) => {
178179
// get the KEY from this string: $.contentData[?(@.key == 'KEY')]
179180
const key = extractJsonQueryProps(message.path).key;
180181
if (key && contentKeys.indexOf(key) === -1) {
181-
this.#validationContext.messages.removeMessageByKey(message.key);
182+
validationMessagesToRemove.push(message.key);
182183
}
183184
});
184185

@@ -187,9 +188,12 @@ export class UmbPropertyEditorUIBlockListElement
187188
// get the key from this string: $.settingsData[?(@.key == 'KEY')]
188189
const key = extractJsonQueryProps(message.path).key;
189190
if (key && settingsKeys.indexOf(key) === -1) {
190-
this.#validationContext.messages.removeMessageByKey(message.key);
191+
validationMessagesToRemove.push(message.key);
191192
}
192193
});
194+
195+
// Remove the messages after the loop to prevent changing the array while iterating over it.
196+
this.#validationContext.messages.removeMessageByKeys(validationMessagesToRemove);
193197
},
194198
null,
195199
);

src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation-messages.manager.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ export class UmbValidationMessagesManager {
3333
this.messages.subscribe((x) => console.log(logName, x));
3434
}
3535

36+
getMessages(): Array<UmbValidationMessage> {
37+
return this.#messages.getValue();
38+
}
39+
40+
#updateLock = 0;
41+
initiateChange() {
42+
this.#updateLock++;
43+
this.#messages.mute();
44+
// TODO: When ready enable this code will enable handling a finish automatically by this implementation 'using myState.initiatePropertyValueChange()' (Relies on TS support of Using) [NL]
45+
/*return {
46+
[Symbol.dispose]: this.finishPropertyValueChange,
47+
};*/
48+
}
49+
finishChange() {
50+
this.#updateLock--;
51+
if (this.#updateLock === 0) {
52+
this.#messages.unmute();
53+
}
54+
}
55+
3656
getHasAnyMessages(): boolean {
3757
return this.#messages.getValue().length !== 0;
3858
}
@@ -75,7 +95,9 @@ export class UmbValidationMessagesManager {
7595
if (this.#messages.getValue().find((x) => x.type === type && x.path === path && x.body === body)) {
7696
return;
7797
}
98+
this.initiateChange();
7899
this.#messages.appendOne({ type, key, path, body: body });
100+
this.finishChange();
79101
}
80102

81103
addMessages(type: UmbValidationMessageType, path: string, bodies: Array<string>): void {
@@ -86,27 +108,42 @@ export class UmbValidationMessagesManager {
86108
const newBodies = bodies.filter(
87109
(message) => existingMessages.find((x) => x.type === type && x.path === path && x.body === message) === undefined,
88110
);
111+
this.initiateChange();
89112
this.#messages.append(newBodies.map((body) => ({ type, key: UmbId.new(), path, body })));
113+
this.finishChange();
90114
}
91115

92116
removeMessageByKey(key: string): void {
117+
this.initiateChange();
93118
this.#messages.removeOne(key);
119+
this.finishChange();
94120
}
95121
removeMessageByKeys(keys: Array<string>): void {
122+
if (keys.length === 0) return;
123+
this.initiateChange();
96124
this.#messages.filter((x) => keys.indexOf(x.key) === -1);
125+
this.finishChange();
97126
}
98127
removeMessagesByType(type: UmbValidationMessageType): void {
128+
this.initiateChange();
99129
this.#messages.filter((x) => x.type !== type);
130+
this.finishChange();
100131
}
101132
removeMessagesByPath(path: string): void {
133+
this.initiateChange();
102134
this.#messages.filter((x) => x.path !== path);
135+
this.finishChange();
103136
}
104137
removeMessagesAndDescendantsByPath(path: string): void {
138+
this.initiateChange();
105139
this.#messages.filter((x) => MatchPathOrDescendantPath(x.path, path));
140+
this.finishChange();
106141
}
107142
removeMessagesByTypeAndPath(type: UmbValidationMessageType, path: string): void {
108143
//path = path.toLowerCase();
144+
this.initiateChange();
109145
this.#messages.filter((x) => !(x.type === type && x.path === path));
146+
this.finishChange();
110147
}
111148

112149
#translatePath(path: string): string | undefined {
@@ -124,6 +161,7 @@ export class UmbValidationMessagesManager {
124161

125162
#translators: Array<UmbValidationMessageTranslator> = [];
126163
addTranslator(translator: UmbValidationMessageTranslator): void {
164+
this.initiateChange();
127165
if (this.#translators.indexOf(translator) === -1) {
128166
this.#translators.push(translator);
129167
}
@@ -137,6 +175,7 @@ export class UmbValidationMessagesManager {
137175
this.#messages.updateOne(msg.key, { path: newPath });
138176
}
139177
}
178+
this.finishChange();
140179
}
141180

142181
removeTranslator(translator: UmbValidationMessageTranslator): void {

src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ export class UmbFormControlValidator extends UmbControllerBase implements UmbVal
3535
}
3636
});
3737
this.#control = formControl;
38-
this.#control.addEventListener(UmbValidationInvalidEvent.TYPE, this.#setInvalid);
39-
this.#control.addEventListener(UmbValidationValidEvent.TYPE, this.#setValid);
4038
}
4139

4240
get isValid(): boolean {
@@ -82,12 +80,18 @@ export class UmbFormControlValidator extends UmbControllerBase implements UmbVal
8280

8381
override hostConnected(): void {
8482
super.hostConnected();
83+
this.#control.addEventListener(UmbValidationInvalidEvent.TYPE, this.#setInvalid);
84+
this.#control.addEventListener(UmbValidationValidEvent.TYPE, this.#setValid);
8585
if (this.#context) {
8686
this.#context.addValidator(this);
8787
}
8888
}
8989
override hostDisconnected(): void {
9090
super.hostDisconnected();
91+
if (this.#control) {
92+
this.#control.removeEventListener(UmbValidationInvalidEvent.TYPE, this.#setInvalid);
93+
this.#control.removeEventListener(UmbValidationValidEvent.TYPE, this.#setValid);
94+
}
9195
if (this.#context) {
9296
this.#context.removeValidator(this);
9397
// Remove any messages that this validator has added:
@@ -99,11 +103,9 @@ export class UmbFormControlValidator extends UmbControllerBase implements UmbVal
99103
}
100104

101105
override destroy(): void {
106+
super.destroy();
102107
if (this.#control) {
103-
this.#control.removeEventListener(UmbValidationInvalidEvent.TYPE, this.#setInvalid);
104-
this.#control.removeEventListener(UmbValidationValidEvent.TYPE, this.#setValid);
105108
this.#control = undefined as any;
106109
}
107-
super.destroy();
108110
}
109111
}

src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,12 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
122122
this.observe(
123123
parent.messages.messagesOfPathAndDescendant(dataPath),
124124
(msgs) => {
125+
this.messages.initiateChange();
125126
//this.messages.appendMessages(msgs);
126127
if (this.#parentMessages) {
127128
// Remove the local messages that does not exist in the parent anymore:
128129
const toRemove = this.#parentMessages.filter((msg) => !msgs.find((m) => m.key === msg.key));
129-
this.#parent!.messages.removeMessageByKeys(toRemove.map((msg) => msg.key));
130+
this.messages.removeMessageByKeys(toRemove.map((msg) => msg.key));
130131
}
131132
this.#parentMessages = msgs;
132133
msgs.forEach((msg) => {
@@ -139,6 +140,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
139140
// Notice, the local message uses the same key. [NL]
140141
this.messages.addMessage(msg.type, path, msg.body, msg.key);
141142
});
143+
this.messages.finishChange();
142144
},
143145
'observeParentMessages',
144146
);
@@ -147,6 +149,9 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
147149
this.messages.messages,
148150
(msgs) => {
149151
if (!this.#parent) return;
152+
153+
this.#parent!.messages.initiateChange();
154+
150155
//this.messages.appendMessages(msgs);
151156
if (this.#localMessages) {
152157
// Remove the parent messages that does not exist locally anymore:
@@ -165,13 +170,28 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
165170
// Notice, the parent message uses the same key. [NL]
166171
this.#parent!.messages.addMessage(msg.type, path, msg.body, msg.key);
167172
});
173+
174+
this.#parent!.messages.finishChange();
168175
},
169176
'observeLocalMessages',
170177
);
171178
}).skipHost();
172179
// Notice skipHost ^^, this is because we do not want it to consume it self, as this would be a match for this consumption, instead we will look at the parent and above. [NL]
173180
}
174181

182+
override hostConnected(): void {
183+
super.hostConnected();
184+
if (this.#parent) {
185+
this.#parent.addValidator(this);
186+
}
187+
}
188+
override hostDisconnected(): void {
189+
super.hostDisconnected();
190+
if (this.#parent) {
191+
this.#parent.removeValidator(this);
192+
}
193+
}
194+
175195
/**
176196
* Get if this context is valid.
177197
* Notice this does not verify the validity.
@@ -229,18 +249,27 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
229249
() => false,
230250
);
231251

232-
if (!this.messages) {
252+
/*if (this.#validators.length === 0 && resultsStatus === false) {
253+
throw new Error('No validators to validate, but validation failed');
254+
}*/
255+
256+
if (this.messages === undefined) {
233257
// This Context has been destroyed while is was validating, so we should not continue.
234258
return Promise.reject();
235259
}
236260

261+
const hasMessages = this.messages.getHasAnyMessages();
262+
237263
// If we have any messages then we are not valid, otherwise lets check the validation results: [NL]
238264
// This enables us to keep client validations though UI is not present anymore — because the client validations got defined as messages. [NL]
239-
const isValid = this.messages.getHasAnyMessages() ? false : resultsStatus;
265+
const isValid = hasMessages ? false : resultsStatus;
240266

241267
this.#isValid = isValid;
242268

243269
if (isValid === false) {
270+
/*if (hasMessages === false && resultsStatus === false) {
271+
throw new Error('Missing validation messages to represent why a child validation context is invalid.');
272+
}*/
244273
// Focus first invalid element:
245274
this.focusFirstInvalidElement();
246275
return Promise.reject();
@@ -278,6 +307,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
278307
}
279308

280309
override destroy(): void {
310+
this.#providerCtrl?.destroy();
281311
this.#providerCtrl = undefined;
282312
if (this.#parent) {
283313
this.#parent.removeValidator(this);

0 commit comments

Comments
 (0)