Loading on demand
Instead of sending the whole tree to the browser, you can send only the top level and fetch each subtree when the user opens it.
This tree starts with two nodes and fetches every folder when you open it. Watch the event log, or the network tab:
Click a node, open a folder, or drag something.
new TreeElement({
dataUrl: (node) => (node ? `/demo/node-${node.id}.json` : "/demo/root.json"),
htmlElement,
});INFO
The urls above are static json files in docs/public/demo/, generated by docs/scripts/makeDemoFixtures.mjs. They stand in for a server that answers ?node=<id>.
Server side
Mark the nodes that have children on the server with load_on_demand:
[
{ "name": "node1", "id": 1, "load_on_demand": true },
{ "name": "node2", "id": 2 }
]A node with load_on_demand: true is rendered as a closed folder even though it has no children yet. When the user opens it, the tree fetches its children from dataUrl with the node's id in a node query parameter:
GET /my-tree/?node=1&_=1735689600000The node parameter is added even when dataUrl is a function, so a url that already identifies the node gets it twice — harmless, but worth knowing when you match on the query. The _ parameter is a timestamp the tree adds to every request to defeat caching; ignore it.
The response is a json array of the children of that node — the same format as the top level, and children may be marked load_on_demand again:
[
{ "name": "child1", "id": 3 },
{ "name": "child2", "id": 4, "load_on_demand": true }
]After the children arrive, load_on_demand is cleared on the node and the folder opens.
Client side
Set dataUrl and let the tree do the requests:
new TreeElement({
dataUrl: "/my-tree/",
htmlElement,
});Use a function when the url depends on the node — it is called with the node that is being loaded, and with no argument for the initial load:
new TreeElement({
dataUrl: (node) => (node ? `/my-tree/${node.id}/children/` : "/my-tree/"),
htmlElement,
});Loading indicator
While a request is in flight, the node gets the tree-element-loading class and its is_loading property is true. Its treeitem gets aria-busy="true", which tells screen readers that its children are loading. When the request fails, the node stays closed; opening it again retries. The default stylesheet does not draw a spinner, so add one yourself:
.tree-element-loading > .tree-element-element .tree-element-title {
background: url(spinner.gif) right center no-repeat;
padding-right: 20px;
}You can also react in javascript, with the tree.loading_data and tree.loaded_data events:
element.addEventListener("tree.loading_data", (e) => {
const { node } = e.detail;
console.log(node ? node.name : "the tree", "is loading");
});
element.addEventListener("tree.loaded_data", (e) => {
const { node } = e.detail;
console.log(node ? node.name : "the tree", "is done");
});Loading a subtree yourself
loadDataFromUrl fetches into a node on demand, and returns a promise that is resolved when the data has arrived:
const node = tree.getNodeById(1);
await tree.loadDataFromUrl("/my-tree/?node=1", node);
tree.openNode(node);loadData does the same with data you already have:
tree.loadData([{ name: "child1", id: 3 }], node);Saving state
Saving state works with load-on-demand: nodes that were open in the saved state but not yet loaded are fetched, and the selected node is passed to the server as a selected_node query parameter on the initial request, so it can send back the branches needed to reveal it.