How to apply min/max attribute to v-model in Vue?

2020-07-22 18:25发布

Current constraint of min and max are not respected due to the way v-on is implemented:

<input id="passwordLength"
       class="form-control form-control-sm"
       type="number"
       min="5"
       max="35"
       v-model="options.length">
<span class="input-group-btn" v-on:click="options.length+=1">
  <button class="btn btn-secondary" type="button">
    <i class="fa fa-plus"></i>
  </button>
</span>

Question

How can I respect the constrain and still keep an elegant implementation?

3条回答
仙女界的扛把子
2楼-- · 2020-07-22 18:56

Use @Focus to apply a max & min number validation to your :rules. Make sure to set the max with large default number. Both max & min default will be updated upon focusing on that specific input box. (This text-fields are being created with a loop)

<v-text-field type="number" 
 @focus="validateWhenFocused(item)"
 :rules="numberRules"
 :label="item.questionName"
 v-model="item.value"
 outline>
</v-text-field>

export default {
 data() {
  return {
   numberRules: [
    v => !!v || "Input is required!",
    v =>
      v < this.maxLength ||
      `${this.errorName} must be less than ${this.maxLength} numbers`,
    v =>
      v > this.minLength ||
      `${this.errorName} must be greater than ${this.minLength} numbers`
   ],
   maxLength: 100,
   minLength: 0,
   errorName: "",
  },
  methods: {
   validateWhenFocused(item){
    this.maxLength = item.maxValue
    this.minLength = item.minValue;
    this.errorName = item.questionName

    }
  }
}
查看更多
倾城 Initia
3楼-- · 2020-07-22 18:57

I did something basic:

Usage

<span class="input-group-btn" 
    v-on:click="options.length=decrement(options.length, {min: 5, max: 35})">
    <button class="btn btn-secondary" type="button">
        <i class="fa fa-minus"></i>
    </button>
</span>

Service Tests

import test from "ava";
import formValidator from "../src/services/form-validator";

test("formValidator.increment()", t => {
  t.is(formValidator.increment(1, { min: 0, max: 10 }), 2);
  t.is(formValidator.increment(9, { min: 0, max: 10 }), 10);
  t.is(formValidator.increment(10, { min: 0, max: 10 }), 10);
  t.is(formValidator.increment(-1, { min: 0, max: 10 }), 0);
  t.is(formValidator.increment(-5, { min: 0, max: 10 }), 0);
  t.is(formValidator.increment(5, { min: 0 }), 6);
});

test("formValidator.decrement()", t => {
  t.is(formValidator.decrement(2, { min: 0, max: 10 }), 1);
  t.is(formValidator.decrement(1, { min: 0, max: 10 }), 0);
  t.is(formValidator.decrement(0, { min: 0, max: 10 }), 0);
  t.is(formValidator.decrement(-1, { min: 0, max: 10 }), 0);
  t.is(formValidator.decrement(15, { min: 0, max: 10 }), 10);
});

Service Code

export default {
  increment(value, { min = 0, max }) {
    let newValue = value + 1;

    if (newValue < min) return min;
    if (typeof max === "undefined" || newValue <= max) return newValue;
    return value;
  },
  decrement(value, { min, max }) {
    let newValue = value - 1;

    if (newValue < min) return min;
    if (newValue > max) return max;
    if (newValue >= min) return newValue;
    return value;
  }
};
查看更多
甜甜的少女心
4楼-- · 2020-07-22 19:15

You could add a custom modifier to the v-model directive:

// function that gets the min and max values for the element and prevents the value of the model from going below the min or above the max
function bindNumberInRange(el, binding, vnode) {
  let model = binding.expression;
  let min = parseInt(el.min);
  let max = parseInt(el.max);
  let val = parseInt(binding.value);

  if ((min !== NaN) && (min >= val)) {
    vnode.context[model] = min;
  } else if ((max !== NaN) && (max <= val)) {
    vnode.context[model] = max;
  }

  el.value = val;
}

// get the original reference to the v-model directive
let modelDirective = Vue.directive('model')

// set a new definition of the v-model directive
Vue.directive('model', {
  bind: function(el, binding, vnode) { 
    // first fire the original v-model bind hook
    modelDirective.bind(el, binding, vnode);

    if (binding.modifiers.range) {
      bindNumberInRange(el, binding, vnode)
    }
  },
  componentUpdated: function(el, binding, vnode) {
    // first fire the original v-model componentUpdated hook
    modelDirective.componentUpdated(el, binding, vnode);

    if (binding.modifiers.range) {
      bindNumberInRange(el, binding, vnode)
    }
  }
})

Then, all you would need to do is add a .range modifier to v-model when you want the model to respect the min and max attributes of the affected element:

<input type="number" min="4" max="10" v-model.range="foo">

Here's a CodePen Example.

Here's Vue's (semi-lacking) documentation on directives.

查看更多
登录 后发表回答