markus randa
markus randa

Reputation: 305

Are there a way to overwrite increment/decrement-handling for a input of type number?

What i would like to do is manipulate the built-in html input buttons for increment and decrement of numbers. If there is a "vue-way" of doing this, that would of course be preferred.

First of all i'm working on a small vue-app that i've created for learning Vue, what i got now is a Vuex store which contains the state and methods for a shopping cart. I have bound the value item.itemCount seen in the image, to the value of the inputfield. But i would like the increment/decrement buttons to actually update the vuex-state in a proper way.

            <input
              class="slim"
              type="number"
              v-model.number="item.itemCount"
            />

I understand that i can just stop using a input-field, and create my own "count-view" + two buttons, but i'm curious if it's possible to do something like this.

UPDATE Shoppingcart.vue

<template>
  <div class="sliding-panel">
    <span class="header">Shopping Cart</span>
    <table>
      <thead>
        <th>Item Name</th>
        <th>Count</th>
        <th>Remove</th>
      </thead>

      <transition-group name="fade">
        <tr v-for="item in items" :key="item.id">
          <td>{{ item.name }}</td>
          <td>
            <input class="slim" type="number" v-model.number="item.itemCount" />
          </td>
          <td><button @click="removeProductFromCart(item)">Remove</button></td>
        </tr>
      </transition-group>

      <tr>
        Current sum:
        {{
          sum
        }}
        of
        {{
          count
        }}
        products.
      </tr>
    </table>
  </div>
</template>

<script>
import { mapState, mapActions } from "vuex";

export default {
  computed: mapState({
    items: (state) => state.cart.items,
    count: (state) => state.cart.count,
    sum: (state) => state.cart.sum,
  }),
  methods: mapActions("cart", ["removeProductFromCart"]),
};
</script>


<style>
</style>

Upvotes: 3

Views: 464

Answers (1)

Michal Lev&#253;
Michal Lev&#253;

Reputation: 37873

First you don't need to "overwrite increment/decrement handling" in any way. You have the <input> so you need to handle all user inputs changing value - be it inc/dec buttons or user typing value directly...

Proper way of updating Vuex state is by using mutations. So even it's technically possible to bind v-model to some property of object stored in Vuex (as you do), it's not correct "Vuex way"

If there is only single value, you can use computed prop like this:

computed: {
  myValue: {
    get() { return this.$store.state.myValue },
    set(value) { this.$store.commit('changemyvalue', value )} // "changemyvalue" is defined mutation in the store
  }
}

...and bind it to input

<input type="number" v-model="myValue" />

But because you are working with array of values, it is more practical to skip v-model entirely - in the end v-model is just syntactic sugar for :value="myValue" @input="myValue = $event.target.value"

In this case

<input type="number" :value="item.itemCount" min="1" @input="setItemCount({ id: item.id, count: $event.target.value})"/>

...where setItemCount is mutation created to change item count in the cart

Working example:

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    items: [
      { id: 1, name: 'Socks', itemCount: 2},
      { id: 2, name: 'Trousers', itemCount: 1}
    ]
  },  
  mutations: {
    setItemCount(state, { id, count }) {      
      const index = state.items.findIndex((item) => item.id === id);
      if(index > -1) {
        const item = state.items[index]
        item.itemCount = count;
        console.log(`Changing count of item '${item.name}' to ${count}`)
      }
    }
  }
})
const app = new Vue({
  store,
  template: `
  <div>
    <span>Shopping Cart</span>
    <table>
      <thead>
        <th>Item Name</th>
        <th>Count</th>
        <th>Remove</th>
      </thead>

      <transition-group name="fade">
        <tr v-for="item in items" :key="item.id">
          <td>{{ item.name }}</td>
          <td>
            <input type="number" :value="item.itemCount" min="1" @input="setItemCount({ id: item.id, count: $event.target.value})"/>
          </td>
          <td><button>Remove</button></td>
        </tr>
      </transition-group>
    </table>
  </div>
  `,
  computed: {
    ...Vuex.mapState({
      items: (state) => state.items,
    })
  },
  methods: {
    ...Vuex.mapMutations(['setItemCount'])
  }
})
app.$mount("#app")
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.5.1/vuex.min.js"></script>

<div id="app"> </div>

Upvotes: 1

Related Questions