Passing an array of checkboxes in Vue.js to the parent template results in only one value being passed

After exploring various options for similarities, like the one found here, I have not been able to find a solution to my particular issue.

The situation at hand involves a component consisting of organizations with labels and checkboxes attached to a v-model. This component will be utilized in conjunction with other form components. Currently, it is functioning correctly, but it only returns one value to the parent even when both checkboxes are clicked.

Form page:

<template>
  <section>
    <h1>Hello</h1>
    <list-orgs v-model="selectedOrgs"></list-orgs>
    <button type="submit" v-on:click="submit">Submit</button>
  </section>
</template>

<script>
// eslint-disable-next-line
import Database from '@/database.js'
import ListOrgs from '@/components/controls/list-orgs'

export default {
  name: 'CreateDb',
  data: function () {
    return {
      selectedOrgs: []
    }
  },
  components: {
    'list-orgs': ListOrgs,
  },
  methods: {
    submit: function () {
      console.log(this.$data)
    }
  }
}
</script>

Select Orgs Component

<template>
  <ul>
    <li v-for="org in orgs" :key="org.id">
      <input type="checkbox" :value="org.id" name="selectedOrgs[]" v-on:input="$emit('input', $event.target.value)"  />
      {{org.name}}
    </li>
  </ul>
</template>

<script>
import {db} from '@/database'

export default {
  name: 'ListOrgs',
  data: () => {
    return {
      orgs: []
    }
  },
  methods: {
    populateOrgs: async function (vueObj) {
      await db.orgs.toCollection().toArray(function (orgs) {
        orgs.forEach(org => {
          vueObj.$data.orgs.push(org)
        })
      })
    }
  },
  mounted () {
    this.populateOrgs(this)
  }
}
</script>

There are currently two mock organizations in the database with IDs 1 and 2. Upon selecting both checkboxes and submitting, the selectedOrgs array only contains 2 as if the second click replaced the first. Testing by checking only one box reveals that either 1 or 2 gets passed. It appears that the array method functions at the component level but not at the component-to-parent level.

Any assistance would be greatly appreciated.

UPDATE

Upon following the advice from puelo's comment, I modified my orgListing component to emit the array linked to the v-model like so:

export default {
  name: 'ListOrgs',
  data: () => {
    return {
      orgs: [],
      selectedOrgs: []
    }
  },
  methods: {
    populateOrgs: async function (vueObj) {
      await db.orgs.toCollection().toArray(function (orgs) {
        orgs.forEach(org => {
          vueObj.$data.orgs.push(org)
        })
      })
    },
    updateOrgs: function () {
      this.$emit('updateOrgs', this.$data.selectedOrgs)
    }
  },
  mounted () {
    this.populateOrgs(this)
  }
}

At the receiving end, I am simply performing a console log on the returned value. Though this "works," one drawback is that the $emit seems to trigger before the selectedOrgs value has been updated, resulting in it always being one step behind. Ideally, I want the emit to wait until the $data object has been fully updated. Is there a way to achieve this?

Answer №1

Big shoutout to @puelo for the assistance, it definitely shed some light on a few things although it didn't completely resolve my issue. I was looking for a way to use v-model on checkboxes to populate an array and then send that data up to the parent component while still keeping everything encapsulated.

So, I decided to tweak things a bit:

Choose Organizations Component

<template>
  <ul>
    <li v-for="org in orgs" :key="org.id">
      <input type="checkbox" :value="org.id" v-model="selectedOrgs" name="selectedOrgs[]" v-on:change="updateOrgs" />
      {{org.name}}
    </li>
  </ul>
</template>

<script>
import {db} from '@/database'

export default {
  name: 'ListOrgs',
  data: () => {
    return {
      orgs: []
    }
  },
  methods: {
    fetchOrgs: async function (vueObject) {
      await db.orgs.toCollection().toArray(function (orgs) {
        orgs.forEach(org => {
          vueObject.$data.orgs.push(org)
        })
      })
    },
    updateOrgs: function () {
      this.$emit('updateOrgs', this.$data.selectedOrgs)
    }
  },
  mounted () {
    this.fetchOrgs(this)
  }
}
</script>

Submit Form Page

<template>
  <section>
    <h1>Hello</h1>
    <list-orgs v-model="selectedOrgs" v-on:updateOrgs="updateSelectedOrgs"></list-orgs>
    <button type="submit" v-on:click="submit">Submit</button>
  </section>
</template>

<script>
// eslint-disable-next-line
import Database from '@/database.js'
import ListOrgs from '@/components/controls/list-orgs'

export default {
  name: 'CreateDb',
  data: function () {
    return {
      selectedOrgs: []
    }
  },
  components: {
    'list-orgs': ListOrgs
  },
  methods: {
    updateSelectedOrgs: function (org) {
      console.log(org)
    },
    submit: function () {
      console.log(this.$data)
    }
  }
}
</script>

The key change here is that now I trigger the `updateOrgs` method when a checkbox is clicked, passing the entire `selectedOrgs` array using `this.$emit('updateOrgs', this.$data.selectedOrgs)`.

This utilizes v-model to manage the checked status of each checkbox in the array. Then, on the form page, I simply listen for this event within the component using `v-on:updateOrgs="updateSelectedOrgs"`, which includes the populated array and maintains encapsulation.

Answer №2

When it comes to form binding, the documentation for custom components still applies just like with v-model:

v-model is essentially syntax sugar for updating data on user input events...

If you're interested, check out these links for more information: https://v2.vuejs.org/v2/guide/forms.html#Basic-Usage and https://v2.vuejs.org/v2/guide/components-custom-events.html#Customizing-Component-v-model

For example, in your code snippet:

<list-orgs v-model="selectedOrgs"></list-orgs>

it can be translated to:

<list-orgs :value="selectedOrgs" @input="selectedOrgs = $event.target.value"></list-orgs>

This means that each emit inside

v-on:input="$emit('input', $event.target.value)
is actually overwriting the array with only a single value: the state of the checkbox.

To address a specific comment:

You might consider avoiding the use of v-model entirely and instead listen to an event like

@orgSelectionChange="onOrgSelectionChanged"
.

Then, you could emit an object containing the checkbox state and org ID (to prevent duplicates):

v-on:input="$emit('orgSelectionChanged', {id: org.id, state: $event.target.value})"

And on the receiving end, the method would check for duplicates like so:

onOrgSelectionChanged: function (orgState) {
    const index = selectedOrgs.findIndex((org) => { return org.id === orgState.id })
    if (index >= 0) selectedOrgs.splice(index, 1, orgState)
    else selectedOrgs.push(orgState)
}

This solution is quite basic and untested, but it should provide some insight into how you could potentially solve this issue.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Including a JavaScript file in an HTML document initiates a server request, which can lead to potential errors

I am currently developing a web application using Express and Node.js on the backend, with EJS as the templating engine on the frontend. Here is a snippet of my app.js file: app.get('/book/:id', (req, res)=>{ var book_id = req.params.id; cons ...

Tips for creating a column with add, edit, and delete buttons in PHP

I found this code on a website that handles pagination and search functionality. However, I am looking to enhance it by adding an icon or button in a column that will allow users to link to the edit and delete functions for specific data selected in the ta ...

Using jQuery to verify the status of every input when the page loads

My jQuery price calculator is missing a feature to check if inputs are selected when the page loads. Currently, it only recognizes that Apple is checked after clicking on Banana. How can I modify it to automatically check all inputs at the start? I attem ...

Is there a way to create a function in JavaScript that can iterate through all node elements within a node collection?

Trying to ask a question in English when only having half knowledge of the language can be quite confusing. Therefore, I aim to create a function that can differentiate between a single node or a collection of nodes as its parameter and then retrieve the d ...

"Enhance your data management with a dynamic React JS table

While attempting to modify the table from the react js docs linked here, I encountered some unexpected issues as shown in the screenshots below. https://i.sstatic.net/u2ck6.png We are trying to filter based on "lBird" https://i.sstatic.net/vCta4.png Th ...

Restrict the number of items in each list to just one

I'm trying to customize a query that displays a list of numbers. My goal is to only display each unique number once. For example, if there are three instances of the number 18 in the database, I want it to show as 18, 18, 18. If there are two occurre ...

What is the best way to direct or link the search filter to perform searches in a different Vue component?

How can I link the search function of one search component to the search filter of another component? Code snippet for the first component: <input type="text" class="searchTerm" placeholder="What are you looking for?" v-mo ...

What is preventing me from extracting HTML content from a cookie using JavaScript?

I've encountered an issue where I saved HTML code to an HTTP cookie generated through JavaScript, but when attempting to parse it using JavaScript, the code gets cut off at the first double quote. Here's an example: function loadBasket() { co ...

Creating multiple Vue apps under one project can be a great way to manage different sections

Recently, I started learning Vue.js and working on an e-commerce project using Firebase. While exploring Firebase, I discovered the multiple hosting features it offers which could assist me in hosting the client and admin sides on two different domains. ...

Find out whether a point lies inside a specific part of the surface of a sphere

As I delve into working with webGL using Three.js, I have encountered the need to determine if a click on a sphere falls within a specific section of its surface. Currently, I am able to detect when the sphere is clicked and retrieve the coordinates of th ...

Creating a quiz with just one question in React: A step-by-step guide

How can I add the functionality to handle correct and incorrect answers? I've designed a single-question quiz with multiple options. The Quiz component has been created. See the code snippet below: Quiz Component export default function Quiz(props) { ...

Issue with Node.js connection to MySQL database resulting in timeout error (ETIMEDOUT)

I am attempting to establish a basic connection to an online mysql database using a node.js server. Here is the code I have written for this purpose: var mysql = require('mysql'); var con = mysql.createConnection({ host: 'example.org& ...

Customizing Checkbox using CSS (additional assistance required)

I am currently working on implementing Checkbox Four with a custom design. You can check out the example I found on this website: My goal is to enhance this checkbox by incorporating red colored text 'N' when it's unchecked, and blue colore ...

Trying to assign a value to 'currentStatus' using "this" on an undefined property

In my attempt to display the state of formSubmit in vue js, I've encountered an issue. My lack of familiarity with using "this" has led to errors in the code, particularly when trying to indicate the status using "this.currentStatus". This is the cod ...

What steps should be taken to switch a class from one li element to another and remove it in the process?

As I develop the navigation bar for my website, I am faced with a challenge. I want to create a button-like behavior where clicking on an li element triggers a drop-down section underneath it without using the # attribute to prevent jumping to the top of t ...

Having trouble with Angular + Rails combination: Angular can't seem to locate the controller

Issue: I am encountering an error in my Angular / Rails app. When trying to access a Rails controller function from the Angular controller, I receive the following error: Uncaught TypeError: tasks.moveOrder is not a function. Strangely, all other functions ...

Issue with the Animated Skill Bar not functioning properly when scrolling

I've been trying to implement an animated skill bar in my Portfolio using JQuery, but I'm facing some challenges. Despite following various tutorials, the code doesn't seem to work as expected. I've tried calculating section scroll posi ...

One can use basic JavaScript or jQuery code for a straightforward solution

I am facing an issue. While I have a good grasp of HTML, CSS, PHP, and design, I lack knowledge in JavaScript and jQuery. There's a simple jQuery function that I need to implement, but I am unsure about how to go about it. The task at hand is to acce ...

Can background images be lazily loaded using react-lazyload?

I've developed a Section component that accepts an image as a property and its children to be displayed within the section. Here's how you can use this component... <Section image={'path/to/image'}> //content </Section> T ...

Experiencing difficulty retrieving the variable within a NodeJs function

Currently, I am utilizing the NodeJS postgresql client to retrieve data, iterate through it and provide an output. To accomplish this, I have integrated ExpressJS with the postgresql client. This is a snippet of my code var main_data = an array conta ...