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:
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"unprovide"
|
||||
]
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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] === '['),
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user