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 4df96b1c01..50074d598c 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 @@ -149,7 +149,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen // Currently there is no server validation for areas. So we can leave out the data path for it for now. [NL] this.#controlValidator = new UmbFormControlValidator(this, this); - //new UmbBindServerValidationToFormControl(this, this, "$.values.[?(@.alias = 'my-input-alias')].value"); + //new UmbBindServerValidationToFormControl(this, this, "$.values.[?(@.alias == 'my-input-alias')].value"); } } public get areaKey(): string | null | undefined { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts index 3accdffc9c..c09879f9a6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts @@ -118,7 +118,6 @@ export class UmbPropertyEditorUIBlockListElement /** * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. * @type {boolean} - * @attr * @default */ @property({ type: Boolean, reflect: true }) @@ -156,8 +155,6 @@ export class UmbPropertyEditorUIBlockListElement constructor() { super(); - //this.#validationContext.messages.debug('block list'); - this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => { this.observe( context.dataPath, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-data-property-validation-path-translator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-data-property-validation-path-translator.controller.ts deleted file mode 100644 index 096eec3967..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-data-property-validation-path-translator.controller.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { - GetPropertyNameFromPath, - UmbDataPathPropertyValueQuery, - UmbValidationPathTranslatorBase, -} from '@umbraco-cms/backoffice/validation'; - -export class UmbBlockElementDataValidationPathTranslator extends UmbValidationPathTranslatorBase { - constructor(host: UmbControllerHost) { - super(host); - } - - translate(path: string) { - if (!this._context) return; - if (path.indexOf('$.') !== 0) { - // We do not handle this path. - return false; - } - - const rest = path.substring(2); - const key = GetPropertyNameFromPath(rest); - - const specificValue = { alias: key }; - // replace the values[ number ] with JSON-Path filter values[@.(...)], continues by the rest of the path: - //return '$.values' + UmbVariantValuesValidationPathTranslator(specificValue) + path.substring(path.indexOf(']')); - return '$.values[' + UmbDataPathPropertyValueQuery(specificValue) + '.value'; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-data-validation-path-translator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-element-data-validation-path-translator.controller.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-data-validation-path-translator.controller.ts rename to src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-element-data-validation-path-translator.controller.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts new file mode 100644 index 0000000000..2c2b9da88c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts @@ -0,0 +1,17 @@ +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { + UmbAbstractArrayValidationPathTranslator, + UmbDataPathPropertyValueQuery, +} from '@umbraco-cms/backoffice/validation'; + +export class UmbBlockElementValuesDataValidationPathTranslator extends UmbAbstractArrayValidationPathTranslator { + constructor(host: UmbControllerHost) { + super(host, '$.values[', UmbDataPathPropertyValueQuery); + } + + getDataFromIndex(index: number) { + if (!this._context) return; + const data = this._context.getTranslationData(); + return data.values[index]; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/data-path-element-data-query.function.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/data-path-element-data-query.function.ts index 517e393897..1d624a3123 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/data-path-element-data-query.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/data-path-element-data-query.function.ts @@ -2,14 +2,14 @@ import type { UmbBlockDataModel } from '../types.js'; /** * Validation Data Path Query generator for Block Element Data. - * write a JSON-Path filter similar to `?(@.key = 'my-key://1234')` + * write a JSON-Path filter similar to `?(@.key == 'my-key://1234')` * @param key {string} - The key of the block Element data. * @param data {{key: string}} - A data object with the key property. * @returns */ export function UmbDataPathBlockElementDataQuery(data: Pick): string { // write a array of strings for each property, where alias must be present and culture and segment are optional - //const filters: Array = [`@.key = '${key}'`]; + //const filters: Array = [`@.key == '${key}'`]; //return `?(${filters.join(' && ')})`; - return `?(@.key = '${data.key}')`; + return `?(@.key == '${data.key}')`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/index.ts index 331352a0d8..f2af316128 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/index.ts @@ -1,2 +1,3 @@ -export * from './block-data-validation-path-translator.controller.js'; +export * from './block-element-values-validation-path-translator.controller.js'; +export * from './block-element-data-validation-path-translator.controller.js'; export * from './data-path-element-data-query.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts index 868fbb81bb..efadfcfb2e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts @@ -1,4 +1,5 @@ import type { UmbBlockDataModel, UmbBlockDataValueModel } from '../types.js'; +import { UmbBlockElementValuesDataValidationPathTranslator } from '../validation/block-element-values-validation-path-translator.controller.js'; import { UmbBlockElementPropertyDatasetContext } from './block-element-property-dataset.context.js'; import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; @@ -43,7 +44,7 @@ export class UmbBlockElementManager extends UmbControllerBase { this.observe(this.contentTypeId, (id) => this.structure.loadType(id)); this.observe(this.unique, (key) => { if (key) { - this.validation.setDataPath('$.' + dataPathPropertyName + `[?(@.key = '${key}')]`); + this.validation.setDataPath('$.' + dataPathPropertyName + `[?(@.key == '${key}')]`); } }); } @@ -198,6 +199,9 @@ export class UmbBlockElementManager extends UmbControllerBase { // Provide Validation Context for this view: this.validation.provideAt(host); + + // TODO: Implement ctrl alias. + new UmbBlockElementValuesDataValidationPathTranslator(host); } public override destroy(): void { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts index cd570f360d..1b1acc7425 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts @@ -5,6 +5,8 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; import { UmbContentTypePropertyStructureHelper } from '@umbraco-cms/backoffice/content-type'; import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbDataPathPropertyValueQuery } from '@umbraco-cms/backoffice/validation'; @customElement('umb-block-workspace-view-edit-properties') export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { @@ -38,12 +40,22 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { @state() private _ownerEntityType?: string; + #variantId?: UmbVariantId; + constructor() { super(); this.consumeContext(UMB_BLOCK_WORKSPACE_CONTEXT, (workspaceContext) => { this.#blockWorkspace = workspaceContext; this._ownerEntityType = this.#blockWorkspace.getEntityType(); + this.observe( + workspaceContext.variantId, + (variantId) => { + this.#variantId = variantId; + this.#generatePropertyDataPath(); + }, + 'observeVariantId', + ); this.#setStructureManager(); }); } @@ -61,10 +73,24 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { ); } + /* #generatePropertyDataPath() { if (!this._propertyStructure) return; this._dataPaths = this._propertyStructure.map((property) => `$.${property.alias}`); } + */ + + #generatePropertyDataPath() { + if (!this.#variantId || !this._propertyStructure) return; + this._dataPaths = this._propertyStructure.map( + (property) => + `$.values[${UmbDataPathPropertyValueQuery({ + alias: property.alias, + culture: property.variesByCulture ? this.#variantId!.culture : null, + segment: property.variesBySegment ? this.#variantId!.segment : null, + })}].value`, + ); + } override render() { return repeat( diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts index 7fbd02e7ba..e069c69ccb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts @@ -130,7 +130,6 @@ export class UmbPropertyElement extends UmbLitElement { * DataPath, declare the path to the value of the data that this property represents. * @public * @type {string} - * @attr * @default */ @property({ type: String, attribute: 'data-path' }) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md b/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md index d034fb6ca4..b5a79df4e0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md @@ -15,7 +15,7 @@ A Validation message consist of a type, path and body. This typically looks like ``` { type: "client", - path: "$.values[?(@.alias = 'my-property-alias')].value", + path: "$.values[?(@.alias == 'my-property-alias')].value", message: "Must contain at least 3 words" } ``` @@ -61,7 +61,7 @@ Data: JsonPath: ``` -"$.values.[?(@.alias = 'my-alias')].value" +"$.values.[?(@.alias == 'my-alias')].value" ``` Paths are based on JSONPath, using JSON Path Queries when looking up data of an Array. Using Queries enables Paths to not point to specific index, but what makes a entry unique. @@ -107,7 +107,7 @@ Such conversation could be from this path: To this path: ``` -"$.values.[?(@.alias = 'my-alias')].value" +"$.values.[?(@.alias == 'my-alias')].value" ``` Once this path is converted to use Json Path Queries, the Data can be changed. The concerned entry might get another index. Without that affecting the accuracy of the path. @@ -135,7 +135,7 @@ The Data Path is a JSON Path defining where the data of this input is located in this.#validationMessageBinder = new UmbBindServerValidationToFormControl( this, this.querySelector('#myInput"), - "$.values.[?(@.alias = 'my-input-alias')].value", + "$.values.[?(@.alias == 'my-input-alias')].value", ); ``` diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts index 594b65ca45..bdae375157 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts @@ -100,10 +100,10 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal * @example * ```ts * const validationContext = new UmbValidationContext(this); - * validationContext.setDataPath("$.values[?(@.alias='my-property')].value"); + * validationContext.setDataPath("$.values[?(@.alias == 'my-property')].value"); * ``` * - * A message with the path: '$.values[?(@.alias='my-property')].value.innerProperty', will for above example become '$.innerProperty' for the local Validation Context. + * A message with the path: '$.values[?(@.alias == 'my-property')].value.innerProperty', will for above example become '$.innerProperty' for the local Validation Context. */ setDataPath(dataPath: string): void { if (this.#baseDataPath) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-query.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-query.function.ts index d184f6e205..76fb54ce10 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-query.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-query.function.ts @@ -3,21 +3,21 @@ import type { UmbVariantPropertyValueModel } from '@umbraco-cms/backoffice/varia /** * Validation Data Path Query generator for Property Value. - * write a JSON-Path filter similar to `?(@.alias = 'myAlias' && @.culture == 'en-us' && @.segment == 'mySegment')` + * write a JSON-Path filter similar to `?(@.alias == 'myAlias' && @.culture == 'en-us' && @.segment == 'mySegment')` * where culture and segment are optional - * @param value - * @returns + * @param {UmbVariantPropertyValueModel} value - the object holding value and alias. + * @returns {string} - a JSON-path query */ export function UmbDataPathPropertyValueQuery( value: UmbPartialSome, 'culture' | 'segment'>, ): string { // write a array of strings for each property, where alias must be present and culture and segment are optional - const filters: Array = [`@.alias = '${value.alias}'`]; + const filters: Array = [`@.alias == '${value.alias}'`]; if (value.culture !== undefined) { - filters.push(`@.culture = ${value.culture ? `'${value.culture}'` : 'null'}`); + filters.push(`@.culture == ${value.culture ? `'${value.culture}'` : 'null'}`); } if (value.segment !== undefined) { - filters.push(`@.segment = ${value.segment ? `'${value.segment}'` : 'null'}`); + filters.push(`@.segment == ${value.segment ? `'${value.segment}'` : 'null'}`); } return `?(${filters.join(' && ')})`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-variant-query.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-variant-query.function.ts index 25666269cd..175b744992 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-variant-query.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-variant-query.function.ts @@ -12,9 +12,9 @@ export function UmbDataPathVariantQuery( value: UmbPartialSome, 'segment'>, ): string { // write a array of strings for each property, where culture must be present and segment is optional - const filters: Array = [`@.culture = ${value.culture ? `'${value.culture}'` : 'null'}`]; + const filters: Array = [`@.culture == ${value.culture ? `'${value.culture}'` : 'null'}`]; if (value.segment !== undefined) { - filters.push(`@.segment = ${value.segment ? `'${value.segment}'` : 'null'}`); + filters.push(`@.segment == ${value.segment ? `'${value.segment}'` : 'null'}`); } return `?(${filters.join(' && ')})`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts index c7c1244fc0..40ceb3f9da 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts @@ -1,9 +1,10 @@ /** * - * @param data - * @param path + * @param {object} data - object to traverse for the value. + * @param {string} path - the JSON path to the value that should be found + * @returns {unknown} - the found value. */ -export function GetValueByJsonPath(data: any, path: string): any { +export function GetValueByJsonPath(data: unknown, path: string): unknown { // strip $ from the path: const strippedPath = path.startsWith('$.') ? path.slice(2) : path; // get value from the path: @@ -12,23 +13,9 @@ export function GetValueByJsonPath(data: any, path: string): any { /** * - * @param path - */ -export function GetPropertyNameFromPath(path: string): string { - // find next '.' or '[' in the path, using regex: - const match = path.match(/\.|\[/); - // If no match is found, we assume its a single key so lets return the value of the key: - if (match === null || match.index === undefined) return path; - - // split the path at the first match: - return path.slice(0, match.index); -} - -/** - * - * @param data - * @param path - * @returns {any} + * @param {object} data - object to traverse for the value. + * @param {string} path - the JSON path to the value that should be found + * @returns {unknown} - the found value. */ function GetNextPropertyValueFromPath(data: any, path: string): any { if (!data) return undefined; @@ -90,8 +77,8 @@ function GetNextPropertyValueFromPath(data: any, path: string): any { } /** - * @param filter - * @returns {Array<(queryFilter: any) => boolean>} - array of methods that returns true if the given items property value matches the value of the query. + * @param {string} filter - A JSON Query, limited to filtering features. Do not support other JSON PATH Query features. + * @returns {Array<(queryFilter: any) => boolean>} - An array of methods that returns true if the given items property value matches the value of the query. */ function JsFilterFromJsonPathFilter(filter: string): Array<(item: any) => boolean> { // strip ?( and ) from the filter @@ -101,7 +88,7 @@ function JsFilterFromJsonPathFilter(filter: string): Array<(item: any) => boolea // map each part to a function that returns true if the part is true return parts.map((part) => { // split the part into key and value - const [path, equal] = part.split(' = '); + const [path, equal] = part.split(' == '); // remove @. const key = path.slice(2); // remove quotes: diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts index 3673b28ecd..906463e1e5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts @@ -30,7 +30,7 @@ describe('UmbJsonPathFunctions', () => { }); it('query of first entry in an array', () => { - const result = GetValueByJsonPath({ values: [{ id: '123', value: 'test' }] }, "$.values[?(@.id = '123')].value"); + const result = GetValueByJsonPath({ values: [{ id: '123', value: 'test' }] }, "$.values[?(@.id == '123')].value"); expect(result).to.eq('test'); });