Skip to content

Conversation

@AndyButland
Copy link
Contributor

@AndyButland AndyButland commented Nov 6, 2025

Prerequisites

  • I have added steps to test this contribution in the description below

Fixes #20736

Description

This is a WIP PR looking to provide an extension point for property editors to improve presentation in collections. Currently complex objects that are stored as JSON - including the new date pickers - just appears as [object Object].

Here I'm looking to add a new extension point that each property editor can optionally provide to give a presentation, customizable for the different views in which they are displayed.

image image

Testing

Add any or all of the following properties to a collection view:

  • Approved colour
  • Date only
  • Time only
  • Date (unspecified)
  • Datetime with timezone

TODO:

  • Fix linting errors - there are a few of Use the correct import map alias instead of a relative import path that I can't see how to resolve.
  • Is there a better way than used to look up to see if an extension exists, and then use it? With the current approach effectively the filter is being done twice, and there's a noticeable delay in rendering, so it needs improvement.
    • I've tried to partially resolve this by getting all the extensions by type once and caching them in the context, but not sure if that's a good practice since the extensions registered could presumably change after this.
    • The use of umb-extension-slot presumably still requires a filter through all extension, even though I've isolated the one to use.
  • Re-use of code between cards and columns.
    • I've resolved this by moving code to the document collection context.

@leekelleher leekelleher self-requested a review November 10, 2025 08:28
Copy link
Member

@nielslyngsoe nielslyngsoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a look at the code a gave a few comments and perspectives — looks good overall, but there is a few thoughts to make it simpler and to avoid perserving specific names for the system fields.

@leekelleher leekelleher self-assigned this Nov 11, 2025
@AndyButland
Copy link
Contributor Author

Thanks for all the updates and improvements @leekelleher - I pulled it down this morning for another look, and all works as expected with better performance.

Firstly, just a question so I understand moving forward - previously I had some code to see if a property value extension exists for a given property editor alias, and if so, used umb-extension-slot filtered for that same extension. I could see that didn't seem right as it was filtering twice, and see you just have:

	() =>
		when(
			prop?.editorAlias,
			(schemaAlias) => html`
				<umb-extension-slot
					type="propertyValuePresentation"
					.filter=${(m: ManifestPropertyValuePresentation) =>
						stringOrStringArrayContains(m.forPropertyEditorSchemaAlias, schemaAlias)}
					.props=${props}>
					${prop?.value ?? nothing}
				</umb-extension-slot>
			`,
			() => (prop?.value ? html`${prop.value}` : nothing),
		),

So am I right in understanding that if an extension isn't found for that type and filer, the default content for that element - i.e. ${prop?.value ?? nothing} is displayed instead? Can see that's better, as we only filter once.

Even doing that though I do see a small slowness in rendering. Maybe it's not a big deal, but for example if you add to a collection view one property that has a property presentation value extension (e.g. a colour picker) and one that doesn't (e.g. a text field), the latter will appear quicker. Not really sure why that should be the case given both seem to go through the same code path apart from the property presentation value extension itself.

@leekelleher
Copy link
Member

So am I right in understanding that if an extension isn't found for that type and filer, the default content for that element - i.e. ${prop?.value ?? nothing} is displayed instead? Can see that's better, as we only filter once.

Yes, that's correct. Following @nielslyngsoe's comment: #20748 (comment), the slotted content will render if there are no extensions loaded/rendered.

Even doing that though I do see a small slowness in rendering. Maybe it's not a big deal, but for example if you add to a collection view one property that has a property presentation value extension (e.g. a colour picker) and one that doesn't (e.g. a text field), the latter will appear quicker. Not really sure why that should be the case given both seem to go through the same code path apart from the property presentation value extension itself.

I've noticed this too, especially with a Grid collection-view, toggling a card's select/deselect, there's a lag in the re-render.
I suspect it is that the umb-extension-slot component is attempting to load/render an element, whereas your initial logic was to check if the extension's manifest exists (not loading anything in). // @nielslyngsoe @iOvergaard do you have thoughts on this?

@AndyButland
Copy link
Contributor Author

especially with a Grid collection-view, toggling a card's select/deselect, there's a lag in the re-render

Yes, it's more obvious here. It looks like you also see briefly the slotted content. Looking closely I can see the "[object Object]" string rendering before it's replaced by the result of the property value presentation.

@leekelleher leekelleher marked this pull request as ready for review November 12, 2025 15:17
Copilot finished reviewing on behalf of leekelleher November 12, 2025 15:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new extension point (propertyValuePresentation) for property editors to customize how complex property values are displayed in collection views (tables and grids). Previously, complex objects stored as JSON displayed as [object Object]. The PR implements this new extension for five date/time pickers and the color picker, providing formatted output for dates and visual swatches for colors.

Key Changes:

  • Created new propertyValuePresentation extension type with base element
  • Implemented property value presentation components for date pickers (date-only, time-only, datetime, datetime-with-timezone) and color picker
  • Refactored document and media collection views to use the new extension system
  • Added editorAlias to collection item value models to enable property editor lookup
  • Introduced UmbDocumentCollectionItemDataResolver to centralize collection item data resolution logic

Reviewed Changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tsconfig.json Added import alias for new property-value-presentation package
package.json Exported new property-value-presentation package
vite.config.ts Added build configuration for property-value-presentation
property-value-presentation/* Created new extension type, base element, and type definitions
-picker/property-value-presentation-.element.ts Implemented presentation elements for 5 date pickers and color picker
*-picker/manifests.ts Registered property value presentation manifests for each picker
document-collection-item-data-resolver.ts New resolver extending UmbDocumentItemDataResolver for collection-specific logic
document-item-data-resolver.ts Changed #getCurrentVariant to protected _getCurrentVariant for inheritance
document-table-column-*.element.ts Split into system-value and property-value column components
document-grid-collection-card.element.ts Updated to use resolver and extension slot for property rendering
media-table-column-*.element.ts Created system-value and property-value column components for media
types.ts Added editorAlias field to collection item value models
*-collection.server.data-source.ts Mapped editorAlias from server response

@leekelleher leekelleher changed the title Collections: Property display for complex objects propertyValuePresentation extension type for displaying complex property values (fixes #20736) Nov 12, 2025
@leekelleher leekelleher requested review from Copilot and removed request for leekelleher November 12, 2025 15:21
@leekelleher
Copy link
Member

@AndyButland I have made the de-duplication change (for the system value lookup), but I haven't made any progress with the loading/lag of slotted content. I'm hoping @nielslyngsoe and @iOvergaard may have insights on this.

I think the PR is in a good state to merge in and we could pick up on the performance issues in a follow-up PR.

Copilot finished reviewing on behalf of leekelleher November 12, 2025 15:26
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 34 out of 34 changed files in this pull request and generated 5 comments.

Comment on lines +10 to +11
const date = new Date(this.value.date).toLocaleDateString();
return date ? html`<span>${date}</span>` : nothing;
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check date ? is redundant because toLocaleDateString() always returns a string. Additionally, if this.value.date is an invalid date string, new Date(this.value.date).toLocaleDateString() will return "Invalid Date", which would be displayed to the user. Consider validating the date and returning nothing if invalid:

const dateObj = new Date(this.value.date);
if (isNaN(dateObj.getTime())) return nothing;
const date = dateObj.toLocaleDateString();
return html`<span>${date}</span>`;
Suggested change
const date = new Date(this.value.date).toLocaleDateString();
return date ? html`<span>${date}</span>` : nothing;
const dateObj = new Date(this.value.date);
if (isNaN(dateObj.getTime())) return nothing;
const date = dateObj.toLocaleDateString();
return html`<span>${date}</span>`;

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +11
const date = new Date(this.value.date).toLocaleString();
return date ? html`<span>${date}</span>` : nothing;
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check date ? is redundant because toLocaleString() always returns a string. Additionally, if this.value.date is an invalid date string, new Date(this.value.date).toLocaleString() will return "Invalid Date", which would be displayed to the user. Consider validating the date and returning nothing if invalid:

const dateObj = new Date(this.value.date);
if (isNaN(dateObj.getTime())) return nothing;
const date = dateObj.toLocaleString();
return html`<span>${date}</span>`;
Suggested change
const date = new Date(this.value.date).toLocaleString();
return date ? html`<span>${date}</span>` : nothing;
const dateObj = new Date(this.value.date);
if (isNaN(dateObj.getTime())) return nothing;
const date = dateObj.toLocaleString();
return html`<span>${date}</span>`;

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +18
#renderDate() {
if (!this.value?.date) return nothing;
const date = new Date(this.value.date).toLocaleString();
return html`${date}`;
}
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this.value.date is an invalid date string, new Date(this.value.date).toLocaleString() will return "Invalid Date", which would be displayed to the user. Consider validating the date and returning nothing if invalid:

#renderDate() {
	if (!this.value?.date) return nothing;
	const dateObj = new Date(this.value.date);
	if (isNaN(dateObj.getTime())) return nothing;
	return html`${dateObj.toLocaleString()}`;
}

Copilot uses AI. Check for mistakes.
…lection/views/grid/document-grid-collection-card.element.ts

Co-authored-by: Copilot <[email protected]>
@iOvergaard
Copy link
Contributor

especially with a Grid collection-view, toggling a card's select/deselect, there's a lag in the re-render

Yes, it's more obvious here. It looks like you also see briefly the slotted content. Looking closely I can see the "[object Object]" string rendering before it's replaced by the result of the property value presentation.

Looking into the umb-extension-slot element, it seems it follows a cycle of the following:

Frame 1: _permitted is undefined → renders nothing (blank)
Frame 2+: Extensions being loaded, _permitted becomes [] (empty array) → renders slotted fallback content (${prop?.value ?? nothing})
Frame 3+: Extension loads async → _permitted has elements → renders actual extension component

This is why you see "[object Object]" briefly - it's the slotted fallback content showing during async loading.

I think we can solve it by caching the extensions somehow in the extension slot, but I am not quite sure about how to achieve that. Might need some higher-up refactoring in the extensions registry...?

I think the PR is in a good state to merge in and we could pick up on the performance issues in a follow-up PR.

I agree with @leekelleher's sentiment here to merge as-is (after fixing the Copilot suggestions), and then follow up on the performance concerns later on:

@madsrasmussen
Copy link
Contributor

An additional performance improvement we should consider:

I just had a quick look at the PR, and it appears that we are rendering an extension slot for every cell.

I believe we can safely assume that all cells within a column will have the same editorAlias and can, therefore, use the same extension result.

If we initialize the extension programmatically by using one of the extension initializers instead of the extension-slot, we will have only one subscriber per column instead of one per cell.

We might have to think about this across all collection views somehow. Maybe it needs to be handled in the Collection Context so all views can share the same extension result for an editorAlias.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Umbraco 17: Date Picker in custom collection view shows as [object Object]

6 participants