`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.element.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.element.ts
index 66ce27cd73..2830778239 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/installer/database/installer-database.element.ts
@@ -139,6 +139,7 @@ export class UmbInstallerDatabaseElement extends UmbLitElement {
const server = formData.get('server') as string;
const name = formData.get('name') as string;
const useIntegratedAuthentication = formData.has('useIntegratedAuthentication');
+ const trustServerCertificate = formData.has('trustServerCertificate');
const connectionString = formData.get('connectionString') as string;
// Validate connection
@@ -157,10 +158,10 @@ export class UmbInstallerDatabaseElement extends UmbLitElement {
password,
server,
useIntegratedAuthentication,
+ trustServerCertificate,
name,
connectionString,
providerName: selectedDatabase.providerName,
- trustServerCertificate: false,
};
const { error } = await tryExecute(
@@ -184,6 +185,7 @@ export class UmbInstallerDatabaseElement extends UmbLitElement {
server,
name,
useIntegratedAuthentication,
+ trustServerCertificate,
connectionString,
providerName: selectedDatabase.providerName,
};
@@ -289,39 +291,47 @@ export class UmbInstallerDatabaseElement extends UmbLitElement {
name="useIntegratedAuthentication"
label="Use integrated authentication"
@change=${this._handleChange}
- .checked=${this.databaseFormData.useIntegratedAuthentication || false}>
- Use integrated authentication
-
+ .checked=${this.databaseFormData.useIntegratedAuthentication || false}>
+
+
+
- ${!this.databaseFormData.useIntegratedAuthentication
- ? html`
- Username
-
-
+ ${
+ !this.databaseFormData.useIntegratedAuthentication
+ ? html`
+ Username
+
+
-
- Password
-
- `
- : ''}
+
+ Password
+
+ `
+ : ''
+ }
+
`;
private _renderCustom = () => html`
diff --git a/src/Umbraco.Web.UI.Client/src/apps/installer/installer.context.ts b/src/Umbraco.Web.UI.Client/src/apps/installer/installer.context.ts
index 6950a0f0ec..aa8d67d497 100644
--- a/src/Umbraco.Web.UI.Client/src/apps/installer/installer.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/apps/installer/installer.context.ts
@@ -18,7 +18,7 @@ export class UmbInstallerContext {
private _data = new UmbObjectState({
user: { name: '', email: '', password: '', subscribeToNewsletter: false },
database: { id: '', providerName: '', useIntegratedAuthentication: false, trustServerCertificate: false },
- telemetryLevel: TelemetryLevelModel.BASIC,
+ telemetryLevel: TelemetryLevelModel.DETAILED,
});
public readonly data = this._data.asObservable();
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts
index 5df372f9d3..0d1532cf44 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts
@@ -253,6 +253,7 @@ export type { PagedAllowedMediaTypeModel } from './models/PagedAllowedMediaTypeM
export type { PagedAuditLogResponseModel } from './models/PagedAuditLogResponseModel';
export type { PagedAuditLogWithUsernameResponseModel } from './models/PagedAuditLogWithUsernameResponseModel';
export type { PagedCultureReponseModel } from './models/PagedCultureReponseModel';
+export type { PagedDataTypeItemResponseModel } from './models/PagedDataTypeItemResponseModel';
export type { PagedDataTypeTreeItemResponseModel } from './models/PagedDataTypeTreeItemResponseModel';
export type { PagedDictionaryOverviewResponseModel } from './models/PagedDictionaryOverviewResponseModel';
export type { PagedDocumentBlueprintTreeItemResponseModel } from './models/PagedDocumentBlueprintTreeItemResponseModel';
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedDataTypeItemResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedDataTypeItemResponseModel.ts
new file mode 100644
index 0000000000..6f06b0a81c
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedDataTypeItemResponseModel.ts
@@ -0,0 +1,12 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+import type { DataTypeItemResponseModel } from './DataTypeItemResponseModel';
+
+export type PagedDataTypeItemResponseModel = {
+ total: number;
+ items: Array;
+};
+
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/DataTypeResource.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/DataTypeResource.ts
index 6eb1b95c8e..b430515cb7 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/DataTypeResource.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/DataTypeResource.ts
@@ -11,6 +11,7 @@ import type { DataTypeReferenceResponseModel } from '../models/DataTypeReference
import type { DataTypeResponseModel } from '../models/DataTypeResponseModel';
import type { FolderResponseModel } from '../models/FolderResponseModel';
import type { MoveDataTypeRequestModel } from '../models/MoveDataTypeRequestModel';
+import type { PagedDataTypeItemResponseModel } from '../models/PagedDataTypeItemResponseModel';
import type { PagedDataTypeTreeItemResponseModel } from '../models/PagedDataTypeTreeItemResponseModel';
import type { UpdateDataTypeRequestModel } from '../models/UpdateDataTypeRequestModel';
import type { UpdateFolderResponseModel } from '../models/UpdateFolderResponseModel';
@@ -327,6 +328,39 @@ export class DataTypeResource {
});
}
+ /**
+ * @returns PagedDataTypeItemResponseModel Success
+ * @throws ApiError
+ */
+ public static getFilterDataType({
+ skip,
+ take = 100,
+ name = '',
+ editorUiAlias,
+ editorAlias,
+ }: {
+ skip?: number,
+ take?: number,
+ name?: string,
+ editorUiAlias?: string,
+ editorAlias?: string,
+ }): CancelablePromise {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/filter/data-type',
+ query: {
+ 'skip': skip,
+ 'take': take,
+ 'name': name,
+ 'editorUiAlias': editorUiAlias,
+ 'editorAlias': editorAlias,
+ },
+ errors: {
+ 401: `The resource is protected and requires an authentication token`,
+ },
+ });
+ }
+
/**
* @returns any Success
* @throws ApiError
@@ -348,27 +382,6 @@ export class DataTypeResource {
});
}
- /**
- * @returns any Success
- * @throws ApiError
- */
- public static getItemDataTypeByAlias({
- alias,
- }: {
- alias: string,
- }): CancelablePromise {
- return __request(OpenAPI, {
- method: 'GET',
- url: '/umbraco/management/api/v1/item/data-type/{alias}',
- path: {
- 'alias': alias,
- },
- errors: {
- 401: `The resource is protected and requires an authentication token`,
- },
- });
- }
-
/**
* @returns PagedDataTypeTreeItemResponseModel Success
* @throws ApiError
diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/MemberGroupResource.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/MemberGroupResource.ts
index 43af148e65..506c860fe9 100644
--- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/MemberGroupResource.ts
+++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/MemberGroupResource.ts
@@ -81,6 +81,52 @@ export class MemberGroupResource {
});
}
+ /**
+ * @returns any Success
+ * @throws ApiError
+ */
+ public static getMemberGroupById({
+ id,
+ }: {
+ id: string,
+ }): CancelablePromise {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/umbraco/management/api/v1/member-group/{id}',
+ path: {
+ 'id': id,
+ },
+ errors: {
+ 401: `The resource is protected and requires an authentication token`,
+ 404: `Not Found`,
+ },
+ });
+ }
+
+ /**
+ * @returns string Success
+ * @throws ApiError
+ */
+ public static deleteMemberGroupById({
+ id,
+ }: {
+ id: string,
+ }): CancelablePromise {
+ return __request(OpenAPI, {
+ method: 'DELETE',
+ url: '/umbraco/management/api/v1/member-group/{id}',
+ path: {
+ 'id': id,
+ },
+ responseHeader: 'Umb-Notifications',
+ errors: {
+ 400: `Bad Request`,
+ 401: `The resource is protected and requires an authentication token`,
+ 404: `Not Found`,
+ },
+ });
+ }
+
/**
* @returns any Success
* @throws ApiError
@@ -108,30 +154,6 @@ export class MemberGroupResource {
});
}
- /**
- * @returns string Success
- * @throws ApiError
- */
- public static deleteMemberGroupByKey({
- key,
- }: {
- key: string,
- }): CancelablePromise {
- return __request(OpenAPI, {
- method: 'DELETE',
- url: '/umbraco/management/api/v1/member-group/{key}',
- path: {
- 'key': key,
- },
- responseHeader: 'Umb-Notifications',
- errors: {
- 400: `Bad Request`,
- 401: `The resource is protected and requires an authentication token`,
- 404: `Not Found`,
- },
- });
- }
-
/**
* @returns PagedNamedEntityTreeItemResponseModel Success
* @throws ApiError
diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/class-mixin.interface.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/class-mixin.interface.ts
new file mode 100644
index 0000000000..5ff5d47edf
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/class-mixin.interface.ts
@@ -0,0 +1,6 @@
+import type { UmbClassInterface } from './class.interface.js';
+import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api';
+
+export interface UmbClassMixinInterface extends UmbClassInterface, UmbController {
+ _host: UmbControllerHost;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts
index 49ed3116d3..7235a74eb9 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts
@@ -4,19 +4,64 @@ import type {
UmbContextProviderController,
UmbContextToken,
} from '../context-api/index.js';
-import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api';
-import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
+import type { UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import type { ObserverCallback, UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
-export interface UmbClassMixinInterface extends UmbControllerHost, UmbController {
- observe(
- source: Observable | { asObservable: () => Observable },
- callback: (_value: T) => void,
- unique?: string,
- ): UmbObserverController;
+export interface UmbClassInterface extends UmbControllerHost {
+ /**
+ * @description Observe an Observable. An Observable is a declared source of data that can be observed. An observables is declared from a UmbState.
+ * @param {Observable} source An Observable to observe from.
+ * @param {method} callback Callback method called when data is changed.
+ * @return {UmbObserverController} Reference to the created Observer Controller instance.
+ * @memberof UmbClassMixin
+ */
+ observe<
+ ObservableType extends Observable | undefined,
+ T,
+ SpecificT = ObservableType extends Observable
+ ? ObservableType extends undefined
+ ? U | undefined
+ : U
+ : undefined,
+ SpecificR = ObservableType extends undefined
+ ? UmbObserverController | undefined
+ : UmbObserverController,
+ >(
+ // This type dance checks if the Observable given could be undefined, if it potentially could be undefined it means that this potentially could return undefined and then call the callback with undefined. [NL]
+ source: ObservableType,
+ callback: ObserverCallback,
+ controllerAlias?: UmbControllerAlias,
+ ): SpecificR;
+
+ /**
+ * @description Provide a context API for this or child elements.
+ * @param {string} contextAlias
+ * @param {instance} instance The API instance to be exposed.
+ * @return {UmbContextProviderController} Reference to the created Context Provider Controller instance
+ * @memberof UmbClassMixin
+ */
provideContext(alias: string | UmbContextToken, instance: R): UmbContextProviderController;
+
+ /**
+ * @description Setup a subscription for a context. The callback is called when the context is resolved.
+ * @param {string} contextAlias
+ * @param {method} callback Callback method called when context is resolved.
+ * @return {UmbContextConsumerController} Reference to the created Context Consumer Controller instance
+ * @memberof UmbClassMixin
+ */
consumeContext(
alias: string | UmbContextToken,
callback: UmbContextCallback,
): UmbContextConsumerController;
+
+ /**
+ * @description Retrieve a context. Notice this is a one time retrieving of a context, meaning if you expect this to be up to date with reality you should instead use the consumeContext method.
+ * @param {string} contextAlias
+ * @return {Promise} A Promise with the reference to the Context Api Instance
+ * @memberof UmbClassMixin
+ */
+ getContext(
+ alias: string | UmbContextToken,
+ ): Promise;
}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts
index a755cebc59..ce5c4c6f5a 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts
@@ -1,10 +1,9 @@
-import type { UmbClassMixinInterface } from './class.interface.js';
+import type { UmbClassMixinInterface } from './class-mixin.interface.js';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { ClassConstructor } from '@umbraco-cms/backoffice/extension-api';
import {
type UmbControllerHost,
UmbControllerHostMixin,
- type UmbController,
type UmbControllerAlias,
} from '@umbraco-cms/backoffice/controller-api';
import {
@@ -13,89 +12,16 @@ import {
UmbContextConsumerController,
UmbContextProviderController,
} from '@umbraco-cms/backoffice/context-api';
-import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
+import { type ObserverCallback, UmbObserverController, simpleHashCode } from '@umbraco-cms/backoffice/observable-api';
type UmbClassMixinConstructor = new (
host: UmbControllerHost,
controllerAlias?: UmbControllerAlias,
-) => UmbClassMixinDeclaration;
+) => UmbClassMixinInterface;
-// TODO: we need the interface from EventTarget to be part of the controller base. As a temp solution the UmbClassMixinDeclaration extends EventTarget.
-declare class UmbClassMixinDeclaration extends EventTarget implements UmbClassMixinInterface {
- _host: UmbControllerHost;
-
- /**
- * @description Observe a RxJS source of choice.
- * @param {Observable} source RxJS source
- * @param {method} callback Callback method called when data is changed.
- * @return {UmbObserverController} Reference to a Observer Controller instance
- * @memberof UmbClassMixin
- */
- observe(
- source: Observable,
- callback: (_value: T) => void,
- controllerAlias?: UmbControllerAlias,
- ): UmbObserverController;
-
- /**
- * @description Provide a context API for this or child elements.
- * @param {string} contextAlias
- * @param {instance} instance The API instance to be exposed.
- * @return {UmbContextProviderController} Reference to a Context Provider Controller instance
- * @memberof UmbClassMixin
- */
- provideContext<
- BaseType = unknown,
- ResultType extends BaseType = BaseType,
- InstanceType extends ResultType = ResultType,
- >(
- alias: string | UmbContextToken,
- instance: InstanceType,
- ): UmbContextProviderController;
-
- /**
- * @description Setup a subscription for a context. The callback is called when the context is resolved.
- * @param {string} contextAlias
- * @param {method} callback Callback method called when context is resolved.
- * @return {UmbContextConsumerController} Reference to a Context Consumer Controller instance
- * @memberof UmbClassMixin
- */
- consumeContext(
- alias: string | UmbContextToken,
- callback: UmbContextCallback,
- ): UmbContextConsumerController;
-
- /**
- * @description Retrieve a context. Notice this is a one time retrieving of a context, meaning if you expect this to be up to date with reality you should instead use the consumeContext method.
- * @param {string} contextAlias
- * @return {Promise} A Promise with the reference to the Context Api Instance
- * @memberof UmbClassMixin
- */
- getContext(
- alias: string | UmbContextToken,
- ): Promise;
-
- hasController(controller: UmbController): boolean;
- getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[];
- addController(controller: UmbController): void;
- removeControllerByAlias(controllerAlias: UmbControllerAlias): void;
- removeController(controller: UmbController): void;
- getHostElement(): Element;
-
- get controllerAlias(): UmbControllerAlias;
- hostConnected(): void;
- hostDisconnected(): void;
-
- /**
- * @description Destroys the controller and removes it from the host.
- * @memberof UmbClassMixin
- */
- destroy(): void;
-}
-
-export const UmbClassMixin = (superClass: T) => {
- class UmbClassMixinClass extends UmbControllerHostMixin(superClass) implements UmbControllerHost {
- protected _host: UmbControllerHost;
+export const UmbClassMixin = >(superClass: T) => {
+ class UmbClassMixinClass extends UmbControllerHostMixin(superClass) implements UmbClassMixinInterface {
+ _host: UmbControllerHost;
protected _controllerAlias: UmbControllerAlias;
constructor(host: UmbControllerHost, controllerAlias?: UmbControllerAlias) {
@@ -113,12 +39,38 @@ export const UmbClassMixin = (superClass: T) => {
return this._controllerAlias;
}
- observe(
- source: Observable,
- callback: (_value: T) => void,
+ observe<
+ ObservableType extends Observable | undefined,
+ T,
+ SpecificT = ObservableType extends Observable
+ ? ObservableType extends undefined
+ ? U | undefined
+ : U
+ : undefined,
+ SpecificR = ObservableType extends undefined
+ ? UmbObserverController | undefined
+ : UmbObserverController,
+ >(
+ // This type dance checks if the Observable given could be undefined, if it potentially could be undefined it means that this potentially could return undefined and then call the callback with undefined. [NL]
+ source: ObservableType,
+ callback: ObserverCallback,
controllerAlias?: UmbControllerAlias,
- ): UmbObserverController {
- return new UmbObserverController(this, source, callback, controllerAlias);
+ ): SpecificR {
+ // Fallback to use a hash of the provided method, but only if the alias is undefined.
+ controllerAlias ??= controllerAlias === undefined ? simpleHashCode(callback.toString()) : undefined;
+
+ if (source) {
+ return new UmbObserverController(
+ this,
+ source,
+ callback as unknown as ObserverCallback,
+ controllerAlias,
+ ) as unknown as SpecificR;
+ } else {
+ callback(undefined as SpecificT);
+ this.removeControllerByAlias(controllerAlias);
+ return undefined as SpecificR;
+ }
}
provideContext<
@@ -159,5 +111,5 @@ export const UmbClassMixin = (superClass: T) => {
}
}
- return UmbClassMixinClass as unknown as UmbClassMixinConstructor & UmbClassMixinDeclaration;
+ return UmbClassMixinClass as unknown as UmbClassMixinConstructor & T;
};
diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/controller-base.class.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/controller-base.class.ts
index 6d70f03106..3229af0c06 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/class-api/controller-base.class.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/controller-base.class.ts
@@ -1,9 +1,12 @@
import type { UmbController } from '../controller-api/controller.interface.js';
import { UmbClassMixin } from './class.mixin.js';
+import type { ClassConstructor } from '@umbraco-cms/backoffice/extension-api';
/**
* This mixin enables a web-component to host controllers.
* This enables controllers to be added to the life cycle of this element.
*
*/
-export abstract class UmbControllerBase extends UmbClassMixin(EventTarget) implements UmbController {}
+export abstract class UmbControllerBase
+ extends UmbClassMixin>(EventTarget)
+ implements UmbController {}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/index.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/index.ts
index dd4339f71f..71adb0afdb 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/class-api/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/index.ts
@@ -1,4 +1,5 @@
export * from './class.interface.js';
+export * from './class-mixin.interface.js';
export * from './class.mixin.js';
export * from './context.interface.js';
export * from './context-base.class.js';
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 b45fec04d1..f49942f71e 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
@@ -13,7 +13,9 @@ import { UmbContextRequestEventImplementation } from './context-request.event.js
* @class UmbContextConsumer
*/
export class UmbContextConsumer {
- #skipOrigin?: boolean;
+ protected _host: Element;
+
+ #skipHost?: boolean;
#stopAtContextMatch = true;
#callback?: UmbContextCallback;
#promise?: Promise;
@@ -31,16 +33,17 @@ export class UmbContextConsumer,
callback?: UmbContextCallback,
) {
+ this._host = host;
const idSplit = contextIdentifier.toString().split('#');
this.#contextAlias = idSplit[0];
this.#apiAlias = idSplit[1] ?? 'default';
@@ -51,10 +54,11 @@ export class UmbContextConsumer {
@@ -119,7 +124,7 @@ export class UmbContextConsumer boolean): UmbController[];
- addController(controller: UmbController): void;
- removeControllerByAlias(alias: UmbControllerAlias): void;
- removeController(controller: UmbController): void;
- getHostElement(): Element;
-
- destroy(): void;
-}
-
/**
* This mixin enables a web-component to host controllers.
* This enables controllers to be added to the life cycle of this element.
@@ -24,7 +10,10 @@ export declare class UmbControllerHostImplementationElement extends HTMLElement
* @mixin
*/
export const UmbControllerHostElementMixin = (superClass: T) => {
- class UmbControllerHostElementClass extends UmbControllerHostMixin(superClass) implements UmbControllerHost {
+ class UmbControllerHostElementClass
+ extends UmbControllerHostMixin(superClass)
+ implements UmbControllerHostElement
+ {
getHostElement(): Element {
return this;
}
@@ -38,9 +27,11 @@ export const UmbControllerHostElementMixin = (
super.disconnectedCallback?.();
this.hostDisconnected();
}
+
+ destroy(): void {}
}
- return UmbControllerHostElementClass as unknown as HTMLElementConstructor & T;
+ return UmbControllerHostElementClass as unknown as HTMLElementConstructor & T;
};
declare global {
diff --git a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts
index 5b5888121a..0407aa9bed 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/controller-api/controller-host.mixin.ts
@@ -2,13 +2,7 @@ import type { ClassConstructor } from '../extension-api/types/utils.js';
import type { UmbControllerHost } from './controller-host.interface.js';
import type { UmbController } from './controller.interface.js';
-declare class UmbControllerHostBaseDeclaration implements Omit {
- hasController(controller: UmbController): boolean;
- getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[];
- addController(controller: UmbController): void;
- removeControllerByAlias(unique: UmbController['controllerAlias']): void;
- removeController(controller: UmbController): void;
-
+interface UmbControllerHostBaseDeclaration extends Omit {
hostConnected(): void;
hostDisconnected(): void;
destroy(): void;
@@ -22,11 +16,15 @@ declare class UmbControllerHostBaseDeclaration implements Omit(superClass: T) => {
- class UmbControllerHostBaseClass extends superClass {
+ class UmbControllerHostBaseClass extends superClass implements UmbControllerHostBaseDeclaration {
#controllers: UmbController[] = [];
#attached = false;
+ getHostElement() {
+ return undefined as any;
+ }
+
/**
* Tests if a controller is assigned to this element.
* @param {UmbController} ctrl
@@ -58,7 +56,7 @@ export const UmbControllerHostMixin = (superClass: T
this.#controllers.push(ctrl);
if (this.#attached) {
- // If a controller is created on a already attached element, then it will be added directly. This might not be optimal. As the controller it self has not finished its constructor method jet. therefor i postpone the call:
+ // If a controller is created on a already attached element, then it will be added directly. This might not be optimal. As the controller it self has not finished its constructor method jet. therefor i postpone the call: [NL]
Promise.resolve().then(() => {
// Extra check to see if we are still attached at this point:
if (this.#attached) {
@@ -123,7 +121,7 @@ export const UmbControllerHostMixin = (superClass: T
throw new Error(
`Controller with controller alias: '${ctrl.controllerAlias?.toString()}' and class name: '${
(ctrl as any).constructor.name
- }', does not remove it self when destroyed. This can cause memory leaks. Please fix this issue.`,
+ }', does not remove it self when destroyed. This can cause memory leaks. Please fix this issue.\r\nThis usually occurs when you have a destroy() method that doesn't call super.destroy().`,
);
}
prev = ctrl;
diff --git a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.interface.ts b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.interface.ts
new file mode 100644
index 0000000000..ca72552a06
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.interface.ts
@@ -0,0 +1,11 @@
+import type { UmbControllerHostElement } from '../controller-api/controller-host-element.interface.js';
+import type { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
+import type { UmbClassInterface } from '@umbraco-cms/backoffice/class-api';
+
+export interface UmbElement extends UmbClassInterface, UmbControllerHostElement {
+ /**
+ * Use the UmbLocalizeController to localize your element.
+ * @see UmbLocalizationController
+ */
+ localize: UmbLocalizationController;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts
index 534201a5f7..aea4582c81 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts
@@ -1,72 +1,51 @@
+import type { UmbElement } from './element.interface.js';
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { HTMLElementConstructor } from '@umbraco-cms/backoffice/extension-api';
-import {
- UmbControllerHostElementMixin,
- type UmbControllerHostImplementationElement,
-} from '@umbraco-cms/backoffice/controller-api';
+import { type UmbControllerAlias, UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
import type { UmbContextToken, UmbContextCallback } from '@umbraco-cms/backoffice/context-api';
import { UmbContextConsumerController, UmbContextProviderController } from '@umbraco-cms/backoffice/context-api';
import type { ObserverCallback } from '@umbraco-cms/backoffice/observable-api';
-import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
-
-export declare class UmbElement extends UmbControllerHostImplementationElement {
- /**
- * @description Observe a RxJS source of choice.
- * @param {Observable} source RxJS source
- * @param {method} callback Callback method called when data is changed.
- * @return {UmbObserverController} Reference to a Observer Controller instance
- * @memberof UmbElementMixin
- */
- observe(
- source: Observable | { asObservable: () => Observable },
- callback: ObserverCallback,
- unique?: string,
- ): UmbObserverController;
- provideContext<
- BaseType = unknown,
- ResultType extends BaseType = BaseType,
- InstanceType extends ResultType = ResultType,
- >(
- alias: string | UmbContextToken,
- instance: InstanceType,
- ): UmbContextProviderController;
- consumeContext(
- alias: string | UmbContextToken,
- callback: UmbContextCallback,
- ): UmbContextConsumerController;
- getContext(
- alias: string | UmbContextToken,
- ): Promise;
- /**
- * Use the UmbLocalizeController to localize your element.
- * @see UmbLocalizationController
- */
- localize: UmbLocalizationController;
-}
+import { UmbObserverController, simpleHashCode } from '@umbraco-cms/backoffice/observable-api';
export const UmbElementMixin = (superClass: T) => {
class UmbElementMixinClass extends UmbControllerHostElementMixin(superClass) implements UmbElement {
localize: UmbLocalizationController = new UmbLocalizationController(this);
- /**
- * @description Observe a RxJS source of choice.
- * @param {Observable} source RxJS source
- * @param {method} callback Callback method called when data is changed.
- * @return {UmbObserverController} Reference to a Observer Controller instance
- * @memberof UmbElementMixin
- */
- observe(source: Observable, callback: ObserverCallback, unique?: string): UmbObserverController {
- return new UmbObserverController(this, source, callback, unique);
+ observe<
+ ObservableType extends Observable | undefined,
+ T,
+ SpecificT = ObservableType extends Observable
+ ? ObservableType extends undefined
+ ? U | undefined
+ : U
+ : undefined,
+ SpecificR = ObservableType extends undefined
+ ? UmbObserverController | undefined
+ : UmbObserverController,
+ >(
+ // This type dance checks if the Observable given could be undefined, if it potentially could be undefined it means that this potentially could return undefined and then call the callback with undefined. [NL]
+ source: ObservableType,
+ callback: ObserverCallback,
+ controllerAlias?: UmbControllerAlias,
+ ): SpecificR {
+ // Fallback to use a hash of the provided method, but only if the alias is undefined.
+ controllerAlias ??= controllerAlias === undefined ? simpleHashCode(callback.toString()) : undefined;
+
+ if (source) {
+ return new UmbObserverController(
+ this,
+ source,
+ callback as unknown as ObserverCallback,
+ controllerAlias,
+ ) as unknown as SpecificR;
+ } else {
+ callback(undefined as SpecificT);
+ this.removeControllerByAlias(controllerAlias);
+ return undefined as SpecificR;
+ }
}
- /**
- * @description Provide a context API for this or child elements.
- * @param {string} alias
- * @param {instance} instance The API instance to be exposed.
- * @return {UmbContextProviderController} Reference to a Context Provider Controller instance
- * @memberof UmbElementMixin
- */
provideContext<
BaseType = unknown,
ResultType extends BaseType = BaseType,
@@ -78,13 +57,6 @@ export const UmbElementMixin = (superClass: T)
return new UmbContextProviderController(this, alias, instance);
}
- /**
- * @description Setup a subscription for a context. The callback is called when the context is resolved.
- * @param {string} alias
- * @param {method} callback Callback method called when context is resolved.
- * @return {UmbContextConsumerController} Reference to a Context Consumer Controller instance
- * @memberof UmbElementMixin
- */
consumeContext(
alias: string | UmbContextToken,
callback: UmbContextCallback,
@@ -92,13 +64,6 @@ export const UmbElementMixin = (superClass: T)
return new UmbContextConsumerController(this, alias, callback);
}
- /**
- * @description Setup a subscription for a context. The callback is called when the context is resolved.
- * @param {string} contextAlias
- * @param {method} callback Callback method called when context is resolved.
- * @return {UmbContextConsumerController} Reference to a Context Consumer Controller instance
- * @memberof UmbElementMixin
- */
async getContext(
contextAlias: string | UmbContextToken,
): Promise {
@@ -109,11 +74,6 @@ export const UmbElementMixin = (superClass: T)
});
return promise;
}
-
- destroy(): void {
- super.destroy();
- (this.localize as any) = undefined;
- }
}
return UmbElementMixinClass as unknown as HTMLElementConstructor & T;
diff --git a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts
new file mode 100644
index 0000000000..4e363414c2
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts
@@ -0,0 +1,212 @@
+import { expect } from '@open-wc/testing';
+import { UmbElementMixin } from './element.mixin.js';
+import { customElement } from '@umbraco-cms/backoffice/external/lit';
+import { UmbStringState } from '@umbraco-cms/backoffice/observable-api';
+
+@customElement('test-my-umb-element')
+class UmbTestUmbElement extends UmbElementMixin(HTMLElement) {}
+
+describe('UmbElementMixin', () => {
+ let hostElement: UmbTestUmbElement;
+
+ beforeEach(() => {
+ hostElement = document.createElement('test-my-umb-element') as UmbTestUmbElement;
+ });
+
+ describe('Element general controller API', () => {
+ describe('methods', () => {
+ it('has an hasController method', () => {
+ expect(hostElement).to.have.property('hasController').that.is.a('function');
+ });
+ it('has an getControllers method', () => {
+ expect(hostElement).to.have.property('getControllers').that.is.a('function');
+ });
+ it('has an addController method', () => {
+ expect(hostElement).to.have.property('addController').that.is.a('function');
+ });
+ it('has an removeControllerByAlias method', () => {
+ expect(hostElement).to.have.property('removeControllerByAlias').that.is.a('function');
+ });
+ it('has an removeController method', () => {
+ expect(hostElement).to.have.property('removeController').that.is.a('function');
+ });
+ it('has an destroy method', () => {
+ expect(hostElement).to.have.property('destroy').that.is.a('function');
+ });
+ });
+ });
+
+ describe('Element helper methods API', () => {
+ describe('methods', () => {
+ it('has an hasController method', () => {
+ expect(hostElement).to.have.property('getHostElement').that.is.a('function');
+ });
+ it('has an hasController should return it self', () => {
+ expect(hostElement.getHostElement()).to.be.equal(hostElement);
+ });
+ it('has an observe method', () => {
+ expect(hostElement).to.have.property('observe').that.is.a('function');
+ });
+ it('has an provideContext method', () => {
+ expect(hostElement).to.have.property('provideContext').that.is.a('function');
+ });
+ it('has an consumeContext method', () => {
+ expect(hostElement).to.have.property('consumeContext').that.is.a('function');
+ });
+ it('has an getContext method', () => {
+ expect(hostElement).to.have.property('getContext').that.is.a('function');
+ });
+ it('has an localization class instance', () => {
+ expect(hostElement).to.have.property('localize').that.is.a('object');
+ });
+ });
+ });
+
+ describe('Controllers lifecycle', () => {
+ it('observe is removed when destroyed', () => {
+ const myState = new UmbStringState('hello');
+ const myObservable = myState.asObservable();
+
+ const ctrl = hostElement.observe(myObservable, () => {}, 'observer');
+
+ // The controller is now added to the host:
+ expect(hostElement.hasController(ctrl)).to.be.true;
+
+ ctrl.destroy();
+
+ // The controller is removed from the host:
+ expect(hostElement.hasController(ctrl)).to.be.false;
+ });
+
+ it('observe is destroyed then removed', () => {
+ const myState = new UmbStringState('hello');
+ const myObservable = myState.asObservable();
+
+ const ctrl = hostElement.observe(myObservable, () => {}, 'observer');
+
+ // The controller is now added to the host:
+ expect(hostElement.hasController(ctrl)).to.be.true;
+
+ hostElement.removeController(ctrl);
+
+ // The controller is removed from the host:
+ expect(hostElement.hasController(ctrl)).to.be.false;
+ });
+
+ it('observe is destroyed then removed via alias', () => {
+ const myState = new UmbStringState('hello');
+ const myObservable = myState.asObservable();
+
+ const ctrl = hostElement.observe(myObservable, () => {}, 'observer');
+
+ // The controller is now added to the host:
+ expect(hostElement.hasController(ctrl)).to.be.true;
+
+ hostElement.removeControllerByAlias('observer');
+
+ // The controller is removed from the host:
+ expect(hostElement.hasController(ctrl)).to.be.false;
+ });
+
+ it('observe is removed when replaced with alias', () => {
+ const myState = new UmbStringState('hello');
+ const myObservable = myState.asObservable();
+
+ const ctrl = hostElement.observe(myObservable, () => {}, 'observer');
+
+ // The controller is now added to the host:
+ expect(hostElement.hasController(ctrl)).to.be.true;
+
+ const ctrl2 = hostElement.observe(myObservable, () => {}, 'observer');
+
+ // The controller is removed from the host:
+ expect(hostElement.hasController(ctrl)).to.be.false;
+ // The controller is new one is there instead:
+ expect(hostElement.hasController(ctrl2)).to.be.true;
+ });
+
+ it('observe is removed when replaced with alias made of hash of callback method', () => {
+ const myState = new UmbStringState('hello');
+ const myObservable = myState.asObservable();
+
+ const ctrl = hostElement.observe(myObservable, () => {});
+
+ // The controller is now added to the host:
+ expect(hostElement.hasController(ctrl)).to.be.true;
+
+ const ctrl2 = hostElement.observe(myObservable, () => {});
+
+ // The controller is removed from the host:
+ expect(hostElement.hasController(ctrl)).to.be.false;
+ // The controller is new one is there instead:
+ expect(hostElement.hasController(ctrl2)).to.be.true;
+ });
+
+ it('observe is NOT removed when controller alias does not align', () => {
+ const myState = new UmbStringState('hello');
+ const myObservable = myState.asObservable();
+
+ const ctrl = hostElement.observe(myObservable, () => {});
+
+ // The controller is now added to the host:
+ expect(hostElement.hasController(ctrl)).to.be.true;
+
+ const ctrl2 = hostElement.observe(myObservable, (value) => {
+ const a = value + 'bla';
+ });
+
+ // The controller is not removed from the host:
+ expect(hostElement.hasController(ctrl)).to.be.true;
+ expect(hostElement.hasController(ctrl2)).to.be.true;
+ });
+
+ it('observe is removed when observer is undefined and using the same alias', () => {
+ const myState = new UmbStringState('hello');
+ const myObservable = myState.asObservable();
+
+ const ctrl = hostElement.observe(myObservable, () => {}, 'observer');
+
+ // The controller is now added to the host:
+ expect(hostElement.hasController(ctrl)).to.be.true;
+
+ const ctrl2 = hostElement.observe(
+ undefined,
+ () => {
+ const a = 1;
+ },
+ 'observer',
+ );
+
+ // The controller is removed from the host, and the new one was NOT added:
+ expect(hostElement.hasController(ctrl)).to.be.false;
+ expect(ctrl2).to.be.undefined;
+ });
+
+ it('observe is removed when observer is undefined and using the same callback method', () => {
+ const myState = new UmbStringState('hello');
+ const myObservable = myState.asObservable();
+
+ const ctrl = hostElement.observe(myObservable, () => {});
+
+ // The controller is now added to the host:
+ expect(hostElement.hasController(ctrl)).to.be.true;
+
+ const ctrl2 = hostElement.observe(undefined, () => {});
+
+ // The controller is removed from the host, and the new one was NOT added:
+ expect(hostElement.hasController(ctrl)).to.be.false;
+ expect(ctrl2).to.be.undefined;
+ });
+
+ it('an undefined observer executes the callback method with undefined', () => {
+ let callbackWasCalled = false;
+ const ctrl = hostElement.observe(undefined, (value) => {
+ expect(value).to.be.undefined;
+ callbackWasCalled = true;
+ });
+ expect(callbackWasCalled).to.be.true;
+ expect(ctrl).to.be.undefined;
+ expect(hostElement.hasController(ctrl)).to.be.false;
+ });
+ });
+});
diff --git a/src/Umbraco.Web.UI.Client/src/libs/element-api/index.ts b/src/Umbraco.Web.UI.Client/src/libs/element-api/index.ts
index 9b02476fa4..355ddd3d68 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/element-api/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/element-api/index.ts
@@ -1 +1,2 @@
+export * from './element.interface.js';
export * from './element.mixin.js';
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts
index 3485494af4..79e517b91e 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts
@@ -20,7 +20,7 @@ class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLEle
class UmbTestExtensionController extends UmbBaseExtensionInitializer {
constructor(
- host: UmbControllerHostElement,
+ host: UmbControllerHost,
extensionRegistry: UmbExtensionRegistry,
alias: string,
onPermissionChanged: (isPermitted: boolean) => void,
@@ -40,16 +40,16 @@ class UmbTestExtensionController extends UmbBaseExtensionInitializer {
class UmbTestConditionAlwaysValid extends UmbControllerBase implements UmbExtensionCondition {
config: UmbConditionConfigBase;
- constructor(args: { host: UmbControllerHost; config: UmbConditionConfigBase }) {
- super(args.host);
+ constructor(host: UmbControllerHost, args: { config: UmbConditionConfigBase }) {
+ super(host);
this.config = args.config;
}
permitted = true;
}
class UmbTestConditionAlwaysInvalid extends UmbControllerBase implements UmbExtensionCondition {
config: UmbConditionConfigBase;
- constructor(args: { host: UmbControllerHost; config: UmbConditionConfigBase }) {
- super(args.host);
+ constructor(host: UmbControllerHost, args: { config: UmbConditionConfigBase }) {
+ super(host);
this.config = args.config;
}
permitted = false;
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.ts
index a9af1b03fb..d675825b52 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extension-initializer.controller.ts
@@ -205,9 +205,8 @@ export abstract class UmbBaseExtensionInitializer<
// Check if we already have a controller for this config:
const existing = this.#conditionControllers.find((controller) => controller.config === conditionConfig);
if (!existing) {
- const conditionController = await createExtensionApi(conditionManifest, [
+ const conditionController = await createExtensionApi(this, conditionManifest, [
{
- host: this,
manifest: conditionManifest,
config: conditionConfig,
onChange: this.#onConditionsChangedCallback,
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts
index 2123b6647b..9ded7ad7d6 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts
@@ -67,16 +67,16 @@ class UmbTestExtensionsController<
class UmbTestConditionAlwaysValid extends UmbControllerBase implements UmbExtensionCondition {
config: UmbConditionConfigBase;
- constructor(args: { host: UmbControllerHost; config: UmbConditionConfigBase }) {
- super(args.host);
+ constructor(host: UmbControllerHost, args: { config: UmbConditionConfigBase }) {
+ super(host);
this.config = args.config;
}
permitted = true;
}
class UmbTestConditionAlwaysInvalid extends UmbControllerBase implements UmbExtensionCondition {
config: UmbConditionConfigBase;
- constructor(args: { host: UmbControllerHost; config: UmbConditionConfigBase }) {
- super(args.host);
+ constructor(host: UmbControllerHost, args: { config: UmbConditionConfigBase }) {
+ super(host);
this.config = args.config;
}
permitted = false;
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.controller.ts
index 873ad46c60..b8c5c5c3c5 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-api-initializer.controller.ts
@@ -1,4 +1,5 @@
import { createExtensionApi } from '../functions/create-extension-api.function.js';
+import type { UmbApiConstructorArgumentsMethodType } from '../functions/types.js';
import type { UmbApi } from '../models/api.interface.js';
import type { UmbExtensionRegistry } from '../registry/extension.registry.js';
import type { ManifestApi, ManifestCondition } from '../types/index.js';
@@ -24,7 +25,7 @@ export class UmbExtensionApiInitializer<
: UmbApi,
> extends UmbBaseExtensionInitializer {
#api?: ExtensionApiInterface;
- #constructorArguments?: Array;
+ #constructorArguments?: Array | UmbApiConstructorArgumentsMethodType;
/**
* The api that is created for this extension.
@@ -65,7 +66,7 @@ export class UmbExtensionApiInitializer<
host: UmbControllerHost,
extensionRegistry: UmbExtensionRegistry,
alias: string,
- constructorArguments: Array | undefined,
+ constructorArguments: Array | UmbApiConstructorArgumentsMethodType | undefined,
onPermissionChanged?: (isPermitted: boolean, controller: ControllerType) => void,
) {
super(host, extensionRegistry, 'extApi_', alias, onPermissionChanged);
@@ -88,8 +89,9 @@ export class UmbExtensionApiInitializer<
const manifest = this.manifest!; // In this case we are sure its not undefined.
const newApi = await createExtensionApi(
+ this._host,
manifest as unknown as ManifestApi,
- this.#constructorArguments,
+ this.#constructorArguments as any,
);
if (!this._isConditionsPositive) {
// We are not positive anymore, so we will back out of this creation.
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-and-api-initializer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-and-api-initializer.controller.ts
index 4abe43cd99..258f949601 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-and-api-initializer.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extension-element-and-api-initializer.controller.ts
@@ -122,7 +122,7 @@ export class UmbExtensionElementAndApiInitializer<
// TODO: we could optimize this so we only re-set the updated props.
Object.keys(this.#apiProps).forEach((key) => {
- (this.#component as any)[key] = this.#apiProps![key];
+ (this.#api as any)[key] = this.#apiProps![key];
});
};
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extensions-api-initializer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extensions-api-initializer.controller.ts
index 09f91f7509..de723847ce 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extensions-api-initializer.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/extensions-api-initializer.controller.ts
@@ -4,7 +4,12 @@ import {
UmbBaseExtensionsInitializer,
} from './base-extensions-initializer.controller.js';
import { UmbExtensionApiInitializer } from './extension-api-initializer.controller.js';
-import type { ManifestApi, ManifestBase, UmbExtensionRegistry } from '@umbraco-cms/backoffice/extension-api';
+import type {
+ ManifestApi,
+ ManifestBase,
+ UmbApiConstructorArgumentsMethodType,
+ UmbExtensionRegistry,
+} from '@umbraco-cms/backoffice/extension-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
/**
@@ -50,13 +55,13 @@ export class UmbExtensionsApiInitializer<
}
*/
- #constructorArgs: Array | undefined;
+ #constructorArgs: Array | UmbApiConstructorArgumentsMethodType | undefined;
constructor(
host: UmbControllerHost,
extensionRegistry: UmbExtensionRegistry,
type: ManifestTypeName | Array,
- constructorArguments: Array | undefined,
+ constructorArguments: Array | UmbApiConstructorArgumentsMethodType | undefined,
filter?: undefined | null | ((manifest: ManifestTypeAsApi) => boolean),
onChange?: (permittedManifests: Array) => void,
controllerAlias?: string,
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-api.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-api.function.ts
index 2a95855aa0..ef0b8612b9 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-api.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-api.function.ts
@@ -2,19 +2,21 @@ import type { UmbApi } from '../models/api.interface.js';
import type { ManifestApi, ManifestElementAndApi } from '../types/base.types.js';
import { loadManifestApi } from './load-manifest-api.function.js';
import type { UmbApiConstructorArgumentsMethodType } from './types.js';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export async function createExtensionApi(
+ host: UmbControllerHost,
manifest: ManifestApi | ManifestElementAndApi,
- constructorArgs:
+ constructorArgs?:
| Array
- | UmbApiConstructorArgumentsMethodType | ManifestElementAndApi> = [],
+ | UmbApiConstructorArgumentsMethodType | ManifestElementAndApi>,
): Promise {
if (manifest.api) {
const apiConstructor = await loadManifestApi(manifest.api);
if (apiConstructor) {
const additionalArgs =
(typeof constructorArgs === 'function' ? constructorArgs(manifest) : constructorArgs) ?? [];
- return new apiConstructor(...additionalArgs);
+ return new apiConstructor(host, ...additionalArgs);
} else {
console.error(
`-- Extension of alias "${manifest.alias}" did not succeed instantiate a API class via the extension manifest property 'api', using either a 'api' or 'default' export`,
@@ -28,7 +30,7 @@ export async function createExtensionApi(
if (apiConstructor2) {
const additionalArgs =
(typeof constructorArgs === 'function' ? constructorArgs(manifest) : constructorArgs) ?? [];
- return new apiConstructor2(...additionalArgs);
+ return new apiConstructor2(host, ...additionalArgs);
} else {
console.error(
`-- Extension of alias "${manifest.alias}" did not succeed instantiate a API class via the extension manifest property 'js', using either a 'api' or 'default' export`,
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-api.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-api.test.ts
index 5129c098c3..a9e3a0d80f 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-api.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-api.test.ts
@@ -1,7 +1,12 @@
-import { expect } from '@open-wc/testing';
+import { expect, fixture } from '@open-wc/testing';
import type { ManifestApi } from '../types/index.js';
import type { UmbApi } from '../models/api.interface.js';
import { createExtensionApi } from './create-extension-api.function.js';
+import { customElement, html } from '@umbraco-cms/backoffice/external/lit';
+import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
+
+@customElement('umb-test-controller-host')
+class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
class UmbExtensionApiTrueTestClass implements UmbApi {
isValidClassInstance() {
@@ -30,7 +35,13 @@ const jsModuleWithDefaultAndApiExport = {
api: UmbExtensionApiTrueTestClass,
};
-describe('Extension-Api: Create Extension Api', () => {
+describe('Create Extension Api Method', () => {
+ let hostElement: UmbTestControllerHostElement;
+
+ beforeEach(async () => {
+ hostElement = await fixture(html``);
+ });
+
it('Returns `undefined` when manifest does not have any correct properties', async () => {
const manifest: ManifestApi = {
type: 'my-test-type',
@@ -38,7 +49,7 @@ describe('Extension-Api: Create Extension Api', () => {
name: 'pretty name',
};
- const api = await createExtensionApi(manifest, []);
+ const api = await createExtensionApi(hostElement, manifest, []);
expect(api).to.be.undefined;
});
@@ -50,7 +61,7 @@ describe('Extension-Api: Create Extension Api', () => {
api: UmbExtensionApiTrueTestClass,
};
- const api = await createExtensionApi(manifest, []);
+ const api = await createExtensionApi(hostElement, manifest, []);
expect(api).to.not.be.undefined;
if (api) {
expect(api.isValidClassInstance()).to.be.true;
@@ -65,7 +76,7 @@ describe('Extension-Api: Create Extension Api', () => {
js: () => Promise.resolve(jsModuleWithDefaultExport),
};
- const api = await createExtensionApi(manifest, []);
+ const api = await createExtensionApi(hostElement, manifest, []);
expect(api).to.not.be.undefined;
if (api) {
expect(api.isValidClassInstance()).to.be.true;
@@ -80,7 +91,7 @@ describe('Extension-Api: Create Extension Api', () => {
js: () => Promise.resolve(jsModuleWithApiExport),
};
- const api = await createExtensionApi(manifest, []);
+ const api = await createExtensionApi(hostElement, manifest, []);
expect(api).to.not.be.undefined;
if (api) {
expect(api.isValidClassInstance()).to.be.true;
@@ -95,7 +106,7 @@ describe('Extension-Api: Create Extension Api', () => {
js: () => Promise.resolve(jsModuleWithDefaultAndApiExport),
};
- const api = await createExtensionApi(manifest, []);
+ const api = await createExtensionApi(hostElement, manifest, []);
expect(api).to.not.be.undefined;
if (api) {
expect(api.isValidClassInstance()).to.be.true;
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element-with-api.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element-with-api.function.ts
index a8f1475b5e..6e775e9cf0 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element-with-api.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element-with-api.function.ts
@@ -45,11 +45,10 @@ export async function createExtensionElementWithApi<
apiConstructor = await apiPromise;
}
- if (manifest.elementName) {
+ if (!element && manifest.elementName) {
element = document.createElement(manifest.elementName) as ElementType;
}
-
- if (fallbackElement) {
+ if (!element && fallbackElement) {
element = document.createElement(fallbackElement) as ElementType;
}
@@ -62,9 +61,37 @@ export async function createExtensionElementWithApi<
return { element, api };
}
- console.error(
- `-- Extension of alias "${manifest.alias}" did not succeed creating an element with api, missing one or two JavaScript files via the 'element' and 'api' or the 'js' property or with just 'api' and the Element Name in 'elementName' in the manifest.`,
- manifest,
- );
+ // Debug messages:
+ if (!element && apiConstructor) {
+ // If we have a elementPropValue, that means that element or js property was defined, but the element was not created.
+ if (elementPropValue) {
+ console.error(
+ `-- Extension of alias "${manifest.alias}" did not succeed creating an Element with Api, Api was created but the imported Element JS file did not export a 'element' or 'default'. Alternatively define the 'elementName' in the manifest.`,
+ manifest,
+ );
+ } else {
+ console.error(
+ `-- Extension of alias "${manifest.alias}" did not succeed creating an Element with Api, Api was created but the Element was missing a JavaScript file via the 'element' or the 'js' property. Alternatively define a Element Name in 'elementName' in the manifest.`,
+ manifest,
+ );
+ }
+ } else if (element && !apiConstructor) {
+ if (apiPropValue) {
+ console.error(
+ `-- Extension of alias "${manifest.alias}" did not succeed creating an Element with Api, Element was created but the imported Api JS file did not export a 'api' or 'default'.`,
+ manifest,
+ );
+ } else {
+ console.error(
+ `-- Extension of alias "${manifest.alias}" did not succeed creating an Element with Api, Element was created but the Api is missing a JavaScript file via the 'api' or 'js' property.`,
+ manifest,
+ );
+ }
+ } else {
+ console.error(
+ `-- Extension of alias "${manifest.alias}" did not succeed creating an Element with Api, neither an Element or Api was created, missing one or two JavaScript files via the 'element' and 'api' or the 'js' property or with just 'api' and the Element Name in 'elementName' in the manifest.`,
+ manifest,
+ );
+ }
return {};
}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element-with-api.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element-with-api.test.ts
new file mode 100644
index 0000000000..3b72ec300c
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element-with-api.test.ts
@@ -0,0 +1,199 @@
+import { expect } from '@open-wc/testing';
+import type { ManifestElementAndApi } from '../types/index.js';
+import type { UmbApi } from '../index.js';
+import { createExtensionElementWithApi } from './create-extension-element-with-api.function.js';
+import { customElement } from '@umbraco-cms/backoffice/external/lit';
+import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+
+interface UmbExtensionApiBooleanTestElement extends UmbLitElement {
+ isValidElementClassInstance(): boolean;
+}
+
+@customElement('umb-extension-api-true-test-element')
+class UmbExtensionApiTrueTestElement extends UmbLitElement implements UmbExtensionApiBooleanTestElement {
+ isValidElementClassInstance() {
+ return true;
+ }
+}
+
+@customElement('umb-extension-api-false-test-element')
+class UmbExtensionApiFalseTestElement extends UmbLitElement implements UmbExtensionApiBooleanTestElement {
+ isValidElementClassInstance() {
+ return false;
+ }
+}
+
+const elementJsModuleWithDefaultExport = {
+ default: UmbExtensionApiTrueTestElement,
+};
+
+const elementJsModuleWithElementExport = {
+ element: UmbExtensionApiTrueTestElement,
+};
+
+const elementJsModuleWithDefaultAndElementExport = {
+ default: UmbExtensionApiFalseTestElement,
+ element: UmbExtensionApiTrueTestElement,
+};
+
+interface UmbTestApi extends UmbApi {
+ isValidApiClassInstance(): boolean;
+}
+
+class UmbTestApiTrue implements UmbTestApi {
+ isValidApiClassInstance() {
+ return true;
+ }
+ destroy() {}
+}
+class UmbTestApiFalse implements UmbTestApi {
+ isValidApiClassInstance() {
+ return true;
+ }
+ destroy() {}
+}
+
+const apiJsModuleWithDefaultExport = {
+ default: UmbTestApiTrue,
+};
+
+const apiJsModuleWithApiExport = {
+ api: UmbTestApiTrue,
+};
+
+const apiJsModuleWithDefaultAndApiExport = {
+ default: UmbTestApiFalse,
+ api: UmbTestApiTrue,
+};
+
+describe('Create Extension Element and Api Method', () => {
+ it('Returns `undefined` when manifest does not have any correct properties', async () => {
+ const manifest: ManifestElementAndApi = {
+ type: 'my-test-type',
+ alias: 'Umb.Test.CreateManifestElement',
+ name: 'pretty name',
+ };
+
+ const { element, api } = await createExtensionElementWithApi(manifest);
+ expect(element).to.be.undefined;
+ expect(api).to.be.undefined;
+ });
+
+ it('Returns fallback element instance when manifest does not provide element', async () => {
+ const manifest: ManifestElementAndApi = {
+ type: 'my-test-type',
+ alias: 'Umb.Test.CreateManifestElement',
+ name: 'pretty name',
+ js: () => Promise.resolve(apiJsModuleWithApiExport),
+ };
+
+ const { element, api } = await createExtensionElementWithApi(manifest, 'umb-extension-api-true-test-element');
+ expect(element).to.not.be.undefined;
+ expect(api).to.not.be.undefined;
+ if (element) {
+ expect(element.isValidElementClassInstance()).to.be.true;
+ }
+ if (api) {
+ expect(api.isValidApiClassInstance()).to.be.true;
+ }
+ });
+
+ it('Still returns fallback element instance when manifest does not provide element and manifest has a js with an api', async () => {
+ const manifest: ManifestElementAndApi = {
+ type: 'my-test-type',
+ alias: 'Umb.Test.CreateManifestElement',
+ name: 'pretty name',
+ js: () => Promise.resolve(apiJsModuleWithApiExport),
+ };
+
+ const { element, api } = await createExtensionElementWithApi(manifest, 'umb-extension-api-true-test-element');
+ expect(element).to.not.be.undefined;
+ expect(api).to.not.be.undefined;
+ if (element) {
+ expect(element.isValidElementClassInstance()).to.be.true;
+ }
+ if (api) {
+ expect(api.isValidApiClassInstance()).to.be.true;
+ }
+ });
+
+ it('Handles when `api` property contains a class constructor', async () => {
+ const manifest: ManifestElementAndApi = {
+ type: 'my-test-type',
+ alias: 'Umb.Test.CreateManifestElement',
+ name: 'pretty name',
+ elementName: 'umb-extension-api-true-test-element',
+ api: () => Promise.resolve(apiJsModuleWithDefaultExport),
+ };
+
+ const { element, api } = await createExtensionElementWithApi(manifest);
+ expect(element).to.not.be.undefined;
+ expect(api).to.not.be.undefined;
+ if (element) {
+ expect(element.isValidElementClassInstance()).to.be.true;
+ }
+ if (api) {
+ expect(api.isValidApiClassInstance()).to.be.true;
+ }
+ });
+
+ it('Handles when `loader` has a default export', async () => {
+ const manifest: ManifestElementAndApi = {
+ type: 'my-test-type',
+ alias: 'Umb.Test.CreateManifestElement',
+ name: 'pretty name',
+ js: () => Promise.resolve(elementJsModuleWithDefaultExport),
+ api: () => Promise.resolve(apiJsModuleWithDefaultExport),
+ };
+
+ const { element, api } = await createExtensionElementWithApi(manifest);
+ expect(element).to.not.be.undefined;
+ expect(api).to.not.be.undefined;
+ if (element) {
+ expect(element.isValidElementClassInstance()).to.be.true;
+ }
+ if (api) {
+ expect(api.isValidApiClassInstance()).to.be.true;
+ }
+ });
+
+ it('Handles when `loader` has a element export', async () => {
+ const manifest: ManifestElementAndApi = {
+ type: 'my-test-type',
+ alias: 'Umb.Test.CreateManifestElement',
+ name: 'pretty name',
+ js: () => Promise.resolve(elementJsModuleWithElementExport),
+ api: () => Promise.resolve(apiJsModuleWithApiExport),
+ };
+
+ const { element, api } = await createExtensionElementWithApi(manifest);
+ expect(element).to.not.be.undefined;
+ expect(api).to.not.be.undefined;
+ if (element) {
+ expect(element.isValidElementClassInstance()).to.be.true;
+ }
+ if (api) {
+ expect(api.isValidApiClassInstance()).to.be.true;
+ }
+ });
+
+ it('Prioritizes api export from loader property', async () => {
+ const manifest: ManifestElementAndApi = {
+ type: 'my-test-type',
+ alias: 'Umb.Test.CreateManifestElement',
+ name: 'pretty name',
+ js: () => Promise.resolve(elementJsModuleWithDefaultAndElementExport),
+ api: () => Promise.resolve(apiJsModuleWithDefaultAndApiExport),
+ };
+
+ const { element, api } = await createExtensionElementWithApi(manifest);
+ expect(element).to.not.be.undefined;
+ expect(api).to.not.be.undefined;
+ if (element) {
+ expect(element.isValidElementClassInstance()).to.be.true;
+ }
+ if (api) {
+ expect(api.isValidApiClassInstance()).to.be.true;
+ }
+ });
+});
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element.function.ts
index 22fe577de1..754b8ea7aa 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element.function.ts
@@ -5,25 +5,15 @@ export async function createExtensionElement(
manifest: ManifestElement | ManifestElementAndApi,
fallbackElement?: string,
): Promise {
- if (manifest.element) {
- const elementConstructor = await loadManifestElement(manifest.element);
+ const elementPropValue = manifest.element ?? manifest.js;
+
+ if (elementPropValue) {
+ const elementConstructor = await loadManifestElement(elementPropValue);
if (elementConstructor) {
return new elementConstructor();
} else {
console.error(
- `-- Extension of alias "${manifest.alias}" did not succeed creating an element class instance via the extension manifest property 'element', using either a 'element' or 'default' export`,
- manifest,
- );
- }
- }
-
- if (manifest.js) {
- const elementConstructor2 = await loadManifestElement(manifest.js);
- if (elementConstructor2) {
- return new elementConstructor2();
- } else {
- console.error(
- `-- Extension of alias "${manifest.alias}" did not succeed creating an element class instance via the extension manifest property 'js', using either a 'element' or 'default' export`,
+ `-- Extension of alias "${manifest.alias}" did not succeed creating an element class instance via the extension manifest property '${elementPropValue}'. The imported Element JS file did not export a 'element' or 'default'. Alternatively define the 'elementName' in the manifest.`,
manifest,
);
}
@@ -38,8 +28,9 @@ export async function createExtensionElement(
}
console.error(
- `-- Extension of alias "${manifest.alias}" did not succeed creating an element, missing a JavaScript file via the 'element' or 'js' property or a Element Name in 'elementName' in the manifest.`,
+ `-- Extension of alias "${manifest.alias}" did not succeed creating an Element, missing a JavaScript file via the 'element' or 'js' property. Alternatively define a Element Name in 'elementName' in the manifest.`,
manifest,
);
+
return undefined;
}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element.test.ts
index 5f2f8567df..0bcad1161f 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/create-extension-element.test.ts
@@ -31,7 +31,7 @@ const jsModuleWithDefaultAndElementExport = {
element: UmbExtensionApiTrueTestElement,
};
-describe('Extension-Api: Create Extension Element', () => {
+describe('Create Extension Element Method', () => {
it('Returns `undefined` when manifest does not have any correct properties', async () => {
const manifest: ManifestElement = {
type: 'my-test-type',
diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts
index 613000e663..1459943d78 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts
@@ -266,10 +266,6 @@ export class UmbExtensionRegistry<
distinctUntilChanged(extensionAndKindMatchSingleMemoization),
) as Observable;
}
- /**
- * @deprecated Use `byAlias` instead.
- */
- getByAlias = this.byAlias.bind(this);
/**
* Get an observable that provides extensions matching the given type and alias.
@@ -292,10 +288,6 @@ export class UmbExtensionRegistry<
distinctUntilChanged(extensionAndKindMatchSingleMemoization),
) as Observable;
}
- /**
- * @deprecated Use `byTypeAndAlias` instead.
- */
- getByTypeAndAlias = this.byTypeAndAlias.bind(this);
byTypeAndAliases>(
type: Key,
@@ -312,10 +304,6 @@ export class UmbExtensionRegistry<
distinctUntilChanged(extensionAndKindMatchArrayMemoization),
) as Observable>;
}
- /**
- * @deprecated Use `byTypeAndAliases` instead.
- */
- getByTypeAndAliases = this.byTypeAndAliases.bind(this);
/**
* Get an observable of extensions by type and a given filter method.
@@ -383,10 +371,6 @@ export class UmbExtensionRegistry<
distinctUntilChanged(extensionAndKindMatchArrayMemoization),
) as Observable>;
}
- /**
- * @deprecated Use `byType` instead.
- */
- extensionsOfType = this.byType.bind(this);
/**
* Get an observable that provides extensions matching given types.
@@ -399,9 +383,4 @@ export class UmbExtensionRegistry<
distinctUntilChanged(extensionAndKindMatchArrayMemoization),
) as Observable>;
}
-
- /**
- * @deprecated Use `byTypes` instead.
- */
- extensionsOfTypes = this.byTypes.bind(this);
}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts
index 076b93d452..69aa55ab50 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.test.ts
@@ -1,6 +1,7 @@
import { expect } from '@open-wc/testing';
import { UmbObjectState } from './states/object-state.js';
import { UmbObserverController } from './observer.controller.js';
+import { simpleHashCode } from './utils/simple-hash-code.function.js';
import { customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
@@ -42,27 +43,21 @@ describe('UmbObserverController', () => {
expect(hostElement.hasController(secondCtrl)).to.be.true;
});
- it('controller is replacing another controller when using the same callback method and no controller-alias', () => {
- const state = new UmbObjectState(undefined);
- const observable = state.asObservable();
-
- const callbackMethod = (state: unknown) => {};
-
- const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod);
- const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod);
-
- expect(hostElement.hasController(firstCtrl)).to.be.false;
- expect(hostElement.hasController(secondCtrl)).to.be.true;
- });
-
it('controller is NOT replacing another controller when using a null for controller-alias', () => {
const state = new UmbObjectState(undefined);
const observable = state.asObservable();
const callbackMethod = (state: unknown) => {};
- const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod, null);
- const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, null);
+ // Imitates the behavior of the observe method in the UmbClassMixin
+ let controllerAlias1 = null;
+ controllerAlias1 ??= controllerAlias1 === undefined ? simpleHashCode(callbackMethod.toString()) : undefined;
+
+ const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod, controllerAlias1);
+
+ let controllerAlias2 = null;
+ controllerAlias2 ??= controllerAlias2 === undefined ? simpleHashCode(callbackMethod.toString()) : undefined;
+ const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, controllerAlias2);
expect(hostElement.hasController(firstCtrl)).to.be.true;
expect(hostElement.hasController(secondCtrl)).to.be.true;
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts
index 9fa48b5bff..fdd5689913 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts
@@ -1,5 +1,4 @@
import { type ObserverCallback, UmbObserver } from './observer.js';
-import { simpleHashCode } from './utils/simple-hash-code.function.js';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { UmbController, UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
@@ -15,12 +14,11 @@ export class UmbObserverController extends UmbObserver implement
host: UmbControllerHost,
source: Observable,
callback: ObserverCallback,
- alias?: UmbControllerAlias | null,
+ alias: UmbControllerAlias,
) {
super(source, callback);
this.#host = host;
- // Fallback to use a hash of the provided method, but only if the alias is undefined.
- this.#alias = alias ?? (alias === undefined ? simpleHashCode(callback.toString()) : undefined);
+ this.#alias = alias;
// Lets check if controller is already here:
// No we don't want this, as multiple different controllers might be looking at the same source.
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.ts
index 35372c2eb2..f9bee2b51b 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.ts
@@ -6,7 +6,9 @@ export type ObserverCallbackStack = {
complete?: () => void;
};
-export type ObserverCallback = ((_value: T) => void) | ObserverCallbackStack;
+export type ObserverCallback = (_value: T) => void;
+// We do not use the ObserverCallbackStack type, and it was making things more complicated than they need to be so I have taken it out..
+//export type ObserverCallback = ((_value: T) => void) | ObserverCallbackStack;
export class UmbObserver {
#source!: Observable;
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts
index e2d248b5e7..6da96a31f1 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts
@@ -56,11 +56,6 @@ export class UmbArrayState extends UmbDeepState {
}
}
- /**
- * @deprecated - Use `setValue` instead.
- */
- next = this.setValue;
-
/**
* @method remove
* @param {unknown[]} uniques - The unique values to remove.
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/basic-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/basic-state.ts
index 7279c7476d..57aaaa84b6 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/basic-state.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/basic-state.ts
@@ -72,9 +72,4 @@ export class UmbBasicState {
this._subject.next(data);
}
}
-
- /**
- * @deprecated - Use `setValue` instead.
- */
- next = this.setValue;
}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/append-to-frozen-array.function.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/append-to-frozen-array.function.ts
index 0f7eba5a52..b70fa4b335 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/append-to-frozen-array.function.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/append-to-frozen-array.function.ts
@@ -8,8 +8,8 @@
* @description - Inserts or replaces an entry in a frozen array and returns a new array.
* @example
Example append new entry for a UmbArrayState or a part of UmbObjectState/UmbDeepState which is an array. Where the key is unique and the item will be updated if matched with existing.
Example remove an entry of a ArrayState or a part of DeepState/ObjectState it which is an array. Where the key is unique and the item will be updated if matched with existing.
Example append new entry for a ArrayState or a part of UmbDeepState/UmbObjectState it which is an array. Where the key is unique and the item will be updated if matched with existing.