Last active
July 29, 2021 11:14
-
-
Save Saigesp/e662683ac6c79471de63d91bc2721a2f to your computer and use it in GitHub Desktop.
Vue2 ElementUI custom InputNumber to add "." as thousand separator and "," as decimal separator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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="formattedValue" | |
: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> | |
/** | |
* Component copied from https://github.com/ElemeFE/element/blob/dev/packages/input-number/src/input-number.vue | |
* to handle thousand & decimal separators | |
* | |
* Add this following lines to your main.js: | |
* import InputNumber from '@/components/<your-component-path>/InputNumber.vue'; | |
* Vue.component('el-input-number', InputNumber) // It replaces Element InputNumber component | |
* | |
* Works with Vue 2 | |
*/ | |
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: 'InputNumber', | |
mixins: [Focus('input')], | |
inject: { | |
elForm: { | |
default: '' | |
}, | |
elFormItem: { | |
default: '' | |
} | |
}, | |
directives: { | |
repeatClick: RepeatClick | |
}, | |
components: { | |
ElInput | |
}, | |
props: { | |
step: { | |
type: Number, | |
default: 1 | |
}, | |
stepStrictly: { | |
type: Boolean, | |
default: false | |
}, | |
max: { | |
type: Number, | |
default: Infinity | |
}, | |
min: { | |
type: Number, | |
default: -Infinity | |
}, | |
value: {}, | |
disabled: Boolean, | |
size: String, | |
controls: { | |
type: Boolean, | |
default: true | |
}, | |
controlsPosition: { | |
type: String, | |
default: '' | |
}, | |
name: String, | |
label: String, | |
placeholder: String, | |
precision: { | |
type: Number, | |
validator(val) { | |
return val >= 0 && val === parseInt(val, 10); | |
} | |
}, | |
thousandsSep: { | |
type: String, | |
default: '.', | |
}, | |
decimalSep: { | |
type: String, | |
default: ',', | |
}, | |
}, | |
data() { | |
return { | |
currentValue: 0, | |
userInput: null | |
}; | |
}, | |
watch: { | |
value: { | |
immediate: true, | |
handler(value) { | |
let newVal = value === undefined ? value : Number(value); | |
if (newVal !== undefined) { | |
if (isNaN(newVal)) { | |
return; | |
} | |
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') { | |
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; | |
}, | |
formattedValue() { | |
if (!this.displayValue && this.displayValue !== 0) { | |
return undefined; | |
} | |
let cleanedNumber = this.displayValue; | |
if (typeof this.displayValue !== 'number') { | |
if (this.thousandsRegEx) { | |
// Remove thousands separator | |
cleanedNumber = cleanedNumber.replace(this.thousandsRegEx, '$1$3'); | |
} | |
if (this.decimalRegEx) { | |
// Restore decimal separator | |
cleanedNumber = cleanedNumber.replace(this.decimalRegEx, '.'); | |
} | |
} | |
cleanedNumber = cleanedNumber.toString(); | |
if (this.decimalRegEx) { | |
// Change decimal separator | |
cleanedNumber = cleanedNumber.replace(/\./g, this.decimalSep); | |
} | |
if (this.thousandsRegEx) { | |
// Add thousands separator | |
cleanedNumber = cleanedNumber.replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandsSep); | |
} | |
return cleanedNumber; | |
}, | |
}, | |
methods: { | |
toPrecision(num, precision) { | |
if (precision === undefined) precision = this.numPrecision; | |
return parseFloat(Math.round(num * Math.pow(10, precision)) / Math.pow(10, 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; | |
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) { | |
const cleanedValue = value && this.thousandsRegEx | |
? value.replace(this.thousandsRegEx, '$1$3') | |
: value; | |
this.userInput = cleanedValue; | |
}, | |
handleInputChange(value) { | |
let cleanedValue = value; | |
if (value) { | |
if (this.thousandsRegEx) { | |
// remove thousand separator | |
cleanedValue = cleanedValue.replace(/\./g, '') | |
} | |
if (this.decimalRegEx) { | |
// restore decimal separator; | |
cleanedValue = cleanedValue.replace(this.decimalRegEx, '.') | |
} | |
} | |
const newVal = cleanedValue === '' ? undefined : Number(cleanedValue); | |
if (!isNaN(newVal) || value === '') { | |
this.setCurrentValue(newVal); | |
} | |
this.userInput = null; | |
}, | |
select() { | |
this.$refs.input.select(); | |
} | |
}, | |
created() { | |
this.thousandsRegEx = this.thousandsSep ? new RegExp('(\\d+)(\\' + this.thousandsSep + ')(\\d+)', 'g') : null; | |
this.decimalRegEx = this.decimalSep ? new RegExp('\\' + this.decimalSep, 'g') : null; | |
}, | |
mounted() { | |
const 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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment