DEV Community

Matt Brailsford
Matt Brailsford

Posted on

Creating your own UI extension points in Umbraco v14 - Part 1: The Basics

In this mini series I'll take a look at a progressively more advanced example of creating your own UI extensions in Umbraco v14.

I'll loosely base this on a feature in Umbraco Commerce where we display a series of "Quick Action" buttons in a few of the editor views to provide instant access to important features.

Quick Action Buttons

We provide a number of quick actions out of the box, but we also want to provide a means fore developers to add their own based on their customers needs.

NB As you might expect, this is considered a fairly advanced topic for Umbraco v14 and so in the name of brevity, I assume you know a lot of fundamentals of developing for v14. If you are just getting started, you might want to go do some basic learning first before tackling this subject.

In this first post we'll take a look at the most basic implementation of a UI extension point for registering our buttons.

Definition

The foundation of extending the UI in Umbraco v14 lies in defining custom manifests.

Let's start by defining a basic extension manifest interface:

export interface ManifestQuickAction extends ManifestBase {
    type: 'quickAction';
    meta: MetaQuickAction;
}

export interface MetaQuickAction  {
    label: string;
    look?: 'primary' | 'secondary';
}
Enter fullscreen mode Exit fullscreen mode

A manifest consists of two parts. The manifest definition and a meta data object for defining custom properties we want to capture.

In this example we define a ManifestQuickAction manifest and give it a type of "quickAction". This will be how we identify our quick actions and will be used later to look up all manifests with this type.

We also define a meta property of type MetaQuickAction which at this point simply holds a label and optional look attribute which will control the text and style of our button.

In addition to the properties we've defined, we also get a number of standard manifest properties from the ManifestBase interface.

export interface ManifestBase {
    type: string;
    alias: string;
    kind?: unknown;
    name: string;
    weight?: number;
}
Enter fullscreen mode Exit fullscreen mode

These are standard for all manifests and mostly provide a means of identifying an individual manifest definition and controlling it's position.

Implementation

With our manifest interface defined, we can now define our manifest implementations.

export const quickActionManifests: ManifestQuickAction[] = [
    {
        type: 'quickAction',
        alias: 'Mb.QuickAction.SendEmail',
        name: 'Send Email Quick Action',
        weight: 200,
        meta: {
            label: "Send Email",
            look: "primary"
        }
    },
    {
        type: 'quickAction',
        alias: 'Mb.QuickAction.ChangeStatus',
        name: 'Change Status Quick Action',
        weight: 100,
        meta: {
            label: "Change Status"
        }
    }
]
Enter fullscreen mode Exit fullscreen mode

Registration

With our manifests defined we then register them with the extension registry from within our onInit entry point method.

export const onInit: UmbEntryPointOnInit = (host, extensionRegistry) => {
    extensionRegistry.registerMany(quickActionManifests);
};
Enter fullscreen mode Exit fullscreen mode

Rendering

With all our manifests now registered, the next step is to render our buttons for each manifest.

Within the constructor of our workspace element we'll first lookup our manifest instances by querying the extensions registry.

constructor(host: UmbControllerHost) {
    super(host);
    const quickActionsObservable = umbExtensionsRegistry.byType<string, ManifestQuickAction>('quickAction');
}
Enter fullscreen mode Exit fullscreen mode

As with most things in Umbraco v14, what we get back from our query is an observable and so we will want to observe the collection and store it's value in a local state variable.

@state() 
_quickActions: ManifestQuickAction[] = [];

constructor(host: UmbControllerHost) {
    super(host);
    const quickActionsObservable = umbExtensionsRegistry.byType<string, ManifestQuickAction>('quickAction'); 
    this.observe(quickActionsObservable, (manifests) => {
        this._quickActions = manifests
    })
}
Enter fullscreen mode Exit fullscreen mode

With access to our actions, we can then output our buttons in our workspace components render method. For now we'll just output the actions label to the console when the button is clicked.

render() {
   return html`<uui-box headline="Actions">
       ${repeat(this._quickActions,
           (qa) => qa.alias,
           (qa) => html`<uui-button 
                look=${this.manifest.meta.look ?? 'secondary'}
                @click=${() => console.log(qa.meta.label)}>
                ${qa.meta.label}
           </uui-button>`
        )}
   </uui-box>`
}
Enter fullscreen mode Exit fullscreen mode

What's next?

In this post I've shared the most basic example of creating a custom UI extension. If in your use case all you need is to allow developers to provide some level of configuration that you will process and handle in a predefined way, then this should cover all you need.

If as in our example however, your configuration has a 1-to-1 relation to a rendered element (ie, our quick action button) then we can actually go a little bit further, as whilst we have allowed developers to define their own quick action buttons, the implementation of what a quick action is and does is really rather restrictive.

In the next few posts in this series I'll take a look at how we can make this even more flexible by allowing developers to change the buttons behavior when clicked and also how they could swap out the button component entirely.

Until next time 👋

Top comments (0)