I'm currently working on connecting tree nodes with lines in a drawing. I've managed to achieve it to some extent, but there are a few issues like dangling lines that I need to fix.
You can find the implementation on codepen here: https://codepen.io/Dinesh443/pen/wvWjJeB
My main goal is to remove the connecting line before the root node and the other hanging lines after the last node as shown below.
The code is written using Vue.js for a simple recursive tree representation using 'ul' and 'li' tags. I have applied the ::before and ::after pseudo-selectors to create this effect.
HTML:-
<div class="container">
<h4>Vue.js Expandable Tree Menu<br/><small>(Recursive Components)</small></h4>
<div id="app">
<tree-menu
:nodes="tree.nodes"
:depth="0"
:label="tree.label"
></tree-menu>
</div>
</div>
<script type="text/x-template" id="tree-menu">
<div class="tree-menu">
<li>
<div class="label-wrapper" @click="toggleChildren">
<div :class="labelClasses">
<i v-if="nodes" class="fa" :class="iconClasses"></i>
{{ label }}
</div>
</div>
<ul>
<tree-menu
v-if="showChildren"
v-for="node in nodes"
:nodes="node.nodes"
:label="node.label"
:depth="depth + 1"
>
</ul>
</tree-menu>
</li>
</div>
</script>
CSS:
body {
font-family: "Open Sans", sans-serif;
font-size: 18px;
font-weight: 300;
line-height: 1em;
}
.container {
width: 300px;
margin: 0 auto;
}
.tree-menu {
.label-wrapper {
padding-bottom: 10px;
margin-bottom: 10px;
// border-bottom: 1px solid #ccc;
.has-children {
cursor: pointer;
}
}
}
.tree-menu li {
list-style-type: none;
margin:5px;
position: relative;
}
.tree-menu li>ul::before {
content: "";
position: absolute;
top:-7px;
left:-30px;
border-left: 1px solid #ccc;
border-bottom:1px solid #ccc;
border-radius:0 0 0 0px;
// width:20px;
height:100%;
}
.tree-menu li>ul::after {
content:"";
display: block;
position:absolute;
top:8px;
left:-30px;
border-left: 1px solid #ccc;
border-top:1px solid #ccc;
border-radius:0px 0 0 0;
width:20px;
height:100%;
}
JavaScript( Vue ):
let tree = {
label: 'root',
nodes: [
{
label: 'item1',
nodes: [
{
label: 'item1.1'
},
{
label: 'item1.2',
nodes: [
{
label: 'item1.2.1'
}
]
}
]
},
{
label: 'item2'
}
]
}
Vue.component('tree-menu', {
template: '#tree-menu',
props: [ 'nodes', 'label', 'depth' ],
data() {
return {
showChildren: false
}
},
computed: {
iconClasses() {
return {
'fa-plus-square-o': !this.showChildren,
'fa-minus-square-o': this.showChildren
}
},
labelClasses() {
return { 'has-children': this.nodes }
},
// indent() {
// return { transform: `translate(${this.depth * 50}px)` }
// }
},
methods: {
toggleChildren() {
this.showChildren = !this.showChildren;
}
}
});
new Vue({
el: '#app',
data: {
tree
}
})