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": [ "cSpell.words": [
"backoffice",
"pickable",
"Pickable",
"unprovide", "unprovide",
"Unproviding" "Unproviding"
], ],

View File

@@ -1,12 +1,21 @@
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbPickerTreeDataSource } from '@umbraco-cms/backoffice/picker-data-source'; import type {
import type { UmbSearchRequestArgs } from '@umbraco-cms/backoffice/search'; 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'; import type { UmbTreeChildrenOfRequestArgs, UmbTreeItemModel } from '@umbraco-cms/backoffice/tree';
export class ExampleCustomPickerTreePropertyEditorDataSource export class ExampleCustomPickerTreePropertyEditorDataSource
extends UmbControllerBase 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() { async requestTreeRoot() {
const root = { const root = {
unique: null, unique: null,
@@ -60,7 +69,7 @@ export class ExampleCustomPickerTreePropertyEditorDataSource
const data = { const data = {
items: result, items: result,
totalItems: result.length, total: result.length,
}; };
return { data }; return { data };

View File

@@ -3,7 +3,10 @@ import {
UmbDocumentItemRepository, UmbDocumentItemRepository,
UmbDocumentSearchRepository, UmbDocumentSearchRepository,
UmbDocumentTreeRepository, UmbDocumentTreeRepository,
type UmbDocumentSearchItemModel,
type UmbDocumentSearchRequestArgs, type UmbDocumentSearchRequestArgs,
type UmbDocumentTreeItemModel,
type UmbDocumentTreeRootModel,
} from '@umbraco-cms/backoffice/document'; } from '@umbraco-cms/backoffice/document';
import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '@umbraco-cms/backoffice/document-type'; import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '@umbraco-cms/backoffice/document-type';
import type { import type {
@@ -20,16 +23,21 @@ import { getConfigValue, type UmbConfigCollectionModel } from '@umbraco-cms/back
export class ExampleDocumentPickerPropertyEditorDataSource export class ExampleDocumentPickerPropertyEditorDataSource
extends UmbControllerBase extends UmbControllerBase
implements UmbPickerTreeDataSource, UmbPickerSearchableDataSource implements
UmbPickerTreeDataSource<UmbDocumentTreeItemModel, UmbDocumentTreeRootModel>,
UmbPickerSearchableDataSource<UmbDocumentSearchItemModel>
{ {
#tree = new UmbDocumentTreeRepository(this); #tree = new UmbDocumentTreeRepository(this);
#item = new UmbDocumentItemRepository(this); #item = new UmbDocumentItemRepository(this);
#search = new UmbDocumentSearchRepository(this); #search = new UmbDocumentSearchRepository(this);
#config: UmbConfigCollectionModel = []; #config: UmbConfigCollectionModel = [];
treePickableFilter: (treeItem: UmbDocumentTreeItemModel) => boolean = (treeItem) => !!treeItem.unique;
setConfig(config: UmbConfigCollectionModel) { setConfig(config: UmbConfigCollectionModel) {
// TODO: add examples for all config options // TODO: add examples for all config options
this.#config = config; this.#config = config;
this.#applyPickableFilterFromConfig();
} }
getConfig(): UmbConfigCollectionModel { getConfig(): UmbConfigCollectionModel {
@@ -57,6 +65,13 @@ export class ExampleDocumentPickerPropertyEditorDataSource
} }
search(args: UmbSearchRequestArgs) { 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 filterString = getConfigValue<string>(this.#config, 'filter');
const filterArray = filterString ? filterString.split(',') : []; const filterArray = filterString ? filterString.split(',') : [];
const allowedContentTypes: UmbDocumentSearchRequestArgs['allowedContentTypes'] = filterArray.map( const allowedContentTypes: UmbDocumentSearchRequestArgs['allowedContentTypes'] = filterArray.map(
@@ -65,10 +80,14 @@ export class ExampleDocumentPickerPropertyEditorDataSource
unique, unique,
}), }),
); );
return allowedContentTypes;
}
const combinedArgs: UmbDocumentSearchRequestArgs = { ...args, allowedContentTypes }; #applyPickableFilterFromConfig() {
const allowedDocumentTypes = this.#getAllowedDocumentTypesConfig();
return this.#search.search(combinedArgs); 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)); this.modalContext?.dispatchEvent(new UmbDeselectedEvent(event.unique));
} }
#searchSelectableFilter = () => true;
override render() { override render() {
return html` return html`
<umb-body-layout headline=${this.localize.term('general_choose')}> <umb-body-layout headline=${this.localize.term('general_choose')}>
@@ -106,10 +108,14 @@ export class UmbCollectionItemPickerModalElement extends UmbModalBaseElement<
</umb-body-layout> </umb-body-layout>
`; `;
} }
#renderSearch() { #renderSearch() {
const selectableFilter =
this.data?.search?.pickableFilter ?? this.data?.pickableFilter ?? this.#searchSelectableFilter;
return html` return html`
<umb-picker-search-field></umb-picker-search-field> <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 { ElementLoaderProperty } from '@umbraco-cms/backoffice/extension-api';
import type { UUIModalElement, UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui'; import type { UUIModalElement, UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
import type { UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search';
export type * from './extensions/types.js'; export type * from './extensions/types.js';
@@ -10,9 +11,13 @@ export interface UmbPickerModalData<ItemType> {
search?: UmbPickerModalSearchConfig; search?: UmbPickerModalSearchConfig;
} }
export interface UmbPickerModalSearchConfig<QueryParamsType = Record<string, unknown>> { export interface UmbPickerModalSearchConfig<
QueryParamsType = Record<string, unknown>,
SearchResultItemType extends UmbSearchResultItemModel = UmbSearchResultItemModel,
> {
providerAlias: string; providerAlias: string;
queryParams?: QueryParamsType; queryParams?: QueryParamsType;
pickableFilter?: (item: SearchResultItemType) => boolean;
} }
export interface UmbPickerModalValue { export interface UmbPickerModalValue {

View File

@@ -1,5 +1,11 @@
import type { UmbPickerDataSource } from '../data-source/types.js'; import type { UmbPickerDataSource } from '../data-source/types.js';
import type { UmbCollectionRepository } from '@umbraco-cms/backoffice/collection'; 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'; 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 { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import type { UmbConfigCollectionModel } from '@umbraco-cms/backoffice/utils'; 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; setConfig?(config: UmbConfigCollectionModel | undefined): void;
getConfig?(): UmbConfigCollectionModel | undefined; getConfig?(): UmbConfigCollectionModel | undefined;
} }

View File

@@ -1,4 +1,9 @@
import type { UmbPickerDataSource } from '../types.js'; 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 { UmbPickerDataSource } from '../data-source/types.js';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; 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 { UmbSearchRequestArgs, UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search';
import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item'; 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') @customElement('umb-picker-search-result')
export class UmbPickerSearchResultElement extends UmbLitElement { export class UmbPickerSearchResultElement extends UmbLitElement {

View File

@@ -172,6 +172,8 @@ export class UmbTreePickerModalElement<TreeItemType extends UmbTreeItemModelBase
this._pickerContext.expansion.setExpansion(expansion); this._pickerContext.expansion.setExpansion(expansion);
} }
#searchSelectableFilter = () => true;
override render() { override render() {
return html` return html`
<umb-body-layout headline=${this.localize.term('general_choose')}> <umb-body-layout headline=${this.localize.term('general_choose')}>
@@ -181,9 +183,12 @@ export class UmbTreePickerModalElement<TreeItemType extends UmbTreeItemModelBase
`; `;
} }
#renderSearch() { #renderSearch() {
const selectableFilter =
this.data?.search?.pickableFilter ?? this.data?.pickableFilter ?? this.#searchSelectableFilter;
return html` return html`
<umb-picker-search-field></umb-picker-search-field> <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 { UmbModalToken, type UmbPickerModalData } from '@umbraco-cms/backoffice/modal';
import { import {
UMB_TREE_PICKER_MODAL_ALIAS, UMB_TREE_PICKER_MODAL_ALIAS,
type UmbTreeItemModel,
type UmbTreePickerModalData, type UmbTreePickerModalData,
type UmbTreePickerModalValue, type UmbTreePickerModalValue,
} from '@umbraco-cms/backoffice/tree'; } from '@umbraco-cms/backoffice/tree';
@@ -84,6 +85,9 @@ export class UmbEntityDataPickerInputContext extends UmbPickerInputContext<UmbIt
} }
override async openPicker(pickerData?: Partial<UmbPickerModalData<UmbItemModel>>) { 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(); this.modalAlias = this.#getModalToken();
await super.openPicker(pickerData); await super.openPicker(pickerData);
} }
@@ -133,7 +137,7 @@ export class UmbEntityDataPickerInputContext extends UmbPickerInputContext<UmbIt
#createTreeItemPickerModalToken(api: UmbPickerTreeDataSource) { #createTreeItemPickerModalToken(api: UmbPickerTreeDataSource) {
const supportsSearch = isPickerSearchableDataSource(api); const supportsSearch = isPickerSearchableDataSource(api);
return new UmbModalToken<UmbTreePickerModalData<UmbItemModel>, UmbTreePickerModalValue>( return new UmbModalToken<UmbTreePickerModalData<UmbTreeItemModel>, UmbTreePickerModalValue>(
UMB_TREE_PICKER_MODAL_ALIAS, UMB_TREE_PICKER_MODAL_ALIAS,
{ {
modal: { modal: {
@@ -143,9 +147,12 @@ export class UmbEntityDataPickerInputContext extends UmbPickerInputContext<UmbIt
data: { data: {
treeAlias: UMB_ENTITY_DATA_PICKER_TREE_ALIAS, treeAlias: UMB_ENTITY_DATA_PICKER_TREE_ALIAS,
hideTreeRoot: true, hideTreeRoot: true,
// TODO: make specific pickable filter for tree to avoid type issues
pickableFilter: api.treePickableFilter,
search: supportsSearch search: supportsSearch
? { ? {
providerAlias: UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS, providerAlias: UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS,
pickableFilter: api.searchPickableFilter,
} }
: undefined, : undefined,
}, },
@@ -167,9 +174,12 @@ export class UmbEntityDataPickerInputContext extends UmbPickerInputContext<UmbIt
collection: { collection: {
menuAlias: UMB_ENTITY_DATA_PICKER_COLLECTION_MENU_ALIAS, menuAlias: UMB_ENTITY_DATA_PICKER_COLLECTION_MENU_ALIAS,
}, },
// TODO: make specific pickable filter for collection to avoid type issues
pickableFilter: api.collectionPickableFilter,
search: supportsSearch search: supportsSearch
? { ? {
providerAlias: UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS, providerAlias: UMB_ENTITY_DATA_PICKER_SEARCH_PROVIDER_ALIAS,
pickableFilter: api.searchPickableFilter,
} }
: undefined, : undefined,
}, },