【问题标题】:Indeterminate checkboxes with Vue.jsVue.js 的不确定复选框
【发布时间】:2017-09-11 20:48:26
【问题描述】:

我刚开始使用 Vue,我正在尝试可视化嵌套列表。

列表项应包含三态复选框: 选中子项时,父项的复选框应变为“不确定”。当所有子复选框都被选中时,父复选框也应该被选中。 当父项复选框被选中时,所有子项复选框(以及嵌套更深的复选框)也应该被选中。

我有一个可行的解决方案(查看pen 或下面的代码),但复选框逻辑仍然存在缺陷。在此示例中,选中的框为绿色,不确定的为橙色,未选中的为红色。

我已经没有办法解决它了。有人能解释一下如何在 Vue 中实现这一点吗?

'use strict';
Vue.component("book-chapter", Vue.extend({
  name: "book-chapter",
  props: ["data", "current-depth"],
  data: function() {
    return {
      checked: this.data.checked,
      indeterminate: this.data.indeterminate || false
    };
  },
  methods: {
    isChecked: function() {
      return this.checked && !this.indeterminate;
    },
    isIndeterminate: function(){
      return this.indeterminate;
    },
    toggleCheckbox: function(eventData) {
      if (this.currentDepth > 0){

        if (!this.data.children) {
          this.checked != this.children
        } else {
          this.indeterminate = !this.indeterminate;
        }
      }

      if (eventData) {
        // fired by nested chapter
        this.$emit('checked', eventData);

      } else {
        // fired by top level chapter
        this.checked = !this.checked;
        this.$emit('checked', {
          data: this.data
        });
      }
    },
    isRootObject: function() {
      return this.currentDepth === 0;
    },
    isChild: function() {
      return this.currentDepth === 2;
    },
    isGrandChild: function() {
      return this.currentDepth > 2;
    }
  },
  template: `
  <div class='book__chapters'>
   <div
      class='book__chapter'
      v-bind:class="{ 'book__chapter--sub': isChild(), 'book__chapter--subsub': isGrandChild() }"
      v-show='!isRootObject()'>
      <div class='book__chapter__color'></div>
      <div
         class='book__chapter__content'
         v-bind:class="{ 'book__chapter__content--sub': isChild(), 'book__chapter__content--subsub': isGrandChild() }">
         <div class='book__chapter__title'>
            <span class='book__chapter__title__text'>{{data.title}}</span>
         </div>
         <div class='book__chapter__checkbox triple-checkbox'>
            <div class='indeterminatecheckbox'>
               <div
                  class='icon'
                  @click.stop="toggleCheckbox()"
                  v-bind:class="{'icon--checkbox-checked': isChecked(), 'icon--checkbox-unchecked': !isChecked(), 'icon--checkbox-indeterminate': isIndeterminate()}">
               </div>
            </div>
         </div>
      </div>
   </div>
   <book-chapter
      ref='chapter'
      :current-depth='currentDepth + 1'
      v-for='child in data.children'
      key='child.id'
      @checked='toggleCheckbox(arguments[0])'
      :data='child'>
   </book-chapter>
</div>
`
}));

Vue.component("book", Vue.extend({
  name: "book",
  props: ["data"],
  template: `
    <div class='book'>
      <book-chapter 
        :data='this.data'
        :currentDepth='0'>
      </book-chapter>
    </div>
`
}));

var parent = new Vue({
  el: "#container",
  data: function() {
    return {
      book: {}
    };
  },
  mounted: function() {
    this.book = {
      "title": "Book",
      "children": [{
        "title": "1 First title",
        "children": [{
          "title": "1.1 Subtitle"
        }, {
          "title": "1.2 Subtitle"
        }]
      }, {
        "title": "2 Second title",
        "children": [{
          "title": "2.1 Subtitle",
          "children": [{
            "title": "2.1.1 Sub-Sub title"
          }, {
            "title": "2.1.2 Another sub-sub title"
          }]
        }]
      }]
    }
  }
});

【问题讨论】:

    标签: javascript checkbox vue.js


    【解决方案1】:

    更新:修复了@PhillSlevin 发现的错误。请在此处查看pen

    检查this pen,是你想要达到的吗?
    我想你可以使用eventbusvuex 来解决这个问题,
    如果您将每个 的部分视为一个组件。

    'use strict';
    
    var bus = new Vue();
    
    var book = {
      "title": "Book",
      "children": [{
        "title": "1 First title",
        "children": [{
          "title": "1.1 Subtitle"
        }, {
          "title": "1.2 Subtitle"
        }]
      }, {
        "title": "2 Second title",
        "children": [{
          "title": "2.1 Subtitle",
          "children": [{
            "title": "2.1.1 Sub-Sub title"
          }, {
            "title": "2.1.2 Another sub-sub title"
          }]
        }]
      }]
    };
    
    Vue.component('book', {
      template: `
    <div class="book__chapter">
      <p :class="'book__title ' + status" @click="clickEvent">{{title}} {{parent}}</p>
      <book v-for="child in children" :key="child" :info="child"></book>
    </div>
    `,
      props: ['info'],
      data() {
        return {
          parent: this.info.parent,
          title: this.info.title,
          children: [],
          status: this.info.status,
        };
      },
      created() {
        const info = this.info;
        if(info.children) {
          info.children.forEach(child => {
            child.status = "unchecked";
            // use title as ID
            child.parent = info.title;
          });
          this.children = info.children;
        }
      },
      mounted() {
        const vm = this;
        bus.$on('upside', (payload) => {
          const targetArr = vm.children.filter((child) => child.title === payload.from);
          if (targetArr.length === 1) {
            const target = targetArr[0];
            target.status = payload.status;
            if (vm.children.every(ele => ele.status === 'checked')) {
              vm.status = 'checked';
            } else if (vm.children.every(ele => ele.status === 'unchecked')) {
              vm.status = 'unchecked';
            } else {
              vm.status = 'indeterminate';
            }
            bus.$emit('upside', {
              from: vm.title,
              status: vm.status,
            });
          }
        });
        
        bus.$on('downside', (payload) => {
          if (payload.from === this.parent) {
            if (payload.status === 'checked') {
              vm.status = 'checked';
              vm.children.forEach(child => child.status = 'checked');
            } else if (payload.status === 'unchecked') {
              vm.status = 'unchecked';
              vm.children.forEach(child => child.status = 'unchecked')
            }
            bus.$emit('downside', {
              from: vm.title,
              status: vm.status,
            })
          }
        });
      },
      methods: {
        clickEvent() {
          if (this.status === 'checked') {
            this.status = 'unchecked';
            this.children.forEach(child => child.status = 'unchecked');
          } else {
            this.status = 'checked';
            this.children.forEach(child => child.status = 'checked');
          }
          
          const vm = this;
          bus.$emit('upside', {
            from: vm.title,
            status: vm.status,
          });
          bus.$emit('downside', {
            from: vm.title,
            status: vm.status,
          });
        },
      }
    });
    
    var parent = new Vue({
      el: "#container",
      data: function() {
        return {
          book
        };
      },
    });
    .book__title.unchecked::after {
      content: '□';
    }
    
    .book__title.indeterminate::after {
      content: '△';
    }
    
    .book__title.checked::after {
      content: '■';
    }
    
    .book__chapter {
      display: block;
      position: reletive;
      margin-left: 40px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.js"></script>
    <div id="container">
      <book :info="book" :parent="'container'"></book>
    </div>

    【讨论】:

    • 感谢您的回答!在分析您的解决方案时,我遇到了一个错误:当您首先检查第一个父级,然后取消选中其中一个子级时,父级状态被错误地更新为“未选中”。这是由于孩子的状态没有正确更新造成的。修复请参考以下pen
    • 已修复!谢谢。
    猜你喜欢
    • 1970-01-01
    • 2017-10-16
    • 1970-01-01
    • 2013-05-23
    • 2014-10-30
    • 2021-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多