I’m updating an Umbraco 13 to Umbraco 15 package. I have an interresting question about handling save actions in a workspace-view (the former Content Apps) on a document node. Both from a technical perspective (what is possible) and from a user experience perspective. Let me explain.
In Umbraco 13, a content app would not have the default content buttons (save, preview, save and publish):
As you can see, I did just put in a few save buttons. I was never fond of this solution, because the save buttons could go outside the screen on lower resolutions or if the user and group list was too long.
However, by default, in Umbraco 15, the default buttons are added:
So I have a choice to make how to handle this, depending on what is possible and what you would consider nice to have.
I personally think the best option would be to remove the standard buttons and have a custom action. It’s in a place people expect it, and it’s always visible, even then the content of the workspace doesn’t fit in the screen. I’ve done thing on a custom workspace (not a workspace view), where you have complete control over the buttons. Like this:
However, in this case I know how to add buttons/actions and how to override the default buttons, but I don’t know how to remove any of the default buttons. If that even possible?
Keep the old situation with the custom save buttons. That could work, but I think I would want to hide the default workspace footer and I haven’t figured out how to do that either. Also the same issue remains that the buttons could not be visible because they are outside the visible area.
I could add an additionan button next to the default buttons. But from the perspective of a user, that would probably be confusing. But I know how to do this
I could hook into the save and save and publish events so the user uses the default buttons. But semantically, I feel this doesn’t make any sense. The content of the workspace view is NOT content but meta data.
Oh and if it’s possible to remove the default workspace footer, I could also add a custom footer in the workspace view itself. It would be kind of a fake footer, but the user wouldn’t see a difference. But it feels a bit like cheating the system.
And lastly, I could entirely ditch a save button and just save the actions the moment they are performed. It’s a lot more API calls, but the changes would be very persistent. Not sure what the implecations are in that case.
Like I said in this post, it’s a combination of UI design and opinion. So there is not a wrong or right. At the moment, I just added a ‘save’ button at the top for saving the changes to the settings and left the default buttons in place at the bottom:
Do I feel this is the perfect solution? Not really to be honest. I also considered tapping into the save events of the actual content buttons themselves, but in this case, you would want to save the settings seperately from the content. So this is it for now unless I have a better option
One benefit of this approach though, is that if there are more buttons, I have a place for them:
Yeah, this was also something I encountered with my package SeoToolkit. There the Seo settings are handled in a content app so I had the same issues. In the end, I just update my settings whenever you click on the save button, but I don’t have any publish/unpublished functionality so that part I just ignore. It would have been nice if they kept it the same as in Umbraco 13
I actually hadn’t thought of that, I was just happy that I finally managed to get it to work
As for how I intercept it, it is the most ugly way possible. I observe the publish/save dates of the content item and send my own data when they change. I still want to make a PR to introduce events so that it can just be handled nicer
I think you can use the Action Event Context for event handling like saving. I let Claude generate an example based on the Umbraco source code to give you an idea, so use at your own risk.
import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UmbEntityUpdatedEvent } from '@umbraco-cms/backoffice/entity-action';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { customElement } from '@umbraco-cms/backoffice/external/lit';
@customElement('my-workspace-view')
export class MyWorkspaceViewElement extends UmbLitElement {
#documentContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE;
#actionEventContext?: typeof UMB_ACTION_EVENT_CONTEXT.TYPE;
#workspaceEventUnique?: string;
constructor() {
super();
this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => {
this.#documentContext = context;
// Get the workspace's unique event identifier to filter events
this.#workspaceEventUnique = context['_workspaceEventUnique'];
});
this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (context) => {
this.#actionEventContext = context;
// Listen for entity updated events (fired on save/publish)
this.#actionEventContext.addEventListener(
UmbEntityUpdatedEvent.TYPE,
this.#onEntityUpdated as EventListener
);
});
}
#onEntityUpdated = (event: UmbEntityUpdatedEvent) => {
const eventUnique = event.getUnique();
const documentUnique = this.#documentContext?.getUnique();
// Check if this event is for our current document
if (eventUnique === documentUnique) {
// Optionally check if it's from this workspace instance
if (event.getEventUnique() === this.#workspaceEventUnique) {
console.log('Document was saved/published from this workspace!');
this.#onDocumentSaved();
}
}
};
#onDocumentSaved() {
// Your logic here - this is called after successful save/publish
console.log('Document saved successfully!');
}
override disconnectedCallback() {
super.disconnectedCallback();
// Clean up event listener
if (this.#actionEventContext) {
this.#actionEventContext.removeEventListener(
UmbEntityUpdatedEvent.TYPE,
this.#onEntityUpdated as EventListener
);
}
}
}
Initially when I worked on it, this wasn’t available yet. Then with Umbraco 16, I had tried to use this event because I saw it was added but there were some use cases where it didn’t work (I don’t quite remember what is was about again), so I didn’t use it. Could very well be that it now works as I expected it to, but I’ll have to check that again. Thanks for reminding me of it!