1

I'm trying to make a custom input number with + and - spin buttons to increment or decrement the current value received via an http request, if I type numbers directly on the input it changes the value and when I save the content via form POST it works, but if I execute element.stepUp/Down the value seems to change but the received value via POST is the same as the original.

I was trying to trigger an input event with this.$emit('input', newValue) but it does not work because I can not specify the target to be altered. I tried to fire a custom event this.$emit('spin-button', value) but the input is unable to listen to the emmited event with v-on:spin-button="myFunc()".

The v-model cannot be a data variable because is a dynamic form which contains more than 100 inputs.

I only need to dispatch an input event when I click the spin buttons after i make the stepUp/Down to alter the value, but I'm not able to achieve it.

Is there a way to emit an event to a different or sibling element? What is the correct way to do it and change the model value? I've tried with :value but the result is the same.

const app = new Vue({
  methods: {
    showVal(data){
      console.log(data)
    }
  },
  incrementDecrementNumber(action, event) {
			switch (action) {
				case "+": {
					event.target.parentElement.previousElementSibling.stepUp()
					break;
				}
				case "-": {
					event.target.parentElement.nextElementSibling.stepDown()
					break;
				}
			}
		},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">


<div class="input-group mb-3 mx-auto col-md-6 border-dark" v-if="question.type.code === 'NI'">
		<div class="input-group-prepend">
				<button class="btn btn-circle" @click="incrementDecrementNumber('-', $event)">-</button>
		</div>
		<input
			type="number"
			class="form-control text-center form-control-lg"
			@input="showVal($event)"
			name="`test_1_question_5"
			v-model.number="question.stored_data.answer"
		/>
														
		<div class="input-group-append">
			<button class="btn btn-circle" @click="incrementDecrementNumber('+', $event)">+</button>
		</div>
</div>

3
  • Try modifying the question.stored_data.answer property directly. That way it'll propagate to the input element. Commented Mar 25, 2020 at 2:00
  • But I need it to increase the same step the input has. Commented Mar 25, 2020 at 3:17
  • And the number should be validated like the input number does, with min and max values Commented Mar 25, 2020 at 3:20

1 Answer 1

2

You could create your own component to have better control over what's going on. That will also allow you to easily reuse it in other places.

Below is minimal example of how you can accomplish this, but can be expanded upon for better flexibility.

Vue.component('spin-button', {
  template: '#spin-button',
  props: {
    'value': {
      required: true
    }
  },
  computed: {
    internalValue: {
      get() {
        return this.value;
      },
      set(value) {
        this.$emit('input', value)
      }
    }
  },
  methods: {
    incrementDecrementNumber(action) {
      switch (action) {
        case "+":
          this.$refs['input'].stepUp()
          break;
        case "-":
          this.$refs['input'].stepDown()
          break;
      }
      /* Need to manually $emit here since the above methods doesn't trigger our computed set method. */
      this.$emit('input', this.$refs['input'].value)
    }
  }
})

new Vue({
  el: "#app",
  data() {
    return {
      question: {
        stored_data: {
          answer: 5
        }
      }
    }
  },
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

<div id="app">
  <spin-button v-model="question.stored_data.answer" step="5" min="0" max="100">
  </spin-button>
  {{ question.stored_data.answer }}
</div>

<template id="spin-button">
  <div class="input-group">
    <div class="input-group-prepend">
      <button 
        class="btn" 
        @click="incrementDecrementNumber('-')"
      >
        -
      </button>
    </div>
    <input type="number" ref="input" class="form-control text-center" v-model="internalValue" v-bind="$attrs" />
    <div class="input-group-append">
      <button class="btn" @click="incrementDecrementNumber('+')">
        +
      </button>
    </div>
  </div>
</template>

Sign up to request clarification or add additional context in comments.

4 Comments

It seems to work in your snippet. Can you explain me please what does $attrs mean?
$attrs contains the attributes passed from the parent. In this case I'm binding it to the input. That's how the step, min and max props are applied to the input.
So... I dont need to set all the HTML attributes as props? Amazing. There is not a way to do it without a new component?
Correct, you don't need to define all the props you want to use. It it's just normal input attributes, then they should work fine with just v-bind="$attrs". There might be a way to do it without, but i personally think having a component is better for reuseability.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.