From ea505f2e27b53eed907c05dc89ef1a69c1a425e6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Oct 2025 14:38:39 +0200 Subject: [PATCH 1/7] debounce loadCollection calls --- .../default/collection-default.context.ts | 30 ++++++++++++++----- .../default/collection-default.element.ts | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts index a0deb3801e19..0684b29a8362 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts @@ -15,7 +15,7 @@ import { UmbArrayState, UmbBasicState, UmbNumberState, UmbObjectState } from '@u import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; -import { UmbSelectionManager, UmbPaginationManager, UmbDeprecation } from '@umbraco-cms/backoffice/utils'; +import { UmbSelectionManager, UmbPaginationManager, UmbDeprecation, debounce } from '@umbraco-cms/backoffice/utils'; import type { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -121,11 +121,11 @@ export class UmbDefaultCollectionContext< }) .onReject(() => { // TODO: Maybe this can be removed? - this.requestCollection(); + this._requestCollection(); }) .onSubmit(() => { // TODO: Maybe this can be removed? - this.requestCollection(); + this._requestCollection(); }) .observeRouteBuilder((routeBuilder) => { this.#workspacePathBuilder.setValue(routeBuilder); @@ -248,16 +248,30 @@ export class UmbDefaultCollectionContext< return this.manifest?.meta.noItemsLabel ?? this.#config?.noItemsLabel ?? '#collection_noItemsTitle'; } + /* debouncing the load collection method because multiple filters can be set at the same time + that will trigger multiple load calls with different filters args */ + public loadCollection = debounce(() => this._requestCollection(), 100); + /** * Requests the collection from the repository. - * @returns {*} + * @returns {Promise} + * @deprecated Deprecated since v.17.0.0. Use `loadCollection` instead. * @memberof UmbCollectionContext */ public async requestCollection() { + new UmbDeprecation({ + removeInVersion: '19.0.0', + deprecated: 'requestCollection', + solution: 'Use .loadCollection method instead', + }).warn(); + + return this._requestCollection(); + } + + protected async _requestCollection() { await this._init; if (!this._configured) this._configure(); - if (!this._repository) throw new Error(`Missing repository for ${this._manifest}`); this._loading.setValue(true); @@ -281,7 +295,7 @@ export class UmbDefaultCollectionContext< */ public setFilter(filter: Partial) { this._filter.setValue({ ...this._filter.getValue(), ...filter }); - this.requestCollection(); + this.loadCollection(); } public updateFilter(filter: Partial) { @@ -312,7 +326,7 @@ export class UmbDefaultCollectionContext< const items = this._items.getValue(); const hasItem = items.some((item) => item.unique === event.getUnique()); if (hasItem) { - this.requestCollection(); + this._requestCollection(); } }; @@ -324,7 +338,7 @@ export class UmbDefaultCollectionContext< const entityType = entityContext.getEntityType(); if (unique === event.getUnique() && entityType === event.getEntityType()) { - this.requestCollection(); + this._requestCollection(); } }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts index 94440fa140a4..ed3794fc640a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts @@ -45,7 +45,7 @@ export class UmbCollectionDefaultElement extends UmbLitElement { this.#observeCollectionRoutes(); this.#observeTotalItems(); this.#getEmptyStateLabel(); - await this.#collectionContext?.requestCollection(); + this.#collectionContext?.loadCollection(); this._isDoneLoading = true; }); } From 4c273947aa3265698a12ba26d9959b7ef31a10fa Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Oct 2025 14:38:58 +0200 Subject: [PATCH 2/7] update document collection context --- .../collection/document-collection.context.ts | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.context.ts index cff1aedd6589..18c8a5bcf854 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.context.ts @@ -4,6 +4,7 @@ import { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection' import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/variant'; import { UmbStringState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; export class UmbDocumentCollectionContext extends UmbDefaultCollectionContext< UmbDocumentCollectionItemModel, @@ -35,9 +36,25 @@ export class UmbDocumentCollectionContext extends UmbDefaultCollectionContext< ); } - public override async requestCollection() { + /** + * Requests the collection from the repository. + * @returns {Promise} + * @deprecated Deprecated since v.17.0.0. Use `loadCollection` instead. + * @memberof UmbDocumentCollectionContext + */ + public override async requestCollection(): Promise { + new UmbDeprecation({ + removeInVersion: '19.0.0', + deprecated: 'requestCollection', + solution: 'Use .loadCollection method instead', + }).warn(); + + return this._requestCollection(); + } + + protected override async _requestCollection() { await this.observe(this.#displayCultureObservable)?.asPromise(); - await super.requestCollection(); + await super._requestCollection(); } } From 7ac337ce1e6ec3a555b4ffb12b0043582fe12c22 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Oct 2025 14:39:14 +0200 Subject: [PATCH 3/7] update media collection context --- .../media/collection/media-collection.context.ts | 16 ++++++++++++++-- .../media/collection/media-collection.element.ts | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts index 66849f58ff04..08632022edee 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts @@ -5,6 +5,7 @@ import type { UmbFileDropzoneItemStatus } from '@umbraco-cms/backoffice/dropzone import { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; export class UmbMediaCollectionContext extends UmbDefaultCollectionContext< UmbMediaCollectionItemModel, UmbMediaCollectionFilterModel @@ -51,9 +52,20 @@ export class UmbMediaCollectionContext extends UmbDefaultCollectionContext< /** * Requests the collection from the repository. * @returns {Promise} - * @memberof UmbCollectionContext + * @deprecated Deprecated since v.17.0.0. Use `loadCollection` instead. + * @memberof UmbMediaCollectionContext */ - public override async requestCollection() { + public override async requestCollection(): Promise { + new UmbDeprecation({ + removeInVersion: '19.0.0', + deprecated: 'requestCollection', + solution: 'Use .loadCollection method instead', + }).warn(); + + return this._requestCollection(); + } + + protected override async _requestCollection() { await this._init; if (!this._configured) this._configure(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts index 2de841ae73b4..c5cfc74bf99b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts @@ -62,7 +62,7 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement { async #onComplete(event: Event) { event.preventDefault(); this._progress = -1; - this.#collectionContext?.requestCollection(); + this.#collectionContext?.loadCollection(); const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); if (!eventContext) { From 23ff105539102eaf8f3ca265bc8f91ff265cec85 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Oct 2025 14:55:58 +0200 Subject: [PATCH 4/7] remove duplicate request to reload --- .../packages/media/media/collection/media-collection.element.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts index c5cfc74bf99b..a59d2b4c5b6d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts @@ -62,7 +62,6 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement { async #onComplete(event: Event) { event.preventDefault(); this._progress = -1; - this.#collectionContext?.loadCollection(); const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); if (!eventContext) { From c5fd931ee204cd0d22f7471031d4140fdd755b99 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Oct 2025 15:20:16 +0200 Subject: [PATCH 5/7] Update src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../core/collection/default/collection-default.context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts index 0684b29a8362..cb514a6e5075 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts @@ -249,7 +249,7 @@ export class UmbDefaultCollectionContext< } /* debouncing the load collection method because multiple filters can be set at the same time - that will trigger multiple load calls with different filters args */ + that will trigger multiple load calls with different filter arguments */ public loadCollection = debounce(() => this._requestCollection(), 100); /** From fd0a791b12f6d499c716598fbb46cf9c1d06fd56 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Oct 2025 15:46:51 +0200 Subject: [PATCH 6/7] add fadeIn animation to empty state to prevent flicker --- .../default/collection-default.element.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts index ed3794fc640a..712835c5cb7d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts @@ -23,7 +23,6 @@ umbExtensionsRegistry.register(manifest); @customElement('umb-collection-default') export class UmbCollectionDefaultElement extends UmbLitElement { - // #collectionContext?: UmbDefaultCollectionContext; @state() @@ -32,9 +31,6 @@ export class UmbCollectionDefaultElement extends UmbLitElement { @state() private _hasItems = false; - @state() - private _isDoneLoading = false; - @state() private _emptyLabel?: string; @@ -46,7 +42,6 @@ export class UmbCollectionDefaultElement extends UmbLitElement { this.#observeTotalItems(); this.#getEmptyStateLabel(); this.#collectionContext?.loadCollection(); - this._isDoneLoading = true; }); } @@ -106,8 +101,6 @@ export class UmbCollectionDefaultElement extends UmbLitElement { } #renderEmptyState() { - if (!this._isDoneLoading) return nothing; - return html`

${this.localize.string(this._emptyLabel)}

@@ -138,12 +131,20 @@ export class UmbCollectionDefaultElement extends UmbLitElement { height: 80%; align-content: center; text-align: center; + opacity: 0; + animation: fadeIn 200ms 200ms forwards; } router-slot { width: 100%; height: 100%; } + + @keyframes fadeIn { + 100% { + opacity: 100%; + } + } `, ]; } From a0868ffff85817c488a879b8e572a15f6b410bad Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 16 Oct 2025 18:31:17 +0200 Subject: [PATCH 7/7] Update collection-default.element.ts --- .../default/collection-default.element.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts index 712835c5cb7d..1da4b1a59a9c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts @@ -34,10 +34,14 @@ export class UmbCollectionDefaultElement extends UmbLitElement { @state() private _emptyLabel?: string; + @state() + private _initialLoadDone = false; + constructor() { super(); this.consumeContext(UMB_COLLECTION_CONTEXT, async (context) => { this.#collectionContext = context; + this.#observeIsLoading(); this.#observeCollectionRoutes(); this.#observeTotalItems(); this.#getEmptyStateLabel(); @@ -45,6 +49,26 @@ export class UmbCollectionDefaultElement extends UmbLitElement { }); } + #observeIsLoading() { + if (!this.#collectionContext) return; + let hasBeenLoading = false; + + this.observe( + this.#collectionContext.loading, + (isLoading) => { + // We need to know when the initial loading has been done, to not show the empty state before that. + // We can't just check if there are items, because there might be none. + // So we check if it has been loading, and then when it stops loading we know the initial load is done. + if (isLoading) { + hasBeenLoading = true; + } else if (hasBeenLoading) { + this._initialLoadDone = true; + } + }, + 'umbCollectionIsLoadingObserver', + ); + } + #observeCollectionRoutes() { if (!this.#collectionContext) return; @@ -101,6 +125,8 @@ export class UmbCollectionDefaultElement extends UmbLitElement { } #renderEmptyState() { + if (!this._initialLoadDone) return nothing; + return html`

${this.localize.string(this._emptyLabel)}