Join the Stack Overflow Community
Stack Overflow is a community of 6.6 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

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.

share|improve this question
up vote 0 down vote accepted

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.

share|improve this answer
    
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 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 or my answer here or here. – Saurabh Jan 13 at 11:35

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.

share|improve this answer

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;
        }
    }
}
share|improve this answer
1  
This solution is functionnal but need a significant change in the source JSON wich is unwanted. Thanks for your proposal. – Pourpre Jan 13 at 11:06

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.