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.

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

What happens if I attempt to access an undefined value from a JSON array?

I've been attempting to nest a JSON array within another JSON array. I believe I have structured it correctly, but when I try to access the second array, it returns undefined. JSON example : var data = [ {"Items" : [ {"item1" : "item1 ...

What is the best way to extract specific values from a JSON array of objects using JavaScript?

I am facing some challenges in displaying values from the AJAX objects. My goal is to populate a select box with names using AJAX, but I seem to have made an error somewhere in my code. Can someone please assist me in identifying and correcting the mistake ...

Is there a way to submit an object to the server without using ajax during form submission?

I have a web application built on the ASP.NET MVC 5 framework. One of the pages in my application utilizes the amazing jQuery-QueryBuilder plugin, allowing users to create filters to refine the dataset results. When a user submits the form by clicking the ...

What kind of mischief can be wreaked by a malicious individual using JavaScript?

My mind has been consumed by thoughts about the safety of my projects, especially when it comes to password recovery. On the password recovery page, users must fill out a form with valid data and complete a recaptcha test for security. To enhance user ex ...

Executing a function without using the eval() function

I am currently working on a JavaScript code that relies heavily on the eval function. eval(myString) The value of myString is equal to myFunc(arg), and I would like to find a way to call myFunc directly instead of using eval. Unfortunately, I have no co ...

Encountering an Internal Server error with Mongoose in Node.js

My latest project is a web application designed for photo sharing. One particular route in the app is responsible for retrieving and displaying photos from all users in the following array. This is the route: router.get('/getphotos',function(r ...

Applying class to target specific screen size in Bootstrap

I've been utilizing bootstrap for my project. My objective is to assign multiple classes based on screen size, such as sm-col and xs.col. For example, let's say I have a text id <div id="text" class="xs-format lg-format ..."></div> ...

Is it possible to encounter a MongoDB error for the $or operator in a correctly formatted query?

Here is the problem I am facing: const users = this.mongo.db.collection('Users') let query = { '$or': [ { "email": {'$eq': req.body.email }}, {"username": {'$eq': req.body.username }} ] } users.fi ...

Is SimpleHTTPServer included in the package?

Lately, I've been experimenting with the Python SimpleHTTPServer in Mac OS X Bash instead of MAMP to assist with front-end templating. I appreciate its user-friendly nature, but I'm curious if there is a method to incorporate includes for repeate ...

Adjust the navigation menu to display or hide as the page is being scrolled

Implementing an offset to display/hide the navigation menu when the page is scrolled 100px. Attempted to modify from lastScroll = 0 to lastScroll = 100 but it did not work as expected. Jquery: Fiddle // Script lastScroll = 0; $(window).on('scroll&ap ...

Is there a simpler method to access the source element for an event?

I'm just starting to learn JavaScript and jQuery, and right now I have the following code in my HTML: <a id="tog_table0" href="javascript:toggle_table('#tog_table0', '#hideable_table0');">show</a> After that, I hav ...

How can I organize a dictionary based on a specified key order in Jinja2?

Suppose I need to showcase the following data: • b is foo • a is bar • c is baz ...however, my dataset is structured like this (or any other sequence due to JSON's flexibility): { "a": "bar", "b": "foo", "c": "baz" } How can I utilize Jinja2 ...

Guide on Updating the ColModel Dynamically in JAVASCRIPT/HTML using clearGridData, setGridParam, and reloadGrid Functions

I am trying to figure out how to dynamically change the colmodel of my grid using a function that is called by a SELECT. The reason for this is because my grid has different periods and needs to display either cost or tons based on user selection. Below i ...

Python's Selenium Throws No Such Element Exception

Looking to automate tasks involving hyperlinks on my university's SAP Portal, I decided to use Selenium. However, encountering difficulties as many web elements are dynamically generated using JavaScript, making them invisible to the webdriver. The e ...

How to retrieve the first option selected in Material-UI React?

Hey there! I am currently working with React Material UI select. I have a question - how can I set the first option of items as selected without triggering the onChange method? When I change an option, it triggers the onChange method and adds an attribut ...

Experiencing Limitations with Node.JS node-hbase Scan Functionality, Unable to Retrieve More than 1000

I am facing an issue while trying to retrieve records from an HBase table in Node.JS using the node-hbase module, which connects to the rest server. I am able to fetch the first batch of records successfully, but I am struggling to get the next set of re ...

Is it possible for a JavaScript environment to restore function normally after modifying the [[Prototype]] of an object?

After thoroughly reviewing the MDN disclaimers and warnings, as well as an enlightening discussion in a popular online forum, I still find myself seeking answers. This question arose from a previous exchange, which can be found here. Imagine if I were to ...

What is the best way to refresh or reset a component in VueJs: reloading it or destroying it and loading again?

In my VueJs application, I am using a component called Aplayer (or any similar components) to play audio and manage playlists. However, I am facing an issue where I need to reload the component when I change the playlist without changing the routes. Is th ...

Difficulty navigating through nested arrays

One instance involves the use of the fetch API to retrieve data from the NY Times API. Within the response, there is an object titled "Multimedia" containing images. I've devised a template literal to extract the required information but for some reas ...

What is the most efficient way to print multiple JSON arrays simultaneously?

When looping through a database using an array, the following code snippet is used: $checkedProducts = $request->input('products'); $p = null; foreach($checkedProducts as $checkedProduct){ $p .= DB::table('products')->where( ...