From 45dc4a387a8a9af901dc492da1200ae919d9b135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 10 Nov 2023 13:50:05 +0100 Subject: [PATCH] initial refactor of context api --- src/Umbraco.Web.UI.Client/.vscode/settings.json | 1 + .../consume/context-consumer.controller.ts | 2 +- .../context-api/consume/context-consumer.ts | 17 +++++++---------- .../consume/context-request.event.test.ts | 1 + .../consume/context-request.event.ts | 8 ++------ .../provide/context-provider.controller.ts | 10 ++++++++-- .../provide/context-provider.test.ts | 1 + .../context-api/provide/context-provider.ts | 14 +++++++++----- 8 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/.vscode/settings.json b/src/Umbraco.Web.UI.Client/.vscode/settings.json index d94391ca3b..7130f48e85 100644 --- a/src/Umbraco.Web.UI.Client/.vscode/settings.json +++ b/src/Umbraco.Web.UI.Client/.vscode/settings.json @@ -4,6 +4,7 @@ "backoffice", "Backoffice", "combobox", + "devs", "Elementable", "invariantable", "lucide", diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts index 4a37a0a406..ff81bb7be2 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.controller.ts @@ -14,7 +14,7 @@ export class UmbContextConsumerController< public get controllerAlias() { return this.#controllerAlias; } - + constructor(host: UmbControllerHost, contextAlias: string | UmbContextToken, callback: UmbContextCallback) { super(host.getHostElement(), contextAlias, callback); this.#host = host; diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts index 31f75bea1f..406c481cfe 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts @@ -41,27 +41,24 @@ ResultType extends BaseType = BaseType> { ) { this.#contextAlias = contextAlias.toString(); this.#callback = callback; - this.#discriminator = (contextAlias as any).getDiscriminator?.(); + this.#discriminator = (contextAlias as UmbContextToken).getDiscriminator?.(); } - - - /* Idea: Niels: If we need to filter for specific contexts, we could make the response method return true/false. If false, the event should then then not be stopped. Alternatively parse the event it self on to the response-callback. - This will enable the event to continue to bubble up finding a context that matches. - The reason for such would be to have some who are more specific than others. For example, some might just need the current workspace-context, others might need the closest handling a certain entityType. - As I'm writing this is not relevant, but I wanted to keep the idea as we have had some circumstance that might be solved with this approach. - */ - protected _onResponse = (instance: BaseType) => { + + protected _onResponse = (instance: BaseType): boolean => { if (this.#instance === instance) { - return; + return false; } if(this.#discriminator) { // Notice if discriminator returns false, we do not want to setInstance. if(this.#discriminator(instance)) { this.setInstance(instance as unknown as ResultType); + return true; } } else { this.setInstance(instance as ResultType); + return true; } + return false; }; protected setInstance(instance: ResultType) { diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.test.ts index 19f17f929b..f6557a2a75 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.test.ts @@ -4,6 +4,7 @@ import { UmbContextRequestEventImplementation, UmbContextRequestEvent } from './ describe('UmbContextRequestEvent', () => { const contextRequestCallback = () => { console.log('hello from callback'); + return true; }; const event: UmbContextRequestEvent = new UmbContextRequestEventImplementation( diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.ts index 444a788353..6b2d64bafc 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.ts @@ -11,7 +11,7 @@ export type UmbContextCallback = (instance: T) => void; */ export interface UmbContextRequestEvent extends Event { readonly contextAlias: string | UmbContextToken; - readonly callback: UmbContextCallback; + readonly callback: (context: ResultType) => boolean; } /** @@ -23,16 +23,12 @@ export interface UmbContextRequestEvent extends Event { export class UmbContextRequestEventImplementation extends Event implements UmbContextRequestEvent { public constructor( public readonly contextAlias: string | UmbContextToken, - public readonly callback: UmbContextCallback + public readonly callback: (context: ResultType) => boolean ) { super(umbContextRequestEventType, { bubbles: true, composed: true, cancelable: true }); } } -export const isUmbContextRequestEvent = (event: Event): event is UmbContextRequestEventImplementation => { - return event.type === umbContextRequestEventType; -}; - export class UmbContextDebugRequest extends Event { public constructor(public readonly callback: any) { super(umbDebugContextEventType, { bubbles: true, composed: true, cancelable: false }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts index 9eb834bd44..4617a2c5e7 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts @@ -8,14 +8,19 @@ export class UmbContextProviderController< InstanceType extends ResultType = ResultType > extends UmbContextProvider implements UmbController { #host: UmbControllerHost; + #controllerAlias:string; public get controllerAlias() { - return this._contextAlias.toString(); + return this.#controllerAlias; } constructor(host: UmbControllerHost, contextAlias: string | UmbContextToken, instance: InstanceType) { super(host.getHostElement(), contextAlias, instance); this.#host = host; + // Makes the controllerAlias unique for this instance, this enables multiple Contexts to be provided under the same name. (This only makes sense cause of Context Token Discriminators) + // This does mean that if someone provides a context with the same name, but with a different instance, it will not override the previous instance. But its good since it enables extensions to provide contexts at the same scope of other contexts. + this.#controllerAlias = contextAlias.toString() + '_' + (instance as any).constructor?.name; + console.log("this.#controllerAlias ", this.#controllerAlias) // If this API is already provided with this alias? Then we do not want to register this controller: const existingControllers = host.getControllers((x) => x.controllerAlias === this.controllerAlias); @@ -23,9 +28,10 @@ export class UmbContextProviderController< existingControllers.length > 0 && (existingControllers[0] as UmbContextProviderController).providerInstance?.() === instance ) { + // This just an additional awareness feature to make devs Aware, the alternative would be adding it anyway, but that would destroy existing controller of this alias. // Back out, this instance is already provided, by another controller. throw new Error( - `Context API: The context of '${this.controllerAlias}' is already provided with the same API by another Context Provider Controller.` + `Context API: The context of '${this.controllerAlias}' and instance '${(instance as any).constructor?.name ?? 'unnamed'}' is already provided by another Context Provider Controller.` ); } else { host.addController(this); diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts index 70e77fc03f..e8ff8bb119 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts @@ -45,6 +45,7 @@ describe('UmbContextProvider', () => { (_instance: UmbTestContextProviderClass) => { expect(_instance.prop).to.eq('value from provider'); done(); + return true; } ); diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts index e506a5e46f..10d43595ec 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts @@ -1,6 +1,6 @@ import { + UmbContextRequestEvent, umbContextRequestEventType, - isUmbContextRequestEvent, umbDebugContextEventType, } from '../consume/context-request.event.js'; import { UmbContextToken } from '../token/context-token.js'; @@ -46,13 +46,17 @@ export class UmbContextProvider { - if (!isUmbContextRequestEvent(event)) return; + #handleContextRequest = ((event: UmbContextRequestEvent) => { if (event.contextAlias !== this._contextAlias) return; + // Since the alias matches, we will stop it from bubbling further up. But we still allow it to ask the other Contexts of the element. Hence not calling `event.stopImmediatePropagation();` event.stopPropagation(); - event.callback(this.#instance); - }; + + if(event.callback(this.#instance)) { + // Make sure the event not hits any more Contexts as we have found a match. + event.stopImmediatePropagation(); + } + }) as EventListener; /** * @memberof UmbContextProvider