From 07f0b7c6ae3a79dd01d1845c294a8d7aada8d8ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 19 Sep 2025 19:45:46 +0200 Subject: [PATCH] Content/Document Picker: make not existing items appear as not found items (#20198) make not existing items appear as not found items --- .../entity-item-ref.element.ts | 27 +++++++++++++++- .../core/picker-input/picker-input.context.ts | 8 +++-- .../repository/repository-items.manager.ts | 9 +----- .../src/packages/core/repository/types.ts | 8 +++++ .../input-document/input-document.element.ts | 31 +++++++++++++------ 5 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts index 0f2222bc84..97b469d8a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts @@ -120,6 +120,12 @@ export class UmbEntityItemRefElement extends UmbLitElement { } } + @property({ type: Boolean }) + error?: boolean; + + @property({ type: String, attribute: 'error-message', reflect: false }) + errorMessage?: string; + #pathAddendum = new UmbRoutePathAddendumContext(this); #onSelected(event: UmbSelectedEvent) { @@ -155,6 +161,7 @@ export class UmbEntityItemRefElement extends UmbLitElement { this._component?.remove(); const component = extensionControllers[0]?.component || document.createElement('umb-default-item-ref'); + // TODO: I would say this code can use feature of the UmbExtensionsElementInitializer, to set properties and get a fallback element. [NL] // assign the properties to the component component.item = this.#item; component.readonly = this.readonly; @@ -182,7 +189,25 @@ export class UmbEntityItemRefElement extends UmbLitElement { } override render() { - return html`${this._component}`; + if (this._component) { + return html`${this._component}`; + } + // Error: + if (this.error) { + return html` + + + `; + } + // Loading: + return html``; } override destroy(): void { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts index 11d3bdeeb2..c345fd22d4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts @@ -29,6 +29,7 @@ export class UmbPickerInputContext< public readonly selection; public readonly selectedItems; + public readonly statuses; public readonly interactionMemory = new UmbInteractionMemoryManager(this); /** @@ -84,6 +85,7 @@ export class UmbPickerInputContext< this.#itemManager = new UmbRepositoryItemsManager(this, repositoryAlias, getUniqueMethod); this.selection = this.#itemManager.uniques; + this.statuses = this.#itemManager.statuses; this.selectedItems = this.#itemManager.items; } @@ -116,12 +118,12 @@ export class UmbPickerInputContext< async requestRemoveItem(unique: string) { const item = this.#itemManager.getItems().find((item) => this.#getUnique(item) === unique); - if (!item) throw new Error('Could not find item with unique: ' + unique); + const name = item?.name ?? '#general_notFound'; await umbConfirmModal(this, { color: 'danger', - headline: `#actions_remove ${item.name}?`, - content: `#defaultdialogs_confirmremove ${item.name}?`, + headline: `#actions_remove ${name}?`, + content: `#defaultdialogs_confirmremove ${name}?`, confirmLabel: '#actions_remove', }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts index bc3536a3e9..fc9f7fd124 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts @@ -7,17 +7,10 @@ import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-ap import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; import { UmbEntityUpdatedEvent } from '@umbraco-cms/backoffice/entity-action'; +import type { UmbRepositoryItemsStatus } from './types.js'; const ObserveRepositoryAlias = Symbol(); -interface UmbRepositoryItemsStatus { - state: { - type: 'success' | 'error' | 'loading'; - error?: string; - }; - unique: string; -} - export class UmbRepositoryItemsManager extends UmbControllerBase { // repository?: UmbItemRepository; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts index 32d7a55020..7cd8329ff1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/types.ts @@ -11,6 +11,14 @@ export interface UmbRepositoryResponse extends UmbDataSourceResponse {} // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbRepositoryErrorResponse extends UmbDataSourceErrorResponse {} +export interface UmbRepositoryItemsStatus { + state: { + type: 'success' | 'error' | 'loading'; + error?: string; + }; + unique: string; +} + /** * Interface for a repository that can return a paged model. * @template T - The type of items in the paged model. diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index 6d21582800..52161c7974 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -11,6 +11,7 @@ import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '@umbraco-cms/backoffice/document-type'; import type { UmbTreeStartNode } from '@umbraco-cms/backoffice/tree'; import type { UmbInteractionMemoryModel } from '@umbraco-cms/backoffice/interaction-memory'; +import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository'; @customElement('umb-input-document') export class UmbInputDocumentElement extends UmbFormControlMixin( @@ -139,6 +140,9 @@ export class UmbInputDocumentElement extends UmbFormControlMixin; + @state() + private _statuses?: Array; + #pickerInputContext = new UmbDocumentPickerInputContext(this); constructor() { @@ -168,6 +172,8 @@ export class UmbInputDocumentElement extends UmbFormControlMixin (this._statuses = statuses), '_observerStatuses'); + this.observe( this.#pickerInputContext.interactionMemory.memories, (memories) => { @@ -199,8 +205,8 @@ export class UmbInputDocumentElement extends UmbFormControlMixin ${repeat( - this._items, - (item) => item.unique, - (item) => - html` status.unique, + (status) => { + const unique = status.unique; + const item = this._items?.find((x) => x.unique === unique); + return html` ${when( @@ -242,11 +252,12 @@ export class UmbInputDocumentElement extends UmbFormControlMixin this.#onRemove(item)}> + @click=${() => this.#onRemove(unique)}> `, )} - `, + `; + }, )} `;