【问题标题】:How to fix vue updates with checkboxes?如何使用复选框修复 vue 更新?
【发布时间】:2019-03-14 16:40:08
【问题描述】:

我正在尝试制作一个带有复选框的树形视图,其中父级始终处于基于其子级的正确状态。包括不确定的值。而 v-model 将它的值放入一个数组中。这就是我目前所得到的

问题是我将 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: '-';
}

【问题讨论】:

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


    【解决方案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>

    【讨论】:

      猜你喜欢
      • 2017-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-11
      • 2017-01-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多