From 480cab5f15278fcd2a92ffc69ee89b053934ce5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 24 May 2024 11:27:56 +0200 Subject: [PATCH] entity limit client validation --- .../block-grid-entries.element.ts | 55 ++++++++++++++++--- .../property-editor-ui-block-grid.element.ts | 2 +- .../validation/context/validation.context.ts | 3 +- .../form-control-validator.controller.ts | 2 +- .../validation/mixins/form-control.mixin.ts | 14 ++++- .../workspace/document-workspace.context.ts | 1 + 6 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index 76ee38b35f..afab9ee669 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -11,7 +11,11 @@ import { html, customElement, state, repeat, css, property, nothing } from '@umb import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import '../block-grid-entry/index.js'; import { UmbSorterController, type UmbSorterConfig, type resolvePlacementArgs } from '@umbraco-cms/backoffice/sorter'; -import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; +import { + UmbFormControlMixin, + UmbFormControlValidator, + type UmbFormControlValidatorConfig, +} from '@umbraco-cms/backoffice/validation'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; /** @@ -128,6 +132,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen }); #context = new UmbBlockGridEntriesContext(this); + #controlValidator: UmbFormControlValidator; @property({ attribute: false }) public set areaKey(value: string | null | undefined) { @@ -198,33 +203,66 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen 'observeStylesheet', ); }); + + this.#controlValidator = new UmbFormControlValidator(this, this /*, this.#dataPath*/); } - async #setupValidation() { + async #getLimitValidation() { if (this._areaKey === null) { // This validation setup is not be as configurable as it should be, but it is a start. Alternatively we should consume the manager and observe the configuration. [NL] const manager = await this.#context.getManager(); const config = manager.getEditorConfiguration(); const min = config?.getValueByAlias('validationLimit')?.min ?? 0; const max = config?.getValueByAlias('validationLimit')?.max ?? Infinity; + return { min, max }; + } else { + return { min: 3, max: 4 }; + } + } - this.addValidator( + #rangeUnderflowValidator?: UmbFormControlValidatorConfig; + #rangeOverflowValidator?: UmbFormControlValidatorConfig; + async #setupValidation() { + const rangeLimit = await this.#getLimitValidation(); + + console.log('setup validation', this); + + if (this.#rangeUnderflowValidator) { + this.removeValidator(this.#rangeUnderflowValidator); + this.#rangeUnderflowValidator = undefined; + } + if (rangeLimit.min !== 0) { + this.#rangeUnderflowValidator = this.addValidator( 'rangeUnderflow', () => { - return this.localize.term('validation_entriesShort', [min, min - (this._layoutEntries.length ?? 0)]); + return this.localize.term( + 'validation_entriesShort', + rangeLimit.min, + rangeLimit.min - (this._layoutEntries.length ?? 0), + ); }, () => { - return (this._layoutEntries.length ?? 0) < min; + return (this._layoutEntries.length ?? 0) < rangeLimit.min; }, ); + } - this.addValidator( + if (this.#rangeOverflowValidator) { + this.removeValidator(this.#rangeOverflowValidator); + this.#rangeOverflowValidator = undefined; + } + if (rangeLimit.max !== Infinity) { + this.#rangeOverflowValidator = this.addValidator( 'rangeOverflow', () => { - return this.localize.term('validation_entriesExceed', [max, (this._layoutEntries.length ?? 0) - max]); + return this.localize.term( + 'validation_entriesExceed', + rangeLimit.max, + (this._layoutEntries.length ?? 0) - rangeLimit.max, + ); }, () => { - return (this._layoutEntries.length ?? 0) > max; + return (this._layoutEntries.length ?? 0) > rangeLimit.max; }, ); } @@ -247,6 +285,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen `, )} + ${this._canCreate ? this.#renderCreateButton() : nothing} `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts index fba24dea2d..0c5bbe1676 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts @@ -100,7 +100,7 @@ export class UmbPropertyEditorUIBlockGridElement } render() { - return html``; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation.context.ts index 38d815a4c8..d80a066427 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation.context.ts @@ -68,12 +68,13 @@ export class UmbValidationContext extends UmbContextBase i const resultsStatus = await Promise.all(this.#validators.map((v) => v.validate())).then( () => Promise.resolve(true), - () => Promise.reject(false), + () => Promise.resolve(false), ); // If we have any messages then we are not valid, otherwise lets check the validation results: [NL] // This enables us to keep client validations though UI is not present anymore — because the client validations got defined as messages. [NL] const isValid = this.messages.getHasAnyMessages() ? false : resultsStatus; + this.#isValid = isValid; if (isValid === false) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts index b857634f2b..a351208f12 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts @@ -7,7 +7,7 @@ import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export class UmbFormControlValidator extends UmbControllerBase implements UmbValidator { - // The path to the data that this validator is validating. Public so the ValidationContext can access it. + // The path to the data that this validator is validating. readonly #dataPath?: string; #context?: typeof UMB_VALIDATION_CONTEXT.TYPE; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts index 8e12d5ba56..2685be90e0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts @@ -34,7 +34,11 @@ interface UmbFormControlValidatorConfig { } export interface UmbFormControlMixinInterface extends HTMLElement { - addValidator: (flagKey: FlagTypes, getMessageMethod: () => string, checkMethod: () => boolean) => void; + addValidator: ( + flagKey: FlagTypes, + getMessageMethod: () => string, + checkMethod: () => boolean, + ) => UmbFormControlValidatorConfig; removeValidator: (obj: UmbFormControlValidatorConfig) => void; //static formAssociated: boolean; //protected getFormElement(): HTMLElement | undefined | null; // allows for null as it makes it simpler to just implement a querySelector as that might return null. [NL] @@ -56,7 +60,11 @@ export declare abstract class UmbFormControlMixinElement { protected _internals: ElementInternals; protected _runValidators(): void; - addValidator: (flagKey: FlagTypes, getMessageMethod: () => string, checkMethod: () => boolean) => void; + addValidator: ( + flagKey: FlagTypes, + getMessageMethod: () => string, + checkMethod: () => boolean, + ) => UmbFormControlValidatorConfig; removeValidator: (obj: UmbFormControlValidatorConfig) => void; protected addFormControlElement(element: UmbNativeFormControlElement): void; @@ -209,7 +217,7 @@ export function UmbFormControlMixin< flagKey: flagKey, getMessageMethod: getMessageMethod, checkMethod: checkMethod, - }; + } satisfies UmbFormControlValidatorConfig; this.#validators.push(validator); return validator; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index dd8ea44dee..771623fa02 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -676,6 +676,7 @@ export class UmbDocumentWorkspaceContext // Create the validation repository if it does not exist. (we first create this here when we need it) [NL] this.#validationRepository ??= new UmbDocumentValidationRepository(this); + // We ask the server first to get a concatenated set of validation messages. So we see both front-end and back-end validation messages [NL] if (this.getIsNew()) { const parent = this.#parent.getValue(); if (!parent) throw new Error('Parent is not set');