CSS: Problem Arising from Line Connections Between Tree Elements

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.

https://i.sstatic.net/BfOYV.png

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
  }
})

Answer №1

Is this the information you seek?

Your issue with "dangling lines" is likely due to incorrect height calculations for the :before element.

.tree-menu-1st-example ul {
  padding-left: 0px;
}

.tree-menu-1st-example ul li {
  list-style-type: none;
  padding-left: 30px;
  position: relative;
  line-height: 2em;
}

.tree-menu-1st-example ul li:before {
  content: "";
  position: absolute;
  top: 0px;
  left: 0px;
  border-right: 1px solid #ccc;
  border-bottom: 1px solid #ccc;
  border-radius: 0 0 0 0px;
  height: 100%;
}

.tree-menu-1st-example ul li:after {
  content: "";
  display: block;
  position: absolute;
  top: 1em;
  left: 0px;
  border-top: 1px solid #ccc;
  border-radius: 0px 0 0 0;
  width: 20px;
  height: 100%;
}

.tree-menu-1st-example ul li:last-of-type:before {
  height: 1em;
}

/* New Example: */

.tree-menu-2 ul {
  padding-left: 0px;
}

.tree-menu-2 ul li {
  list-style-type: none;
  padding-left: 30px;
  position: relative;
  line-height: 2em;
}

.tree-menu-2 ul .tree-menu-2{
  position:relative;
}
.tree-menu-2 ul .tree-menu-2:before {
  content: "";
  position: absolute;
  top: 0px;
  left: 0px;
  border-right: 1px solid #ccc;
  border-bottom: 1px solid #ccc;
  border-radius: 0 0 0 0px;
  height: 100%;
}

.tree-menu-2 ul li:after {
  content: "";
  display: block;
  position: absolute;
  top: 1em;
  left: 0px;
  border-top: 1px solid #ccc;
  border-radius: 0px 0 0 0;
  width: 20px;
  height: 100%;
}

.tree-menu-2 ul .tree-menu-2:last-of-type:before {
  height: 1em;
}
<div class="tree-menu-1st-example">
    <ul>
        <li>
            Root
            <ul>
                <li>
                    Item1
                    <ul>
                        <li>item 1.1</li>
                        <li>item 1.1</li>
                    </ul>
                </li>
                <li>
                    Item2
                    <ul>
                        <li>item 1.1</li>
                        <li>item 1.1</li>
                    </ul>
                </li>
            </ul>
        </li>
    </ul>
</div>

      <div id="app">
         <div class="tree-menu-2">
            <li>
               <div class="label-wrapper">
                  <div class="has-children"><i class="fa fa-plus-square-o"></i>
                     root
                  </div>
               </div>
               <ul>
                  <div class="tree-menu-2">
                     <li>
                        <div class="label-wrapper">
                           <div class="has-children"><i class="fa fa-plus-square-o"></i>
                              item1
                           </div>
                        </div>
                        <ul>
                           <div class="tree-menu-2">
                              <li>
                                 <div class="label-wrapper">
                                    <div class="">
                                       <!---->
                                       item1.1
                                    </div>
                                 </div>
                                 <ul></ul>
                              </li>
                           </div>
                           <div class="tree-menu-2">
                              <li>
                                 <div class="label-wrapper">
                                    <div class="has-children"><i class="fa fa-plus-square-o"></i>
                                       item1.2
                                    </div>
                                 </div>
                                 <ul>
                                    <div class="tree-menu-2">
                                       <li>
                                          <div class="label-wrapper">
                                             <div class="">
                                                <!---->
                                                item1.2.1
                                             </div>
                                          </div>
                                          <ul></ul>
                                       </li>
                                    </div>
                                 </ul>
                              </li>
                           </div>
                        </ul>
                     </li>
                  </div>
                  <div class="tree-menu-2">
                     <li>
                        <div class="label-wrapper">
                           <div class="">
                              <!---->
                              item2
                           </div>
                        </div>
                        <ul></ul>
                     </li>
                  </div>
               </ul>
            </li>
         </div>
      </div>

Answer №2

I have come up with a unique approach to address the problem at hand.

// modified data from array
var data = {
  id: 1,
  name: '',
  open: true,  // default open
  children: [{
      name: 'B',
      open: true,
      children: [{
        name: 'BC1',
        open: true,
        children: [{
          name: 'BC1-S',
          open: true,
          children: [{
              name: 'BC1-1'
            },
            {
              name: 'BC1-2'
            }
          ]
        }]
      },{
        name: 'BC2',
        open: true,
        children: [{
          name: 'BC2-S',
          open: true,
          children: [{
              name: 'BC2-1'
            },
            {
              name: 'BC2-2'
            }
          ]
        }]
      }]
    },
    {
      name: 'G',
      children: [{
        name: 'GC1',
        children: [{
          name: 'GC1-S1',
          children: [{
              name: 'GC1-1'
            },
            {
              name: 'GC1-2'
            }
          ]
        },{
          name: 'GC1-S2',
          children: [{
              name: 'GC1-3'
            },
            {
              name: 'GC1-4'
            }
          ]
        }]
      }]
    }
  ]
}

// define the item component
Vue.component('item', {
  template: '#item-template',
  props: {
    model: Object
  },
  data: function() {
    return {
      open: this.model.open
    }
  },
  computed: {
    isFolder: function() {
      return this.model.children &&
        this.model.children.length
    }
  },
  methods: {
    toggle: function() {
      if (this.isFolder) {
        this.open = !this.open
      }
    }
  }
})

new Vue({
  el: '#main',
  data: {
    treeData: data
  }
})
body {
  font-family: Menlo, Consolas, monospace;
  color: #444;
}

.tree-view {}

.tree-view>ul {
  padding-left: 16px;
}

.tree-view li {
  list-style-type: none;
  margin: 0;
  padding: 10px 5px 0;
  position: relative;
}

.tree-view li:last-child {
  margin-bottom: 10px;
}

.tree-view li::after,
.tree-view li::before {
  content: '';
  left: -30px;
  position: absolute;
  right: auto;
}

.tree-view li::before {
  border-left: 1px solid #405567;
  height: calc(100% + 10px);
  top: 0;
  width: 1px;
}

.tree-view li::after {
  border-top: 1px solid #405567;
  height: 20px;
  top: 20px;
  width: 35px;
}

.tree-view .item {
  border: 1px solid #405567;
  border-radius: 2px;
  background-color: #fff;
  display: inline-block;
  padding: 2px 6px;
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
}

.tree-view .folder {
  background-color: #fff;
}

.tree-view>ul>li::after,
.tree-view>ul>li::before {
  border: 0;
}

.tree-view li:last-child::before {
  height: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<script type="text/x-template" id="item-template">
  <li>
    <div class="item" :class="{folder: isFolder}" @click="toggle">
      <span v-if="isFolder">{{ open ? '-' : '+' }}</span> {{ model.name }}
    </div>
    <ul v-show="open" v-if="isFolder">
      <item v-for="(model, index) in model.children" :key="index" :model="model">
      </item>
    </ul>
  </li>
</script>

<div class="tree-view" id="main">
  <ul>
    <item :model="treeData">
    </item>
  </ul>
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Retrieve information in JSON format using AngularJS by fetching data from SQL through PHP

My objective is to retrieve data from a mySql database using PHP, convert it to JSON, and then display it through AngularJS. Although I have successfully completed the steps leading up to this point, I am facing issues with the final step. Below are the de ...

Tips for altering promises within nested for loops?

Presented below is a complex function that aims to extract model variants from a csv file, which are stored as strings in an array. The path of this csv file depends on information obtained from other csv files, hence the need for looping. The csvService. ...

What is the best way to handle exceptions when dealing with MongoDB?

Issue Encountering a problem where the try block fails to capture errors when utilized within the MongoClient's connect function in MongoDB. System Details Operating System: Linux (Mint, Tessa) Node.js Version: v10.16.0 (utilizing ES6 with nodem ...

New feature alert! Introducing the Mentio JS menu now available at the bottom of the webpage

I am currently working on creating a Twitter-style @mention feature using Angular JS and a library called MentioJS. One issue I encountered is that after dynamically adding content to the page, a mysterious menu appears at the bottom of the page. This pro ...

PHP - Truncated html string when appending

I have a situation where I am using PHP to generate HTML for the MPDF library in order to create a PDF file. However, when I include a loop within my PHP-generated HTML, it seems to be cutting off the initial part of the string. Below is the code snippet: ...

Would you like to learn how to set an auto-play video as the background in a webpage section, similar to the one found on http://www8.hp.com/in/en/home.html?

I was browsing the HP-India website and noticed they have a cool autoplay video in the background of a section below the header. I would love to recreate something similar on my own website. Any tips on how to achieve this? ...

The SDK generated by AWS API Gateway does not include the JavaScript client file

After setting up my API with the AWS Api Gateway Service, I am now looking to integrate the javascript SDK into a basic webpage. The provided instructions for using the javascript SDK can be found here. However, they mention importing a js file named apig ...

The error detected in the W3Schools validator pertains to CSS for audio elements that do not contain

Could somebody kindly explain why this code is being flagged as an error on w3schools? audio:not([controls]) { display: none; height: 0; } ...

How can I transfer a value from one form to another using AngularJS?

Trying to retrieve the Id from a table and pass it to a controller, however, I am facing an issue where the Id value is lost every time the form changes. Is there a better way to handle this? Below is the service and controller code: //Retrieving IdValue ...

Is it possible to determine the search query if the search term appears within the website URL?

I'm trying to figure out which action url and name term to use when the search term is located in the middle of the URL. For instance, the HTML code below enables me to type text and upon pressing enter or the button, it redirects me to the website w ...

The DiscordBot is configured to send a personalized direct message to users who have chosen a specific role

Having trouble setting up my bot to send a DM to new members who choose the Advertising role, and I can't figure out why. I'm fairly new to this. const { Client, GatewayIntentBits } = require('discord.js'); const client = new Client({ ...

Records are being loaded into the table, but the browser is unresponsive

After making an ajax call, I am receiving over 1000 tr elements for the tbody of a table. However, when I try to load this data into the tbody of the table, Loading the records into the tbody of the table becomes a problem. $('#loadingRecords') ...

How can I show an item repeatedly with each button click in ReactJS?

Currently, I have a setup where I can display a checkbox and its label upon clicking a button. However, the limitation is that only one checkbox can be displayed at a time. Is there a way to modify this so that I can show multiple checkboxes simultaneous ...

Having difficulty accessing a private channel with Laravel and Vue.js due to pusher integration

I've successfully set up a notification system and everything is working fine when I send notifications to Pusher. However, I encounter an error when trying to listen on a private channel. Below is the code from my bootstrap.js file in Laravel-Echo: ...

What are some creative ways to customize the background of a full component?

Is there a way to modify the background color of an entire component? I want the color to cover the full width and height of the component. I attempted using the body tag, but it was ineffective. Do I need to enclose all components in a div and then appl ...

Disregard IDs when linting CSS in ATOM

Is there a way to configure csslint in Atom to exclude "ids" and prevent the warning "Don't use IDs in selectors" from appearing? Update: Despite my question being flagged as potentially duplicate, I delved deeper in my own research and ultimately fo ...

Exploring options for accessing Google Maps API on iPhone without using UIWebView for processing JavaScript

I need to retrieve data from Google Maps using JavaScript, without using a webview. For example, I have two points (lat,lng) and I want to use the Google Maps API to calculate the driving distance between them. Essentially, I want to utilize the Google Ma ...

Tips for creating a consistently positioned div element, regardless of the screen size

Having trouble positioning two bars with green or blue filling in the bottom right corner? They keep moving around and not staying in place regardless of screen size. Check out how they are off-screen here Below is the CSS-Styling + HTML code: .eleme ...

Sending an Array from JavaScript to Asp.net Core

Below is the javascript code that invokes the asp.net CustomHeatMapDate function $('.Date').change(function () { var data = []; console.log(); var dateList = [{"Date":"03/23/2016"}, {"Date":"03/24/2016"}]; $.ajax({ async: ...

Prevent side-to-side scrolling on elements that exceed the width of the screen

I am working on building a navigation system similar to Google Play, where there are fixed tabs for browsing through the app. I have created a container div that holds various content sections. <div id="container"> <div class="section"> .. ...