Reputation: 665
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
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
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)
})
}
}
})
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)
}
}
...
Upvotes: 1
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