如何使用复选框修复Vue更新?

时间:2019-03-14 16:40:09

标签: javascript vue.js vuejs2 vue-component vuetify.js

我正在尝试通过复选框创建树状视图,其中父级始终基于子级处于正确状态。包括不确定的值。然后将其值建模为数组。这就是我到目前为止所得到的

问题是我将B2(叶节点)设置为选中状态,但是我希望它是父级,并且父级不确定。但是事实并非如此。我相信这是因为在树形视图子级中使用v-if(其中有v-if="data.expanded")而不是v-show,并且B2的父级折叠了,所以它不显示。

但是我不明白为什么这是一个问题,因为我只对数据模型进行更改,并在不依赖于实际DOM的虚拟dom中发出事件。有人知道为什么吗?

谢谢

https://jsfiddle.net/s8tkLeqp/

html

<!DOCTYPE html>
<html>
    <head>
        <title>Title of the document</title>
        <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.8/vue.min.js"></script>
    </head>
    <body>
        <template id="ddct-treeview-template">
            <dd-treeview-inner v-bind:data="data" v-bind:root="0" v-on:change="change"></dd-treeview-inner>
        </template>

        <template id="ddct-treeview-inner-template">
            <div :style="{'marginLeft':root>0 || data.collapsible ? '30px' : '0', 'marginBottom' : '4px'}">
                <span v-if="data.collapsible" @click="clickToggle" :class="['ddct_toggle', {'ddct_collapse_toggle' : !data.expanded}]"></span>
                <input type="checkbox" v-model="data.checked" v-indeterminate="data.indeterminate"> <span>{{data.name}}</span>
                <div :class="data.class" v-if="data.expanded">
                    <dd-treeview-inner v-for="child in data.children" v-bind:data="child" v-bind:root="root+1" v-on:change="propUp"></dd-treeview-inner>
                </div>
            </div>
        </template>

        <div id="app">
            <dd-treeview v-bind:data="data" v-model="treeVals"></dd-treeview>
            <div v-for="val in treeVals">{{val}}</div>
        </div>

    </body>
</html>

js

Vue.component('dd-treeview-inner', {
  template : $("#ddct-treeview-inner-template")[0],
  props: ['data', 'root'],
  data : function() {
    return {};
  },
  directives: {
    indeterminate: function(el, binding) {
      el.indeterminate = Boolean(binding.value);
    }
  },
  watch : {
    'data.checked' : function(new_val, old_val) {
      if (this.data.value) {
        this.updateTree(new_val);
      }
    }
  },
  methods : {
    clickToggle : function() {
      this.data.expanded = !this.data.expanded;
    },
    updateTree : function(state) {
      this.data.indeterminate = false;
      this.propDown(state);
      this.$emit('change');
    },
    propDown : function(state) {
      this.data.children.map(function(child) {
        child.checked = state;
        child.indeterminate = false;
        child.propDown(state);
      });
    },
    propUp : function() {
      var children = this.data.children;
      var checked = 0
      var indeterminate = 0;
      children.map(function(child) {
        if (child.checked && !child.indeterminate) checked++;
        if (child.indeterminate) indeterminate++;
      });
      if (indeterminate > 0) {
        this.data.checked = true;
        this.data.indeterminate = true;
      } else if (checked == 0) {
        this.data.checked = false;
        this.data.indeterminate = false;
      } else if (checked == children.length) {
        this.data.checked = true;
        this.data.indeterminate = false;
      } else {
        this.data.checked = true;
        this.data.indeterminate = true;
      }
      this.$emit('change');
    }
  }
});

Vue.component('dd-treeview', {
  template : $("#ddct-treeview-template")[0],
  props: ['value', 'data'],
  watch : {
    value : function(new_val, old_val) {
      this.setValues(new_val);
    }
  },
  data : function() {
    return {};
  },
  mounted : function() {
    this.setValues(this.value);
  },
  methods : {
    setValues : function(values) {
      values = values.map(x => x.toLowerCase());
      function ff(node) {
        if (node.value) {
          node.checked = values.indexOf(node.value.toLowerCase()) != -1;
          node.indeterminate = false;
        }
        node.children.map(ff);
      }
      ff(this.data); 
    },
    change : function() {
      var arr = [];
      function ff(node) {
        if (node.value && node.checked && !node.indeterminate) {
          arr.push(node.value);
        }
        node.children.map(ff);
      }
      ff(this.data);
      this.$emit('input', arr);
    }
  }
});

new Vue({
  el: $("#app")[0],
  data : {
    treeVals : ["B2"],
    data : {
      name : "ROOT",
      collapsible : true,
      expanded : true,
      checked:false,
      indeterminate:false,
      children : [
        {
          name : "A",
          collapsible : true,
          expanded : false,
          checked:false,
          indeterminate:false,
          children : [
            {
              name : "A1",
              children : [],
              checked:false,
              indeterminate:false,
              value : "A1"
            },
            {
              name : "A2",
              children : [],
              checked:false,
              indeterminate:false,
              value : "A2"
            }
          ]
        },
        {
          name : "B",
          collapsible : true,
          expanded : false,
          checked:false,
          indeterminate:false,
          children : [
            {
              name : "B1",
              children : [],
              checked:false,
              indeterminate:false,
              value : "B1"
            },
            {
              name : "B2",
              children : [],
              checked:false,
              indeterminate:false,
              value : "B2"
            }
          ]
        }
      ]
    }
  }
});

css

.ddct_toggle {
  position: relative;
}

.ddct_toggle::before {
  content: '+';
  display: block;
  position: absolute;
  top: 0;
  left: -30px;
  border: 1px solid red;
  border-radius: 50%;
  width: 24px;
  text-align: center;
  transition: all 0.4s;
  cursor: pointer;
}

.ddct_toggle.ddct_collapse_toggle::before {
  content: '-';
}

1 个答案:

答案 0 :(得分:1)

我猜是因为您触发的updateTree方法需要一个已安装的组件。 因此,当前实际上没有任何东西可以观看B2检查的属性。 因此,什么都不会触发。

您可以做的至少可以触发触发的一件事是:

watch : {
 'data.checked' :{
      handler: function(new_val, old_val) {
      if (this.data.value) {
        this.updateTree(new_val);
      }
    },
    immediate: true // this is equal to call of handler in mounted
  }
},

另一件事是只加载空组件:

Vue.component('dd-treeview-inner', {
  template: $("#ddct-treeview-inner-template")[0],
  props: ['data', 'root', 'expanded'], // pass data.expanded
  data: function() {
    return {};
  },
  directives: {
    indeterminate: function(el, binding) {
      el.indeterminate = Boolean(binding.value);
    }
  },
  watch: {
    'data.checked': {
      handler: function(new_val, old_val) {
        if (this.data.value) {
          this.updateTree(new_val);
        }
      },
      /* immediate: true */
    }
  },
  methods: {
    clickToggle: function() {
      this.data.expanded = !this.data.expanded;
    },
    updateTree: function(state) {
      this.data.indeterminate = false;
      this.propDown(state);
      this.$emit('change');
    },
    propDown: function(state) {
      this.data.children.map(function(child) {
        child.checked = state;
        child.indeterminate = false;
        child.propDown(state);
      });
    },
    propUp: function() {
      var children = this.data.children;
      var checked = 0
      var indeterminate = 0;
      children.map(function(child) {
        if (child.checked && !child.indeterminate) checked++;
        if (child.indeterminate) indeterminate++;
      });
      if (indeterminate > 0) {
        this.data.checked = true;
        this.data.indeterminate = true;
      } else if (checked == 0) {
        this.data.checked = false;
        this.data.indeterminate = false;
      } else if (checked == children.length) {
        this.data.checked = true;
        this.data.indeterminate = false;
      } else {
        this.data.checked = true;
        this.data.indeterminate = true;
      }
      this.$emit('change');
    }
  }
});

Vue.component('dd-treeview', {
  template: $("#ddct-treeview-template")[0],
  props: ['value', 'data'],
  watch: {
    value: function(new_val, old_val) {
      this.setValues(new_val);
    }
  },
  data: function() {
    return {};
  },
  mounted: function() {
    this.setValues(this.value);
  },
  methods: {
    setValues: function(values) {
      values = values.map(x => x.toLowerCase());

      function ff(node) {
        if (node.value) {
          node.checked = values.indexOf(node.value.toLowerCase()) != -1;
          node.indeterminate = false;
        }
        node.children.map(ff);
      }
      ff(this.data);
    },
    change: function() {
      var arr = [];

      function ff(node) {
        if (node.value && node.checked && !node.indeterminate) {
          arr.push(node.value);
        }
        node.children.map(ff);
      }
      ff(this.data);
      this.$emit('input', arr);
    }
  }
});

new Vue({
  el: $("#app")[0],
  data: {
    treeVals: ["B2"],
    data: {
      name: "ROOT",
      collapsible: true,
      expanded: true,
      checked: false,
      indeterminate: false,
      children: [{
          name: "A",
          collapsible: true,
          expanded: false,
          checked: false,
          indeterminate: false,
          children: [{
              name: "A1",
              children: [],
              checked: false,
              indeterminate: false,
              value: "A1"
            },
            {
              name: "A2",
              children: [],
              checked: false,
              indeterminate: false,
              value: "A2"
            }
          ]
        },
        {
          name: "B",
          collapsible: true,
          expanded: false,
          checked: false,
          indeterminate: false,
          children: [{
              name: "B1",
              children: [],
              checked: false,
              indeterminate: false,
              value: "B1"
            },
            {
              name: "B2",
              children: [],
              checked: false,
              indeterminate: false,
              value: "B2"
            }
          ]
        }
      ]
    }
  }
});
.ddct_toggle {
  position: relative;
}

.ddct_toggle::before {
  content: '+';
  display: block;
  position: absolute;
  top: 0;
  left: -30px;
  border: 1px solid red;
  border-radius: 50%;
  width: 24px;
  text-align: center;
  transition: all 0.4s;
  cursor: pointer;
}

.ddct_toggle.ddct_collapse_toggle::before {
  content: '-';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!DOCTYPE html>
<html>

<head>
  <title>Title of the document</title>
  <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

</head>

<body>
  <template id="ddct-treeview-template">
            <dd-treeview-inner :expanded="true" v-bind:data="data" v-bind:root="0" v-on:change="change"></dd-treeview-inner>
        </template>

  <template id="ddct-treeview-inner-template">
            <div v-if="expanded" :style="{'marginLeft':root>0 || data.collapsible ? '30px' : '0', 'marginBottom' : '4px'}">
                <span v-if="data.collapsible" @click="clickToggle" :class="['ddct_toggle', {'ddct_collapse_toggle' : !data.expanded}]"></span>
                <input type="checkbox" v-model="data.checked" v-indeterminate="data.indeterminate"> <span>{{data.name}}</span>
                <div :class="data.class">
                    <dd-treeview-inner :expanded="data.expanded" v-for="child in data.children" v-bind:data="child" v-bind:root="root+1" v-on:change="propUp"></dd-treeview-inner>
                </div>
            </div>
            <div v-else>
            <dd-treeview-inner :expanded="data.expanded" v-for="child in data.children" v-bind:data="child" v-bind:root="root+1" v-on:change="propUp"></dd-treeview-inner>
            </div>
        </template>

  <div id="app">
    <dd-treeview v-bind:data="data" v-model="treeVals"></dd-treeview>
    <div v-for="val in treeVals">{{val}}</div>
  </div>

</body>

</html>