append api to folder name
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { UmbControllerHostElement, UmbControllerHostMixin } from './controller-host.mixin';
|
||||
|
||||
export class UmbControllerHostInitializerElement
|
||||
extends UmbControllerHostMixin(HTMLElement)
|
||||
implements UmbControllerHostElement
|
||||
{
|
||||
/**
|
||||
* A way to initialize controllers.
|
||||
* @required
|
||||
*/
|
||||
create?: (host: UmbControllerHostElement) => void;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
const slot = document.createElement('slot');
|
||||
this.shadowRoot?.appendChild(slot);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.create) {
|
||||
this.create(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('umb-controller-host-initializer', UmbControllerHostInitializerElement);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-controller-host-initializer': UmbControllerHostInitializerElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { UmbControllerHostInitializerElement } from './controller-host-initializer.element';
|
||||
import { UmbControllerHostElement, UmbControllerHostMixin } from './controller-host.mixin';
|
||||
import { UmbContextConsumerController, UmbContextProviderController } from '@umbraco-cms/backoffice/context-api';
|
||||
|
||||
@customElement('umb-test-controller-host-initializer-consumer')
|
||||
export class UmbTestControllerHostInitializerConsumerElement extends UmbControllerHostMixin(HTMLElement) {
|
||||
public value: string | null = null;
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
new UmbContextConsumerController<string>(this, 'my-test-context-alias', (value) => {
|
||||
this.value = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
describe('UmbControllerHostTestElement', () => {
|
||||
let element: UmbControllerHostInitializerElement;
|
||||
let consumer: UmbTestControllerHostInitializerConsumerElement;
|
||||
const contextValue = 'test-value';
|
||||
|
||||
beforeEach(async () => {
|
||||
element = await fixture(
|
||||
html` <umb-controller-host-initializer
|
||||
.create=${(host: UmbControllerHostElement) =>
|
||||
new UmbContextProviderController(host, 'my-test-context-alias', contextValue)}>
|
||||
<umb-test-controller-host-initializer-consumer></umb-test-controller-host-initializer-consumer>
|
||||
</umb-controller-host-initializer>`
|
||||
);
|
||||
consumer = element.getElementsByTagName(
|
||||
'umb-test-controller-host-initializer-consumer'
|
||||
)[0] as UmbTestControllerHostInitializerConsumerElement;
|
||||
});
|
||||
|
||||
it('element is defined with its own instance', () => {
|
||||
expect(element).to.be.instanceOf(UmbControllerHostInitializerElement);
|
||||
});
|
||||
|
||||
it('provides the context', () => {
|
||||
expect(consumer.value).to.equal(contextValue);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import { UmbControllerInterface } from './controller.interface';
|
||||
|
||||
type HTMLElementConstructor<T = HTMLElement> = new (...args: any[]) => T;
|
||||
|
||||
export declare class UmbControllerHostElement extends HTMLElement {
|
||||
hasController(controller: UmbControllerInterface): boolean;
|
||||
getControllers(filterMethod: (ctrl: UmbControllerInterface) => boolean): UmbControllerInterface[];
|
||||
addController(controller: UmbControllerInterface): void;
|
||||
removeControllerByUnique(unique: UmbControllerInterface['unique']): void;
|
||||
removeController(controller: UmbControllerInterface): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* This mixin enables the component to host controllers.
|
||||
* This is done by calling the `consumeContext` method.
|
||||
*
|
||||
* @param {Object} superClass - superclass to be extended.
|
||||
* @mixin
|
||||
*/
|
||||
export const UmbControllerHostMixin = <T extends HTMLElementConstructor>(superClass: T) => {
|
||||
class UmbContextConsumerClass extends superClass {
|
||||
#controllers: UmbControllerInterface[] = [];
|
||||
|
||||
#attached = false;
|
||||
|
||||
/**
|
||||
* Tests if a controller is assigned to this element.
|
||||
* @param {UmbControllerInterface} ctrl
|
||||
*/
|
||||
hasController(ctrl: UmbControllerInterface): boolean {
|
||||
return this.#controllers.indexOf(ctrl) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve controllers matching a filter of this element.
|
||||
* @param {method} filterMethod
|
||||
*/
|
||||
getControllers(filterMethod: (ctrl: UmbControllerInterface) => boolean): UmbControllerInterface[] {
|
||||
return this.#controllers.filter(filterMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a controller to this element.
|
||||
* @param {UmbControllerInterface} ctrl
|
||||
*/
|
||||
addController(ctrl: UmbControllerInterface): void {
|
||||
// Check if there is one already with same unique
|
||||
this.removeControllerByUnique(ctrl.unique);
|
||||
|
||||
this.#controllers.push(ctrl);
|
||||
if (this.#attached) {
|
||||
ctrl.hostConnected();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a controller from this element, by its unique/alias.
|
||||
* @param {unknown} unique/alias
|
||||
*/
|
||||
removeControllerByUnique(unique: UmbControllerInterface['unique']): void {
|
||||
if (unique) {
|
||||
this.#controllers.forEach((x) => {
|
||||
if (x.unique === unique) {
|
||||
this.removeController(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a controller from this element.
|
||||
* Notice this will also destroy the controller.
|
||||
* @param {UmbControllerInterface} ctrl
|
||||
*/
|
||||
removeController(ctrl: UmbControllerInterface): void {
|
||||
const index = this.#controllers.indexOf(ctrl);
|
||||
if (index !== -1) {
|
||||
this.#controllers.splice(index, 1);
|
||||
if (this.#attached) {
|
||||
ctrl.hostDisconnected();
|
||||
}
|
||||
ctrl.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a controller from this element by its alias.
|
||||
* Notice this will also destroy the controller.
|
||||
* @param {string} unique
|
||||
*/
|
||||
removeControllerByAlias(unique: string): void {
|
||||
this.#controllers.forEach((x) => {
|
||||
if (x.unique === unique) {
|
||||
this.removeController(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback?.();
|
||||
this.#attached = true;
|
||||
this.#controllers.forEach((ctrl: UmbControllerInterface) => ctrl.hostConnected());
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback?.();
|
||||
this.#attached = false;
|
||||
this.#controllers.forEach((ctrl: UmbControllerInterface) => ctrl.hostDisconnected());
|
||||
}
|
||||
}
|
||||
|
||||
return UmbContextConsumerClass as unknown as HTMLElementConstructor<UmbControllerHostElement> & T;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface HTMLElement {
|
||||
connectedCallback(): void;
|
||||
disconnectedCallback(): void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { UmbControllerHostElement } from './controller-host.mixin';
|
||||
import { UmbControllerInterface } from './controller.interface';
|
||||
|
||||
export abstract class UmbController implements UmbControllerInterface {
|
||||
protected host?: UmbControllerHostElement;
|
||||
|
||||
private _alias?: string;
|
||||
public get unique() {
|
||||
return this._alias;
|
||||
}
|
||||
|
||||
constructor(host: UmbControllerHostElement, alias?: string) {
|
||||
this.host = host;
|
||||
this._alias = alias;
|
||||
this.host.addController(this);
|
||||
}
|
||||
|
||||
abstract hostConnected(): void;
|
||||
abstract hostDisconnected(): void;
|
||||
|
||||
public destroy() {
|
||||
if (this.host) {
|
||||
this.host.removeController(this);
|
||||
}
|
||||
delete this.host;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface UmbControllerInterface {
|
||||
get unique(): string | undefined;
|
||||
hostConnected(): void;
|
||||
hostDisconnected(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { UmbControllerHostElement, UmbControllerHostMixin } from './controller-host.mixin';
|
||||
import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api';
|
||||
|
||||
class UmbTestContext {
|
||||
prop = 'value from provider';
|
||||
}
|
||||
|
||||
@customElement('test-my-controller-host')
|
||||
export class UmbTestControllerHostElement extends UmbControllerHostMixin(HTMLElement) {}
|
||||
|
||||
describe('UmbContextProvider', () => {
|
||||
type NewType = UmbControllerHostElement;
|
||||
|
||||
let hostElement: NewType;
|
||||
const contextInstance = new UmbTestContext();
|
||||
|
||||
beforeEach(() => {
|
||||
hostElement = document.createElement('test-my-controller-host') as UmbControllerHostElement;
|
||||
});
|
||||
|
||||
describe('Destroyed controllers is gone from host', () => {
|
||||
it('has a host property', () => {
|
||||
const ctrl = new UmbContextProviderController(hostElement, 'my-test-context', contextInstance);
|
||||
|
||||
expect(hostElement.hasController(ctrl)).to.be.true;
|
||||
|
||||
ctrl.destroy();
|
||||
|
||||
expect(hostElement.hasController(ctrl)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Unique controllers replace each other', () => {
|
||||
it('has a host property', () => {
|
||||
const firstCtrl = new UmbContextProviderController(hostElement, 'my-test-context', contextInstance);
|
||||
const secondCtrl = new UmbContextProviderController(hostElement, 'my-test-context', new UmbTestContext());
|
||||
|
||||
expect(hostElement.hasController(firstCtrl)).to.be.false;
|
||||
expect(hostElement.hasController(secondCtrl)).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
3
src/Umbraco.Web.UI.Client/libs/controller-api/index.ts
Normal file
3
src/Umbraco.Web.UI.Client/libs/controller-api/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './controller-host.mixin';
|
||||
export * from './controller.class';
|
||||
export * from './controller.interface';
|
||||
Reference in New Issue
Block a user