1

I'm trying to build a treeview component comporting inputs in order to change my source json.

The binding part seems to work fine but the hide/show action on branches is broken :

HTML :

<div id="app">
  <tree :data="json" :link="json"></tree>

  <p>Outside component :</p>
  <pre>{{json}}</pre>
</div>

JS :

let json = {
  nodeA: {
    nodeA1 : "valueA1",
    nodeA2 : "valueA2"
  },
  nodeB: "valueB",
  nodeC: {
    nodeC1 : "valueC1",
    nodeC2 : "valueC2"
  }
};

Vue.component('tree', {
  name: 'treeview',
  props: [
    'data', 
    'link'
  ],
  template: `<ul>
        <li v-for="(val, key) in data">
            <input type="text" v-if="isLeaf(val)" v-model=link[key]>
            <span @click="toggle">{{key}}</span>
            <tree v-if="!isLeaf(val)" v-show="show" :data="val" :link="link[key]">
            </tree>
        </li>
    </ul>`,
  data: function() {
    return {
      show: false
    };
  },
  methods: {
    isLeaf: function(node) {
      return typeof node != 'object';
    },
    toggle: function() {
      this.show = !this.show;
    }
  }
});

new Vue({
  el: '#app',
  data: {
    json: json
  }
});

https://codepen.io/anon/pen/EZKBwL

As you can see, a click on the first branch ("nodeA") activate both the first and the third branches...

I think the problem comes from the click that occurs on the parent component but I can't find a way to fix my code.

Saurabh
  • 71,488
  • 40
  • 181
  • 244
Pourpre
  • 13
  • 1
  • 3

3 Answers3

0

It happens because you bind all elements at the same parameter.

To toggle visibility individually for each element you need to store element states at it's own place like object's field or array.

But i guess better solution is toggle class on a target element by click and control visibility by css via class.

Panama Prophet
  • 1,027
  • 6
  • 6
0

Your all the branches are hiding/showing together as you are using single variable show to hide and show both of those, You have to use different variable for each on node.

It will be impractical to have as many variables as number of nodes, but you can have a hash like following:

  data: function() {
    return {
      show: {}
    };
  },

and change the toggle method to set the variable for each node by creating a key in this show hash for that node. You can use vm.$set for this which sets a property on an object. If the object is reactive, ensure the property is created as a reactive property and trigger view updates.

toggle: function(node) {
  if(this.show[node]){
    this.$set(this.show, node, false)
  } else {
    this.$set(this.show, node, true)
  }
} 

You need to do corresponding changes in HTML as well, which can be viewed in the working codepen here.

tony19
  • 125,647
  • 18
  • 229
  • 307
Saurabh
  • 71,488
  • 40
  • 181
  • 244
  • Simple but efficient :) In addition, is there a better solution for binding the nested inputs with the source JSON than passing a link as a prop ? – Pourpre Jan 13 '17 at 11:25
  • @Pourpre If you are asking about alternative of props, you can use vuex if it suits your requirement, you can look at [docs](https://vuex.vuejs.org/en/) or my answer [here](http://stackoverflow.com/a/40830610/1610034) or [here](http://stackoverflow.com/questions/40953496/vue-shared-data-between-different-pages/40955110#40955110). – Saurabh Jan 13 '17 at 11:35
-1

You may need a show field for each node to toggle their visibility separately, in my improved example, I'm using a data structure like this:

{
    "nodeA": {
        "value": {
            "nodeA1": {
                "value": "valueA1",
                "show": false
            },
            "nodeA2": {
                "value": "valueA2",
                "show": false
            }
        },
        "show": true
    },
    "nodeB": {
        "value": "valueB",
        "show": true
    }
}

my template:

<ul>
    <li v-for="(val, key) in data" v-show='val.show'>
        <input type="text" v-if="isLeaf(val)" v-model='link[key].value'>
        <span @click="toggle(val.value)">{{key}}</span>
        <tree v-if="!isLeaf(val)" :data="val.value" :link="val.value">
        </tree>
    </li>
</ul>

methods:

{
    isLeaf: function(node) {
        return typeof node.value != 'object';
    },
    toggle: function(value) {
        for (const nodeName in value) {
            value[nodeName].show = !value[nodeName].show;
        }
    }
}
JJPandari
  • 3,454
  • 1
  • 17
  • 24
  • 1
    This solution is functionnal but need a significant change in the source JSON wich is unwanted. Thanks for your proposal. – Pourpre Jan 13 '17 at 11:06