From 6d7c722ec378bc9be8ae12f403e0ff32d49b30a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 24 Sep 2025 11:29:42 +0200 Subject: [PATCH] Upload field Property Editor: Fix resetting value to undefined when empty (#20134) * set value to undefined when empty * fix nullable checks * ensure promise rejection when validation fails * avoid js error when detailStore is not present * implement editor as form control * remove unused --------- Co-authored-by: Mads Rasmussen --- .../content-detail-workspace-base.ts | 16 +++++++----- .../validation/mixins/form-control.mixin.ts | 8 ++++++ .../common/save/save.action.ts | 2 +- .../input-upload-field.element.ts | 25 ++++++++++++------- ...property-editor-ui-upload-field.element.ts | 17 +++++++++---- .../media-validation.server.data-source.ts | 6 +++++ .../member-collection.repository.ts | 2 +- .../repository/user-collection.repository.ts | 2 +- 8 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts index a0a978a883..95fd159185 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts @@ -821,7 +821,7 @@ export abstract class UmbContentDetailWorkspaceContextBase< * Request a submit of the workspace, in the case of Document Workspaces the validation does not need to be valid for this to be submitted. * @returns {Promise} a promise which resolves once it has been completed. */ - public override requestSubmit() { + public override requestSubmit(): Promise { return this._handleSubmit(); } @@ -831,7 +831,7 @@ export abstract class UmbContentDetailWorkspaceContextBase< /** * Request a save of the workspace, in the case of Document Workspaces the validation does not need to be valid for this to be saved. - * @returns {Promise} a promise which resolves once it has been completed. + * @returns {Promise} A promise which resolves once it has been completed. */ public requestSave() { return this._handleSave(); @@ -847,11 +847,11 @@ export abstract class UmbContentDetailWorkspaceContextBase< return this._data.constructData(variantIds); } - protected async _handleSubmit() { + protected async _handleSubmit(): Promise { await this._handleSave(); this._closeModal(); } - protected async _handleSave() { + protected async _handleSave(): Promise { const data = this.getData(); if (!data) { throw new Error('Data is missing'); @@ -877,7 +877,9 @@ export abstract class UmbContentDetailWorkspaceContextBase< value: { selection: selected }, }).catch(() => undefined); - if (!result?.selection.length) return; + if (!result?.selection.length) { + return Promise.reject('Cannot save without selecting at least one variant.'); + } variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; } else { @@ -897,7 +899,9 @@ export abstract class UmbContentDetailWorkspaceContextBase< () => false, ); if (valid || this.#ignoreValidationResultOnSubmit) { - return this.performCreateOrUpdate(variantIds, saveData); + await this.performCreateOrUpdate(variantIds, saveData); + } else { + return Promise.reject('Validation issues prevent saving'); } } else { await this.performCreateOrUpdate(variantIds, saveData); 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 51e925802a..c2259ca66b 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 @@ -263,6 +263,14 @@ export function UmbFormControlMixin< * @returns {void} */ protected addFormControlElement(element: UmbNativeFormControlElement) { + if (!element) { + throw new Error('Element is null or undefined'); + } + if (!element.validity) { + console.log(element); + throw new Error('Element is not a Form Control'); + } + if (this.#formCtrlElements.includes(element)) return; this.#formCtrlElements.push(element); element.addEventListener(UmbValidationInvalidEvent.TYPE, this.#runValidatorsCallback); element.addEventListener(UmbValidationValidEvent.TYPE, this.#runValidatorsCallback); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/save/save.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/save/save.action.ts index e4b9905186..5cd20e0976 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/save/save.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/save/save.action.ts @@ -49,6 +49,6 @@ export class UmbSaveWorkspaceAction< override async execute() { await this._retrieveWorkspaceContext; - return await this._workspaceContext?.requestSave(); + await this._workspaceContext?.requestSave(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts index ed92ba6453..6accb523ab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/input-upload-field.element.ts @@ -16,19 +16,26 @@ import type { } from '@umbraco-cms/backoffice/dropzone'; import type { UmbTemporaryFileModel } from '@umbraco-cms/backoffice/temporary-file'; import { UMB_SERVER_CONTEXT } from '@umbraco-cms/backoffice/server'; +import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; @customElement('umb-input-upload-field') -export class UmbInputUploadFieldElement extends UmbLitElement { +export class UmbInputUploadFieldElement extends UmbFormControlMixin( + UmbLitElement, +) { @property({ type: Object, attribute: false }) - set value(value: UmbMediaValueType) { + override set value(value: UmbMediaValueType | undefined) { + super.value = value; this.#src = value?.src ?? ''; this.#setPreviewAlias(); } - get value(): UmbMediaValueType { - return { - src: this.#src, - temporaryFileId: this.temporaryFile?.temporaryUnique, - }; + override get value(): UmbMediaValueType | undefined { + if (this.#src || this.temporaryFile?.temporaryUnique) { + return { + src: this.#src, + temporaryFileId: this.temporaryFile?.temporaryUnique, + }; + } + return undefined; } #src = ''; @@ -86,7 +93,7 @@ export class UmbInputUploadFieldElement extends UmbLitElement { } async #getPreviewElementAlias() { - if (!this.value.src) return; + if (!this.value?.src) return; const manifests = await this.#getManifests(); const fallbackAlias = manifests.find((manifest) => stringOrStringArrayContains(manifest.forMimeTypes, '*/*'), @@ -158,7 +165,7 @@ export class UmbInputUploadFieldElement extends UmbLitElement { } override render() { - if (!this.temporaryFile && !this.value.src) { + if (!this.temporaryFile && !this.value?.src) { return this.#renderDropzone(); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts index 3390df2062..09d2ba58b9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/property-editor-ui-upload-field.element.ts @@ -1,21 +1,24 @@ import type { UmbInputUploadFieldElement } from '../../components/input-upload-field/input-upload-field.element.js'; import type { UmbMediaValueType } from './types.js'; -import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorUiElement, UmbPropertyEditorConfigCollection, } from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; + +import '../../components/input-upload-field/input-upload-field.element.js'; /** * @element umb-property-editor-ui-upload-field */ @customElement('umb-property-editor-ui-upload-field') -export class UmbPropertyEditorUIUploadFieldElement extends UmbLitElement implements UmbPropertyEditorUiElement { - @property({ type: Object }) - value: UmbMediaValueType = {}; - +export class UmbPropertyEditorUIUploadFieldElement + extends UmbFormControlMixin(UmbLitElement) + implements UmbPropertyEditorUiElement +{ @state() private _fileExtensions?: Array; @@ -29,6 +32,10 @@ export class UmbPropertyEditorUIUploadFieldElement extends UmbLitElement impleme } } + override firstUpdated() { + this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-upload-field')!); + } + #onChange(event: CustomEvent) { this.value = (event.target as UmbInputUploadFieldElement).value; this.dispatchEvent(new UmbChangeEvent()); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/media-validation.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/media-validation.server.data-source.ts index 00f5852875..f930d0d167 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/media-validation.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/media-validation.server.data-source.ts @@ -49,6 +49,9 @@ export class UmbMediaValidationServerDataSource { MediaService.postMediaValidate({ body, }), + { + disableNotifications: true, + }, ); if (data && typeof data === 'string') { @@ -86,6 +89,9 @@ export class UmbMediaValidationServerDataSource { path: { id: model.unique }, body, }), + { + disableNotifications: true, + }, ); if (data && typeof data === 'string') { diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/collection/repository/member-collection.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/collection/repository/member-collection.repository.ts index 62ec20d00d..995f5d7900 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/collection/repository/member-collection.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/collection/repository/member-collection.repository.ts @@ -19,7 +19,7 @@ export class UmbMemberCollectionRepository extends UmbMemberRepositoryBase imple const { data, error } = await this.#collectionSource.getCollection(filter); if (data) { - this.detailStore!.appendItems(data.items); + this.detailStore?.appendItems(data.items); } return { data, error, asObservable: () => this.detailStore!.all() }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/repository/user-collection.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/repository/user-collection.repository.ts index b5c454fe32..1849365064 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/repository/user-collection.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/repository/user-collection.repository.ts @@ -19,7 +19,7 @@ export class UmbUserCollectionRepository extends UmbUserRepositoryBase implement const { data, error } = await this.#collectionSource.getCollection(filter); if (data) { - this.detailStore!.appendItems(data.items); + this.detailStore?.appendItems(data.items); } return { data, error, asObservable: () => this.detailStore!.all() };