umbraco-tree
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUmbraco Tree
Umbraco Tree
What is it?
什么是Umbraco树形结构?
A tree in Umbraco is a hierarchical structure of nodes registered in the Backoffice extension registry. Trees display organized content hierarchies and can be rendered anywhere in the Backoffice using the element. They require a data source implementation to fetch root items, children, and ancestors.
<umb-tree />Umbraco中的树形结构是在后台扩展注册表中注册的节点分层结构。树形结构用于展示组织化的内容层级,可使用元素在后台的任意位置渲染。它们需要实现数据源来获取根项、子项和祖先项。
<umb-tree />Documentation
文档说明
Always fetch the latest docs before implementing:
- Main docs: https://docs.umbraco.com/umbraco-cms/customizing/extending-overview/extension-types/tree
- Sections & Trees: https://docs.umbraco.com/umbraco-cms/customizing/overview
- Foundation: https://docs.umbraco.com/umbraco-cms/customizing/foundation
- Extension Registry: https://docs.umbraco.com/umbraco-cms/customizing/extending-overview/extension-registry
在实现前请务必获取最新文档:
- 主文档:https://docs.umbraco.com/umbraco-cms/customizing/extending-overview/extension-types/tree
- 章节与树形结构:https://docs.umbraco.com/umbraco-cms/customizing/overview
- 基础框架:https://docs.umbraco.com/umbraco-cms/customizing/foundation
- 扩展注册表:https://docs.umbraco.com/umbraco-cms/customizing/extending-overview/extension-registry
CRITICAL: Tree + Workspace Integration
重要提示:树形结构与工作区的集成
Trees and workspaces are tightly coupled. When using tree items:
kind: 'default'-
Tree items REQUIRE workspaces - Clicking a tree item navigates to a workspace for that entity type. Without a workspace registered for the, clicking causes "forever loading"
entityType -
Workspaces must be- For proper tree item selection state and navigation between items of the same type, use
kind: 'routable'workspaces (notkind: 'routable')kind: 'default' -
Entity types link trees to workspaces - Thein your tree item data must match the
entityTypein your workspace manifestentityType
When implementing trees with clickable items, also reference the skill.
umbraco-workspace树形结构与工作区紧密耦合。当使用类型的树形结构项时:
kind: 'default'-
树形结构项必须关联工作区 - 点击树形结构项会导航至对应实体类型的工作区。如果未为注册工作区,点击后会出现“永久加载”的问题
entityType -
工作区必须设置为- 为了确保树形结构项的选中状态正常,以及同类型项之间的导航正常,请使用
kind: 'routable'类型的工作区(而非kind: 'routable')kind: 'default' -
实体类型关联树形结构与工作区 - 树形结构项数据中的必须与工作区清单中的
entityType匹配entityType
当实现可点击的树形结构项时,请同时参考技能文档。
umbraco-workspaceFile Structure
文件结构
Modern trees use 2-3 files:
my-tree/
├── manifest.ts # Registers repository and tree
├── tree.repository.ts # Repository + inline data source
└── types.ts # Type definitions (optional)现代树形结构实现通常使用2-3个文件:
my-tree/
├── manifest.ts # 注册仓库与树形结构
├── tree.repository.ts # 仓库 + 内联数据源
└── types.ts # 类型定义(可选)Reference Example
参考示例
The Umbraco source includes a working example:
Location:
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/tree/This example demonstrates a complete custom tree with data source, repository, and menu integration. Study this for production patterns.
Umbraco源码中包含一个可用示例:
位置:
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/tree/该示例展示了一个完整的自定义树形结构,包含数据源、仓库和菜单集成。请学习该示例以遵循生产环境的开发模式。
Related Foundation Skills
相关基础技能
If you need to explain these foundational concepts when implementing trees, reference these skills:
-
Repository Pattern: When implementing tree data sources, repositories, data fetching, or CRUD operations
- Reference skill:
umbraco-repository-pattern
- Reference skill:
-
Context API: When implementing repository contexts or explaining how repositories connect to UI components
- Reference skill:
umbraco-context-api
- Reference skill:
-
State Management: When implementing reactive tree updates, observables, or managing tree state
- Reference skill:
umbraco-state-management
- Reference skill:
如果在实现树形结构时需要解释这些基础概念,请参考以下技能文档:
-
仓库模式:当实现树形结构数据源、仓库、数据获取或CRUD操作时
- 参考技能:
umbraco-repository-pattern
- 参考技能:
-
Context API:当实现仓库上下文或解释仓库如何与UI组件连接时
- 参考技能:
umbraco-context-api
- 参考技能:
-
状态管理:当实现响应式树形结构更新、可观察对象或管理树形结构状态时
- 参考技能:
umbraco-state-management
- 参考技能:
Workflow
实现流程
- Fetch docs - Use WebFetch on the URLs above
- Ask questions - What data will the tree display? What repository will provide the data? Where will it appear? Will tree items be clickable?
- Generate files - Create minimal files based on latest docs
- ✅ Create: ,
manifest.ts(with inline data source)tree.repository.ts - ❌ Don't create: (deprecated), separate
tree.store.ts(can inline)tree.data-source.ts - Use the inline data source pattern shown in examples below
- ✅ Create:
- If clickable - Also create routable workspaces for each entity type (reference skill)
umbraco-workspace - Explain - Show what was created and how to test
- 获取文档 - 使用WebFetch访问上述URL
- 确认需求 - 树形结构将展示什么数据?由哪个仓库提供数据?它将出现在哪里?树形结构项是否可点击?
- 生成文件 - 根据最新文档创建最简文件
- ✅ 创建:、
manifest.ts(包含内联数据源)tree.repository.ts - ❌ 无需创建:(已废弃)、单独的
tree.store.ts(可内联实现)tree.data-source.ts - 使用如下示例所示的内联数据源模式
- ✅ 创建:
- 若支持点击 - 同时为每个实体类型创建可路由的工作区(参考技能文档)
umbraco-workspace - 说明实现 - 展示已创建的内容以及测试方法
Key Configuration Options
关键配置选项
hideTreeRoot on MenuItem (NOT on Tree)
在MenuItem上设置hideTreeRoot(而非在Tree上)
To show tree items at root level (without a parent folder), use on the menuItem manifest:
hideTreeRoot: truetypescript
// CORRECT - hideTreeRoot on menuItem
{
type: 'menuItem',
kind: 'tree',
alias: 'My.MenuItem.Tree',
meta: {
treeAlias: 'My.Tree',
menus: ['My.Menu'],
hideTreeRoot: true, // Shows items at root level
},
}
// WRONG - hideTreeRoot on tree (has no effect)
{
type: 'tree',
meta: {
hideTreeRoot: true, // This does nothing!
},
}若要在根级别展示树形结构项(无父文件夹),请在MenuItem清单中设置:
hideTreeRoot: truetypescript
// 正确方式 - 在MenuItem上设置hideTreeRoot
{
type: 'menuItem',
kind: 'tree',
alias: 'My.MenuItem.Tree',
meta: {
treeAlias: 'My.Tree',
menus: ['My.Menu'],
hideTreeRoot: true, // 在根级别展示项
},
}
// 错误方式 - 在Tree上设置hideTreeRoot(无效果)
{
type: 'tree',
meta: {
hideTreeRoot: true, // 此设置无效!
},
}Minimal Examples
最简示例
Tree Manifest
树形结构清单
typescript
export const manifests: UmbExtensionManifest[] = [
// Repository
{
type: 'repository',
alias: 'My.Tree.Repository',
name: 'My Tree Repository',
api: () => import('./tree.repository.js'),
},
// Tree
{
type: 'tree',
kind: 'default',
alias: 'My.Tree',
name: 'My Tree',
meta: {
repositoryAlias: 'My.Tree.Repository',
},
},
// Tree Items - use kind: 'default' when workspaces exist
{
type: 'treeItem',
kind: 'default',
alias: 'My.TreeItem',
name: 'My Tree Item',
forEntityTypes: ['my-entity'],
},
// MenuItem - hideTreeRoot here
{
type: 'menuItem',
kind: 'tree',
alias: 'My.MenuItem.Tree',
meta: {
treeAlias: 'My.Tree',
menus: ['My.Menu'],
hideTreeRoot: true,
},
},
];typescript
export const manifests: UmbExtensionManifest[] = [
// 仓库
{
type: 'repository',
alias: 'My.Tree.Repository',
name: 'My Tree Repository',
api: () => import('./tree.repository.js'),
},
// 树形结构
{
type: 'tree',
kind: 'default',
alias: 'My.Tree',
name: 'My Tree',
meta: {
repositoryAlias: 'My.Tree.Repository',
},
},
// 树形结构项 - 当存在工作区时使用kind: 'default'
{
type: 'treeItem',
kind: 'default',
alias: 'My.TreeItem',
name: 'My Tree Item',
forEntityTypes: ['my-entity'],
},
// MenuItem - 在此处设置hideTreeRoot
{
type: 'menuItem',
kind: 'tree',
alias: 'My.MenuItem.Tree',
meta: {
treeAlias: 'My.Tree',
menus: ['My.Menu'],
hideTreeRoot: true,
},
},
];Repository with Inline Data Source (tree.repository.ts)
包含内联数据源的仓库(tree.repository.ts)
Modern simplified pattern - everything in one file:
typescript
import { UmbTreeRepositoryBase, UmbTreeServerDataSourceBase } from '@umbraco-cms/backoffice/tree';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { MyTreeItemModel, MyTreeRootModel } from './types.js';
import { MY_ROOT_ENTITY_TYPE, MY_ENTITY_TYPE } from './entity.js';
// Data source as simple class using helper base
class MyTreeDataSource extends UmbTreeServerDataSourceBase<any, MyTreeItemModel> {
constructor(host: UmbControllerHost) {
super(host, {
getRootItems: async (args) => {
// Fetch from API or return mock data
const items: MyTreeItemModel[] = [
{
unique: 'item-1',
parent: { unique: null, entityType: MY_ROOT_ENTITY_TYPE },
entityType: MY_ENTITY_TYPE,
name: 'Item 1',
hasChildren: false,
isFolder: false,
icon: 'icon-document',
},
];
return { data: { items, total: items.length } };
},
getChildrenOf: async (args) => {
// Return children for parent
return { data: { items: [], total: 0 } };
},
getAncestorsOf: async (args) => {
// Return ancestor path
return { data: [] };
},
mapper: (item: any) => item, // Identity mapper for this example
});
}
}
// Repository
export class MyTreeRepository
extends UmbTreeRepositoryBase<MyTreeItemModel, MyTreeRootModel>
implements UmbApi
{
constructor(host: UmbControllerHost) {
super(host, MyTreeDataSource);
}
async requestTreeRoot() {
const data: MyTreeRootModel = {
unique: null,
entityType: MY_ROOT_ENTITY_TYPE,
name: 'My Tree',
hasChildren: true,
isFolder: true,
};
return { data };
}
}
export { MyTreeRepository as api };Why this is simpler:
- ✅ One file instead of two
- ✅ Uses helper (pass functions, not methods)
UmbTreeServerDataSourceBase - ✅ Data fetching logic inline (easier to understand)
- ✅ No separate data source file to maintain
For complex trees with API calls, you can still separate into different files, but it's not required.
That's it! Always fetch fresh docs, keep examples minimal, generate complete working code.
现代简化模式 - 所有内容在一个文件中:
typescript
import { UmbTreeRepositoryBase, UmbTreeServerDataSourceBase } from '@umbraco-cms/backoffice/tree';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { MyTreeItemModel, MyTreeRootModel } from './types.js';
import { MY_ROOT_ENTITY_TYPE, MY_ENTITY_TYPE } from './entity.js';
// Data source as simple class using helper base
class MyTreeDataSource extends UmbTreeServerDataSourceBase<any, MyTreeItemModel> {
constructor(host: UmbControllerHost) {
super(host, {
getRootItems: async (args) => {
// Fetch from API or return mock data
const items: MyTreeItemModel[] = [
{
unique: 'item-1',
parent: { unique: null, entityType: MY_ROOT_ENTITY_TYPE },
entityType: MY_ENTITY_TYPE,
name: 'Item 1',
hasChildren: false,
isFolder: false,
icon: 'icon-document',
},
];
return { data: { items, total: items.length } };
},
getChildrenOf: async (args) => {
// Return children for parent
return { data: { items: [], total: 0 } };
},
getAncestorsOf: async (args) => {
// Return ancestor path
return { data: [] };
},
mapper: (item: any) => item, // Identity mapper for this example
});
}
}
// Repository
export class MyTreeRepository
extends UmbTreeRepositoryBase<MyTreeItemModel, MyTreeRootModel>
implements UmbApi
{
constructor(host: UmbControllerHost) {
super(host, MyTreeDataSource);
}
async requestTreeRoot() {
const data: MyTreeRootModel = {
unique: null,
entityType: MY_ROOT_ENTITY_TYPE,
name: 'My Tree',
hasChildren: true,
isFolder: true,
};
return { data };
}
}
export { MyTreeRepository as api };为何此模式更简洁:
- ✅ 一个文件而非两个
- ✅ 使用助手类(传入函数而非方法)
UmbTreeServerDataSourceBase - ✅ 数据获取逻辑内联(更易理解)
- ✅ 无需维护单独的数据源文件
对于包含API调用的复杂树形结构,你仍可将代码拆分到不同文件中,但这并非必须。
就是这样!请始终获取最新文档,保持示例最简,生成可正常运行的完整代码。