Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ export class ExampleCustomPickerCollectionPropertyEditorDataSource
collectionPickableFilter = (item: ExampleCollectionItemModel) => item.isPickable;

async requestCollection(args: UmbCollectionFilterModel) {
// TODO: use args to filter/paginate etc
console.log(args);
const skip = args.skip ?? 0;
const take = args.take ?? 100;

const paginatedItems = customItems.slice(skip, skip + take);

const data = {
items: customItems,
items: paginatedItems,
total: customItems.length,
};

Expand All @@ -34,12 +37,15 @@ export class ExampleCustomPickerCollectionPropertyEditorDataSource
}

async search(args: UmbSearchRequestArgs) {
const items = customItems.filter((item) => item.name?.toLowerCase().includes(args.query.toLowerCase()));
const total = items.length;
const skip = args.paging?.skip ?? 0;
const take = args.paging?.take ?? 100;

const filteredItems = customItems.filter((item) => item.name?.toLowerCase().includes(args.query.toLowerCase()));
const paginatedItems = filteredItems.slice(skip, skip + take);

const data = {
items,
total,
items: paginatedItems,
total: filteredItems.length,
};

return { data };
Expand Down Expand Up @@ -84,4 +90,74 @@ const customItems: Array<ExampleCollectionItemModel> = [
icon: 'icon-shape-triangle blue',
isPickable: true,
},
{
unique: '6',
entityType: 'example',
name: 'Example 6',
icon: 'icon-shape-triangle blue',
isPickable: true,
},
{
unique: '7',
entityType: 'example',
name: 'Example 7',
icon: 'icon-shape-triangle blue',
isPickable: true,
},
{
unique: '8',
entityType: 'example',
name: 'Example 8',
icon: 'icon-shape-triangle blue',
isPickable: true,
},
{
unique: '9',
entityType: 'example',
name: 'Example 9',
icon: 'icon-shape-triangle blue',
isPickable: false,
},
{
unique: '10',
entityType: 'example',
name: 'Example 10',
icon: 'icon-shape-triangle blue',
isPickable: true,
},
{
unique: '11',
entityType: 'example',
name: 'Example 11',
icon: 'icon-shape-triangle blue',
isPickable: true,
},
{
unique: '12',
entityType: 'example',
name: 'Example 12',
icon: 'icon-shape-triangle blue',
isPickable: true,
},
{
unique: '13',
entityType: 'example',
name: 'Example 13',
icon: 'icon-shape-triangle blue',
isPickable: true,
},
{
unique: '14',
entityType: 'example',
name: 'Example 14',
icon: 'icon-shape-triangle blue',
isPickable: false,
},
{
unique: '15',
entityType: 'example',
name: 'Example 15',
icon: 'icon-shape-triangle blue',
isPickable: true,
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export class UmbEntityItemRefElement extends UmbLitElement {
}

// Loading:
return html`<uui-loader-bar style="margin-top:10px;"></uui-loader-bar>`;
return html`<uui-loader-bar id="loader"></uui-loader-bar>`;
}

override destroy(): void {
Expand All @@ -235,6 +235,18 @@ export class UmbEntityItemRefElement extends UmbLitElement {
position: relative;
}
#loader {
margin-top: 10px;
opacity: 0;
animation: show-loader 0s 120ms forwards;
}
@keyframes show-loader {
to {
opacity: 1;
}
}
:host::after {
content: '';
position: absolute;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { UmbPickerSearchManagerConfig } from './types.js';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { debounce } from '@umbraco-cms/backoffice/utils';
import { debounce, UmbPaginationManager } from '@umbraco-cms/backoffice/utils';
import { UmbArrayState, UmbBooleanState, UmbNumberState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbSearchProvider, UmbSearchRequestArgs, UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';

/**
* A manager for searching items in a picker.
Expand All @@ -23,6 +24,9 @@ export class UmbPickerSearchManager<
#query = new UmbObjectState<SearchRequestArgsType | undefined>(undefined);
public readonly query = this.#query.asObservable();

#executedQuery = new UmbObjectState<SearchRequestArgsType | undefined>(undefined);
public readonly executedQuery = this.#executedQuery.asObservable();

#searching = new UmbBooleanState(false);
public readonly searching = this.#searching.asObservable();

Expand All @@ -32,9 +36,17 @@ export class UmbPickerSearchManager<
#resultTotalItems = new UmbNumberState(0);
public readonly resultTotalItems = this.#resultTotalItems.asObservable();

#pagination = new UmbPaginationManager();
public readonly pagination = this.#pagination;

#config?: UmbPickerSearchManagerConfig;
#searchProvider?: UmbSearchProvider<UmbSearchResultItemModel, SearchRequestArgsType>;

constructor(host: UmbControllerHost) {
super(host);
this.#pagination.setPageSize(100);
}

/**
* Set the configuration for the search manager.
* @param {UmbPickerSearchManagerConfig} config The configuration for the search manager.
Expand Down Expand Up @@ -105,9 +117,11 @@ export class UmbPickerSearchManager<
*/
public clear() {
this.#query.setValue(undefined);
this.#executedQuery.setValue(undefined);
this.#resultItems.setValue([]);
this.#searching.setValue(false);
this.#resultTotalItems.setValue(0);
this.#pagination.clear();
}

/**
Expand Down Expand Up @@ -139,6 +153,7 @@ export class UmbPickerSearchManager<
* @memberof UmbPickerSearchManager
*/
public updateQuery(query: Partial<SearchRequestArgsType>) {
this.#pagination.setCurrentPageNumber(1);
const mergedQuery = { ...this.getQuery(), ...query } as SearchRequestArgsType;
this.#query.setValue(mergedQuery);
}
Expand Down Expand Up @@ -179,12 +194,19 @@ export class UmbPickerSearchManager<
searchFrom: this.#config?.searchFrom,
// TODO: Move this implementation to another place. The generic picker search manager shouldn't be aware of data types.
dataTypeUnique: this.#config?.dataTypeUnique,
paging: {
skip: this.#pagination.getSkip(),
take: this.#pagination.getPageSize(),
},
};

const { data } = await this.#searchProvider.search(args);
const items = (data?.items as ResultItemType[]) ?? [];
this.#resultItems.setValue(items);
this.#resultTotalItems.setValue(data?.total ?? 0);
const total = data?.total ?? 0;
this.#resultTotalItems.setValue(total);
this.#pagination.setTotalItems(total);
this.#executedQuery.setValue(query);
this.#searching.setValue(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { ManifestPickerSearchResultItem } from './result-item/picker-search
import { UmbDefaultPickerSearchResultItemContext } from './result-item/default/default-picker-search-result-item.context.js';
import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui';
import type { UmbSearchRequestArgs, UmbSearchResultItemModel } from '@umbraco-cms/backoffice/search';
import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item';

Expand All @@ -13,7 +14,7 @@ type PickableFilterMethodType<T extends UmbSearchResultItemModel = UmbSearchResu
@customElement('umb-picker-search-result')
export class UmbPickerSearchResultElement extends UmbLitElement {
@state()
private _query?: UmbSearchRequestArgs;
private _executedQuery?: UmbSearchRequestArgs;

@state()
private _searching: boolean = false;
Expand All @@ -24,6 +25,15 @@ export class UmbPickerSearchResultElement extends UmbLitElement {
@state()
private _isSearchable: boolean = false;

@state()
private _currentPage = 1;

@state()
private _totalPages = 1;

@state()
private _totalItems = 0;

@property({ attribute: false })
pickableFilter: PickableFilterMethodType = () => true;

Expand All @@ -34,25 +44,47 @@ export class UmbPickerSearchResultElement extends UmbLitElement {

this.consumeContext(UMB_PICKER_CONTEXT, (context) => {
this.#pickerContext = context;

this.observe(
this.#pickerContext?.search.searchable,
(isSearchable) => (this._isSearchable = isSearchable ?? false),
'obsSearchable',
null,
);

this.observe(
this.#pickerContext?.search.executedQuery,
(executedQuery) => (this._executedQuery = executedQuery),
null,
);

this.observe(this.#pickerContext?.search.searching, (searching) => (this._searching = searching ?? false), null);

this.observe(this.#pickerContext?.search.resultItems, (items) => (this._items = items ?? []), null);

this.observe(
this.#pickerContext?.search.pagination.currentPage,
(currentPage) => (this._currentPage = currentPage ?? 1),
null,
);
this.observe(this.#pickerContext?.search.query, (query) => (this._query = query), 'obsQuery');

this.observe(
this.#pickerContext?.search.pagination.totalPages,
(totalPages) => (this._totalPages = totalPages ?? 1),
null,
);

this.observe(
this.#pickerContext?.search.searching,
(query) => (this._searching = query ?? false),
'obsSearching',
this.#pickerContext?.search.resultTotalItems,
(totalItems) => (this._totalItems = totalItems ?? 0),
null,
);
this.observe(this.#pickerContext?.search.resultItems, (items) => (this._items = items ?? []), 'obsResultItems');
});
}

override render() {
if (!this._isSearchable) return nothing;

if (this._query?.query && this._searching === false && this._items.length === 0) {
if (this._executedQuery?.query && this._searching === false && this._items.length === 0) {
return this.#renderEmptyResult();
}

Expand All @@ -61,19 +93,41 @@ export class UmbPickerSearchResultElement extends UmbLitElement {
}

return html`
<uui-box id="result-container">
<uui-box id="result-container" class=${this._searching ? 'loading' : ''}>
${repeat(
this._items,
(item) => item.unique,
(item) => this.#renderResultItem(item),
)}
${this.#renderPagination()}
</uui-box>
`;
}

#onPageChange(event: UUIPaginationEvent) {
this.#pickerContext?.search.pagination.setCurrentPageNumber(event.target.current);
this.#pickerContext?.search.search();
}

#renderPagination() {
// Don't show pagination if all items are loaded or there's only one page
if (this._items.length === this._totalItems || this._totalPages <= 1) {
return nothing;
}

return html`<uui-pagination
.current=${this._currentPage}
.total=${this._totalPages}
firstlabel=${this.localize.term('general_first')}
previouslabel=${this.localize.term('general_previous')}
nextlabel=${this.localize.term('general_next')}
lastlabel=${this.localize.term('general_last')}
@change=${this.#onPageChange}></uui-pagination>`;
}

#renderEmptyResult() {
return html`<uui-box>
<small>No result for <strong>"${this._query?.query}"</strong>.</small>
<small>No result for <strong>"${this._executedQuery?.query}"</strong>.</small>
</uui-box>`;
}

Expand Down Expand Up @@ -104,6 +158,10 @@ export class UmbPickerSearchResultElement extends UmbLitElement {
display: block;
}

#result-container.loading {
opacity: 0.5;
}

umb-extension-with-api-slot {
display: block;
margin-bottom: var(--uui-size-3);
Expand All @@ -112,6 +170,11 @@ export class UmbPickerSearchResultElement extends UmbLitElement {
margin-bottom: 0;
}
}

uui-pagination {
display: block;
margin-top: var(--uui-size-layout-1);
}
`,
];
}
Expand Down
2 changes: 2 additions & 0 deletions src/Umbraco.Web.UI.Client/src/packages/core/search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { UmbItemModel } from '@umbraco-cms/backoffice/entity-item';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { FieldPresentationModel, SearchResultResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbPagedModel, UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository';
import type { UmbOffsetPaginationRequestModel } from '@umbraco-cms/backoffice/utils';

export type { UmbSearchDataSource } from './search-data-source.interface.js';
export type { UmbSearchRepository } from './search-repository.interface.js';
Expand All @@ -19,6 +20,7 @@ export interface UmbSearchResultItemModel extends UmbItemModel {
export type UmbSearchRequestArgs = {
query: string;
searchFrom?: UmbEntityModel;
paging?: UmbOffsetPaginationRequestModel;
};

export type UmbSearchFieldPresentationModel = FieldPresentationModel;
Expand Down
Loading
Loading