6

I'm trying to create a numeric input component in Vue with min and max values that doesn't allow to type outside outside limits without success:

<template id="custom-input">
  <div>
    <input :value="value" type="number" @input="onInput">
  </div>
</template>

<div id="app">
  <div>
    <span>Value: {{ value }}</span>
    <custom-input v-model="value" :max-value="50"/>
  </div>
</div>
Vue.component('custom-input', {
  template: '#custom-input',
  props: {
    value: Number,
    maxValue: Number
  },
  methods: {
    onInput(event) {
      const newValue = parseInt(event.target.value)
      const clampedValue = Math.min(newValue, this.maxValue)
      this.$emit('input', clampedValue)
    }
  }
})

new Vue({
  el: "#app",
  data: {
    value: 5
  }
})

Fiddle here: https://jsfiddle.net/8dzhy5bk/6/

In the previous example, the max value is set in 50. If I type 60 it's converted automatically to 50 inside the input, but if I type a third digit it allow to continue typing. The value passed to the parent is clamped, but I also need to limit the input so no more digits can be entered.

5
  • Have you tried assigning min & max in your input tag? Commented Sep 27, 2018 at 17:45
  • Yes, but doesn't work Commented Sep 27, 2018 at 18:00
  • According to MDN all browsers (except possibly Edge?) fully support number input elements, including min and max attributes. Why do you say they don't work Commented Sep 27, 2018 at 18:28
  • @StephenThomas, because I tried that before and didn't work. Try this updated fiddle with max="50" in the input tag and type 500 jsfiddle.net/8dzhy5bk/9 Commented Sep 27, 2018 at 19:02
  • @StephenThomas according to MDN: "You can still manually enter a number outside these bounds, but it will be considered invalid." That's the behavior I wanted to change, disallow the user to enter a value outside the bounds. Commented Sep 27, 2018 at 19:14

1 Answer 1

7

When the value of input is great than 10, it will always emit 10 to parent component, but the value keeps same (always=10) so it will not trigger reactvity.

One solution, always emit actual value (=parseInt(event.target.value)) first, then emit the max value (=Math.min(newValue, this.maxValue)) in vm.$nextTick()

Another solution is use this.$forceUpdate().

Below is the demo for $nextTick.

Vue.component('custom-input', {
  template: '#custom-input',
  props: {
    value: Number,
    maxValue: Number
  },
  methods: {
    onInput(event) {
      const newValue = parseInt(event.target.value)
      const clampedValue = Math.min(newValue, this.maxValue)
      this.$emit('input', newValue)
      this.$nextTick(()=>{
      	this.$emit('input', clampedValue)
      })
    }
  }
})

new Vue({
  el: "#app",
  data: {
    value: 5
  },
  methods: {
  }
})
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>

<template id="custom-input">
  <div>
    <input 
      :value="value" 
      type="number" 
      @input="onInput" 
     >
  </div>
</template>

<div id="app">
  <div>
    <span>Value: {{ value }}</span>
    <custom-input v-model="value" :max-value="10"/>
  </div>
</div>

Below is the demo for vm.$forceUpdate.

Vue.component('custom-input', {
  template: '#custom-input',
  props: {
    value: Number,
    maxValue: Number
  },
  methods: {
    onInput(event) {
      const newValue = parseInt(event.target.value)
      const clampedValue = Math.min(newValue, this.maxValue)
      this.$emit('input', clampedValue)
      this.$forceUpdate()
    }
  }
})

new Vue({
  el: "#app",
  data: {
    value: 5
  },
  methods: {
  }
})
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>

<template id="custom-input">
  <div>
    <input 
      :value="value" 
      type="number" 
      @input="onInput" 
     >
  </div>
</template>

<div id="app">
  <div>
    <span>Value: {{ value }}</span>
    <custom-input v-model="value" :max-value="10"/>
  </div>
</div>

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

3 Comments

$forceUpdate() worked. This is the updated fiddle jsfiddle.net/8dzhy5bk/7. I noted that I posted a previous fiddle in my original question with some errors.
@Camilo, vm.$nextTick() also works, check this fiddle
Your edit with :max="maxValue" doesn't work. Try typing 100. The expected result is 10 in the input field, not 100. Your previous answer worked well.

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.