Table of Contents
Some Thoughts
There are several methods to create custom trees. One approach is using a full Tree structure, which involves numerous components like ManifestTree
, ManifestTreeItem
, ManifestTreeStore
, DataSource
, Repository
, and many more files.
However, I find this approach overly complex and unnecessary for small, simple trees.
For a basic tree with, I prefer using a menu with custom-rendered menu items using a custom Lit element, as I will demonstrate in this example.
Building Custom Menu Item Components
Rendering Menu Items with Umbraco's UI Menu Item Component
To render your menu items in Umbraco, you can make use of the powerful Umbraco UI Menu Item component. This component allows you to easily create nested menu structures with just a few lines of code.
Example:
<uui-menu-item label="Menu Item 1" has-children>
<uui-menu-item label="Nested Menu Item 1"></uui-menu-item>
<uui-menu-item label="Nested Menu Item 2"></uui-menu-item>
</uui-menu-item>
Example: Fetching and Rendering a Dynamic Tree
In this example, the entire tree is fetched when the component is rendered. Once the data is loaded, we put the items in a @state()
. This will trigger a re-render of the tree because it is a reactive property.
With the retrieved data, I dynamically render nested menu items, each with relevant icons. The core functionality of the tree such as opening, closing, and indenting nodes comes from the menu item component itself.
To display the caret icon indicating nested items, you can set the has-children
attribute dynamically like this: ?has-children=${bool}
.
menu-items.ts:
import { UmbMenuItemElement } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { html, TemplateResult } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { FormXTreeItemResponseModel, FormXTreeResource } from '../../../api';
const elementName = 'formx-menu-item';
@customElement(elementName)
class FormXMenuItems extends UmbLitElement implements UmbMenuItemElement {
@state()
private _items: FormXTreeItemResponseModel[] = []; // Store fetched items
@state()
private _loading: boolean = true; // Track loading state
@state()
private _error: string | null = null; // Track any errors
constructor() {
super();
this.fetchInitialItems(); // Start fetching on component load
}
// Fetch initial items
async fetchInitialItems() {
try {
this._loading = true;
this._items = ((await FormXTreeResource.getFormxApiV1Tree()).items); // Fetch root-level items
} catch (e) {
this._error = 'Error fetching items';
} finally {
this._loading = false;
}
}
// Render items
renderItems(items: FormXTreeItemResponseModel[]): TemplateResult {
return html`
${items.map(element => html`
<uui-menu-item label="${element.name}" ?has-children=${element.hasChildren}>
${element.type === 1
? html`<uui-icon slot="icon" name="icon-folder"></uui-icon>`
: html`<uui-icon slot="icon" name="icon-autofill"></uui-icon>`}
${element.hasChildren ? this.renderItems(element.children) : ''}
</uui-menu-item>
`)}
`;
}
// Main render function
render() {
if (this._loading) {
return html`<uui-loader></uui-loader>`;
}
if (this._error) {
return html`<uui-menu-item active disabled label="Could not load form tree!">
</uui-menu-item>`;
}
// Render items if loading is done and no error occurred
return html`${this.renderItems(this._items)}`;
}
}
export { FormXMenuItems as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: FormXMenuItems;
}
}
The result will be a menu that looks like the following:
Manifests
For this setup, you'll need three key manifests that work together to render a tree using a custom menu.
Let’s start by defining a reusable alias:
const FORMX_MENU_ALIAS = "Our.Umbraco.FormX.menu";
Defining the Tree Space with ManifestSectionSidebarApp
The first manifest defines the space where your tree will appear. This is achieved by creating a ManifestSectionSidebarApp
. We can connect the menu to the sidebar using the menu alias.
const sidebarApps: Array<ManifestSectionSidebarApp> = [
{
type: 'sectionSidebarApp',
kind: 'menuWithEntityActions',
alias: 'Our.Umbraco.FormX.sidebar',
name: 'FormX Sidebar App',
meta: {
label: "Forms",
menu: FORMX_MENU_ALIAS
},
conditions: [
{
alias: "Umb.Condition.SectionAlias",
match: "Our.Umbraco.FormX.section"
}
]
}
];
Defining the Menu with ManifestMenu
Next, you’ll define the actual menu where your items will be rendered. For this we can use a ManifestMenu
.
const menuManifest: Array<ManifestMenu> = [
{
type: 'menu',
alias: FORMX_MENU_ALIAS,
name: 'FormX Menu'
}
];
Linking the Custom Elements with ManifestMenuItem
Lastly, we need to link our custom elements that fetch and render your menu items. This is where the ManifestMenuItem
comes in.
const menuItemManifest: Array<ManifestMenuItem> = [
{
type: 'menuItem',
alias: 'formx-menu-items',
name: 'FormX Menu Items',
meta: {
label: 'FormX',
menus: [FORMX_MENU_ALIAS]
},
element: () => import('./menu-items.ts')
}
];
Final Result
Once the SectionSidebarApp
is linked to our custom section, the tree will be rendered with the specified label and custom elements.
Top comments (2)
Hi, thanks for sharing! The official docs are sadly VERY bad wrt extending the backoffice in v14 so posts like this are really helpful. Let's say you wanted to navigate to a view showing details about a form, how would you do this? Would that be a section view?
Hi @kennethsolberg,
I'm glad you found it helpful!
Yes, for adding more details about a form, you can definitely use a section view.
I actually had a post on this topic in draft, which I've just published. You can check it out here.