zoukankan      html  css  js  c++  java
  • element input-number源码

    input-number.vue

    <template>
      <div
        @dragstart.prevent
        :class="[
          'el-input-number',
          inputNumberSize ? 'el-input-number--' + inputNumberSize : '',
          { 'is-disabled': inputNumberDisabled },
          { 'is-without-controls': !controls },
          { 'is-controls-right': controlsAtRight }
        ]">
        <span
          class="el-input-number__decrease"
          role="button"
          v-if="controls"
          v-repeat-click="decrease"
          :class="{'is-disabled': minDisabled}"
          @keydown.enter="decrease">
          <i :class="`el-icon-${controlsAtRight ? 'arrow-down' : 'minus'}`"></i>
        </span>
        <span
          class="el-input-number__increase"
          role="button"
          v-if="controls"
          v-repeat-click="increase"
          :class="{'is-disabled': maxDisabled}"
          @keydown.enter="increase">
          <i :class="`el-icon-${controlsAtRight ? 'arrow-up' : 'plus'}`"></i>
        </span>
        <el-input
          ref="input"
          :value="displayValue"
          :placeholder="placeholder"
          :disabled="inputNumberDisabled"
          :size="inputNumberSize"
          :max="max"
          :min="min"
          :name="name"
          :label="label"
          @keydown.up.native.prevent="increase"
          @keydown.down.native.prevent="decrease"
          @blur="handleBlur"
          @focus="handleFocus"
          @input="handleInput"
          @change="handleInputChange">
        </el-input>
      </div>
    </template>
    <script>
      import ElInput from 'element-ui/packages/input';
      import Focus from 'element-ui/src/mixins/focus';
      import RepeatClick from 'element-ui/src/directives/repeat-click';
    
      export default {
        name: 'ElInputNumber',
        mixins: [Focus('input')],
        inject: {
          elForm: {
            default: ''
          },
          elFormItem: {
            default: ''
          }
        },
        directives: {
          repeatClick: RepeatClick
        },
        components: {
          ElInput
        },
        props: {
              // 计数器步长
          step: {
            type: Number,
            default: 1
          },
          // 是否只能输入 step 的倍数
          stepStrictly: {
            type: Boolean,
            default: false
          },
          // 设置计数器允许的最大值
          max: {
            type: Number,
            default: Infinity
          },
          // 设置计数器允许的最小值
          min: {
            type: Number,
            default: -Infinity
          },
          // value / v-model    绑定值
          value: {},
          // 是否禁用计数器
          disabled: Boolean,
          // 计数器尺寸    string    large, small
          size: String,
          // 是否使用控制按钮
          controls: {
            type: Boolean,
            default: true
          },
          // 控制按钮位置    string    right
          controlsPosition: {
            type: String,
            default: ''
          },
          // 原生属性
          name: String,
          // 输入框关联的label文字
          label: String,
          // 输入框默认 placeholder
          placeholder: String,
          // 数值精度
          precision: {
            type: Number,
            validator(val) {
              return val >= 0 && val === parseInt(val, 10);
            }
          }
        },
        data() {
          return {
            currentValue: 0,
            userInput: null
          };
        },
        watch: {
          // vaue值变化
          value: {
            // 立即触发
            immediate: true,
            // 自定义函数
            handler(value) {
              let newVal = value === undefined ? value : Number(value);
              if (newVal !== undefined) {
                if (isNaN(newVal)) {
                  return;
                }
                // 是否只能输入 step 的倍数
                if (this.stepStrictly) {
                  const stepPrecision = this.getPrecision(this.step);
                  const precisionFactor = Math.pow(10, stepPrecision);
                  newVal = Math.round(newVal / this.step) * precisionFactor * this.step / precisionFactor;
                }
    
                if (this.precision !== undefined) {
                  newVal = this.toPrecision(newVal, this.precision);
                }
              }
              if (newVal >= this.max) newVal = this.max;
              if (newVal <= this.min) newVal = this.min;
              this.currentValue = newVal;
              this.userInput = null;
              this.$emit('input', newVal);
            }
          }
        },
        computed: {
          // 不能小于最小数
          minDisabled() {
            return this._decrease(this.value, this.step) < this.min;
          },
          // 不能大于最大数
          maxDisabled() {
            return this._increase(this.value, this.step) > this.max;
          },
          numPrecision() {
            const { value, step, getPrecision, precision } = this;
            const stepPrecision = getPrecision(step);
            if (precision !== undefined) {
              if (stepPrecision > precision) {
                console.warn('[Element Warn][InputNumber]precision should not be less than the decimal places of step');
              }
              return precision;
            } else {
              return Math.max(getPrecision(value), stepPrecision);
            }
          },
          // 控制条在右侧
          controlsAtRight() {
            return this.controls && this.controlsPosition === 'right';
          },
          _elFormItemSize() {
            return (this.elFormItem || {}).elFormItemSize;
          },
          inputNumberSize() {
            return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
          },
          inputNumberDisabled() {
            return this.disabled || (this.elForm || {}).disabled;
          },
          displayValue() {
            if (this.userInput !== null) {
              return this.userInput;
            }
    
            let currentValue = this.currentValue;
    
            if (typeof currentValue === 'number') {
              // 如果只能输入 step 的倍数
              if (this.stepStrictly) {
                // 小数点后面几位小数
                const stepPrecision = this.getPrecision(this.step);
                const precisionFactor = Math.pow(10, stepPrecision);
                currentValue = Math.round(currentValue / this.step) * precisionFactor * this.step / precisionFactor;
              }
    
              if (this.precision !== undefined) {
                currentValue = currentValue.toFixed(this.precision);
              }
            }
    
            return currentValue;
          }
        },
        methods: {
          // 截取为传入的位数
          toPrecision(num, precision) {
            if (precision === undefined) precision = this.numPrecision;
            return parseFloat(Number(num).toFixed(precision));
          },
          // 获取小数点后面还有几位
          getPrecision(value) {
            if (value === undefined) return 0;
            const valueString = value.toString();
            const dotPosition = valueString.indexOf('.');
            let precision = 0;
            if (dotPosition !== -1) {
              precision = valueString.length - dotPosition - 1;
            }
            return precision;
          },
          _increase(val, step) {
            if (typeof val !== 'number' && val !== undefined) return this.currentValue;
    
            const precisionFactor = Math.pow(10, this.numPrecision);
            // Solve the accuracy problem of JS decimal calculation by converting the value to integer.
            return this.toPrecision((precisionFactor * val + precisionFactor * step) / precisionFactor);
          },
          // 递减函数
          _decrease(val, step) {
            if (typeof val !== 'number' && val !== undefined) return this.currentValue;
            // eg: 10的0.1次方
            const precisionFactor = Math.pow(10, this.numPrecision);
    
            return this.toPrecision((precisionFactor * val - precisionFactor * step) / precisionFactor);
          },
          increase() {
            if (this.inputNumberDisabled || this.maxDisabled) return;
            const value = this.value || 0;
            const newVal = this._increase(value, this.step);
            this.setCurrentValue(newVal);
          },
          decrease() {
            if (this.inputNumberDisabled || this.minDisabled) return;
            const value = this.value || 0;
            const newVal = this._decrease(value, this.step);
            this.setCurrentValue(newVal);
          },
          handleBlur(event) {
            this.$emit('blur', event);
          },
          handleFocus(event) {
            this.$emit('focus', event);
          },
          setCurrentValue(newVal) {
            const oldVal = this.currentValue;
            if (typeof newVal === 'number' && this.precision !== undefined) {
              newVal = this.toPrecision(newVal, this.precision);
            }
            if (newVal >= this.max) newVal = this.max;
            if (newVal <= this.min) newVal = this.min;
            if (oldVal === newVal) return;
            this.userInput = null;
            this.$emit('input', newVal);
            this.$emit('change', newVal, oldVal);
            this.currentValue = newVal;
          },
          handleInput(value) {
            this.userInput = value;
          },
          handleInputChange(value) {
            const newVal = value === '' ? undefined : Number(value);
            if (!isNaN(newVal) || value === '') {
              this.setCurrentValue(newVal);
            }
            this.userInput = null;
          },
          select() {
            this.$refs.input.select();
          }
        },
        mounted() {
          let innerInput = this.$refs.input.$refs.input;
          innerInput.setAttribute('role', 'spinbutton');
          innerInput.setAttribute('aria-valuemax', this.max);
          innerInput.setAttribute('aria-valuemin', this.min);
          innerInput.setAttribute('aria-valuenow', this.currentValue);
          innerInput.setAttribute('aria-disabled', this.inputNumberDisabled);
        },
        updated() {
          if (!this.$refs || !this.$refs.input) return;
          const innerInput = this.$refs.input.$refs.input;
          innerInput.setAttribute('aria-valuenow', this.currentValue);
        }
      };
    </script>
    repeat-click.js

     

    import { once, on } from 'element-ui/src/utils/dom';
    
    export default {
      bind (el, binding, vnode) {
        let interval = null;
        let startTime;
        /**
         * context是一个 Component 类型的数据结构,这个Component是flow定义的结构,具体可看vue源码中的flow内的内容,
         * Component就是组件,所以这个context就是该vnode所在的组件上下文,再来看 binding.expression, 官网说这就是 
         * v - repeat - click="decrease" 中的decrease方法,这个方法写在组件的methods内,
         * 那么 context[binding.expression] 就是 context['decrease'] 因此就拿到了组件内的decrease方法,
         * 类似于在组件中使用 this.decrease 一样,然后最后的 apply()
        就很奇怪了,apply的用法是参数的第一个表示要执行的目标对象,如果为null或者undefined则表示在window上调用该方法,这里没有参数,
        那就是undefined,所以是在window上执行
         *  */
        const handler = () => vnode.context[binding.expression].apply();
        const clear = () => {
          if (Date.now() - startTime < 100) {
            handler();
          }
          clearInterval(interval);
          interval = null;
        };
    
        on(el, 'mousedown', (e) => {
          /* 
            这个方法就是给元素绑定事件,if-else处理了兼容性的情况, attachEvent 是ie的方法, addEventListener 是其他主流浏览器的方法。
             on 的第三个参数就是事件处理函数, on 中第一句 if (e.button !== 0) return 的 e.button 是按下了鼠标的哪个键
            Element源码分析系列7-InputNumber(数字输入框)
            不等于0则是说明按下的不是左键,因为一般只处理左键的点击事件,注意 onclick 只响应鼠标左键的按下,而 onmousedown
            则响应3个键的按下,所以这里要区分。
    
    
            on 最后一句 interval = setInterval(handler, 100) 设置了定时器定时执行handler方法从而每隔0.1s触发一次数字增加或减少事件,
            然后我们思考,按下去鼠标时给dom元素添加了事件:定时执行handler,那么在鼠标抬起时肯定要销毁这个定时器,否则将会无限触发handler方法,
            造成数字一直增加或减少,因此 once(document, 'mouseup', clear) 这句话就是在鼠标抬起时销毁定时器,先看clear方法
            里面就是clearInterval销毁定时器,前面的if逻辑很关键,在按下鼠标时记录一个时间,抬起鼠标时检测当前时间 - 按下时的时间
             < 100毫秒,如果是则触发一次点击,如果不写这个if,则无法实现单击操作,因为如果不写,由于interval = setInterval(handler, 100),
             在按下后100毫秒后才会触发一次点击,则在100毫秒内抬起鼠标时interval已经被clear了。最后注意下 once(document, 'mouseup', clear) ,
              once 是只触发一次的高阶函数
          */
          if (e.button !== 0) return;
          startTime = Date.now();
          once(document, 'mouseup', clear);
          clearInterval(interval);
          interval = setInterval(handler, 100);
        });
      }
    };
  • 相关阅读:
    随机数模块(random)
    时间模块(time)
    collection模块 1
    collection模块
    re模块
    正则
    Bootstrap 关于下拉菜单的使用
    Bootstrap 关于Glyphicons 字体图标的使用
    oracle拼音排序
    ajax缓存问题
  • 原文地址:https://www.cnblogs.com/wsk1576025821/p/10954835.html
Copyright © 2011-2022 走看看