vuejs passing array of checkboxes to parent templa

2019-08-18 03:36发布

I looked at potential dupes of this, such as this one and it doesn't necessarily solve my issue.

My scenario is that I have a component of orgs with label and checkbox attached to a v-model. That component will be used in combination of other form components. Currently, it works - but it only passes back one value to the parent even when both checkboxes are click.

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>

Currently there are two fake orgs in the database with an ID of 1 and 2. Upon clicking both checkboxes and clicking the submit button, the selectedOrgs array only contains 2 as though the second click actually over-wrote the first. I have verified this by only checking one box and hitting submit and the value of 1 or 2 is passed. It seems that the array method works at the component level but not on the component to parent level.

Any help is appreciated.

UPDATE

Thanks to the comment from puelo I switched my orgListing component to emit the array that is attached 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)
  }
}

Then on the other end I am merely console.log() the return. This "works" but has one downside, it seems that the $emit is being fired before the value of selectedOrgs has been updated so it's always one "check" behind. Effectively,I want the emit to wait until the $data object has been updated, is this possible?

2条回答
做个烂人
2楼-- · 2019-08-18 04:17

Thank you so much to @puelo for the help, it helped clarify some things but didn't necessarily solve my problem. As what I wanted was the simplicity of v-model on the checkboxes populating an array and then to pass that up to the parent all while keeping encapsulation.

So, I made a small change:

Select Orgs 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: {
    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)
  }
}
</script>

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>

What the primary change here is I now fire a method of updateOrgs when the checkbox is clicked and I pass the entire selectedOrgs array via the this.$emit('updateOrgs', this.$data.selectedOrgs)`

This takes advantage of v-model maintaining the array of whether they're checked or not. Then on the forms page I simply listen for this event on the component using v-on:updateOrgs="updateSelectedOrgs" which contains the populated array and maintains encapsulation.

查看更多
smile是对你的礼貌
3楼-- · 2019-08-18 04:37

The documentation for v-model in form binding still applies to custom components, as in:

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

https://vuejs.org/v2/guide/forms.html#Basic-Usage and https://vuejs.org/v2/guide/components-custom-events.html#Customizing-Component-v-model

So in your code

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

gets 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.

EDIT to address the comment:

Maybe don't use v-model at all and only listen to one event like @orgSelectionChange="onOrgSelectionChanged".

Then you can emit an object with the state of the checkbox and the id of the org (to prevent duplicates):

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

And finally the method on the other end check for duplicates:

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 is very basic and not tested, but should give you an idea of how to maybe solve this.

查看更多
登录 后发表回答