Hello gang
I am working on finishing up porting a package that notifies another user has locked the page.
I achieve this by using a workspace context that makes an API call to my controller to check if the node is locked or not.
If it is locked then it should open/display a Modal to prompt the user someone has this page locked.
I had this previously working in V14 back in Sept/Nov time by using the ModalManager to open a modal I created and this worked, but now this no longer seems to work for me as something has changed between now and then.
Possible cause
From my investigation of the Umbraco source code is that the ModalManagerContext file is opening and closing the modal immediately, is that when the content node is finished loading it is hitting the onNavigationSuccess method.
So I am not sure I can workaround this issue for my usecase of showing a modal immediately when the node loads, unless anyone can give me some ideas or pointers to try.
My hack/workaround
I have tried to work around this problem, by using a native HTML dialog element which probably what the ModalManager is doing under the hood. But I have more control to insert this element into the DOM and open/close and destroy/remove it as needed.
contentlock.workspace.context.ts
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UMB_DOCUMENT_WORKSPACE_CONTEXT, UmbDocumentWorkspaceContext } from '@umbraco-cms/backoffice/document';
import { UmbBooleanState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
import { ContentLockService, ContentLockStatus } from '../api';
import { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
import '../components/dialog/locked-content-dialog';
import { LockedContentDialogElement } from '../components/dialog/locked-content-dialog';
export class ContentLockWorkspaceContext extends UmbControllerBase {
private _docWorkspaceCtx?: UmbDocumentWorkspaceContext;
private _unique: UmbEntityUnique | undefined;
private _dialogElement: LockedContentDialogElement | null = null;
#isLocked = new UmbBooleanState(false);
isLocked = this.#isLocked.asObservable();
#isLockedBySelf = new UmbBooleanState(false);
isLockedBySelf = this.#isLockedBySelf.asObservable();
#lockedByName = new UmbStringState('');
lockedByName = this.#lockedByName.asObservable();
constructor(host: UmbControllerHost) {
super(host, CONTENTLOCK_WORKSPACE_CONTEXT.toString());
this.provideContext(CONTENTLOCK_WORKSPACE_CONTEXT, this);
this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (docWorkspaceCtx) => {
this._docWorkspaceCtx = docWorkspaceCtx;
this._docWorkspaceCtx?.unique.subscribe((unique) => {
this._unique = unique;
if(!this._unique){
return;
}
// Call API now we have assigned the unique
this.checkContentLockState();
});
});
// Create and append the dialog element to the body
// This feels a bit hacky to use a native HTML5 dialog and not modalManager context
// As the modal manager closes the modal immediately after opening it when we navigate to the page
// This is due to closeNoneRoutableModals AFAIK from Umbraco modalManagerCtx
console.log('Add dialog to body for us to open');
// Create and append the dialog element to the body
this._dialogElement = document.createElement('locked-content-dialog') as LockedContentDialogElement;
document.body.appendChild(this._dialogElement);
}
override destroy() {
super.destroy();
console.log('DESTROY THE DIALOG');
if (this._dialogElement) {
document.body.removeChild(this._dialogElement);
this._dialogElement = null;
}
}
private async _getStatus(key: string) : Promise<ContentLockStatus | undefined> {
const { data, error } = await ContentLockService.status({path:{key:key}});
if (error){
console.error(error);
return undefined;
}
return data;
}
async checkContentLockState() {
// Check if the current document is locked and its not locked by self
await this._getStatus(this._unique!).then(async (status) => {
// Set the observable bool that we consume as part of condition
this.setIsLocked(status?.isLocked ?? false);
this.setIsLockedBySelf(status?.lockedBySelf ?? false);
this.setLockedByName(status?.lockedByName ?? '');
if(status?.isLocked && status.lockedBySelf === false){
try {
// Display the dialog if the document is locked by someone else
const dialog = this._dialogElement;
if(dialog){
dialog.lockedBy = status.lockedByName!;
dialog.openDialog();
}
} catch (error) {
console.error('Error opening dialog:', error);
}
}
});
}
getIsLocked() {
return this.#isLocked.getValue();
}
setIsLocked(isLocked: boolean) {
this.#isLocked.setValue(isLocked);
}
getIsLockedBySelf() {
return this.#isLockedBySelf.getValue();
}
setIsLockedBySelf(isLockedBySelf: boolean) {
this.#isLockedBySelf.setValue(isLockedBySelf);
}
getLockedByName() {
return this.#lockedByName.getValue();
}
setLockedByName(lockedBy: string) {
this.#lockedByName.setValue(lockedBy);
}
}
// Declare a api export, so Extension Registry can initialize this class:
export const api = ContentLockWorkspaceContext;
// Declare a Context Token that other elements can use to request the WorkspaceContextCounter:
export const CONTENTLOCK_WORKSPACE_CONTEXT = new UmbContextToken<ContentLockWorkspaceContext>('UmbWorkspaceContext', 'contentlock.workspacecontext');
locked-content-dialog.ts
import { css, customElement, html, LitElement, property, query } from '@umbraco-cms/backoffice/external/lit';
@customElement('locked-content-dialog')
export class LockedContentDialog extends LitElement {
// Pass in a name of a person who has locked the content
@property({ type: String })
lockedBy = '';
@query('#locked-modal')
dialogEl!: HTMLDialogElement;
openDialog() {
this.dialogEl?.showModal();
}
closeDialog() {
this.dialogEl?.close();
}
render() {
return html`
<dialog id="locked-modal">
<uui-dialog-layout headline="Content Lock - This page is locked">
<p>This page is currently locked by <strong>${this.lockedBy}</strong></p>
<uui-button slot="actions" @click=${this.closeDialog}>Close</uui-button>
</uui-dialog-layout>
</dialog>
`;
}
static styles = css`
dialog {
border: none;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.2);
}
`;
}
export default LockedContentDialog;
export interface LockedContentDialogElement extends HTMLElement {
lockedBy: string;
openDialog: () => void;
closeDialog: () => void;
}
Thoughts?
Is is the bast way to solve this, I am not 100% sure, but I would love to get some feedback or thoughts please gang
So should I go with my custom HTML5 dialog that I have added to the DOM to launch mod modal/dialog or should I try to use the ModalManager from Umbraco to achieve the same thing?