Nikolay Traykov
Nikolay Traykov

Reputation: 1695

Checking checkbox programmatically doesn't render the change when using nested arrays with objects

сеI have an array with categories. Each category has children.

When a parent is checked/unchecked, its children must be checked/unchecked as well. If all children are checked, the parent must be checked as well.

Vue updates the fields as expected, but doesn't re-render. I cannot understand why.

Here is my code:

<template>
    <div class="card">
            <div class="card-body">
                    <ul class="list-tree">
                        <li v-for="category in categories" :key="category.id" v-show="category.show">
                            <div class="custom-control custom-checkbox">
                                <input :id="'category-' + category.id"
                                             v-model="category.checked"
                                             @change="checkParent(category)"
                                             type="checkbox"
                                             class="custom-control-input" />
                                <label class="custom-control-label"
                                             :for="'category-' + category.id">
                                    {{ category.name }}
                                </label>
                            </div>
                            <ul>
                                <li v-for="child in category.children" :key="child.id" v-show="child.show">
                                    <div class="custom-control custom-checkbox">
                                        <input :id="'category-' + child.id"
                                                     v-model="child.checked"
                                                     @change="checkChild(child, category)"
                                                     type="checkbox"
                                                     class="custom-control-input" />
                                        <label class="custom-control-label" :for="'category-' + child.id">
                                            {{ child.name }}
                                            <small class="counter">({{ child.products_count }})</small>
                                        </label>
                                    </div>
                                </li>
                            </ul>
                        </li>
                    </ul>
                </div>
    </div>
</template>

export default {

    data () {
        return {
            categories: [],
            listItemTemplate: { show: true, markedText: null, checked: false }
        }
    },

    methods: {

        checkParent (category) {
            category.children.forEach(child => {
                child.checked = category.checked
            })
        },            

        initializeCategories () {
            this.categories = []

            this.originalCategories.forEach(originalCategory => {
                var parent = this.copyObject(originalCategory)

                this.categories.push(parent)

                parent.children.forEach (child => {
                    child = this.copyObject(child)
                })
            })
        },

        copyObject (category) {
           return Object.assign(category,   {...this.listItemTemplate})
        }
    },

    computed: {
        ...mapState({
             originalCategories: state => state.categories,
        })

     },

     mounted () {
        this.initializeCategories()
     }

}

Upvotes: 2

Views: 574

Answers (2)

Nikolay Traykov
Nikolay Traykov

Reputation: 1695

I fixed it. The problem wasn't related to the checkboxes at all. The problem was related to the way I've created the categories array.

When I initialize the component, I copy the array from vuex and add new properties (like checked) in order to check the children when the parent is checked. I didn't follow the rules for adding new fields, that's why the children wasn't reactive and didn't get checked when the parent was checked.

Thanks a lot for your effort to help me!

Upvotes: 0

Nikola Kirincic
Nikola Kirincic

Reputation: 3757

You need to expand your scope, since you are changing it only within checkParent() method, variables that you are making changes to will not have an effect onto components variables.

Use the index instead of value in categories iteration to find correct category, and then apply changes in scope of whole component:

<li v-for="(category, categoryIndex) in categories" :key="category.id" v-show="category.show">
     <div class="custom-control custom-checkbox">
                                    <input :id="'category-' + category.id"
                                                 v-model="category.checked"
                                                 @change="checkParent(categoryIndex)"
                                                 type="checkbox"
                                                 class="custom-control-input" />
                                    <label class="custom-control-label"
                                                 :for="'category-' + category.id">

                                    {{ category.name }}     
                     </label>
              </div> <!-- the rest of the code ... -->

And then in component's method:

methods: {

    checkParent (categoryIndex) {
       let categoryChecked = this.categories[categoryIndex];
        this.categories[categoryIndex].children.forEach((child, childIndex) => {
            this.categories[categoryIndex].children[childIndex].checked = categoryChecked;
        })
    },     

Upvotes: 1

Related Questions