Appearance
Build a Content Hierarchy
This recipe explains how to construct a documentation content hierarchy by using Tree View, the M2O alias for creating recursive relationships.
Author: Eron Powell
Directus Version: 9.18.1
Explanation
In documentation, it is incredibly common (if not the standard) to have a hierarchy of content or data: a parent page, with child pages.
If you're working with markdown files or an SSG, content hierarchies are created easily with files and folders. If your content is in a database, you can create a hierarchy of nested items with a recursive O2M relationship, which is the focus of this recipe.
In Directus, there is a special O2M alias field called Tree View, which makes it easier to create and manage this type of recursive relationship.
For this recipe, we'll use Tree View to build a simple content hierarchy for documentation:
App Overview
Configuration
Flows
Data Model
Collections
Fields
Here's the general outline of what's to come:
- Use Tree View to create a recursive relationship in a collection called
docs
. - Add
parent
<->child
items to the collection, using that recursive relationship. - Give the public role read permissions on
docs
, so users can access them.
At the end, in Final Tips, we'll point out some additional steps you can take to make your content hierarchy easier to work with in a frontend.
The Recipe
Requirements
You'll need to be familiar with permissions.
- Create a collection and add fields as follows:
docs
- id
- title (a STRING input field)
- parent_id (an integer input field)
- children (a Tree View O2M alias field, configured with docs.parent_id as the foreign key and the O2M Interface)
- body (optional: a text input field with a Markdown interface)
Configure the public role with read permissions for the
docs
collection.Create nested items on the
docs
collection as desired:- To create top-level parent items, create an item as usual.
- To add a nested child, edit the parent item and click Create New or Add Existing.
Optional: manually sort items into desired order.
Final Tips
Now that your nested content is created, you'll need to access and use it in the frontend. Here are two key points to consider.
Fetch Nested Data
A basic api call to get items, such as /items/docs/
, returns flat data:
Toggle open to view flat data.
json
{
"data": [
{
"id": 1,
"title": "App Overview",
"parent_id": null,
"body": "# App Overview\n\nGeneral info about the app.",
"sort": 1,
"children": []
},
{
"id": 2,
"title": "Config Overview",
"parent_id": null,
"body": "# Config Overview\n\nThere are many config options available",
"sort": 2,
"children": [3]
},
{
"id": 3,
"title": "Data Model",
"parent_id": 2,
"body": "# How the Data Model Works\n\n",
"sort": 3,
"children": [4, 5]
},
{
"id": 4,
"title": "Fields",
"parent_id": 3,
"body": "# How Fields Work",
"sort": 4,
"children": []
},
{
"id": 5,
"title": "Collections",
"parent_id": 3,
"body": "# Collections\n\nThey're data tables.",
"sort": 5,
"children": []
}
]
}
To fix this, you could write an algorithm that iterates through each item in the data array and re-nests it on the frontend. But that may be a bit resource intensive, depending on your collection's data. Alternatively, we can add query parameters on the Directus API call to nest it for us. In this case, we could make an api call like this:
/items/docs?
filter[parent_id][_null]=true
&fields=id,title,body,sort,
children.id,children.body,children.title,children.sort,
children.children.id,children.children.title,children.children.body,children.children.sort, children.children.children
Which will return data properly nested as seen here:
Toggle open to view nested data
json
{
"data": [
{
"id": 1,
"name": "App Overview",
"body": "# App Overview\n\nGeneral info about the app.",
"sort": 1,
"nest": []
},
{
"id": 2,
"name": "Config Overview",
"body": "# Config Overview\n\nMany config options available",
"sort": 2,
"nest": [
{
"id": 3,
"body": "# How the Data Model Works",
"name": "Data Model",
"sort": 3,
"nest": [
{
"id": 4,
"name": "Fields",
"body": "# How Fields Work",
"sort": 4,
"nest": []
},
{
"id": 5,
"name": "Collections",
"body": "# Collections\n\nThey're data tables.",
"sort": 5,
"nest": []
}
]
}
]
}
]
}
In the call above, the filter[parent_id][_null]=true
query parameter serves to filter non-parents at the first level. The fields query parameters let us extract specific fields, including nested relational fields. There are two key things to note with this type of query:
- We knew our content hierarchy was three levels deep. You'll also need to be aware of (and account for) the depth of your content hierarchy and make the api call accordingly.
- The
parent_id
foreign key field was not added, as it would re-nest parent item data under its own child items, which is duplicate and useless.
Create Relative Paths
Nested content by itself is nice, but it would be helpful if we could create a path for each item, for routing in frontend navigation. To do this, you could use flows to create a path dynamically. Here's one general approach:
- Add a
path
field to thedocs
collection and configure it so edits are turned off on the item detail page. - Configure a flow with an
Event Hook
trigger, which runs onitems.create
anditems.update
for thedocs
collection. - Add operations to the flow to read the parent's path of the item being created or updated, append the item's title, write the new path into the present item's
path
field value, then update the paths of its children (if it has any children).
TIP
In this recipe, for the sake of a clean visual example, we created a body
field directly on the docs
collection. However, you may find it more user-friendly to keep the markdown content in another relationally-linked collection, then just use Tree View to store the path and/or keep track of the content hierarchy.