fix editable workspace + form context

This commit is contained in:
Niels Lyngsø
2024-03-25 20:06:40 +01:00
parent 51a2be8051
commit 919b2ee4e4
15 changed files with 132 additions and 27 deletions

View File

@@ -60,7 +60,21 @@ export class UmbInputNumberRangeElement extends UmbFormControlMixin(UmbLitElemen
}
protected getFormElement() {
return this;
return undefined;
}
constructor() {
super();
this.addValidator(
'customError',
() => {
return 'The low value must be less than the high value';
},
() => {
return this._minValue !== undefined && this._maxValue !== undefined && this._minValue > this._maxValue;
},
);
}
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {

View File

@@ -39,7 +39,11 @@ import type { ManifestLocalization } from './localization.model.js';
import type { ManifestTree } from './tree.model.js';
import type { ManifestTreeItem } from './tree-item.model.js';
import type { ManifestUserProfileApp } from './user-profile-app.model.js';
import type { ManifestWorkspace, ManifestWorkspaceRoutableKind } from './workspace.model.js';
import type {
ManifestWorkspace,
ManifestWorkspaceRoutableKind,
ManifestWorkspaceEditableKind,
} from './workspace.model.js';
import type { ManifestWorkspaceAction, ManifestWorkspaceActionDefaultKind } from './workspace-action.model.js';
import type { ManifestWorkspaceActionMenuItem } from './workspace-action-menu-item.model.js';
import type { ManifestWorkspaceContext } from './workspace-context.model.js';
@@ -114,7 +118,7 @@ export type ManifestPropertyActions = ManifestPropertyAction | ManifestPropertyA
export type ManifestWorkspaceActions = ManifestWorkspaceAction | ManifestWorkspaceActionDefaultKind;
export type ManifestWorkspaces = ManifestWorkspace | ManifestWorkspaceRoutableKind;
export type ManifestWorkspaces = ManifestWorkspace | ManifestWorkspaceRoutableKind | ManifestWorkspaceRoutableKind;
export type ManifestWorkspaceViews = ManifestWorkspaceView | ManifestWorkspaceViewContentTypeDesignEditorKind;
export type ManifestTypes =

View File

@@ -1,4 +1,5 @@
import type { UmbRoutableWorkspaceContext } from '../../workspace/contexts/tokens/routable-workspace-context.interface.js';
import type { UmbEditableWorkspaceContext } from '../../workspace/contexts/tokens/editable-workspace-context.interface.js';
import type { UmbWorkspaceContext } from '../../workspace/contexts/tokens/workspace-context.interface.js';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import type { ManifestElementAndApi } from '@umbraco-cms/backoffice/extension-api';
@@ -24,3 +25,11 @@ export interface ManifestWorkspaceRoutableKind
}
export interface MetaWorkspaceRoutableKind extends MetaWorkspace {}
export interface ManifestWorkspaceEditableKind
extends ManifestWorkspace<MetaWorkspaceEditableKind, UmbControllerHostElement, UmbEditableWorkspaceContext> {
type: 'workspace';
kind: 'routable';
}
export interface MetaWorkspaceEditableKind extends MetaWorkspace {}

View File

@@ -1,5 +1,5 @@
import { UmbFormContext } from '../context/form.context.js';
import { type PropertyValueMap, customElement, html } from '@umbraco-cms/backoffice/external/lit';
import { type PropertyValueMap, customElement, html, css } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@customElement('umb-form')
@@ -19,4 +19,12 @@ export class UmbFormElement extends UmbLitElement {
</form>
</uui-form>`;
}
static styles = [
css`
form {
display: contents;
}
`,
];
}

View File

@@ -13,6 +13,7 @@ export class UmbFormContext extends UmbContextBase<UmbFormContext> {
constructor(host: UmbControllerHost) {
super(host, UMB_FORM_CONTEXT);
console.log('providing it self as', UMB_FORM_CONTEXT, this);
}
/**
@@ -20,6 +21,7 @@ export class UmbFormContext extends UmbContextBase<UmbFormContext> {
* @param element {HTMLFormElement | null} - The Form element to be used for this context.
*/
setFormElement(element: HTMLFormElement | null) {
console.log('setFormElement', element, this);
if (this.#formElement === element) return;
if (this.#formElement) {
this.#formElement.removeEventListener('submit', this.onSubmit);
@@ -46,25 +48,29 @@ export class UmbFormContext extends UmbContextBase<UmbFormContext> {
*/
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();
this.#formElement?.requestSubmit();
}
/**
* @description Triggered by the form, when it fires a submit event
*/
onSubmit = (event: SubmitEvent) => {
console.log('onSubmit', event);
event?.preventDefault();
//this.dispatchEvent(new CustomEvent('submit-requested'));
// Check client validation:
const isClientValid = this.#formElement?.checkValidity();
console.log('isClientValid', isClientValid);
// ask validation managers to validate the form.
const isValid = isClientValid ?? false;
if (!isValid) {
// Fire invalid..
// TODO: consider naming it something like submit failed?
this.dispatchEvent(new CustomEvent('invalid'));
return;
}

View File

@@ -1 +1,2 @@
export * from './context/index.js';
export * from './component/form.element.js';

View File

@@ -15,6 +15,7 @@ export * from './entity-action/index.js';
export * from './entity-bulk-action/index.js';
export * from './extension-registry/index.js';
export * from './id/index.js';
export * from './form/index.js';
export * from './menu/index.js';
export * from './modal/index.js';
export * from './notification/index.js';

View File

@@ -23,6 +23,7 @@ export abstract class UmbSaveableWorkspaceContextBase<WorkspaceDataModelType>
#form?: typeof UMB_FORM_CONTEXT.TYPE;
#savePromise: Promise<void> | undefined;
#saveResolve: (() => void) | undefined;
#saveReject: (() => void) | undefined;
abstract readonly unique: Observable<string | null | undefined>;
@@ -46,13 +47,17 @@ export abstract class UmbSaveableWorkspaceContextBase<WorkspaceDataModelType>
this.consumeContext(UMB_MODAL_CONTEXT, (context) => {
(this.modalContext as UmbModalContext) = context;
});
console.log('about to consume form context', UMB_FORM_CONTEXT);
this.consumeContext(UMB_FORM_CONTEXT, (context) => {
console.log('consume form context', context);
if (this.#form === context) return;
if (this.#form) {
this.#form.removeEventListener('submit', this.#performSubmitBind);
this.#form.removeEventListener('invalid', this.#invalidForm);
}
this.#form = context;
this.#form.addEventListener('submit', this.#performSubmitBind);
this.#form.addEventListener('invalid', this.#invalidForm);
this._gotFormContext(context);
});
}
@@ -69,9 +74,41 @@ export abstract class UmbSaveableWorkspaceContextBase<WorkspaceDataModelType>
this.#isNew.setValue(isNew);
}
requestSubmit(): Promise<void> {
if (this.#savePromise) {
return this.#savePromise;
}
if (!this.#form) {
throw new Error('Form context not available');
}
this.#savePromise = new Promise<void>((resolve, reject) => {
this.#saveResolve = resolve;
this.#saveReject = reject;
});
console.log('REQUEST SUBMIT', this.#form);
this.#form.requestSubmit();
return this.#savePromise;
}
#invalidForm = (event: Event) => {
console.log('workspace context got invalid form', event);
if (this.#savePromise) {
this.#saveReject?.();
this.#savePromise = undefined;
this.#saveResolve = undefined;
this.#saveReject = undefined;
}
};
protected submitComplete(data: WorkspaceDataModelType | undefined) {
// Resolve the save promise:
this.#saveResolve?.();
// TODO: We need a way to fail the save promise..
this.#savePromise = undefined;
this.#saveResolve = undefined;
this.#saveReject = undefined;
if (this.modalContext) {
if (data) {
@@ -87,20 +124,6 @@ export abstract class UmbSaveableWorkspaceContextBase<WorkspaceDataModelType>
abstract getUnique(): string | undefined;
abstract getEntityType(): string;
abstract getData(): WorkspaceDataModelType | undefined;
requestSubmit(): Promise<void> {
if (this.#savePromise) {
return this.#savePromise;
}
if (!this.#form) {
throw new Error('Form context not available');
}
this.#form.requestSubmit();
this.#savePromise = new Promise<void>((resolve) => {
this.#saveResolve = resolve;
});
return this.#savePromise;
}
#performSubmitBind: () => void;
protected abstract submit(): void;

View File

@@ -0,0 +1,3 @@
import type { UmbRoutableWorkspaceContext } from '../../index.js';
export interface UmbEditableWorkspaceContext extends UmbRoutableWorkspaceContext {}

View File

@@ -0,0 +1,10 @@
import type { UmbEditableWorkspaceContext } from './editable-workspace-context.interface.js';
import type { UmbWorkspaceContext } from './workspace-context.interface.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export const UMB_EDITABLE_WORKSPACE_CONTEXT = new UmbContextToken<UmbWorkspaceContext, UmbEditableWorkspaceContext>(
'UmbWorkspaceContext',
undefined,
// TODO: Make proper discriminator:
(context): context is UmbEditableWorkspaceContext => 'routes' in context,
);

View File

@@ -1,4 +1,5 @@
export * from './collection-workspace.context-token.js';
export * from './editable-workspace.context-token.js';
export * from './entity-workspace.context-token.js';
export * from './publishable-workspace.context-token.js';
export * from './routable-workspace.context-token.js';
@@ -6,6 +7,7 @@ export * from './saveable-workspace.context-token.js';
export * from './variant-workspace.context-token.js';
export * from './workspace.context-token.js';
export type * from './collection-workspace-context.interface.js';
export type * from './editable-workspace-context.interface.js';
export type * from './entity-workspace-context.interface.js';
export type * from './invariant-dataset-workspace-context.interface.js';
export type * from './property-structure-workspace-context.interface.js';

View File

@@ -1,12 +1,15 @@
import type { UmbSaveableWorkspaceContext } from '../../contexts/tokens/saveable-workspace-context.interface.js';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { html, customElement, state, css, type PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbRoute } from '@umbraco-cms/backoffice/router';
import { UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { UmbFormContext } from '@umbraco-cms/backoffice/form';
@customElement('umb-editable-workspace')
export class UmbEditableWorkspaceElement extends UmbLitElement {
readonly #formContext = new UmbFormContext(this);
@state()
_routes: UmbRoute[] = [];
@@ -16,11 +19,28 @@ export class UmbEditableWorkspaceElement extends UmbLitElement {
new UmbExtensionsApiInitializer(this, umbExtensionsRegistry, 'workspaceContext', [api]);
}
render() {
return html`<umb-form>
<umb-router-slot .routes="${this._routes}"></umb-router-slot>
</umb-form>`;
protected firstUpdated(_changedProperties: PropertyValueMap<unknown> | Map<PropertyKey, unknown>): void {
super.firstUpdated(_changedProperties);
this.#formContext.setFormElement(this.shadowRoot!.querySelector<HTMLFormElement>('form'));
}
render() {
return html`<uui-form>
<form>
<uui-input name="tester" required></uui-input>
<umb-router-slot .routes="${this._routes}"></umb-router-slot>
</form>
</uui-form>`;
}
static styles = [
css`
form {
display: contents;
}
`,
];
}
export default UmbEditableWorkspaceElement;

View File

@@ -1,3 +1,4 @@
import { manifest as editableKindManifest } from './editable/editable-workspace.kind.js';
import { manifest as routableKindManifest } from './routable/routable-workspace.kind.js';
export const manifests = [routableKindManifest];
export const manifests = [routableKindManifest, editableKindManifest];

View File

@@ -5,6 +5,7 @@ import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property
import type {
UmbInvariantDatasetWorkspaceContext,
UmbRoutableWorkspaceContext,
UmbEditableWorkspaceContext,
} from '@umbraco-cms/backoffice/workspace';
import {
UmbSaveableWorkspaceContextBase,
@@ -33,7 +34,7 @@ import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice
type EntityType = UmbDataTypeDetailModel;
export class UmbDataTypeWorkspaceContext
extends UmbSaveableWorkspaceContextBase<EntityType>
implements UmbInvariantDatasetWorkspaceContext, UmbRoutableWorkspaceContext
implements UmbInvariantDatasetWorkspaceContext, UmbRoutableWorkspaceContext, UmbEditableWorkspaceContext
{
//
public readonly repository: UmbDataTypeDetailRepository = new UmbDataTypeDetailRepository(this);
@@ -315,6 +316,8 @@ export class UmbDataTypeWorkspaceContext
}
async submit() {
console.log('SUBMIT');
if (!this.#currentData.value) return;
if (!this.#currentData.value.unique) return;

View File

@@ -9,7 +9,7 @@ const DATA_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.DataType';
const workspace: ManifestWorkspaces = {
type: 'workspace',
kind: 'routable',
kind: 'editable',
alias: DATA_TYPE_WORKSPACE_ALIAS,
name: 'Data Type Workspace',
api: () => import('./data-type-workspace.context.js'),