Rimuru Tempest
Rimuru Tempest

Reputation: 665

Vuejs checkbox grouping select all

I have an Array of objects which have been divided into groups. each group has their own items and I need to check all the child check boxes if parents are checked and uncheck if parents are not checked. also if selectAll checked all groups and group items should be checked

what I want to achieve is the same as this example

selectall.gif

I've jsfiddle setup here, and this what I've done so far.

Data

const groups = [
   {
      id: 1,
      name: "TEST A",
      items: [
         { id: 1, name: "item 1" },
         { id: 2, name: "item 2" }
      ]
   },
   {
      id: 2,
      name: "TEST B",
      items: [
         { id: 3, name: "item 1" },
         { id: 4, name: "item 2" }
      ]
   }
]

HTML

<div id="app">
  <div>
    
    <input type="checkbox" class="mb-2" v-model="selectAll" /> select all
    
    <div v-for="group in groups" :key="group.id" class="mb-2">

      <input
        type="checkbox"
        v-model="selectedGroups"
        :value="group.id"
      />
      
      <b>{{ group.name }}</b>

      <c-item 
        v-for="item in group.items"
        :item="item"
        :key="item.id"
        @change="onChangeItem"
        :checked="selectedItems.includes(item.id)"
      />

    </div>
    
  </div>
</div>

<template id="x-item">
  <div>
  
    <input 
     type="checkbox"
     @change="$emit('change', item.id, $event)"
     :value="item.id"
     :checked="checked"
    />
    {{ item.name }}
    
  </div>
</template>

JS

Vue.component('c-item', {
    props: ['item', 'checked'],
  template: "#x-item",
})

new Vue({
  el: "#app",
  data: {
    selectedItems: [],
    selectedGroups: [],
    groups: [
       {
          id: 1,
          name: "TEST A",
          items: [
             { id: 1, name: "item 1" },
             { id: 2, name: "item 2" }
          ]
       },
       {
          id: 2,
          name: "TEST B",
          items: [
             { id: 3, name: "item 1" },
             { id: 4, name: "item 2" }
          ]
       }
    ]
  },
  methods: {
        onChangeItem(itemId, $event) {
      if ($event.target.checked) this.selectedItems.push(itemId);
      else {
        const index = this.selectedItems.findIndex(c => c === itemId);
        if (index >= 0) this.selectedItems.splice(index, 1);
      }
    }
  },
  computed: {
    selectAll: {
      get() {
        return this.groups
          ? this.selectedGroups.length == this.groups.length
          : false;
      },
      set(value) {
        let selectedGroups = [];

        if (value) {
          this.groups.forEach(function(item) {
            selectedGroups.push(item.id);
          });
        }

        this.selectedGroups = selectedGroups;
      }
    }
  }
})

Can someone help me? Thank you in advance!

Upvotes: 0

Views: 2883

Answers (2)

User 28
User 28

Reputation: 5158

Generally, You can solve this kind of problem with the recursive component. The concept is build an object tree and at any node when checked change then call update parent and children and changes will go recursively.

<template id='x-item'>
  <div>
    <label>
      <input
        type='checkbox'
        :checked='item.checked'
        @change='onChange($event.target.checked)'>
      <span>{{ item.label }}</span>
    </label>
    <template v-if='item.children'>
      <x-item
        v-for='item in item.children'
        ref='children'
        :item='item'
        @change='onChildChange'>
      </x-item>
    </template>
  </div>
</template>
Vue.component('x-item', {
  template: '#x-item',
  props: ['item'],
  methods: {
    onChange(checked) {
      this.item.checked = checked
      this.updateParent()
      this.updateChildren(checked)
    },
    onChildChange() {
      this.item.checked = this.$refs.children
        .every(child => child.item.checked)
      this.updateParent()
    },
    updateParent() {
      this.$emit('change')
    },
    updateChildren(checked) {
      if (!this.item.children) return
      this.$refs.children.forEach(child => {
        child.item.checked = checked
        child.updateChildren(checked)
      })
    }
  }
})

Example

Sometimes the recursive component may not easy to style, if the shape is not dynamic you could do iterative too.

<div id='app'>
  <template v-if='root'>
    <x-item :item='root' @change='onChange'></x-item>
    <div v-for='group in root.children'>
      <x-item :item='group' @change='onChange'></x-item>
      <div v-for='item in group.children'>
        <x-item :item='item' @change='onChange'></x-item>
      </div>
    </div>
  </template>
</div>

<template id='x-item'>
  <label>
    <input
      type='checkbox'
      :checked='item.checked'
      @change='$emit("change", item, $event.target.checked)'>
    <span>{{ item.name }}</span>
  </label>
</template>
...
  methods: {
    onChange(node, checked) {
      node.checked = checked
      this.updateChildren(node, checked)
      this.updateTree()
    },
    updateChildren(node, checked) {
      if (!node.children) return
      node.children.forEach(child => {
        child.checked = checked
        this.updateChildren(child, checked)
      })
    },
    updateTree() {
      (function update(node) {
        if (!node.children) return
        node.children.forEach(update)
        node.checked = node.children.every(child => child.checked)
      })(this.root)
    }
  }
...

Example

Upvotes: 1

Kamil Bęben
Kamil Bęben

Reputation: 1172

You could define selectAll setter as this:


      set(value) {
        // optionally, if value is false then unselect all
        if (!value) {
            this.selectedGroups = [];
            this.selectedItems = [];
            return;
        }
        // otherwise, select all
        this.selectedGroups = this.groups.map(group => group.id);
        this.selectedItems = this.groups
          .map(group => group.items)
           // java flatMap equivalent
           // converts 2 dimensional array ([[], []]) into 1 dimensional array ([])
          .reduce((all, next) => all.concat(next), [])
          .map(item => item.id)
      }

Upvotes: 0

Related Questions