Tree view
A tree view represents the site content within a controlled, hierarchical view in the content editor.
After configuring a content source, Visual Editor automatically lists models in the content editor, separated by model type (only page
and data
models are shown).
A tree view can be added using the treeViews
property, which provides the ability to create a customized representation of the site content.
# Tree node types
Nodes can be one of two types:
- Text Node: A label that groups multiple documents together.
- Document Node: A node that represents a document, which can be edited from the document list on the right side of the screen.
See the reference for how the objects differ between the node types.
# Build a tree
Building a tree typically involves fetching and filtering documents, then building a recursive list of TreeViewNode
objects.
# Display children
As nodes in the tree are highlighted, their children are displayed in the panel to the right of the tree, providing the option to edit document nodes.
Note that all root nodes must have children
, otherwise the tree will not be rendered.
# Edit documents
Document nodes can be edited by clicking the Edit button when they appear in the panel on the right (when their parents is the active tree item).
# Fetch documents
The typical pattern for building a tree begins with fetching content from the site. This is usually done via the getDocuments()
method provided to the treeViews
within the options parameter.
export default defineStackbitConfig({
treeViews: async options => {
const allPages = options
.getDocuments()
.filter(document => document.modelName === "Page");
//
// Build the tree ...
//
}
});
# Examples
Here are a few very simple examples to get started. Both were built using Git CMS, but can be applied to any content source.
# List of page documents
This example creates a root node called "Site Pages" and lists all document of model Page
under it.
//stackbit.config.ts
import {
defineStackbitConfig,
DocumentWithSource,
TreeViewNode
} from "@stackbit/types";
export default defineStackbitConfig({
stackbitVersion: "~0.6.0",
contentSources: [
/* ... */
],
treeViews: async ({ getDocuments }) => {
const children: TreeViewNode["children"] = getPages(getDocuments()).map(
document => ({
document,
label: getFieldValue(document, "title")
})
);
return [
{ label: "Site Pages", children, stableId: "pages-tree" }
] as TreeViewNode[];
}
});
function getFieldValue(page: DocumentWithSource, field: string) {
const fieldObject = page.fields[field];
if (!fieldObject || !("value" in fieldObject)) return;
return fieldObject.value;
}
function getPages(documents: DocumentWithSource[]) {
return documents.filter(document => document.modelName === "Page");
}
# List pages by URL path
Here's a more complex example, which builds a tree based on a nested URL structure, similar to how the sitemap navigator behaves.
//stackbit.config.ts
import {
defineStackbitConfig,
DocumentWithSource,
TreeViewNode
} from "@stackbit/types";
export default defineStackbitConfig({
stackbitVersion: "~0.6.0",
ssgName: "nextjs",
nodeVersion: "16",
contentSources: [
/* ... */
],
treeViews: async ({ getDocuments }) => {
type UrlTree = {
[key: string]: UrlTreeNode;
};
type UrlTreeNode = {
id: string;
document?: DocumentWithSource;
slug?: string;
children?: UrlTree;
};
type ReducedUrlTree = {
tree: UrlTree;
urlPath?: string;
};
let urlTree: UrlTree = {};
getPages(getDocuments()).forEach(page => {
const urlParts = getUrlParts(page);
let docNode;
urlParts.reduce<ReducedUrlTree>(
(acc, part): ReducedUrlTree => {
const id = acc.urlPath ? `${acc.urlPath}__${part}` : part;
if (!acc.tree[part]) acc.tree[part] = { id };
if (!acc.tree[part].children) acc.tree[part].children = {};
docNode = acc.tree[part];
return { tree: acc.tree[part].children || {}, urlPath: id };
},
{ tree: urlTree }
);
docNode.document = page;
docNode.slug = urlParts[urlParts.length - 1];
});
function pagesTree(tree?: UrlTree): TreeViewNode[] {
if (!tree || Object.keys(tree).length === 0) return [];
return Object.entries(tree)
.map(([slug, node]) => {
const children = pagesTree(node.children);
const label = slug === "/" ? "Home Page" : `/${slug}`;
if (node.document) {
return { document: node.document, children, label };
}
return { label, children, stableId: node.id };
})
.filter(Boolean) as TreeViewNode[];
}
const tree: TreeViewNode[] = [
{
label: "Site Pages",
children: pagesTree(urlTree),
stableId: "pages-tree"
}
];
return tree;
}
});
function getFieldValue(page: DocumentWithSource, field: string) {
const fieldObject = page.fields[field];
if (!fieldObject || !("value" in fieldObject)) return;
return fieldObject.value;
}
function getPages(documents: DocumentWithSource[]) {
return documents.filter(document => document.modelName === "Page");
}
function getUrlParts(page: DocumentWithSource) {
const urlParts = `/${getFieldValue(page, "_filePath_slug")}`
.replace(/^[\/]+/, "/")
.replace(/\/index$/, "")
.split("/")
.filter(Boolean);
if (urlParts.length === 0) urlParts.push("/");
return urlParts;
}
Did you find this doc useful?
Your feedback helps us improve our docs.