body {
margin: 0;
padding: 16px;
font-size: 16px;
font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
}
.tree {
gap: 8px;
display: grid;
grid-auto-rows: max-content;
&-item {
outline: 1px solid #333333;
&:hover {
outline: 2px solid rgb(65 105 225);
}
&__content {
padding: 8px;
&:hover {
background-color: rgb(65 105 225 / 20%);
box-shadow: inset 0 0 0 4px #ffffff;
}
}
&__list {
margin: 0;
padding: 0;
list-style: none;
}
&__inner {
padding-left: 24px;
gap: 8px;
display: grid;
grid-auto-rows: max-content;
}
}
}
View Compiled
interface Item {
id: string;
parentId: null | string;
categoriesIds: string[];
title: string;
userId: string;
}
const itemList: Item[] = [
{
id: "i1",
parentId: null,
categoriesIds: ["c8"],
title: "rubber",
userId: "u81"
},
{
id: "i2",
parentId: null,
categoriesIds: ["c2"],
title: "tragedy",
userId: "u76"
},
{
id: "i3",
parentId: "i2",
categoriesIds: [],
title: "ease",
userId: "u81"
},
{
id: "i4",
parentId: "i3",
categoriesIds: [],
title: "round",
userId: "u65"
},
{
id: "i5",
parentId: "i4",
categoriesIds: ["c3"],
title: "ladybug",
userId: "u54"
},
{
id: "i6",
parentId: "i3",
categoriesIds: ["c20"],
title: "spinach",
userId: "u14"
},
{
id: "i7",
parentId: "i3",
categoriesIds: ["c23", "c10"],
title: "impact",
userId: "u48"
},
{
id: "i8",
parentId: "i7",
categoriesIds: ["c30"],
title: "clerk",
userId: "u51"
},
{
id: "i9",
parentId: "i8",
categoriesIds: ["c7", "c5", "c16", "c28"],
title: "dot",
userId: "u23"
}
];
type Branch<T> = {
data: T;
children: Branch<T>[];
};
type Tree<T> = Branch<T>[];
const createTree = <T, K>(
collection: T[],
extractId: (entry: T) => K,
extractParentId: (entry: T) => K | null
): Tree<T> => {
const cache = new Map<K, Branch<T>>();
const tree: Tree<T> = [];
for (const entry of collection) {
const id = extractId(entry);
const parentId = extractParentId(entry);
const branch: Branch<T> = {
data: entry,
children: []
};
const root =
parentId !== null ? cache.get(parentId)?.children ?? tree : tree;
root.push(branch);
cache.set(id, branch);
}
return tree;
};
const tree = createTree(
itemList,
(entry) => entry.id,
(entry) => entry.parentId
);
const visualizeTreeInner = <T,>(tree: Tree<T>, root) => {
for (const { data, children } of tree) {
const template = `
<div class="tree-item">
<div class="tree-item__content">
<ul class="tree-item__list">
${Object.entries(data).map(([key, value]) => `
<li><strong>${key}</strong> – <code>${JSON.stringify(value)}</code></li>
`).join('')}
</ul>
</div>
<div class="tree-item__inner"></div>
</div>
`;
const wrapper = document.createElement('div');
wrapper.innerHTML = template;
const element = wrapper.children[0];
visualizeTreeInner(children, element.querySelector('.tree-item__inner'));
root.append(element);
}
};
const visualizeTree = <T,>(tree: Tree<T>) => {
const wrapper = document.createElement('div');
wrapper.className = 'tree';
visualizeTreeInner(tree, wrapper);
return wrapper;
}
document.body.append(
visualizeTree(tree)
);
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.