We have Vue 2 app and there is simple modal dialog with text input fields. One of them (OKATO) is field where we should type 11 characters or we can paste data. All characters must be digits (no blank spaces as well).
The code looks as following:
The modal dialog
<div class="v-add-dialog-city__grid__okato">
<v-fieldset :label="$r('okato')">
<v-digits-text-box v-model="entity.Okato" :placeholder="$r('placeholder')" maxlength="11" @input="updateButton($event,'Okato')" />
</v-fieldset>
</div>
...
updateButton($event: string, typeField: string) {
switch (typeField) {
case 'City': {
this.isSaveDisable =
$event.length == 0 ||
this.entity.Subject.length == 0 ||
this.entity.District.length == 0 ||
this.entity.Okato.length < 11;
break;
}
case 'Subject': {
this.isSaveDisable =
this.entity.City.length == 0 ||
$event.length == 0 ||
this.entity.District.length == 0 ||
this.entity.Okato.length < 11;
break;
}
case 'District': {
this.isSaveDisable =
this.entity.City.length == 0 ||
this.entity.Subject.length == 0 ||
$event.length == 0 ||
this.entity.Okato.length < 11;
break;
}
case 'Okato': {
console.log('OKATO');
this.isSaveDisable =
this.entity.City.length == 0 ||
this.entity.Subject.length == 0 ||
this.entity.District.length == 0 ||
$event.length < 11;
break;
}
}
}
v-digits-text-box.vue
<template>
<v-text-input v-bind="$attrs" v-on="$listeners" @keypress="isNumber($event)" @paste="pasteTest($event)" />
</template>
<script>
export default {
methods: {
isNumber(evt) {
evt = evt ? evt : window.event;
const charCode = evt.which ? evt.which : evt.keyCode;
if (charCode > 31 && (charCode < 48 || charCode > 57) && charCode !== 46) {
evt.preventDefault();
} else {
return true;
}
},
pasteTest(event) {
window.setTimeout(() => {
var characters = event.target.value;
window.setTimeout(() => {
if(!(/^d+$/.test(characters))){
event.target.value = event.target.value.replace(/D/g, '');
}
});
});
}
}
}
</script>
v-text-input.vue
<template>
<div class="v-text-input" :class="rootClasses">
<div v-click-outside="unfocus" class="v-text-input-control" @click="focus">
<div class="v-text-input-control-inner">
<div v-if="iconPrepend" class="v-text-input__icon v-text-input__icon--prepend">
<i aria-hidden="true" class="v-icon" :class="iconPrepend" />
</div>
<div class="v-text-input-control-inner">
<label
v-if="!noLabel"
ref="label"
aria-hidden="true"
class="v-label"
:class="labelClasses"
:for="id"
style="left: 0px; right: auto; position: absolute;"
>{{labelValue}}</label>
<input
:id="id"
ref="input"
type="text"
:readonly="readonly"
:disabled="disabled"
:placeholder="placeholder"
v-bind="$attrs"
:value="mutableValue"
v-on="listeners"
/>
</div>
<slot name="icon">
<div v-if="iconAppend" class="v-text-input__icon v-text-input__icon--append">
<i aria-hidden="true" class="v-icon" :class="iconAppend" />
</div>
</slot>
</div>
<div v-if="detailMessage || detail" class="v-text-input-details">
<div class="v-text-input-details-inner">
<div class="v-text-input-details-inner">{{detailMessage || detail}}</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop, Watch, Model } from 'vue-property-decorator';
@Component({ inheritAttrs: false })
export default class TextInputComponent extends Vue {
$refs!: {
input: HTMLInputElement;
label: HTMLLabelElement;
};
@Model('change', { type: [String, Number] }) readonly value!: string | null;
@Prop(String) readonly id!: string;
@Prop(String) readonly iconPrepend!: string | null;
@Prop(String) readonly iconAppend!: string | null;
@Prop(String) readonly label!: string;
@Prop(String) readonly detail!: string;
@Prop(String) readonly placeholder!: string;
@Prop(Boolean) readonly bordered!: boolean;
@Prop(Boolean) readonly readonly!: boolean;
@Prop(Boolean) readonly required!: boolean;
@Prop(Boolean) readonly narrow!: boolean;
@Prop(Boolean) readonly noLabel!: boolean;
@Prop(Boolean) readonly disabled!: boolean;
@Prop(String) readonly counter!: string | null;
@Prop({
type: Function,
default: function (): string | true {
return true;
},
})
readonly validation!: (value: string | null | undefined) => string | true;
detailMessage: string | null = null;
mutableValue: string | null = null;
rootClasses = {
'v-text-input--focused': false,
'secondary--text': false,
'error--text': false,
'v-text-input--is-readonly': false,
'v-text-input--is-disabled': false,
'v-text-input--narrow': false,
'v-text-input--no-label': false,
'v-text-input--required': false,
'v-text-input--bordered': false,
};
labelClasses = {
'v-label--active': false,
};
get labelValue() {
if (this.label && this.counter) return this.label + `(${this.mutableValue?.length ?? 0}/${this.counter})`;
if (this.label) return this.label;
if (this.counter) return `${this.mutableValue?.length ?? 0}/${this.counter}`;
return null;
}
get listeners() {
return Object.assign({}, this.$listeners, {
input: (event: InputEvent & { target: HTMLInputElement }) => {
this.mutableValue = event.target.value;
let validation = this.validation(this.mutableValue);
if (this.readonly || this.disabled) validation = true;
this.rootClasses['error--text'] = validation !== true;
if (this.detailMessage !== validation) this.$emit('update:valid', validation === true);
this.detailMessage = validation === true ? null : validation;
this.$emit('input', event.target.value, event);
},
change: (event: Event & { target: HTMLInputElement }) => {
this.mutableValue = event.target.value;
if (this.validation(this.mutableValue) === true) this.$emit('change', this.mutableValue, event);
},
focus: () => {
this.rootClasses['v-text-input--focused'] = true;
this.rootClasses['secondary--text'] = true;
this.$emit('focus');
},
blur: () => {
this.rootClasses['v-text-input--focused'] = false;
this.rootClasses['secondary--text'] = false;
this.validationValue();
this.$emit('blur');
},
});
}
focus() {
if (this.disabled) return;
this.rootClasses['v-text-input--focused'] = true;
if (!this.rootClasses['error--text']) this.rootClasses['secondary--text'] = true;
this.labelClasses['v-label--active'] = true;
}
unfocus() {
if (document.activeElement === this.$refs.input) return;
this.rootClasses['v-text-input--focused'] = false;
this.rootClasses['secondary--text'] = false;
if (!this.mutableValue && !this.placeholder) this.labelClasses['v-label--active'] = false;
this.validationValue();
}
@Watch('mutableValue', { immediate: true }) onMutableValue() {
this.labelClasses['v-label--active'] = Boolean(Boolean(this.mutableValue) || this.placeholder);
this.validationValue();
}
@Watch('value', { immediate: true }) onValue() {
this.mutableValue = this.value;
}
@Watch('narrow', { immediate: true }) onNarrow() {
this.rootClasses['v-text-input--narrow'] = this.narrow;
}
@Watch('bordered', { immediate: true }) onBordered() {
this.rootClasses['v-text-input--bordered'] = this.bordered;
}
@Watch('noLabel', { immediate: true }) onNoLabel() {
this.rootClasses['v-text-input--no-label'] = this.noLabel;
}
@Watch('required', { immediate: true }) onRequired() {
this.rootClasses['v-text-input--required'] = this.required;
}
@Watch('disabled', { immediate: true }) onDisabled() {
this.rootClasses['v-text-input--is-disabled'] = this.disabled;
}
@Watch('readonly', { immediate: true }) onReadonly() {
this.rootClasses['v-text-input--is-readonly'] = this.readonly;
}
validationValue() {
let validation = this.validation(this.mutableValue);
if (this.readonly || this.disabled) validation = true;
this.rootClasses['error--text'] = validation !== true;
this.detailMessage = validation === true ? null : validation;
}
}
</script>
When I type everything is fine, but there is an issue on pasting data. For example, if I clear OKATO field and paste 35 403 000 000
, I can see 35403000
, which is correct, but when inspecting the element I see the following:
<div class="v-text-input" placeholder="..." maxlength="11" value="35 403 000 ">
I don’t understand why the value in field is correct (what I need), but inspected value is unchanged pasted value.