Search code examples
javascriptcheckboxvue.jsvuex

Vuex dynamic checkboxes binding


I have a problem with binding checkboxes using Vuex. On checkbox I use v-model with variable which has getter and setter to set or get value in store, the problem is that I get wrong data in store and I don't understand what cause the problem. Checkboxes bind to store property and this property must contain array of id's from checkboxes, but when I click checkbox more than one time it rewrite or remove store values. Can anyone help me to understand why does this happens? Link to jsFiddle.

The code

const store = new Vuex.Store({
state: {
checkboxes: {},
checked: {}
},
mutations: {
  setCheckboxes(state, dataObj){
    console.log(dataObj);
    state.checkboxes = dataObj.data;
    let firstElem = dataObj.data[Object.keys(dataObj.data)[0]];
    state.checked[firstElem.parent_id] = [firstElem.id];
    console.log(state.checked);
  },
  setTreeState(state, dataObj){
    state.checked[dataObj.id] = dataObj.value;
    console.log(state.checked);
  }
 }
});


Vue.component('checkboxTree', {
  template: "#checkboxTree",
});

Vue.component('checkboxToggle', {
  template: "#checkboxToggle",
  data(){
    return {
      store
    }
  },
  computed: {
    value:{
      get(){ 
        return store.state.checked[this.checkbox.parent_id];
      },
      set(val){ 
        store.commit({
        type: 'setTreeState',
        id: this.checkbox.parent_id,
        value: val
      });
    },
  },
},
props: ['checkbox']
});

const app = new Vue({
  el: "#app",
  store,
  data: {
    checkboxData: {
    ...
    },

  },
  mounted(){
    this.$store.commit({
      type: 'setCheckboxes',
      data: this.checkboxData
    });
  }
})

Template

<div id="app">
  <checkbox-tree :checkboxData="checkboxData"></checkbox-tree>
</div>    

<template id="checkboxTree">
  <div>
    <p>checkbox tree</p>
  <form>
   <ul>
     <li v-for="checkbox in $store.state.checkboxes">
       <checkbox-toggle :checkbox="checkbox"></checkbox-toggle>
     </li>
   </ul>
  </form>
  </div>
</template>

<template id="checkboxToggle">
  <div>
  <label>{{ checkbox.id }}</label>
   <input type="checkbox" 
    :value="checkbox.id"
    :id="'checkbox-' + checkbox.id"
    :name="'checkbox-' + checkbox.id"
    v-model="value"
    >
  </div>
</template>

Solution

  • Okay, assuming you want checked to contain ids of selected objects, I had to restructure your code significantly:

    const removeFromArray = (array, value) => {
    	const newArray = [...array];
      const index = newArray.indexOf(value);
      if (index > -1) {
        newArray.splice(index, 1);
        return newArray;
      }
      return array;
    }
    
    const store = new Vuex.Store({
      state: {
        checkboxes: {},
        checked: [],
      },
      mutations: {
      	addToChecked(state, id) {
        	state.checked.push(id);
        },
    		removeFromChecked(state, id) {
          const newArray = removeFromArray(state.checked, id);
          state.checked = newArray;
        },
        setCheckboxes(state, data) {
          state.checkboxes = data;
        },
      }
    });
    
    Vue.component('checkboxTree', {
      template: "#checkboxTree",
      computed: {
        checkboxes() {
        	return this.$store.state.checkboxes;
        },
      },
    });
    
    Vue.component('checkboxToggle', {
      template: "#checkboxToggle",
    	computed: {
        value:{
          get(){
            return this.$store.state.checked.indexOf(this.checkbox.id) > -1;
          },
          set(val){
            const mutation = val ? 'addToChecked' : 'removeFromChecked';
            this.$store.commit(mutation, this.checkbox.id);
          },
        },
      },
      props: ['checkbox'],
    });
    
    const app = new Vue({
      el: "#app",
      store,
      data: {
        checkboxData: {
          "5479": {
            "id": 5479,
            "title": "Место оказания услуг",
            "type": "checkbox",
            "dependencies": "",
            "description": "",
            "parent_id": 5478,
            "npas": ""
          },
          "5480": {
            "id": 5480,
            "title": "Способы оказания услуг",
            "type": "checkbox",
            "dependencies": "",
            "description": "",
            "parent_id": 5478,
            "npas": "50"
          },
          "5481": {
            "id": 5481,
            "title": "Объем и порядок содействия Заказчика в оказании услуг",
            "type": "checkbox",
            "dependencies": "",
            "description": "",
            "parent_id": 5478,
            "npas": "54"
          },
        }
      },
      computed: {
      	stateRaw() {
        	return JSON.stringify(this.$store.state, null, 2);
        },
      },
      mounted() {
        this.$store.commit('setCheckboxes', this.checkboxData);
        const firstElementKey = Object.keys(this.checkboxData)[0];
        const firstElement = this.checkboxData[firstElementKey];
        this.$store.commit('addToChecked', firstElement.id);
      }
    })
    <script src="https://unpkg.com/vue"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
    
    <div id="app">
      <checkbox-tree :checkboxData="checkboxData"></checkbox-tree>
      <pre v-text="stateRaw"></pre>
    </div>
    
    <template id="checkboxTree">
      <div>
        <p>checkbox tree</p>
        <form>
          <ul>
            <li v-for="checkbox in checkboxes">
              <checkbox-toggle :checkbox="checkbox"></checkbox-toggle>
            </li>
          </ul>
        </form>
      </div>
    </template>
    
    <template id="checkboxToggle">
      <div>
       <label>{{ checkbox.id }}</label>
       <input 
         type="checkbox" 
         :value="checkbox.id" 
         :id="'checkbox-' + checkbox.id" 
         :name="'checkbox-' + checkbox.id"
         v-model="value">
        {{value}}
      </div>
    </template>

    Using this code as an example, you can populate checked however you want to.

    Also, a jsfiddle link for you: https://jsfiddle.net/oniondomes/ckj7mgny/