Docs: Collection example (#19593)

* add basic collection example

* add card view example

* update example readme

* Add workspace view example with collection
This commit is contained in:
Mads Rasmussen
2025-06-24 09:31:26 +02:00
committed by GitHub
parent e6791246e4
commit ef453ad62e
18 changed files with 416 additions and 1 deletions

View File

@@ -0,0 +1,20 @@
# Collection Example
This example demonstrates how to register a collection with collection views.
The example includes:
- Collection Registration
- Collection Repository
- Collection Pagination
- Table Collection View
- Card Collection View
- Collection as a Dashboard
- Collection as a Workspace View
TODO: This example is not complete, it is missing the following features:
- Collection Action
- Collection Filtering
- Entity Actions
- Selection + Bulk Actions

View File

@@ -0,0 +1,82 @@
import type { ExampleCollectionItemModel } from '../repository/types.js';
import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@customElement('example-card-collection-view')
export class ExampleCardCollectionViewElement extends UmbLitElement {
@state()
private _items: Array<ExampleCollectionItemModel> = [];
#collectionContext?: UmbDefaultCollectionContext<ExampleCollectionItemModel>;
constructor() {
super();
this.consumeContext(UMB_COLLECTION_CONTEXT, (instance) => {
this.#collectionContext = instance;
this.#observeCollectionItems();
});
}
#observeCollectionItems() {
this.observe(this.#collectionContext?.items, (items) => (this._items = items || []), 'umbCollectionItemsObserver');
}
override render() {
return html`
<div id="card-grid">
${repeat(
this._items,
(item) => item.unique,
(item) =>
html` <uui-card>
<uui-icon name="icon-newspaper"></uui-icon>
<div>${item.name}</div>
</uui-card>`,
)}
</div>
`;
}
static override styles = [
UmbTextStyles,
css`
:host {
display: flex;
flex-direction: column;
}
#card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-auto-rows: 200px;
gap: var(--uui-size-space-5);
}
uui-card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
height: 100%;
uui-icon {
font-size: 2em;
margin-bottom: var(--uui-size-space-4);
}
}
`,
];
}
export { ExampleCardCollectionViewElement as element };
declare global {
interface HTMLElementTagNameMap {
'example-card-collection-view': ExampleCardCollectionViewElement;
}
}

View File

@@ -0,0 +1 @@
export { UMB_LANGUAGE_TABLE_COLLECTION_VIEW_ALIAS } from './manifests.js';

View File

@@ -0,0 +1,23 @@
import { EXAMPLE_COLLECTION_ALIAS } from '../constants.js';
import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'collectionView',
alias: 'Example.CollectionView.Card',
name: 'Example Card Collection View',
js: () => import('./collection-view.element.js'),
weight: 50,
meta: {
label: 'Card',
icon: 'icon-grid',
pathName: 'card',
},
conditions: [
{
alias: UMB_COLLECTION_ALIAS_CONDITION,
match: EXAMPLE_COLLECTION_ALIAS,
},
],
},
];

View File

@@ -0,0 +1 @@
export const EXAMPLE_COLLECTION_ALIAS = 'Example.Collection';

View File

@@ -0,0 +1,20 @@
import { EXAMPLE_COLLECTION_ALIAS } from './constants.js';
import { EXAMPLE_COLLECTION_REPOSITORY_ALIAS } from './repository/constants.js';
import { manifests as cardViewManifests } from './card-view/manifests.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as tableViewManifests } from './table-view/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'collection',
kind: 'default',
alias: EXAMPLE_COLLECTION_ALIAS,
name: 'Example Collection',
meta: {
repositoryAlias: EXAMPLE_COLLECTION_REPOSITORY_ALIAS,
},
},
...cardViewManifests,
...repositoryManifests,
...tableViewManifests,
];

View File

@@ -0,0 +1,64 @@
import type { ExampleCollectionFilterModel, ExampleCollectionItemModel } from './types.js';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
import type { UmbCollectionRepository } from '@umbraco-cms/backoffice/collection';
export class ExampleCollectionRepository
extends UmbRepositoryBase
implements UmbCollectionRepository<ExampleCollectionItemModel, ExampleCollectionFilterModel>
{
async requestCollection(args: ExampleCollectionFilterModel) {
const skip = args.skip || 0;
const take = args.take || 10;
// Simulating a data fetch. This would in most cases be replaced with an API call.
let items = [
{
unique: '3e31e9c5-7d66-4c99-a9e5-d9f2b1e2b22f',
entityType: 'example',
name: 'Example Item 1',
},
{
unique: 'bc9b6e24-4b11-4dd6-8d4e-7c4f70e59f3c',
entityType: 'example',
name: 'Example Item 2',
},
{
unique: '5a2f4e3a-ef7e-470e-8c3c-3d859c02ae0d',
entityType: 'example',
name: 'Example Item 3',
},
{
unique: 'f4c3d8b8-6d79-4c87-9aa9-56b1d8fda702',
entityType: 'example',
name: 'Example Item 4',
},
{
unique: 'c9f0a8a3-1b49-4724-bde3-70e31592eb6e',
entityType: 'example',
name: 'Example Item 5',
},
];
// Simulating filtering based on the args
if (args.filter) {
items = items.filter((item) => item.name.toLowerCase().includes(args.filter!.toLowerCase()));
}
// Get the total number of items before pagination
const totalItems = items.length;
// Simulating pagination
const start = skip;
const end = start + take;
items = items.slice(start, end);
const data = {
items,
total: totalItems,
};
return { data };
}
}
export { ExampleCollectionRepository as api };

View File

@@ -0,0 +1 @@
export const EXAMPLE_COLLECTION_REPOSITORY_ALIAS = 'Example.Repository.Collection';

View File

@@ -0,0 +1,10 @@
import { EXAMPLE_COLLECTION_REPOSITORY_ALIAS } from './constants.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'repository',
alias: EXAMPLE_COLLECTION_REPOSITORY_ALIAS,
name: 'Example Collection Repository',
api: () => import('./collection.repository.js'),
},
];

View File

@@ -0,0 +1,9 @@
import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection';
export interface ExampleCollectionItemModel {
unique: string;
entityType: string;
name: string;
}
export interface ExampleCollectionFilterModel extends UmbCollectionFilterModel {}

View File

@@ -0,0 +1,89 @@
import type { ExampleCollectionItemModel } from '../repository/types.js';
import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
import type { UmbTableColumn, UmbTableConfig, UmbTableItem } from '@umbraco-cms/backoffice/components';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@customElement('example-table-collection-view')
export class ExampleTableCollectionViewElement extends UmbLitElement {
@state()
private _tableConfig: UmbTableConfig = {
allowSelection: false,
};
@state()
private _tableColumns: Array<UmbTableColumn> = [
{
name: 'Name',
alias: 'name',
},
];
@state()
private _tableItems: Array<UmbTableItem> = [];
#collectionContext?: UmbDefaultCollectionContext<ExampleCollectionItemModel>;
constructor() {
super();
this.consumeContext(UMB_COLLECTION_CONTEXT, (instance) => {
this.#collectionContext = instance;
this.#observeCollectionItems();
});
}
#observeCollectionItems() {
this.observe(
this.#collectionContext?.items,
(items) => this.#createTableItems(items),
'umbCollectionItemsObserver',
);
}
#createTableItems(items: Array<ExampleCollectionItemModel> | undefined) {
if (!items) {
this._tableItems = [];
return;
}
this._tableItems = items.map((item) => {
return {
id: item.unique,
icon: 'icon-newspaper',
data: [
{
columnAlias: 'name',
value: item.name,
},
],
};
});
}
override render() {
return html`
<umb-table .config=${this._tableConfig} .columns=${this._tableColumns} .items=${this._tableItems}></umb-table>
`;
}
static override styles = [
UmbTextStyles,
css`
:host {
display: flex;
flex-direction: column;
}
`,
];
}
export { ExampleTableCollectionViewElement as element };
declare global {
interface HTMLElementTagNameMap {
'example-table-collection-view': ExampleTableCollectionViewElement;
}
}

View File

@@ -0,0 +1 @@
export { UMB_LANGUAGE_TABLE_COLLECTION_VIEW_ALIAS } from './manifests.js';

View File

@@ -0,0 +1,23 @@
import { EXAMPLE_COLLECTION_ALIAS } from '../constants.js';
import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'collectionView',
alias: 'Example.CollectionView.Table',
name: 'Example Table Collection View',
js: () => import('./collection-view.element.js'),
weight: 100,
meta: {
label: 'Table',
icon: 'icon-list',
pathName: 'table',
},
conditions: [
{
alias: UMB_COLLECTION_ALIAS_CONDITION,
match: EXAMPLE_COLLECTION_ALIAS,
},
],
},
];

View File

@@ -0,0 +1,23 @@
import { EXAMPLE_COLLECTION_ALIAS } from '../collection/constants.js';
import { html, customElement, LitElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import type { UmbCollectionConfiguration } from '@umbraco-cms/backoffice/collection';
@customElement('example-dashboard-with-collection')
export class ExampleDashboardWithCollection extends UmbElementMixin(LitElement) {
#config: UmbCollectionConfiguration = {
pageSize: 3,
};
override render() {
return html`<umb-collection alias=${EXAMPLE_COLLECTION_ALIAS} .config=${this.#config}></umb-collection>`;
}
}
export { ExampleDashboardWithCollection as element };
declare global {
interface HTMLElementTagNameMap {
'example-dashboard-with-collection': ExampleDashboardWithCollection;
}
}

View File

@@ -0,0 +1,14 @@
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'dashboard',
kind: 'default',
name: 'Example Dashboard With Collection',
alias: 'Example.Dashboard.WithCollection',
element: () => import('./dashboard-with-collection.element.js'),
weight: 3000,
meta: {
label: 'Collection Example',
pathname: 'collection-example',
},
},
];

View File

@@ -0,0 +1,9 @@
import { manifests as collectionManifests } from './collection/manifests.js';
import { manifests as dashboardManifests } from './dashboard-with-collection/manifests.js';
import { manifests as workspaceViewManifests } from './workspace-view-with-collection/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [
...collectionManifests,
...dashboardManifests,
...workspaceViewManifests,
];

View File

@@ -0,0 +1,25 @@
import { EXAMPLE_COLLECTION_ALIAS } from '../collection/constants.js';
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
import { UMB_DOCUMENT_WORKSPACE_ALIAS } from '@umbraco-cms/backoffice/document';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'workspaceView',
kind: 'collection',
name: 'Example Workspace View With Collection',
alias: 'Example.WorkspaceView.WithCollection',
weight: 3000,
meta: {
label: 'Collection Example',
pathname: 'collection-example',
icon: 'icon-layers',
collectionAlias: EXAMPLE_COLLECTION_ALIAS,
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_DOCUMENT_WORKSPACE_ALIAS,
},
],
},
];