Souames
Souames

Reputation: 1155

How to select checked checkboxes inside multiple divs with VueJS

I'm relatively new to VueJS, and I'm facing a problem which would have been easy to solve with jQuery but I can't find away with VueJS even though I'm sure there is.

I have a list of paths selected by the user, for each path, I'm creating a div with 3 checkbox in that div : add, edit and delete.

Suppose I have 2 paths : '/foo' and '/bar', for these 2 paths I'm creating 3 checkboxes : add, edit and delete for each path. If the user check add, edit and the first path, and checks only delete on the second path, then I would like to programatically build the following object:

[
{path: '/foo', actions: ["add","edit"]}, 
{path: '/bar' , actions: ["delete"]}
]

The function to create this object is called at the end when the user press a final submit button, here's some of the related code:

In the component template:

<li v-for = "selectedPath in newProfile.paths">
                <Card :card-name="selectedPath">
                    <h5>{{selectedPath}}</h5>
                    <base-checkbox :data-path="selectedPath" type = "default" name = "add">Ajout</base-checkbox>
                    <base-checkbox :data-path="selectedPath" name = "edit">Edition</base-checkbox>
                    <base-checkbox :data-path="selectedPath" name = "delete">Supression</base-checkbox>
                </Card>
            </li>

The JS code contains vanilla JS to select the checkboxes but the problem is that I'm using component checkboxes that came with a template I'm using so no way to get the checked attribute unless you get what's inside each checkbox component and this becomes quickly ugly and non-vuejs way to do it.

So How can I achieve this (building that object based on checked boxes in each div) ?

Upvotes: 0

Views: 685

Answers (1)

skirtle
skirtle

Reputation: 29132

You need to listen for events on your base-checkbox components and update suitable data structures accordingly. In Vue the DOM is secondary, your primary source of truth is in JavaScript data.

There are many different ways to approach this depending on what data structures make most sense in a given scenario. To quote The Mythical Man-Month:

Representation is the essence of programming.

With that in mind, I'm not convinced that the example I've constructed below is necessarily using the best data structures to represent the data, or even that I'm using those data structure as well as they can be used.

Some notes:

  • The computed property out holds the data in the finished format, the one used when the submit button is clicked. I haven't included a submit button, I've just dumped that data out so you can see it.
  • Keeping paths and selectedPaths separate is not strictly required but it seemed closer to your original code, with paths being analogous to newProfile.paths.
  • The format within selectedPaths is {path1: {add: true, edit: false, delete: true}, path2: ...}. The properties are created lazily, defaulting to all checkboxes being false.
  • Because selectedPaths is initially created empty its properties won't be reactive. That's why $set is being used. If it were possible to prepopulate this object within the data method it wouldn't be necessary to use $set.
  • I've used HTML <input> elements for my checkboxes but the approach would be exactly the same with a checkbox component. A prop passes in the current value and an event is used to update the data when the value changes.
  • When using a v-for like this, it's often simpler to split off a separate child component for the children. Rather than manipulating complex data structures in the parent, some of the work can be offloaded onto each child. I haven't done that here as I was trying to keep everything within one component but it is something I would explore if I were doing this for real.
<template>
    <div>
        <ul>
            <li v-for="path in pathsWithSelections" :key="path.path">
                {{ path.path }}
                <input type="checkbox" :checked="path.add" @input="onChecked(path.path, 'add')">
                <input type="checkbox" :checked="path.edit" @input="onChecked(path.path, 'edit')">
                <input type="checkbox" :checked="path.delete" @input="onChecked(path.path, 'delete')">
            </li>
        </ul>
        {{ out }}
    </div>
</template>

<script>
    export default {
        data () {
            return {
                paths: ['path1', 'path2', 'path3'],
                selectedPaths: {}
            }
        },

        computed: {
            pathsWithSelections () {
                return this.paths.map(path => {
                    const selected = this.selectedPaths[path] || {}

                    return {
                        path,
                        ...selected
                    }
                })
            },

            out () {
                const out = []

                for (const path of this.pathsWithSelections) {
                    const actions = []

                    for (const action of ['add', 'edit', 'delete']) {
                        if (path[action]) {
                            actions.push(action)
                        }
                    }

                    if (actions.length) {
                        out.push({
                            path: path.path,
                            actions
                        })
                    }
                }

                return out
            }
        },

        methods: {
            onChecked (path, action) {
                const selectedPaths = this.selectedPaths

                const selected = selectedPaths[path] || {}

                this.$set(selectedPaths, path, selected)
                this.$set(selected, action, !selected[action])
            }
        }
    }
</script>

Upvotes: 0

Related Questions