A JS Tree Viewer in VueJS To learn Vue.js I have made a tree viewer with the code below. Can anyone give me some advice on how to improve the code?



&lt;script src="https://unpkg.com/vue@2.2.6"&gt;&lt;/script&gt;
&lt;div id="app"&gt;
  &lt;tree v-bind:nodes="nodes"&gt;&lt;/tree&gt;
&lt;/div&gt;
&lt;style&gt;
  .selected {
    font-weight: bold;
  }
&lt;/style&gt;
&lt;script&gt;
var selectedNode;
Vue.component("tree", {
  template: `
  &lt;ul&gt;
   &lt;div v-for="(node, index) of nodes"&gt;
       &lt;node
          v-bind:node="typeof node === 'string' ? node : Object.keys(node)[0]"
          v-bind:nestingTree="typeof node === 'string' ? undefined : Object.values(node)[0]"
          v-bind:id="index + '/' + (nestingLevel || 0)"
          v-bind:nestingLevel="nestingLevel || 0"
        &gt;&lt;/node&gt;
    &lt;/div&gt;
  &lt;/ul&gt;
  `,
  props: ["nodes", "nestingLevel"]
});
Vue.component("node", {
  template: `
    &lt;li&gt;
      &lt;div v-on:click="onClick" v-bind:id="id"&gt;
        {{node}} {{
          nestingTree
            ? open ? "[-]" : "[+]"
            : ""}}
      &lt;/div&gt;
      &lt;tree v-if="nestingTree &amp;&amp; open" v-bind:nodes="nestingTree" v-bind:nestingLevel="nestingLevel + 1"&gt;&lt;/tree&gt;
    &lt;/li&gt;
  `,
  props: ["node", "nestingTree", "nestingLevel", "id"],
  data: function() {
    return {
      open: false
    }
  },
  methods: {
    onClick: function(event) {
      this.toggleOpenClosedState(event);
      this.selectCurrentNode(event);
    },
    toggleOpenClosedState: function(event) {
      this.open = !this.open;
    },
    selectCurrentNode: function(event) {
      if (selectedNode) {
        document.getElementById(selectedNode).classList.remove("selected");
      }
      selectedNode = this.id;
      document.getElementById(selectedNode).classList.add("selected");
    },
    isSelected: function() {
      console.log(this.id)
      return selectedNode === this.id
    }
  }
});
var app = new Vue({
  el: "#app",
  data: {
    nodes: [
      "dfadsfa",
      "fkjsdafkj", {
        carrots: [
          "caveats", {
            moreNesting: [
              "bwah bwah bwah"
            ]
          }
        ]
      }
    ]
  }
});

&lt;/script&gt;
This looks similar to the VueJS documentation Tree View example but it is different because of the unique selected aspect.

I would recommend not modifying the class names of elements based on the id, but instead use class bindings. So for the &lt;node&gt;:

&lt;div v-on:click="onClick" v-bind:id="id" :class="{selected: id == selectedId}"&gt;


What is selectedId? If you look at the example below, you will see I added a property to the &lt;tree&gt; and &lt;node&gt; components, and set that to a new item in the data of the root app.

var app = new Vue({
  el: "#app",
  data: {
    nodes: [/*...*/],
    selectedId: ''
  },
  methods: {
    setSelectedId: function(selectedId) {
      this.selectedId = selectedId;
    }
  }
});


Originally I was going to add properties to both the &lt;tree&gt; and &lt;node&gt; components and use traditional parent-child communication techniques but it got a little cumbersome with emitting from the nested children. Then I decided to use an Event bus to set the selected id (see Non Parent-Child Communication). One could use $root from the &lt;node&gt; component and call the method directly but some believe that goes against the traditional parent-child communication flow.

Also notice the code below uses the shorthand syntax for v-bind.



var selectedNode;
var bus = new Vue();
Vue.component("tree", {
  template: `
  &lt;ul&gt;
   &lt;div v-for="(node, index) of nodes"&gt;
       &lt;node
          :node="typeof node === 'string' ? node : Object.keys(node)[0]"
          :nestingTree="typeof node === 'string' ? undefined : Object.values(node)[0]"
          :id="index + '/' + (nestingLevel || 0)"
          :nestingLevel="nestingLevel || 0"
          :selected-id="selectedId" 
          
        &gt;&lt;/node&gt;
    &lt;/div&gt;
  &lt;/ul&gt;
  `,
  props: ["nodes", "nestingLevel", "selectedId"],
});
Vue.component("node", {
  template: `
    &lt;li&gt;
      &lt;div v-on:click="onClick" v-bind:id="id" :class="{selected: id == selectedId}"&gt;
        {{node}} {{
          nestingTree
            ? open ? "[-]" : "[+]"
            : ""}} 
      &lt;/div&gt;
      &lt;tree v-if="nestingTree &amp;&amp; open" :nodes="nestingTree" :nestingLevel="nestingLevel + 1" :selected-id="selectedId" &gt;&lt;/tree&gt;
    &lt;/li&gt;
  `,
  props: ["node", "nestingTree", "nestingLevel", "id", "selectedId"],
  data: function() {
    return {
      open: false
    }
  },
  methods: {
    onClick: function(event) {
      this.toggleOpenClosedState(event);
      bus.$emit('setSelectedId', this.id);
    },
    toggleOpenClosedState: function(event) {
      this.open = !this.open;
    }
  }
});
var app = new Vue({
  el: "#app",
  data: {
    nodes: [
      "dfadsfa",
      "fkjsdafkj", {
        carrots: [
          "caveats", {
            moreNesting: [
              "bwah bwah bwah"
            ]
          }
        ]
      }
    ],
    selectedId: ''
  },
  mounted: function() {
      bus.$on('setSelectedId', this.setSelectedId);
  },
  methods: {
    setSelectedId: function(selectedId) {
      this.selectedId = selectedId;
    }
  }
});
.selected {
  font-weight: bold;
}
&lt;script src="https://unpkg.com/vue@2.2.6"&gt;&lt;/script&gt;
&lt;div id="app"&gt;
  &lt;tree :nodes="nodes" :selected-id="selectedId"&gt;&lt;/tree&gt;

&lt;/div&gt;