Feature: validation synchronization as opt in (#18798)

* allow for this word

* getMessages

* split inherit and sync method

* sync feature

* rename sync report

* auto report + impl

* remove log

* double inheritance test

* one more test
This commit is contained in:
Niels Lyngsø
2025-03-25 15:48:07 +01:00
committed by GitHub
parent 10f37494b6
commit 0dd4443b75
10 changed files with 392 additions and 66 deletions

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"cSpell.words": [
"unprovide"
]
}

View File

@@ -95,6 +95,7 @@ export class UmbPropertyEditorUIBlockGridElement
if (dataPath) {
// Set the data path for the local validation context:
this.#validationContext.setDataPath(dataPath);
this.#validationContext.autoReport();
}
},
'observeDataPath',

View File

@@ -261,6 +261,7 @@ export class UmbPropertyEditorUIBlockListElement
if (dataPath) {
// Set the data path for the local validation context:
this.#validationContext.setDataPath(dataPath);
this.#validationContext.autoReport();
}
},
'observeDataPath',

View File

@@ -486,6 +486,13 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
this.#expose(layoutData.contentKey);
this.setIsNew(false);
this.#reportValidation();
}
#reportValidation() {
this.content.validation.report();
this.settings.validation.report();
}
expose() {

View File

@@ -228,6 +228,7 @@ export abstract class UmbContentDetailWorkspaceContextBase<
if (missingThis) {
const context = new UmbValidationController(this);
context.inheritFrom(this.validationContext, '$');
context.autoReport();
context.setVariantId(UmbVariantId.Create(variantOption));
this.#variantValidationContexts.push(context);
}

View File

@@ -32,7 +32,11 @@ export class UmbValidationMessagesManager {
messages = this.#messages.asObservable();
filteredMessages = this.#messages.asObservablePart((msgs) => (this.#filter ? msgs.filter(this.#filter) : msgs));
getFilteredMessages(): Array<UmbValidationMessage> {
getNotFilteredMessages(): Array<UmbValidationMessage> {
return this.#messages.getValue();
}
getMessages(): Array<UmbValidationMessage> {
const msgs = this.#messages.getValue();
return this.#filter ? msgs.filter(this.#filter) : msgs;
}
@@ -68,12 +72,12 @@ export class UmbValidationMessagesManager {
}
getHasAnyMessages(): boolean {
return this.getFilteredMessages().length !== 0;
return this.getMessages().length !== 0;
}
getMessagesOfPathAndDescendant(path: string): Array<UmbValidationMessage> {
//path = path.toLowerCase();
return this.getFilteredMessages().filter((x) => MatchPathOrDescendantPath(x.path, path));
return this.getMessages().filter((x) => MatchPathOrDescendantPath(x.path, path));
}
messagesOfPathAndDescendant(path: string): Observable<Array<UmbValidationMessage>> {
@@ -99,7 +103,7 @@ export class UmbValidationMessagesManager {
}
getHasMessagesOfPathAndDescendant(path: string): boolean {
//path = path.toLowerCase();
return this.getFilteredMessages().some(
return this.getMessages().some(
(x) =>
x.path.indexOf(path) === 0 &&
(x.path.length === path.length || x.path[path.length] === '.' || x.path[path.length] === '['),

View File

@@ -17,16 +17,24 @@ describe('UmbValidationController', () => {
ctrl = new UmbValidationController(host);
});
afterEach(() => {
host.destroy();
});
describe('Basics', () => {
it('is invalid when holding messages', async () => {
ctrl.messages.addMessage('server', '$.test', 'test');
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.false;
});
it('is valid when holding no messages', async () => {
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.true;
});
it('is invalid when holding messages', async () => {
ctrl.messages.addMessage('server', '$.test', 'test');
await ctrl.validate().catch(() => undefined);
it('is not valid in its initial state', async () => {
expect(ctrl.isValid).to.be.false;
});
});
@@ -70,14 +78,24 @@ describe('UmbValidationController', () => {
beforeEach(() => {
child = new UmbValidationController(host);
});
afterEach(() => {
child.destroy();
});
it('is invalid when not inherited a message', async () => {
it('is valid despite a child begin created', async () => {
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.true;
expect(ctrl.messages.getHasAnyMessages()).to.be.false;
});
it('is valid when not inherited a message', async () => {
ctrl.messages.addMessage('server', "$.values[?(@.alias == 'my-other')].value.test", 'test');
child.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
await Promise.resolve();
await ctrl.validate().catch(() => undefined);
await child.validate().catch(() => undefined);
expect(child.isValid).to.be.true;
expect(child.messages.getHasAnyMessages()).to.be.false;
});
@@ -89,22 +107,97 @@ describe('UmbValidationController', () => {
await ctrl.validate().catch(() => undefined);
expect(child.isValid).to.be.false;
expect(child.messages.getHasAnyMessages()).to.be.true;
expect(child.messages.getFilteredMessages()?.[0].body).to.be.equal('test-body');
expect(child.messages.getMessages()?.[0].body).to.be.equal('test-body');
});
it('is invalid base on a message bubbling up', async () => {
it('is invalid bases on a message from a parent', async () => {
ctrl.messages.addMessage('server', "$.values[?(@.alias == 'my-property')].value.test", 'test-body');
child.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child.autoReport();
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
expect(ctrl.messages.getFilteredMessages()?.[0].body).to.be.equal('test-body');
expect(ctrl.messages.getMessages()?.[0].body).to.be.equal('test-body');
});
it('is invalid based on a synced message from a child', async () => {
child.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child.messages.addMessage('server', '$.test', 'test-body');
child.autoReport();
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
expect(ctrl.messages.getMessages()?.[0].body).to.be.equal('test-body');
});
it('is invalid based on a syncOnce message from a child', async () => {
child.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child.messages.addMessage('server', '$.test', 'test-body');
child.report();
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
expect(ctrl.messages.getMessages()?.[0].body).to.be.equal('test-body');
});
it('is invalid based on a syncOnce message from a child who later got the message removed.', async () => {
child.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child.messages.addMessage('server', '$.test', 'test-body', 'test-msg-key');
child.report();
child.messages.removeMessageByKey('test-msg-key');
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
expect(ctrl.messages.getMessages()?.[0].body).to.be.equal('test-body');
});
it('is valid based on a syncOnce message from a child who later got removed and syncOnce.', async () => {
child.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child.messages.addMessage('server', '$.test', 'test-body', 'test-msg-key');
child.report();
child.messages.removeMessageByKey('test-msg-key');
child.report();
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.true;
expect(ctrl.messages.getHasAnyMessages()).to.be.false;
});
it('is valid despite child previously had a syncOnce executed', async () => {
child.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child.report();
child.messages.addMessage('server', '$.test', 'test-body');
expect(child.isValid).to.be.false;
expect(child.messages.getHasAnyMessages()).to.be.true;
expect(child.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body');
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.true;
expect(ctrl.messages.getHasAnyMessages()).to.be.false;
});
it('is still valid despite non-synchronizing child is invalid', async () => {
child.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child.messages.addMessage('server', '$.test', 'test-body');
await ctrl.validate().catch(() => undefined);
await child.validate().catch(() => undefined);
expect(child.isValid).to.be.false;
expect(child.messages.getHasAnyMessages()).to.be.true;
expect(child.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body');
expect(ctrl.isValid).to.be.true;
expect(ctrl.messages.getHasAnyMessages()).to.be.false;
});
it('is valid when a message has been removed from a child context', async () => {
ctrl.messages.addMessage('server', "$.values[?(@.alias == 'my-property')].value.test", 'test-body');
child.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child.autoReport();
// First they are invalid:
await ctrl.validate().catch(() => undefined);
@@ -123,13 +216,27 @@ describe('UmbValidationController', () => {
expect(ctrl.messages.getHasAnyMessages()).to.be.false;
});
it('is still invalid despite a message has been removed from a non-synchronizing child context', async () => {
ctrl.messages.addMessage('server', "$.values[?(@.alias == 'my-property')].value.test", 'test-body');
child.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child.messages.removeMessagesByPath('$.test');
// After the removal they are valid:
await child.validate().catch(() => undefined);
expect(child.isValid).to.be.true;
expect(child.messages.getHasAnyMessages()).to.be.false;
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
});
describe('Inheritance + Variant Filter', () => {
it('is invalid when not inherited a message', async () => {
it('is valid when not inherited a message', async () => {
child.setVariantId(new UmbVariantId('en-us'));
child.inheritFrom(
ctrl,
"$.values[?(@.alias == 'my-property' && @.culture == 'en-us' && @.segment == null)].value",
);
child.autoReport();
ctrl.messages.addMessage(
'server',
@@ -168,6 +275,7 @@ describe('UmbValidationController', () => {
'test-body',
);
child.inheritFrom(ctrl, '$');
child.autoReport();
// First they are invalid:
await ctrl.validate().catch(() => undefined);
@@ -189,4 +297,148 @@ describe('UmbValidationController', () => {
});
});
});
describe('Double inheritance', () => {
let child1: UmbValidationController;
let child2: UmbValidationController;
beforeEach(() => {
child1 = new UmbValidationController(host);
child2 = new UmbValidationController(host);
});
afterEach(() => {
child1.destroy();
child2.destroy();
});
it('is auto reporting from two sub contexts', async () => {
ctrl.messages.addMessage('server', "$.values[?(@.alias == 'my-property-1')].value.test", 'test-body-1');
ctrl.messages.addMessage('server', "$.values[?(@.alias == 'my-property-2')].value.test", 'test-body-2');
child1.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property-1')].value");
child2.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property-2')].value");
child1.autoReport();
child2.autoReport();
// First they are invalid:
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
expect(child1.isValid).to.be.false;
expect(child1.messages.getHasAnyMessages()).to.be.true;
expect(child1.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body-1');
expect(child2.isValid).to.be.false;
expect(child2.messages.getHasAnyMessages()).to.be.true;
expect(child2.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body-2');
child1.messages.removeMessagesByPath('$.test');
await child1.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
expect(child1.isValid).to.be.true;
expect(child1.messages.getHasAnyMessages()).to.be.false;
expect(child2.isValid).to.be.false;
expect(child2.messages.getHasAnyMessages()).to.be.true;
child2.messages.removeMessagesByPath('$.test');
await child2.validate().catch(() => undefined);
expect(child1.isValid).to.be.true;
expect(child1.messages.getHasAnyMessages()).to.be.false;
expect(child2.isValid).to.be.true;
expect(child2.messages.getHasAnyMessages()).to.be.false;
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid, 'root context to be valid').to.be.true;
expect(ctrl.messages.getHasAnyMessages(), 'root context have no messages').to.be.false;
});
it('is reporting between two sub context', async () => {
ctrl.messages.addMessage('server', "$.values[?(@.alias == 'my-property')].value.test1", 'test-body-1');
ctrl.messages.addMessage('server', "$.values[?(@.alias == 'my-property')].value.test2", 'test-body-2');
child1.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child2.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child1.autoReport();
child2.autoReport();
await Promise.resolve();
// First they are invalid:
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
expect(child1.isValid).to.be.false;
expect(child1.messages.getHasAnyMessages()).to.be.true;
expect(child1.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body-1');
expect(child1.messages.getNotFilteredMessages()?.[1].body).to.be.equal('test-body-2');
expect(child2.isValid).to.be.false;
expect(child2.messages.getHasAnyMessages()).to.be.true;
expect(child2.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body-1');
expect(child2.messages.getNotFilteredMessages()?.[1].body).to.be.equal('test-body-2');
child1.messages.removeMessagesByPath('$.test1');
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
expect(child1.isValid).to.be.false;
expect(child1.messages.getHasAnyMessages()).to.be.true;
expect(child1.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body-2');
expect(child2.isValid).to.be.false;
expect(child2.messages.getHasAnyMessages()).to.be.true;
expect(child2.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body-2');
child2.messages.removeMessagesByPath('$.test2');
// Only need to validate the root, because the other controllers are auto reporting.
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid, 'root context is valid').to.be.true;
expect(child1.isValid, 'child1 context is valid').to.be.true;
expect(child2.isValid, 'child2 context is valid').to.be.true;
});
it('is reporting between two sub context', async () => {
ctrl.messages.addMessage('server', "$.values[?(@.alias == 'my-property')].value.test1", 'test-body-1');
ctrl.messages.addMessage('server', "$.values[?(@.alias == 'my-property')].value.test2", 'test-body-2');
child1.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
child2.inheritFrom(ctrl, "$.values[?(@.alias == 'my-property')].value");
await Promise.resolve();
// First they are invalid:
await ctrl.validate().catch(() => undefined);
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
expect(child1.isValid).to.be.false;
expect(child1.messages.getHasAnyMessages()).to.be.true;
expect(child1.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body-1');
expect(child1.messages.getNotFilteredMessages()?.[1].body).to.be.equal('test-body-2');
expect(child2.isValid).to.be.false;
expect(child2.messages.getHasAnyMessages()).to.be.true;
expect(child2.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body-1');
expect(child2.messages.getNotFilteredMessages()?.[1].body).to.be.equal('test-body-2');
child1.messages.removeMessagesByPath('$.test1');
child1.report();
expect(ctrl.isValid).to.be.false;
expect(ctrl.messages.getHasAnyMessages()).to.be.true;
expect(child1.isValid).to.be.false;
expect(child1.messages.getHasAnyMessages()).to.be.true;
expect(child1.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body-2');
expect(child2.isValid).to.be.false;
expect(child2.messages.getHasAnyMessages()).to.be.true;
expect(child2.messages.getNotFilteredMessages()?.[0].body).to.be.equal('test-body-2');
child2.messages.removeMessagesByPath('$.test2');
child2.report();
// We need to validate to the not auto reporting validation controllers updating their isValid state.
await ctrl.validate().catch(() => undefined);
await child1.validate().catch(() => undefined);
await child2.validate().catch(() => undefined);
expect(ctrl.isValid, 'root context is valid').to.be.true;
expect(child1.isValid, 'child1 context is valid').to.be.true;
expect(child2.isValid, 'child2 context is valid').to.be.true;
});
});
});

View File

@@ -9,6 +9,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import { ReplaceStartOfPath } from '../utils/replace-start-of-path.function.js';
import type { UmbVariantId } from '../../variant/variant-id.class.js';
import { UmbDeprecation } from '../../utils/deprecation/deprecation.js';
const Regex = /@\.culture == ('[^']*'|null) *&& *@\.segment == ('[^']*'|null)/g;
@@ -26,15 +27,31 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
>;
#inUnprovidingState: boolean = false;
// @reprecated - Will be removed in v.17
// Local version of the data send to the server, only use-case is for translation.
#translationData = new UmbObjectState<any>(undefined);
/**
* @deprecated Use extension type 'propertyValidationPathTranslator' instead. Will be removed in v.17
*/
translationDataOf(path: string): any {
return this.#translationData.asObservablePart((data) => GetValueByJsonPath(data, path));
}
/**
* @deprecated Use extension type 'propertyValidationPathTranslator' instead. Will be removed in v.17
*/
setTranslationData(data: any): void {
this.#translationData.setValue(data);
}
/**
* @deprecated Use extension type 'propertyValidationPathTranslator' instead. Will be removed in v.17
*/
getTranslationData(): any {
new UmbDeprecation({
removeInVersion: '17',
deprecated: 'getTranslationData',
solution: 'getTranslationData is deprecated.',
}).warn();
return this.#translationData.getValue();
}
@@ -43,6 +60,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
#isValid: boolean = false;
#parent?: UmbValidationController;
#sync?: boolean;
#parentMessages?: Array<UmbValidationMessage>;
#localMessages?: Array<UmbValidationMessage>;
#baseDataPath?: string;
@@ -135,8 +153,9 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
/**
* Define a specific data path for this validation context.
* This will turn this validation context into a sub-context of the parent validation context.
* This means that a two-way binding for messages will be established between the parent and the sub-context.
* And it will inherit the Translation Data from its parent.
* This will make this context inherit the messages from the parent validation context.
* @see {@link report} Call `report()` to propagate changes to the parent context.
* @see {@link autoReport} Call `autoReport()` to continuously synchronize changes to the parent context.
*
* messages and data will be localizes accordingly to the given data path.
* @param dataPath {string} - The data path to bind this validation context to.
@@ -176,12 +195,14 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
this.#parent.removeValidator(this);
}
this.#parent = parent;
parent.addValidator(this);
this.#readyToSync();
this.messages.clear();
this.#localMessages = undefined;
this.#baseDataPath = dataPath;
// @deprecated - Will be removed in v.17
this.observe(
parent.translationDataOf(dataPath),
(data) => {
@@ -214,14 +235,60 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
this.messages.addMessage(msg.type, path, msg.body, msg.key);
});
}
this.#localMessages = this.messages.getNotFilteredMessages();
this.messages.finishChange();
},
'observeParentMessages',
);
}
this.observe(
this.messages.messages,
(msgs) => {
#stopInheritance(): void {
this.removeUmbControllerByAlias('observeTranslationData');
this.removeUmbControllerByAlias('observeParentMessages');
if (this.#parent) {
this.#parent.removeValidator(this);
}
this.messages.clear();
this.#localMessages = undefined;
this.setTranslationData(undefined);
}
#readyToSync() {
if (this.#sync && this.#parent) {
this.#parent.addValidator(this);
}
}
/**
* Continuously synchronize the messages from this context to the parent context.
*/
autoReport() {
this.#sync = true;
this.#readyToSync();
this.observe(this.messages.messages, this.#transferMessages, 'observeLocalMessages');
}
// no need for this method at this movement. [NL]
/*
#stopSync() {
this.removeUmbControllerByAlias('observeLocalMessages');
}
*/
/**
* Perform a one time transfer of the messages from this context to the parent context.
*/
report(): void {
if (!this.#parent) return;
if (!this.#sync) {
this.#transferMessages(this.messages.getNotFilteredMessages());
}
}
#transferMessages = (msgs: Array<UmbValidationMessage>) => {
if (!this.#parent) return;
this.#parent!.messages.initiateChange();
@@ -231,7 +298,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
const toRemove = this.#localMessages.filter((msg) => !msgs.find((m) => m.key === msg.key));
this.#parent!.messages.removeMessageByKeys(toRemove.map((msg) => msg.key));
}
this.#localMessages = msgs;
if (this.#baseDataPath === '$') {
this.#parent!.messages.addMessageObjects(msgs);
} else {
@@ -239,9 +306,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
// replace this.#baseDataPath (if it starts with it) with $ in the path, so it becomes relative to the parent context
const path = ReplaceStartOfPath(msg.path, '$', this.#baseDataPath!);
if (path === undefined) {
throw new Error(
'Path was not transformed correctly and can therefor not be synced with parent messages.',
);
throw new Error('Path was not transformed correctly and can therefor not be synced with parent messages.');
}
// Notice, the parent message uses the same key. [NL]
this.#parent!.messages.addMessage(msg.type, path, msg.body, msg.key);
@@ -249,28 +314,11 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
}
this.#parent!.messages.finishChange();
},
'observeLocalMessages',
);
}
#stopInheritance(): void {
if (this.#parent) {
this.#parent.removeValidator(this);
}
this.messages.clear();
this.setTranslationData(undefined);
this.removeUmbControllerByAlias('observeTranslationData');
this.removeUmbControllerByAlias('observeParentMessages');
this.removeUmbControllerByAlias('observeLocalMessages');
}
};
override hostConnected(): void {
super.hostConnected();
if (this.#parent) {
this.#parent.addValidator(this);
}
this.#readyToSync();
}
override hostDisconnected(): void {
super.hostDisconnected();
@@ -317,7 +365,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
this.#validators.splice(index, 1);
// If we are in validation mode then we should re-validate to focus next invalid element:
if (this.#validationMode) {
this.validate();
this.validate().catch(() => undefined);
}
}
}
@@ -328,10 +376,12 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
* @returns succeed {Promise<boolean>} - Returns a promise that resolves to true if the validation succeeded.
*/
async validate(): Promise<void> {
// TODO: clear server messages here?, well maybe only if we know we will get new server messages? Do the server messages hook into the system like another validator?
this.#validationMode = true;
const resultsStatus = await Promise.all(this.#validators.map((v) => v.validate())).then(
const resultsStatus =
this.#validators.length === 0
? true
: await Promise.all(this.#validators.map((v) => v.validate())).then(
() => true,
() => false,
);
@@ -341,10 +391,11 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
}
if (this.messages === undefined) {
// This Context has been destroyed while is was validating, so we should not continue.
// This Context has been destroyed while is was validating, so we should not continue. [NL]
return Promise.reject();
}
// We need to ask again for messages, as they might have been added during the validation process. [NL]
const hasMessages = this.messages.getHasAnyMessages();
// If we have any messages then we are not valid, otherwise lets check the validation results: [NL]
@@ -398,17 +449,20 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
}
override destroy(): void {
this.#validationMode = false;
if (this.#inUnprovidingState === true) {
return;
}
this.#destroyValidators();
this.unprovide();
this.messages?.destroy();
(this.messages as unknown) = undefined;
if (this.#parent) {
this.#parent.removeValidator(this);
}
this.#localMessages = undefined;
this.#parentMessages = undefined;
this.#parent = undefined;
this.#destroyValidators();
this.messages?.destroy();
(this.messages as unknown) = undefined;
super.destroy();
}
}

View File

@@ -103,7 +103,7 @@ export abstract class UmbSubmittableWorkspaceContextBase<WorkspaceDataModelType>
// TODO: Implement developer-mode logging here. [NL]
console.warn(
'Validation failed because of these validation messages still begin present: ',
this.#validationContexts.flatMap((x) => x.messages.getFilteredMessages()),
this.#validationContexts.flatMap((x) => x.messages.getMessages()),
);
onInvalid(error).then(this.#resolveSubmit, this.#rejectSubmit);
},

View File

@@ -145,6 +145,7 @@ export abstract class UmbPropertyEditorUiRteElementBase
if (dataPath) {
// Set the data path for the local validation context:
this.#validationContext.setDataPath(dataPath + '.blocks');
this.#validationContext.autoReport();
}
},
'observeDataPath',