For steps on setting up a new Aurelia project please refer to this link:
https://www.technical-recipes.com/2019/getting-started-with-aurelia-js-part-2/
(For reference I am using the TypeScript option when creating this new Aurelia project and building / running it in Visual Studio Code.)
Some other useful links on this topic can be found here:
https://www.syntaxsuccess.com/viewarticle/building-a-treeview-in-aurelia
https://gist.run/?id=53dc5b81ef1549cdcc56c3b1fa1e67ba (GistRun link)
Anyway the following are the files I added to Visual Studio Code project:
app.html
<template> <require from="styles.css"></require> <require from="tree-view"></require> <tree-view></tree-view> </template>
app.ts
export class App {}
node-model.ts
export class NodeModel {
public id: string;
public icon: string;
public children: NodeModel[];
public expanded: boolean;
public visible: boolean;
constructor(idVal: string, children: NodeModel[]) {
this.id = idVal;
this.visible = true;
this.children = children || [];
if (this.hasChildren()) {
this.icon = "fa fa-minus";
this.expanded = true;
}
}
hasChildren() {
return this.children.length > 0;
}
toggleNode() {
for (var i = 0; i < this.children.length; i++) {
this.children[i].visible = !this.children[i].visible;
if (this.expanded) {
this.children[i].toggleNode();
}
}
this.expanded = !this.expanded;
if (this.expanded === true) {
this.icon = "fa fa-minus";
} else {
this.icon = "fa fa-plus";
}
}
}
styles.css
ul{
list-style-type:none;
margin:0;
}
tree-node ul:first-child{
padding-left:0;
}
.fa.fa-plus{
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTJDBGvsAAAAmUlEQVQ4T6WTuw2AMBBDXbEDo7AQQ9AgISRYgRUYhIqK3wjsQHWEE5wOiJDCFa+xbEtxEhDRDVRjg7yNHPhC/DrMQr0QqqlD1scOCEEFXDKuKIbEASa4gEvmDeWUOn4WSNF7F/HrMAu+goPHLuLXYRZ84Qu1i/h1mAVfUHPuIn4dZsEX0pgKTEcwjfj7Gk0PyfSUTZ8p6DsTdnMcPQl3gNV4AAAAAElFTkSuQmCC);
display:inline-block;
}
.fa.fa-minus{
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUuNSAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6M0I4RDFEMDE5MDc4MTFFNjhBRkU4RjUxMTMwMTFBNDciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6M0I4RDFEMDI5MDc4MTFFNjhBRkU4RjUxMTMwMTFBNDciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQjhEMUNGRjkwNzgxMUU2OEFGRThGNTExMzAxMUE0NyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozQjhEMUQwMDkwNzgxMUU2OEFGRThGNTExMzAxMUE0NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtWsFOoAAACvSURBVHjaYvz//z8DJYBx6BvAwthxZSYDI3MaWbr//53FxPDjei7D/3/HSdcM1APUywRk/mL4/CMYaNpzEmx+DtYD1MsEFXrO8Ps/0JD/vwhrBqoBqQXpAQImJKnjDH//5xI0AKIG7mUmNOlZoIDBF2hgNcjRyFC/Cl0ZGwOH1gEGRiZLzEC75gAOMyTAhMUezEBFCjR0xUw4HIsIVLRAw0hIeIILOVCPD968ABBgAN4Tav6F/rCJAAAAAElFTkSuQmCC);
display:inline-block;
}
.StartOfTreeNode > tree-node > .StartOfTreeNode{
padding-left:20px;
}
.mainBack{
background:#F7F7F7;
}
.left{float:left;}
.right{float:right;}
.clear{clear:both;}
.WorkQueueDropContent{
color:#343434;
}
.EachMiniQueueWithOutCollapse{
padding:15px 10px;
border-bottom:1px solid #F1F1F1;
}
.eachMinQueueCheckSection{
margin-right:10px;
}
.leftGaptoRightElement{
margin-left:10px;
}
.lightFontForMInQueue{
color:#A8A8A8;
}
.DateOfMinQueue{
font-weight:bold;
color:#0E7EC6;
}
tree-node.html
<template>
<div show.bind="current.visible" class="StartOfTreeNode">
<div class="EachMiniQueueWithOutCollapse">
<span if.bind="current.hasChildren()"
click.trigger="current.toggleNode(current)"
class="${current.icon} left eachMinQueueCheckSection">
</span>
<div class="left eachMinQueueCheckSection" if.bind="!current.hasChildren()">
<label class="cb_control control--checkbox">
<input type="checkbox">
<span class="control__indicator"></span>
</label>
</div>
<div class="left">
<div class="TitleOfMinQueue">
<span class="fontBold"><b>${current.id}</b></span>
</div>
</div>
</div>
<tree-node
repeat.for="node of current.children"
current.bind="node">
</tree-node>
</div>
</template>
tree-node.ts
import { bindable } from "aurelia-framework";
import { NodeModel } from "node-model";
export class TreeNode {
@bindable current: NodeModel;
}
tree-view.html
<template>
<require from='./tree-node'></require>
<tree-node repeat.for="node of nodes" current.bind="node"></tree-node>
</template>
tree-view.ts
import { NodeModel } from "node-model";
export class TreeView {
public nodes: NodeModel[] = [];
constructor() {
var node1 = new NodeModel("Wiltshire", [
new NodeModel("Chippenham", null),
new NodeModel("Devizes", null),
new NodeModel("Bradford-on-Avon", null)
]);
var node3 = new NodeModel("Somerset", [
new NodeModel("Yeovil", null),
new NodeModel("Taunton", [
new NodeModel("Hestercombe House", null),
new NodeModel("Vivary Park", null)
])
]);
this.nodes = [node1, node3];
}
}
Giving the following output:

