3
\$\begingroup\$

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?

<script src="https://unpkg.com/[email protected]"></script>
<div id="app">
  <tree v-bind:nodes="nodes"></tree>
</div>
<style>
  .selected {
    font-weight: bold;
  }
</style>
<script>
var selectedNode;
Vue.component("tree", {
  template: `
  <ul>
  	<div v-for="(node, index) of nodes">
      	<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"
        ></node>
    </div>
  </ul>
  `,
  props: ["nodes", "nestingLevel"]
});
Vue.component("node", {
  template: `
    <li>
      <div v-on:click="onClick" v-bind:id="id">
        {{node}} {{
          nestingTree
            ? open ? "[-]" : "[+]"
            : ""}}
      </div>
      <tree v-if="nestingTree && open" v-bind:nodes="nestingTree" v-bind:nestingLevel="nestingLevel + 1"></tree>
    </li>
  `,
  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"
            ]
          }
        ]
      }
    ]
  }
});

</script>

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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 <node>:

<div v-on:click="onClick" v-bind:id="id" :class="{selected: id == selectedId}">

What is selectedId? If you look at the example below, you will see I added a property to the <tree> and <node> 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 <tree> and <node> 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 <node> 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: `
  <ul>
    <div v-for="(node, index) of nodes">
        <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" 
          
        ></node>
    </div>
  </ul>
  `,
  props: ["nodes", "nestingLevel", "selectedId"],
});
Vue.component("node", {
  template: `
    <li>
      <div v-on:click="onClick" v-bind:id="id" :class="{selected: id == selectedId}">
        {{node}} {{
          nestingTree
            ? open ? "[-]" : "[+]"
            : ""}} 
      </div>
      <tree v-if="nestingTree && open" :nodes="nestingTree" :nestingLevel="nestingLevel + 1" :selected-id="selectedId" ></tree>
    </li>
  `,
  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;
}
<script src="https://unpkg.com/[email protected]"></script>
<div id="app">
  <tree :nodes="nodes" :selected-id="selectedId"></tree>

</div>

\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much. But I switched to React now :p \$\endgroup\$
    – user123460
    Commented Sep 27, 2017 at 15:31