Creating a custom condition for Umbraco Bellissima aka V14+

In this blog post I will show you how to create a custom condition for your Umbraco manifests. In this example I will show you how we can create a custom condition to show or hide our workspace view (aka previously known as Content/Context apps in previous versions of Umbraco)

The problem

In previous versions of Umbraco up to the current release of V13 we have been able to register dashboards and other UI components for the Backoffice with C# and allowing us to specify custom logic on when these should be loaded and visible to logged in users. With this new evolution of the Backoffice all UI extensions are registered solely in JavaScript, hence we need to use a new extension point, bring in a new extension point called Conditions.

What is a manifest condition?

A manifest condition is code that determines and evaluates custom logic to determine if another manifest extension point of Umbraco V14 should load or not.

Umbraco V14 ships with built in ones such as (This is not an exhaustive list)

  • Umb.Condition.SectionAlias
    Matches against a section alias, allowing you to load other Umbraco extension points only if the current visible/loaded section alias matches
  • Umb.Condition.WorkspaceAlias
    Similar to the SectionAlias condition above, this checks to see if the current workspace you are viewing matches the alias. For example you could be wanting to load extensions inside the Log Viewer found inside the Settings section by matching with the alias of Umb.Workspace.LogViewer
  • Umb.Condition.SectionUserPermission
    This condition, checks if the current logged in user is allowed to have access to a section specified as part of the condition.

To see the full list of conditions available to use, you are able to browse to the Settings Section and navigate to the Extensions tree item to view all loaded extensions. This workspace has a handy filter to select the extension type Condition.

An Example

You can use the Umb.Condition.SectionAlias condition above in a manifest for a dashboard to help decide what sections the dashboard is visible in.

{
  "name": "My.WelcomePackage",
  "version": "0.1.0",
  "extensions": [
    {
      "type": "dashboard",
      "alias": "my.welcome.dashboard",
      "name": "My Welcome Dashboard",
      "js": "/App_Plugins/welcome-dashboard/dist/welcome-dashboard.js",
      "elementName": "my-welcome-dashboard",
      "weight": -1,
      "meta": {
        "label": "Welcome Dashboard",
        "pathname": "welcome-dashboard"
      },
      "conditions": [
        {
          "alias": "Umb.Condition.SectionAlias",
          "match": "Umb.Section.Content"
        }
      ]
    }
  ]
}

What are we going to build?

Below is a condition that uses the Document Workspace Context, when you are viewing/editing a content node and is observing the value of the templateId property.

We use a custom configuration for our condition to have a property called hasTemplateSet which is a Boolean to allow the condition to be configured and the logic inversed to check if the current document does or does not have a template set.

For a condition to pass, we need to set the property permitted to true, that is coming from UmbConditionBase and UmbExtensionCondition

Show me the code

import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbConditionConfigBase, UmbConditionControllerArguments, UmbExtensionCondition } from "@umbraco-cms/backoffice/extension-api";
import { UMB_DOCUMENT_WORKSPACE_CONTEXT  } from '@umbraco-cms/backoffice/document';
import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';

export class TemplateSetCondition extends UmbConditionBase<TemplateSetConditionConfig> implements UmbExtensionCondition
{
    config: TemplateSetConditionConfig;

    constructor(host: UmbControllerHost, args: UmbConditionControllerArguments<TemplateSetConditionConfig>) {
        super(host, args);
        
        this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT , (workspaceCtx) => {

            // Observe the template ID value in the workspace context
            // So that when it changes value we can re-evaluate the condition
            this.observe(workspaceCtx.templateId, (templateId) => {

                // No template set === null
                // Tempate set we get a GUID back

                // Config says it passes if template IS set (aka NOT null)
                if(this.config.hasTemplateSet && templateId !== null) {
                    this.permitted = true;
                    return;
                }

                // Config says it passes if template is NOT set (aka IS null)
                if(this.config.hasTemplateSet === false && templateId === null) {
                    this.permitted = true;
                    return;
                }

                this.permitted = false;
            });
		});
    }
}

export type TemplateSetConditionConfig = UmbConditionConfigBase<'MyThing.Condition.TemplateSet'> & {
    /**
     * HasTemplateSet
	 * Decide if the current document should have a template set or not
	 *
	 * @example
	 * true
	 */
    hasTemplateSet: boolean;
};

Consuming our Condition

Now we have created our condition and registered it with Umbraco, either with an umbraco-package.json or using an entrypoint file and registering the extension with extensionRegistry.registerMany()

We need a way to tell our WorkspaceView aka the Content/Context app in our content node to be only visible if it has a template set.

import { ManifestWorkspaceView } from "@umbraco-cms/backoffice/extension-registry";
import { TemplateSetConditionConfig } from "../Conditions/mything.condition.templateset.js";

const workspaceView: ManifestWorkspaceView = {
    alias: 'mything.workspaceview',
    name: 'My Thing Workspace View',
    type: 'workspaceView',
    js: () => import('./mything.workspaceview.element.js'),
    weight: 190,
    meta: {
        icon: 'icon-people',
        label: 'My Thing',
        pathname: 'my-thing'
    },
    conditions: [
        {
            alias: 'Umb.Condition.WorkspaceAlias',
            match: 'Umb.Workspace.Document',
        },
        {
            alias: 'MyThing.Condition.TemplateSet',
            hasTemplateSet: true
        } as TemplateSetConditionConfig
    ],
}
export const manifests = [workspaceView];

Summary

I hope this has inspired you to create your own custom conditions for the upcoming Umbraco V14 and help understand how you will add custom logic to show/hide your new Umbraco extension points without the concept of C# to register UI with business logic to control when they show.

Quick Snippet: Toggle Tree Navigation in Umbraco Backoffice using JavaScript

Hello,
Here is a very quick blog post on how to toggle the tree navigation in the Umbraco 7 (aka Belle) back office using JavaScript with some AngularJS services that the Umbraco core team has given to us to use.

First of all you may be wondering why you would want to toggle the display of the tree navigation, considering that this is the main & primary navigation when using the Umbraco backoffice, however on a project I am doing at work a new property editor that is being developed requires more screen real estate and toggling the tree navigation allows the content editor to see & use this property editor a lot easier, as it involves plotting objects on a canvas. So as much space as possible is useful in this case.

So enough of the why and I will get straight down to the very simple JavaScript code snippet that allows us to do this:

angular.module('umbraco').controller('demo.toggler', function($scope, appState, eventsService) {
  //Create an Angular JS controller and inject Umbraco services appState & eventsService

  //Button Click - ToggleUmbracoNavigation
  $scope.toggleUmbracoNavigation = function() {

    //Get the current state of showNavigation
    var currentNavigationState = appState.getGlobalState('showNavigation');

    //console.log("currentNavigationState", currentNavigationState);
    //console.log("Inverse of currentNavigationState", !currentNavigationState);

    //Toggle the tree visibility
    appState.setGlobalState("showNavigation", !currentNavigationState);
  }

  //The eventService allows us to easily listen for any events that the Umbraco applciation fires
  //Let's listen for globalState changes...
  eventsService.on("appState.globalState.changed", function (e, args) {
    //console.log("appState.globalState.changed (args)", args);

    if (args.key === "showNavigation") {
      //console.log("showNavigation value", args.key, args.value);

      //If false (So hiding navigation)
      if(!args.value) {
        //Set css left position to 80px (width of appBar)
        document.getElementById("contentwrapper").style.left = "80px";
      }
      else {
        //Remove the CSS we set so default CSS of Umbraco kicks in
        document.getElementById("contentwrapper").style.left = "";
      }
    }
  });

});

Hopefully the JavaScript is commented enough and is easy to follow, but the ensence of this is that we have a button or anchor link in our view with the Angular directive of ng-click applied to it calling out method, i.e ng-click=”toggleUmbracoNavigation()” this toggles the value stored in Umbraco’s globabl settings in this case these values & settings are stored in an AngularJS service called appState, that allows us to get & set values stored in here. I recommend you look at the source code on GitHub to see what other values are stored in appState for you to use in your customisations to the Umbraco back office.

I would be interested to hear what other people think of this and should this tree toggle become part of the core by doing a Pull Request and if was to become part of the main UI, where should the button go or by double clicking the grey area with the application/section icons toggle this action?

Well let me know by leaving your thoughts in the comments.

Thanks,
Warren 🙂