Skip to content

Commit c530342

Browse files
Feature: Entity Item Ref Extension (#18265)
* wip entity-item-ref extension point * clean up * add ref list element * fix styling * Update document-item-ref.element.ts * move item repo * implement for input member * enable action slot * add null check * fix sorting again * fix sorting again * use member element * add draft styling back * move item repository * implement for user input * pass readonly and standalone props * make editPath a state * Update member-item-ref.element.ts * Fix user item ref * remove open button * remove unused * remove unused * check for section permission * add fallback element * add unique to modal route registration * add unique to modal router * remove unused id * Update member-item-ref.element.ts * append unique * compare with old value * only recreate the controller if the entity type changes * fix console warning * use addUniquePaths for modal registration
1 parent b360762 commit c530342

File tree

74 files changed

+659
-179
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+659
-179
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { UmbDefaultItemModel } from '../types.js';
2+
import { customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit';
3+
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
4+
5+
@customElement('umb-default-item-ref')
6+
export class UmbDefaultItemRefElement extends UmbLitElement {
7+
@property({ type: Object })
8+
item?: UmbDefaultItemModel;
9+
10+
@property({ type: Boolean })
11+
readonly = false;
12+
13+
@property({ type: Boolean })
14+
standalone = false;
15+
16+
override render() {
17+
if (!this.item) return nothing;
18+
19+
return html`
20+
<uui-ref-node name=${this.item.name} ?readonly=${this.readonly} ?standalone=${this.standalone}>
21+
<slot name="actions" slot="actions"></slot>
22+
${this.#renderIcon(this.item)}
23+
</uui-ref-node>
24+
`;
25+
}
26+
27+
#renderIcon(item: UmbDefaultItemModel) {
28+
if (!item.icon) return;
29+
return html`<umb-icon slot="icon" name=${item.icon}></umb-icon>`;
30+
}
31+
}
32+
33+
declare global {
34+
interface HTMLElementTagNameMap {
35+
'umb-default-item-ref': UmbDefaultItemRefElement;
36+
}
37+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import type { UmbEntityModel } from '../types.js';
2+
import type { ManifestEntityItemRef } from './entity-item-ref.extension.js';
3+
import { customElement, property, type PropertyValueMap, state, css, html } from '@umbraco-cms/backoffice/external/lit';
4+
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
5+
import { UmbExtensionsElementInitializer } from '@umbraco-cms/backoffice/extension-api';
6+
import { UMB_MARK_ATTRIBUTE_NAME } from '@umbraco-cms/backoffice/const';
7+
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
8+
9+
import './default-item-ref.element.js';
10+
11+
@customElement('umb-entity-item-ref')
12+
export class UmbEntityItemRefElement extends UmbLitElement {
13+
#extensionsController?: UmbExtensionsElementInitializer<any>;
14+
#item?: UmbEntityModel;
15+
16+
@state()
17+
_component?: any; // TODO: Add type
18+
19+
@property({ type: Object, attribute: false })
20+
public get item(): UmbEntityModel | undefined {
21+
return this.#item;
22+
}
23+
public set item(value: UmbEntityModel | undefined) {
24+
const oldValue = this.#item;
25+
this.#item = value;
26+
27+
if (value === oldValue) return;
28+
if (!value) return;
29+
30+
// If the component is already created and the entity type is the same, we can just update the item.
31+
if (this._component && value.entityType === oldValue?.entityType) {
32+
this._component.item = value;
33+
return;
34+
}
35+
36+
// If the component is already created, but the entity type is different, we need to destroy the component.
37+
this.#createController(value.entityType);
38+
}
39+
40+
#readonly = false;
41+
@property({ type: Boolean, attribute: 'readonly' })
42+
public get readonly() {
43+
return this.#readonly;
44+
}
45+
public set readonly(value) {
46+
this.#readonly = value;
47+
48+
if (this._component) {
49+
this._component.readonly = this.#readonly;
50+
}
51+
}
52+
53+
#standalone = false;
54+
@property({ type: Boolean, attribute: 'standalone' })
55+
public get standalone() {
56+
return this.#standalone;
57+
}
58+
public set standalone(value) {
59+
this.#standalone = value;
60+
61+
if (this._component) {
62+
this._component.standalone = this.#standalone;
63+
}
64+
}
65+
66+
protected override firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
67+
super.firstUpdated(_changedProperties);
68+
this.setAttribute(UMB_MARK_ATTRIBUTE_NAME, 'entity-item-ref');
69+
}
70+
71+
#createController(entityType: string) {
72+
if (this.#extensionsController) {
73+
this.#extensionsController.destroy();
74+
}
75+
76+
this.#extensionsController = new UmbExtensionsElementInitializer(
77+
this,
78+
umbExtensionsRegistry,
79+
'entityItemRef',
80+
(manifest: ManifestEntityItemRef) => manifest.forEntityTypes.includes(entityType),
81+
(extensionControllers) => {
82+
this._component?.remove();
83+
const component = extensionControllers[0]?.component || document.createElement('umb-default-item-ref');
84+
85+
// assign the properties to the component
86+
component.item = this.#item;
87+
component.readonly = this.readonly;
88+
component.standalone = this.standalone;
89+
90+
// Proxy the actions slot to the component
91+
const slotElement = document.createElement('slot');
92+
slotElement.name = 'actions';
93+
slotElement.setAttribute('slot', 'actions');
94+
component.appendChild(slotElement);
95+
96+
this._component = component;
97+
},
98+
undefined, // We can leave the alias to undefined, as we destroy this our selfs.
99+
undefined,
100+
{ single: true },
101+
);
102+
}
103+
104+
override render() {
105+
return html`${this._component}`;
106+
}
107+
108+
static override styles = [
109+
css`
110+
:host {
111+
display: block;
112+
position: relative;
113+
}
114+
`,
115+
];
116+
}
117+
118+
export { UmbEntityItemRefElement as element };
119+
120+
declare global {
121+
interface HTMLElementTagNameMap {
122+
'umb-entity-item-ref': UmbEntityItemRefElement;
123+
}
124+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
2+
3+
export interface ManifestEntityItemRef<MetaType extends MetaEntityItemRef = MetaEntityItemRef>
4+
extends ManifestElement<any>,
5+
ManifestWithDynamicConditions<UmbExtensionConditionConfig> {
6+
type: 'entityItemRef';
7+
meta: MetaType;
8+
forEntityTypes: Array<string>;
9+
}
10+
11+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
12+
export interface MetaEntityItemRef {}
13+
14+
declare global {
15+
interface UmbExtensionManifestMap {
16+
umbManifestEntityItemRef: ManifestEntityItemRef;
17+
}
18+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import './entity-item-ref.element.js';
2+
3+
export * from './entity-item-ref.element.js';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type * from './types.js';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { UMB_ENTITY_CONTEXT } from './entity.context-token.js';
22
export { UmbEntityContext } from './entity.context.js';
3+
export * from './entity-item-ref/index.js';
34
export type * from './types.js';

src/Umbraco.Web.UI.Client/src/packages/core/entity/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ export interface UmbEntityModel {
88
export interface UmbNamedEntityModel extends UmbEntityModel {
99
name: string;
1010
}
11+
12+
export interface UmbDefaultItemModel extends UmbNamedEntityModel {
13+
icon?: string;
14+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './picker-input.context.js';
2+
export * from './picker-input.context-token.js';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { UmbPickerInputContext } from './picker-input.context.js';
2+
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
3+
4+
export const UMB_PICKER_INPUT_CONTEXT = new UmbContextToken<UmbPickerInputContext>('UmbPickerInputContext');

src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { UMB_PICKER_INPUT_CONTEXT } from './picker-input.context-token.js';
12
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
2-
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
3+
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
34
import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository';
45
import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from '@umbraco-cms/backoffice/modal';
56
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
@@ -13,7 +14,7 @@ export class UmbPickerInputContext<
1314
PickerItemType extends PickerItemBaseType = PickedItemType,
1415
PickerModalConfigType extends UmbPickerModalData<PickerItemType> = UmbPickerModalData<PickerItemType>,
1516
PickerModalValueType extends UmbPickerModalValue = UmbPickerModalValue,
16-
> extends UmbControllerBase {
17+
> extends UmbContextBase<UmbPickerInputContext> {
1718
modalAlias: string | UmbModalToken<UmbPickerModalData<PickerItemType>, PickerModalValueType>;
1819
repository?: UmbItemRepository<PickedItemType>;
1920
#getUnique: (entry: PickedItemType) => string | undefined;
@@ -59,9 +60,8 @@ export class UmbPickerInputContext<
5960
modalAlias: string | UmbModalToken<UmbPickerModalData<PickerItemType>, PickerModalValueType>,
6061
getUniqueMethod?: (entry: PickedItemType) => string | undefined,
6162
) {
62-
super(host);
63+
super(host, UMB_PICKER_INPUT_CONTEXT);
6364
this.modalAlias = modalAlias;
64-
this.#getUnique = getUniqueMethod || ((entry) => entry.unique);
6565

6666
this.#getUnique = getUniqueMethod
6767
? (entry: PickedItemType) => {
@@ -74,7 +74,7 @@ export class UmbPickerInputContext<
7474
}
7575
: (entry) => entry.unique;
7676

77-
this.#itemManager = new UmbRepositoryItemsManager<PickedItemType>(this, repositoryAlias, this.#getUnique);
77+
this.#itemManager = new UmbRepositoryItemsManager<PickedItemType>(this, repositoryAlias, getUniqueMethod);
7878

7979
this.selection = this.#itemManager.uniques;
8080
this.selectedItems = this.#itemManager.items;

0 commit comments

Comments
 (0)