Picker data source: Add support for pickable filters (#20491)

* add pickable to vs code dictionary

* set up types for pickable filters in data sources

* pass search pickable filter to search result

* apply filter config in document data source example

* add pickable filters to custom tree example

* Update input-entity-data.context.ts

* remove unused

* Update types.ts
This commit is contained in:
Mads Rasmussen
2025-10-14 18:11:34 +02:00
committed by GitHub
parent 3892670d92
commit 12297ea617
12 changed files with 97 additions and 19 deletions

View File

@@ -1,5 +1,8 @@
{
"cSpell.words": [
"backoffice",
"pickable",
"Pickable",
"unprovide",
"Unproviding"
],

View File

@@ -1,12 +1,21 @@
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbPickerTreeDataSource } from '@umbraco-cms/backoffice/picker-data-source';
import type { UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search';
import type {
UmbPickerSearchableDataSource,
UmbPickerTreeDataSource,
} from '@umbraco-cms/backoffice/picker-data-source';
import type { UmbSearchRequestArgs, UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search';
import type { UmbTreeChildrenOfRequestArgs, UmbTreeItemModel } from '@umbraco-cms/backoffice/tree';
export class ExampleCustomPickerTreePropertyEditorDataSource
extends UmbControllerBase
implements UmbPickerTreeDataSource
implements UmbPickerTreeDataSource, UmbPickerSearchableDataSource
{
treePickableFilter: (treeItem: UmbTreeItemModel) => boolean = (treeItem) =>
!!treeItem.unique && treeItem.entityType === 'example';
searchPickableFilter: (searchItem: UmbSearchResultItemModel) => boolean = (searchItem) =>
!!searchItem.unique && searchItem.entityType === 'example';
async requestTreeRoot() {
const root = {
unique: null,
@@ -60,7 +69,7 @@ export class ExampleCustomPickerTreePropertyEditorDataSource
const data = {
items: result,
totalItems: result.length,
total: result.length,
};
return { data };

View File

@@ -3,7 +3,10 @@ import {
UmbDocumentItemRepository,
UmbDocumentSearchRepository,
UmbDocumentTreeRepository,
type UmbDocumentSearchItemModel,
type UmbDocumentSearchRequestArgs,
type UmbDocumentTreeItemModel,
type UmbDocumentTreeRootModel,
} from '@umbraco-cms/backoffice/document';
import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '@umbraco-cms/backoffice/document-type';
import type {
@@ -20,16 +23,21 @@ import { getConfigValue, type UmbConfigCollectionModel } from '@umbraco-cms/back
export class ExampleDocumentPickerPropertyEditorDataSource
extends UmbControllerBase
implements UmbPickerTreeDataSource, UmbPickerSearchableDataSource
implements
UmbPickerTreeDataSource<UmbDocumentTreeItemModel, UmbDocumentTreeRootModel>,
UmbPickerSearchableDataSource<UmbDocumentSearchItemModel>
{
#tree = new UmbDocumentTreeRepository(this);
#item = new UmbDocumentItemRepository(this);
#search = new UmbDocumentSearchRepository(this);
#config: UmbConfigCollectionModel = [];
treePickableFilter: (treeItem: UmbDocumentTreeItemModel) => boolean = (treeItem) => !!treeItem.unique;
setConfig(config: UmbConfigCollectionModel) {
// TODO: add examples for all config options
this.#config = config;
this.#applyPickableFilterFromConfig();
}
getConfig(): UmbConfigCollectionModel {
@@ -57,6 +65,13 @@ export class ExampleDocumentPickerPropertyEditorDataSource
}
search(args: UmbSearchRequestArgs) {
const allowedContentTypes = this.#getAllowedDocumentTypesConfig();
const combinedArgs: UmbDocumentSearchRequestArgs = { ...args, allowedContentTypes };
return this.#search.search(combinedArgs);
}
#getAllowedDocumentTypesConfig() {
const filterString = getConfigValue<string>(this.#config, 'filter');
const filterArray = filterString ? filterString.split(',') : [];
const allowedContentTypes: UmbDocumentSearchRequestArgs['allowedContentTypes'] = filterArray.map(
@@ -65,10 +80,14 @@ export class ExampleDocumentPickerPropertyEditorDataSource
unique,
}),
);
return allowedContentTypes;
}
const combinedArgs: UmbDocumentSearchRequestArgs = { ...args, allowedContentTypes };
return this.#search.search(combinedArgs);
#applyPickableFilterFromConfig() {
const allowedDocumentTypes = this.#getAllowedDocumentTypesConfig();
if (!allowedDocumentTypes || allowedDocumentTypes.length === 0) return;
this.treePickableFilter = (treeItem: UmbDocumentTreeItemModel) =>
allowedDocumentTypes.some((entityType) => entityType.unique === treeItem.documentType.unique);
}
}

View File

@@ -98,6 +98,8 @@ export class UmbCollectionItemPickerModalElement extends UmbModalBaseElement<
this.modalContext?.dispatchEvent(new UmbDeselectedEvent(event.unique));
}
#searchSelectableFilter = () => true;
override render() {
return html`
<umb-body-layout headline=${this.localize.term('general_choose')}>
@@ -106,10 +108,14 @@ export class UmbCollectionItemPickerModalElement extends UmbModalBaseElement<
</umb-body-layout>
`;
}
#renderSearch() {
const selectableFilter =
this.data?.search?.pickableFilter ?? this.data?.pickableFilter ?? this.#searchSelectableFilter;
return html`
<umb-picker-search-field></umb-picker-search-field>
<umb-picker-search-result .pickableFilter=${this.data?.pickableFilter}></umb-picker-search-result>
<umb-picker-search-result .pickableFilter=${selectableFilter}></umb-picker-search-result>
`;
}

View File

@@ -1,5 +1,6 @@
import type { ElementLoaderProperty } from '@umbraco-cms/backoffice/extension-api';
import type { UUIModalElement, UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
import type { UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search';
export type * from './extensions/types.js';
@@ -10,9 +11,13 @@ export interface UmbPickerModalData<ItemType> {
search?: UmbPickerModalSearchConfig;
}
export interface UmbPickerModalSearchConfig<QueryParamsType = Record<string, unknown>> {
export interface UmbPickerModalSearchConfig<
QueryParamsType = Record<string, unknown>,
SearchResultItemType extends UmbSearchResultItemModel = UmbSearchResultItemModel,
> {
providerAlias: string;
queryParams?: QueryParamsType;
pickableFilter?: (item: SearchResultItemType) => boolean;
}
export interface UmbPickerModalValue {

View File

@@ -1,5 +1,11 @@
import type { UmbPickerDataSource } from '../data-source/types.js';
import type { UmbCollectionRepository } from '@umbraco-cms/backoffice/collection';
import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
export interface UmbPickerCollectionDataSource extends UmbPickerDataSource, UmbCollectionRepository, UmbApi {}
export interface UmbPickerCollectionDataSource<CollectionItemType extends UmbItemModel = UmbItemModel>
extends UmbPickerDataSource,
UmbCollectionRepository<CollectionItemType>,
UmbApi {
collectionPickableFilter?: (item: CollectionItemType) => boolean;
}

View File

@@ -1,8 +1,11 @@
import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import type { UmbConfigCollectionModel } from '@umbraco-cms/backoffice/utils';
export interface UmbPickerDataSource extends UmbItemRepository<any>, UmbApi {
export interface UmbPickerDataSource<PickedItemType extends UmbItemModel = UmbItemModel>
extends UmbItemRepository<PickedItemType>,
UmbApi {
setConfig?(config: UmbConfigCollectionModel | undefined): void;
getConfig?(): UmbConfigCollectionModel | undefined;
}

View File

@@ -1,4 +1,9 @@
import type { UmbPickerDataSource } from '../types.js';
import type { UmbSearchRepository } from '@umbraco-cms/backoffice/search';
import type { UmbSearchRepository, UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search';
export interface UmbPickerSearchableDataSource extends UmbPickerDataSource, UmbSearchRepository<any> {}
export interface UmbPickerSearchableDataSource<
SearchResultItemType extends UmbSearchResultItemModel = UmbSearchResultItemModel,
> extends UmbPickerDataSource,
UmbSearchRepository<SearchResultItemType> {
searchPickableFilter?: (searchItem: SearchResultItemType) => boolean;
}

View File

@@ -1,5 +1,12 @@
import type { UmbPickerDataSource } from '../data-source/types.js';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { UmbTreeRepository } from '@umbraco-cms/backoffice/tree';
import type { UmbTreeItemModel, UmbTreeRepository, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree';
export interface UmbPickerTreeDataSource extends UmbPickerDataSource, UmbTreeRepository, UmbApi {}
export interface UmbPickerTreeDataSource<
TreeItemType extends UmbTreeItemModel = UmbTreeItemModel,
TreeRootType extends UmbTreeRootModel = UmbTreeRootModel,
> extends UmbPickerDataSource,
UmbTreeRepository<TreeItemType, TreeRootType>,
UmbApi {
treePickableFilter?: (item: TreeItemType) => boolean;
}

View File

@@ -8,7 +8,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbSearchRequestArgs, UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search';
import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item';
type PickableFilterMethodType<T extends UmbItemModel = UmbItemModel> = (item: T) => boolean;
type PickableFilterMethodType<T extends UmbSearchResultItemModel = UmbSearchResultItemModel> = (item: T) => boolean;
@customElement('umb-picker-search-result')
export class UmbPickerSearchResultElement extends UmbLitElement {

View File

@@ -172,6 +172,8 @@ export class UmbTreePickerModalElement<TreeItemType extends UmbTreeItemModelBase
this._pickerContext.expansion.setExpansion(expansion);
}
#searchSelectableFilter = () => true;
override render() {
return html`
<umb-body-layout headline=${this.localize.term('general_choose')}>
@@ -181,9 +183,12 @@ export class UmbTreePickerModalElement<TreeItemType extends UmbTreeItemModelBase
`;
}
#renderSearch() {
const selectableFilter =
this.data?.search?.pickableFilter ?? this.data?.pickableFilter ?? this.#searchSelectableFilter;
return html`
<umb-picker-search-field></umb-picker-search-field>
<umb-picker-search-result .pickableFilter=${this.data?.pickableFilter}></umb-picker-search-result>
<umb-picker-search-result .pickableFilter=${selectableFilter}></umb-picker-search-result>
`;
}

View File

@@ -14,6 +14,7 @@ import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registr
import { UmbModalToken, type UmbPickerModalData } from '@umbraco-cms/backoffice/modal';
import {
UMB_TREE_PICKER_MODAL_ALIAS,
type UmbTreeItemModel,
type UmbTreePickerModalData,
type UmbTreePickerModalValue,
} from '@umbraco-cms/backoffice/tree';
@@ -84,6 +85,9 @@ export class UmbEntityDataPickerInputContext extends UmbPickerInputContext<UmbIt
}
override async openPicker(pickerData?: Partial<UmbPickerModalData<UmbItemModel>>) {
// TODO: investigate type issues
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.modalAlias = this.#getModalToken();
await super.openPicker(pickerData);
}
@@ -133,7 +137,7 @@ export class UmbEntityDataPickerInputContext extends UmbPickerInputContext<UmbIt
#createTreeItemPickerModalToken(api: UmbPickerTreeDataSource) {
const supportsSearch = isPickerSearchableDataSource(api);
return new UmbModalToken<UmbTreePickerModalData<UmbItemModel>, UmbTreePickerModalValue>(
return new UmbModalToken<UmbTreePickerModalData<UmbTreeItemModel>, UmbTreePickerModalValue>(
UMB_TREE_PICKER_MODAL_ALIAS,
{
modal: {
@@ -143,9 +147,12 @@ export class UmbEntityDataPickerInputContext extends UmbPickerInputContext<UmbIt
data: {
treeAlias: UMB_ENTITY_DATA_PICKER_TREE_ALIAS,
hideTreeRoot: true,
// TODO: make specific pickable filter for tree to avoid type issues
pickableFilter: api.treePickableFilter,
search: supportsSearch
? {
providerAlias: UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS,
pickableFilter: api.searchPickableFilter,
}
: undefined,
},
@@ -167,9 +174,12 @@ export class UmbEntityDataPickerInputContext extends UmbPickerInputContext<UmbIt
collection: {
menuAlias: UMB_ENTITY_DATA_PICKER_COLLECTION_MENU_ALIAS,
},
// TODO: make specific pickable filter for collection to avoid type issues
pickableFilter: api.collectionPickableFilter,
search: supportsSearch
? {
providerAlias: UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS,
pickableFilter: api.searchPickableFilter,
}
: undefined,
},