From eb2b7fd4dda869780c7a32d24aedffe97dfa2d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 25 Mar 2024 14:23:39 +0100 Subject: [PATCH] Core form package --- src/Umbraco.Web.UI.Client/package.json | 1 + .../core/form/component/form.element.ts | 22 +++++ .../core/form/context/form.context-token.ts | 4 + .../core/form/context/form.context.ts | 81 +++++++++++++++++++ .../src/packages/core/form/context/index.ts | 2 + .../src/packages/core/form/index.ts | 1 + .../src/packages/core/validation/index.ts | 1 + .../core/validation/interfaces/index.ts | 1 + .../validation-manager.interface.ts | 4 + .../editable/editable-workspace.element.ts | 8 +- src/Umbraco.Web.UI.Client/tsconfig.json | 1 + 11 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/form/component/form.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/form/context/form.context-token.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/form/context/form.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/form/context/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/form/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/validation/interfaces/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/validation/interfaces/validation-manager.interface.ts diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index dec2f440a5..3ac7aab157 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -38,6 +38,7 @@ "./entity-bulk-action": "./dist-cms/packages/core/entity-bulk-action/index.js", "./event": "./dist-cms/packages/core/event/index.js", "./extension-registry": "./dist-cms/packages/core/extension-registry/index.js", + "./form": "./dist-cms/packages/core/form/index.js", "./icon": "./dist-cms/packages/core/icon-registry/index.js", "./id": "./dist-cms/packages/core/id/index.js", "./language": "./dist-cms/packages/language/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/form/component/form.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/form/component/form.element.ts new file mode 100644 index 0000000000..959472f040 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/form/component/form.element.ts @@ -0,0 +1,22 @@ +import { UmbFormContext } from '../context/form.context.js'; +import { type PropertyValueMap, customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('umb-form') +export class UmbFormElement extends UmbLitElement { + readonly #context = new UmbFormContext(this); + + protected firstUpdated(_changedProperties: PropertyValueMap | Map): void { + super.firstUpdated(_changedProperties); + + this.#context.setFormElement(this.shadowRoot!.querySelector('form')); + } + + render() { + return html` +
+ +
+
`; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/form/context/form.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/form/context/form.context-token.ts new file mode 100644 index 0000000000..4e44d45c83 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/form/context/form.context-token.ts @@ -0,0 +1,4 @@ +import type { UmbFormContext } from './form.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_FORM_CONTEXT = new UmbContextToken('UmbFormContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/form/context/form.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/form/context/form.context.ts new file mode 100644 index 0000000000..b92f97f6ea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/form/context/form.context.ts @@ -0,0 +1,81 @@ +import { UMB_FORM_CONTEXT } from './form.context-token.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbValidationManager } from '@umbraco-cms/backoffice/validation'; + +/** + * @description The Form Context is used to handle form submission and validation. + * @event submit - Fired when the form is submitted. + */ +export class UmbFormContext extends UmbContextBase { + #formElement?: HTMLFormElement; + #validation: UmbValidationManager[] = []; + + constructor(host: UmbControllerHost) { + super(host, UMB_FORM_CONTEXT); + } + + /** + * Method to call in the implementation once the form element is available. + * @param element {HTMLFormElement | null} - The Form element to be used for this context. + */ + setFormElement(element: HTMLFormElement | null) { + if (this.#formElement === element) return; + if (this.#formElement) { + this.#formElement.removeEventListener('submit', this.onSubmit); + this.#formElement.removeEventListener('reset', this.onReset); + } + if (element) { + this.#formElement = element; + this.#formElement.addEventListener('submit', this.onSubmit); + this.#formElement.addEventListener('reset', this.onReset); + } + } + + /** + * Define one or more validation managers to be used for this form. + * These will be called when the form is requested to be submitted. + * @param manager {UmbValidationManager} - A manager to be bound to this form. + */ + registerValidationManager(manager: UmbValidationManager) { + this.#validation.push(manager); + } + + /** + * Call this method to submit the form + */ + requestSubmit() { + // We do not call requestSubmit here, as we want the form to submit, and then we will handle the validation as part of the submit event handling. + this.#formElement?.submit(); + } + + /** + * @description Triggered by the form, when it fires a submit event + */ + onSubmit = (event: SubmitEvent) => { + event?.preventDefault(); + //this.dispatchEvent(new CustomEvent('submit-requested')); + + // Check client validation: + const isClientValid = this.#formElement?.checkValidity(); + + // ask validation managers to validate the form. + + const isValid = isClientValid ?? false; + + if (!isValid) { + // Fire invalid.. + return; + } + + // Fire submit event... + this.dispatchEvent(new CustomEvent('submit')); + }; + + /** + * @description Triggered by the form, when it fires a reset event + */ + onReset = (event: Event) => { + // ask validation managers to reset. + }; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/form/context/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/form/context/index.ts new file mode 100644 index 0000000000..fd4cf8da9e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/form/context/index.ts @@ -0,0 +1,2 @@ +export * from './form.context.js'; +export * from './form.context-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/form/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/form/index.ts new file mode 100644 index 0000000000..00c55032bc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/form/index.ts @@ -0,0 +1 @@ +export * from './context/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/index.ts index 807bd98e24..a10074f553 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/index.ts @@ -1,3 +1,4 @@ export * from './events/index.js'; +export * from './interfaces/index.js'; export * from './mixins/index.js'; export * from './context/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/interfaces/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/interfaces/index.ts new file mode 100644 index 0000000000..76b00c66bc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/interfaces/index.ts @@ -0,0 +1 @@ +export type * from './validation-manager.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/interfaces/validation-manager.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/interfaces/validation-manager.interface.ts new file mode 100644 index 0000000000..0bb167c36b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/interfaces/validation-manager.interface.ts @@ -0,0 +1,4 @@ +export interface UmbValidationManager { + validate(): Promise; + // reset? +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/editable/editable-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/editable/editable-workspace.element.ts index 85b8dbf8ab..3d5affa225 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/editable/editable-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/editable/editable-workspace.element.ts @@ -17,11 +17,9 @@ export class UmbEditableWorkspaceElement extends UmbLitElement { } render() { - return html` -
- -
-
`; + return html` + + `; } } diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 33bf53145e..1a7d0bcdf8 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -56,6 +56,7 @@ "@umbraco-cms/backoffice/entity-bulk-action": ["./src/packages/core/entity-bulk-action/index.ts"], "@umbraco-cms/backoffice/event": ["./src/packages/core/event/index.ts"], "@umbraco-cms/backoffice/extension-registry": ["./src/packages/core/extension-registry/index.ts"], + "@umbraco-cms/backoffice/form": ["./src/packages/core/form/index.ts"], "@umbraco-cms/backoffice/icon": ["./src/packages/core/icon-registry/index.ts"], "@umbraco-cms/backoffice/id": ["./src/packages/core/id/index.ts"], "@umbraco-cms/backoffice/language": ["./src/packages/language/index.ts"],