I have two inputs and an array with two positions in which both are numbers, in each input its v-model will make a value of the array in each position, writing in each input changes the heat in the array and this is fine.
I am trying to write in each input to format it and add commas of thousands and millions but keeping the value in the array without format and of type number, I have a function that formats it well in the console, but I cannot show it in the input, how can I achieve this?
<template>
<input
type="text"
class="text-center"
v-model="rangePrice[0]"
/>
<input
type="text"
class="text-center"
v-model="rangePrice[1]"
/>
</template>
<script setup>
const rangePrice = ref([0, 100000000])
// this function displays the formatted value to the console
const format = (e) => {
console.log(rangePrice.value[0].toString().replace(/\D/g, "").replace(/\B(?=(\d{3})+(?!\d))/g, ","))
}
</script>
I am using vue form slider where I have two scroll points and these two minimum and maximum values are the values of the array, which when moving the bar converts the values to a number:
<Slider
class="slider"
v-model="rangePrice"
:lazy="false"
:min="0"
:max="100000000"
:tooltips="false"
/>
Try with #input and :value instead v-model:
const { ref } = Vue
const app = Vue.createApp({
setup() {
const rangePrice = ref([0, 100000000])
const format = (e) => {
return e.toString().replace(/\D/g, "").replace(/\B(?=(\d{3})+(?!\d))/g, ",")
}
const saveInput = (e, i) => {
rangePrice.value[i] = parseInt(e.target.value.replaceAll(',', ''))
}
return {
rangePrice, format, saveInput
};
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<input
type="text"
class="text-center"
:value="format(rangePrice[0])"
#input="saveInput($event, 0)"
>
<input
type="text"
class="text-center"
:value="format(rangePrice[1])"
#input="saveInput($event, 1)"
>
{{rangePrice}}
</div>
Related
Based on Is there specific number input component in Vuetify? I'm trying to create a numeric input.
The input and output value is unknown so it could be undefined or null because one might want to clear the field so it should not respond with 0.
The input component should not have "up"/"down" buttons if possible.
If the user passes in a flag isAcceptingFloatingPointNumbers = false this input should only accept integer values ( it should not be possible to type floats )
Reproduction link
<template>
<v-app>
<v-main>
<v-text-field
type="number"
label="number input"
:clearable="true"
:model-value="num"
#update:modelValue="num = $event"
/>
</v-main>
</v-app>
</template>
<script setup lang="ts">
import { ref, watch, Ref } from 'vue'
const num: Ref<unknown> = ref(undefined)
watch(num, () => console.log(num.value))
</script>
How can I make sure the user can only type integer values if the flag isAcceptingFloatingPointNumbers returns false? The only thing coming to my mind is to append a custom rule like
v => Number.isInteger(v) || 'Must be integer'
but AFAIK this rule would trigger even if the value could be undefined. Is there a way to prevent the user input instead?
Based on yoduh's answer I tried this ( reproduction link )
NumberField.vue
<template>
<v-text-field
type="number"
label="number input"
:clearable="true"
:model-value="num"
#update:modelValue="emit('update:modelValue', $event)"
#keypress="filterInput"
/>
</template>
<script setup lang="ts">
const props = defineProps<{
num: unknown;
isAcceptingFloatingPointNumbers: boolean;
}>();
const emit = defineEmits<{
(e: "update:modelValue", newValue: unknown): void;
}>();
function filterInput(inputEvent) {
if(props.isAcceptingFloatingPointNumbers.value) {
return true;
}
const inputAsString = inputEvent.target.value.toString() + inputEvent.key.toString();
const inputValue = Number(inputAsString);
if(!Number.isInteger(inputValue)) {
inputEvent.preventDefault();
}
return true;
}
</script>
I'm consuming the component like so
<template>
<number-field :num="num" :isAcceptingFloatingPointNumbers="false" #update:model-value="num = $event" />
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import NumberField from "./NumberField.vue";
const num: Ref<unknown> = ref(undefined);
watch(num, () => console.log(num.value));
</script>
The problem is that my filter function is wrong. It's still possible to type "12.4" because the filter ignores "12." and then converts "12.4" to 124.
Does someone got any ideas how to fix this?
Since an integer is made only of digits, you can test only if each pressed key is a digit, no need to check the whole input value.
function filterInput(inputEvent) {
if(props.isAcceptingFloatingPointNumbers.value) {
return true;
}
if(!inputEvent.target.value.length && inputEvent.key === '-'){
return true;
}
if(!Number.isInteger(Number(inputEvent.key))) {
// Of course, you can choose any other method to check if the key
// pressed was a number key, for ex. check if the event.keyCode is
// in range 48-57.
inputEvent.preventDefault();
}
return true;
}
Concerning the arrows, it is not a Vuetify specific element, but elements added by the browser to inputs of type number. You can disable them like this.
As per my understanding you have below requirments :
To prevent the user input based on the isAcceptingFloatingPointNumbers flag value (Only accept integers if flag is false else field should accept the floating numbers).
No up/down arrows in the input field.
Input field should accept the 0 value.
If my above understandings are correct, You can simply achieve this requirement by normal text field and on every keyup event, You can replace the input value with an empty string if it's not matched with passed valid regEx.
Live Demo :
const { ref } = Vue;
const { createVuetify } = Vuetify;
const vuetify = createVuetify();
let options = {
setup: function () {
let num = ref('');
let isAcceptingFloatingPointNumbers = ref(false);
const validateInput = () => {
const numbersRegEx = !isAcceptingFloatingPointNumbers.value ? /[^-\d]/g : /[^-\d.]/g;
num.value = num.value.replace(numbersRegEx, '');
}
return {
num,
validateInput
};
}
};
let app = Vue
.createApp(options)
.use(vuetify)
.mount('#app');
<script src="https://unpkg.com/vue#next/dist/vue.global.js"></script>
<script src="https://unpkg.com/#vuetify/nightly#3.1.1/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/#vuetify/nightly#3.1.1/dist/vuetify.css"/>
<div id="app">
<v-text-field label="Numper Input" v-model="num" v-on:keyup="validateInput"></v-text-field>
</div>
I think the best way would be to create a custom filter function that runs on keypress. With your own custom filter you can also remove the type="number" since it's no longer necessary and will remove the up/down arrows on the input.
<v-text-field
label="number input"
:clearable="true"
:model-value="num"
#update:modelValue="num = $event"
#keypress="filter(event)"
/>
const filter = (e) => {
e = (e) ? e : window.event;
const input = e.target.value.toString() + e.key.toString();
if (!/^[0-9]*$/.test(input)) {
e.preventDefault();
} else {
return true;
}
}
updated sandbox
As per your comment on #yoduh's answer, if you want to stick with type="number" (good to reduce the step to validate the non-numeric characters), then hide the arrows using following CSS-
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type=number] {
-moz-appearance: textfield;
}
Logic 1-
On the keyup event, check if isAcceptingFloatingPointNumbers is false and the typed input is not an integer, empty the input field's value. To check if the input value is an integer or not-
You can use a regex pattern, /^-?[0-9]+$/.test(num).
You can use the JS method Number.isInteger(num).
Though, in the second method the input value will always be of type string (why?). To resolve this, use the built-in Vue.js directive v-model.number to recast the input value's type to a number.
Demo-
const { ref } = Vue;
const { createVuetify } = Vuetify;
const vuetify = createVuetify();
let options = {
setup: function() {
let num = ref(null);
let error = ref('');
let isAcceptingFloatingPointNumbers = ref(false);
const validateInput = () => {
// If floats not allowed and input is not a integer, clean it.
if (
!isAcceptingFloatingPointNumbers.value &&
!Number.isInteger(num.value)
) {
num.value = null;
error.value = "Only integers are allowed."
} else {
error.value = '';
}
};
return {
num,
error,
validateInput,
};
},
};
let app = Vue.createApp(options)
.use(vuetify)
.mount("#app");
.error {
color: red;
}
<script src="https://unpkg.com/vue#next/dist/vue.global.js"></script>
<script src="https://unpkg.com/#vuetify/nightly#3.1.1/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/#vuetify/nightly#3.1.1/dist/vuetify.css"/>
<div id="app">
<v-text-field
type="number"
label="number input"
:clearable="true"
v-model.number="num"
#keyup="validateInput"
>
</v-text-field>
<label class="error">{{ error }}</label>
</div>
The only glitch here is if the user types 123. and stops typing then the dot will be visible because of the type="number" but if you use this value, it will always be decoded as 123.
If you want to restrict the typing of the dot, detect the key on the keypress event and prevent further execution.
EDIT------------------
Logic 2
If a user tries to input the float number, you can return the integer part of that floating-point number by removing the fractional digits using Math.trunc(num) method.
Demo-
const { ref } = Vue;
const { createVuetify } = Vuetify;
const vuetify = createVuetify();
let options = {
setup: function() {
let num = ref(null);
let error = ref('');
let isAcceptingFloatingPointNumbers = ref(false);
const validateInput = () => {
if (!isAcceptingFloatingPointNumbers.value && !Number.isInteger(num.value)) {
error.value = "Only integer is allowed.";
// Keep only integer part.
num.value = Math.trunc(num.value);
} else {
error.value = ''
}
};
return {
num,
error,
validateInput,
};
},
};
let app = Vue.createApp(options)
.use(vuetify)
.mount("#app");
.error {
color: red;
}
<script src="https://unpkg.com/vue#next/dist/vue.global.js"></script>
<script src="https://unpkg.com/#vuetify/nightly#3.1.1/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/#vuetify/nightly#3.1.1/dist/vuetify.css"/>
<div id="app">
<v-text-field
type="number"
label="number input"
:clearable="true"
v-model.number="num"
#keyup="validateInput"
>
</v-text-field>
<label class="error">{{ error }}</label>
</div>
I am now trying to create a new input which will reject if the number user type in exceed the limit but it is not working the
let say the max number is 99
Currently my best way is to create something like this
<input
type="text"
:maxlength="2"
aria-controls="none"
class="number-field-input"
inputmode="numeric"
pattern="/d+"
v-model="inputOne"
/>
This will limit the max number to 99 since the max length is 2 but I don't want something like this I want something like this but its not working
<input
type="number"
min="1"
max="99" //may not be 99 but something between 1 and 99
aria-controls="none"
class="number-field-input"
inputmode="numeric"
pattern="/d+"
v-model="inputOne"
/>
You can use watcher :
const { ref, watch } = Vue
const app = Vue.createApp({
setup() {
let inputOne = ref(0)
watch(inputOne,
(newValue) => {
inputOne.value = newValue > 99 ? 99 : newValue
},
);
return { inputOne };
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div>
<input
type="number"
min="1"
max="99"
aria-controls="none"
class="number-field-input"
inputmode="numeric"
pattern="/d+"
v-model="inputOne"
/>
</div>
You can simply achieve that by single line of code with the help of #input event.
Live Demo :
new Vue({
el: '#app',
data: {
value: 0
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="number" v-model="value" #input="() => { if(value > 150 || value < 0) { value = 150 }}">
</div>
I am attempting to set up a number input field in Vue 3 that prevents the user from entering a value below 1. So far I have the following input with min = 1 to prevent clicking the input arrows below 1:
<input min="1" type="number" />
However, the user can still manually enter 0 or a negative number. How can I prevent the user entering a number below 1?
You can check value on keyup:
const { ref } = Vue
const app = Vue.createApp({
setup() {
const numValue = ref(null)
const setMin = () => {
if(numValue.value < 1) numValue.value = null
}
return { numValue, setMin }
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<input #keyup="setMin" min="1" v-model="numValue" type="number" />
</div>
How can I add an value to an input type.
I'm currently working on a converter for meter to kilometer, to mile, ...
I've created different input types in my HTML Code and took the value of them in JS, which worked completely fine, and I prevented the page from reloading after I click submit. Now I want to reassign the new, calculated value (kilometer.value = meter / 1000), but this doesn't work.
It works completely fine, when I'm putting an console.log after the variable meter and the first variable kilometer. It logs the correct number - the reassignment just doesn't work.
JavaScript:
const calculateMeter = () => {
let meter = document.getElementById("meter").value;
let kilometer = document.getElementById("kilometer").value;
kilometer.value = meter / 1000;
}
HTML:
<form id="calculator" onsubmit="calculateMeter(); return false">
<label for="kilometer">Kilometer:</label>
<input type="number" id="kilometer"><br>
<label for="meter">Meter:</label>
<input type="number" id="meter"><br>
value of <input> is always a string, but in case of numerical input types, like type="number", or type="range", you can get the number directly using valueAsNumber:
const calculateMeter = () => {
let kilometer = document.getElementById("kilometer").valueAsNumber;
document.getElementById("meter").value = kilometer * 1000;
}
const calculateKilometer = () => {
let meter = document.getElementById("meter").valueAsNumber;
document.getElementById("kilometer").value = meter / 1000;
}
<label for="kilometer">Kilometer:</label>
<input type="number" id="kilometer" oninput="calculateMeter()"><br>
<label for="meter">Meter:</label>
<input type="number" id="meter" oninput="calculateKilometer()"><br>
Did you mean something like, that?
let meterInp = document.getElementById("meter");
let kilometerInp = document.getElementById("kilometer");
meterInp.addEventListener("change", ()=>{
kilometerInp.value = (+meterInp.value)/1000;
});
kilometerInp.addEventListener("change", ()=>{
meterInp.value = (+kilometerInp.value)*1000;
});
<form id="calculator" onsubmit="calculateMeter(); return false">
<label for="kilometer">Kilometer:</label>
<input type="number" id="kilometer" step="0.00001"><br>
<label for="meter">Meter:</label>
<input type="number" step="0.01" id="meter"><br>
</form>
The (+) converts string to number.
for e.g. (+meterInp.value)/1000
You should convert meter to intiger
const calculateMeter = () => {
let meter = document.getElementById("meter").value;
let kilometer = document.getElementById("kilometer").value;
kilometer.value = Number(meter) / 1000;
}
I want to fill the inputs value of a form with default values once the modal is opened
I did it with pure javascript using document.getElementById(textId).value='some value as follow:
for(var i=0; i<details_data.length;i++){
let textId='text'+i;
let amountId='amount'+i;
document.getElementById(textId).value=details_data[i].text
}
This worked fine. but I want to know how to do it with React since I don't believe this is a best practice.
what i tried is to set the input value like this:
<input name='text' id={textId} value={el.text} onChange={details_handler.bind(index)}/>
But this woudn't let me change the value of the input. it just set the default value, and when i type in the input the value woudn't change as I want.
This is my code
const [details_data,set_details_data]=useState([
{'text':'','amount':''}
])
// called this function on `onChange` and store the data in `details_data`
function details_handler(e){
let name=e.target.name;
let value=e.target.value;
details_data[this][name]=value;
set_details_data(details_data);
}
JSX:
(In my case user can add inputs as many as he wants,That's why I put the inputs in a the mapping loop)
{
details_data.map((el,index) => {
let textId='text'+index;
let amountId='amount'+index;
return (
<div key={index}>
<input name='text' id={textId} value={el.text} onChange={details_handler.bind(index)}/>
<input name='amount' id={amountId} onChange={details_handler.bind(index)}/>
</div>
);
})
}
useEffect(() => {
if(detailsProps) {
set_details_data(detailsProps);
}
}, [detailsProps])
where your detailsProps (data from the api) will look something like this
detailsProps = [
{'text':'text1','amount':'100'},
{'text':'text2','amount':'200'}
]
onChange Function
const details_handler = (event, index) => {
const items = [...details_data];
items[index][event.target.name] = event.target.value;
set_details_data(items);
}
your view
{
details_data.map((el,index) => {
return (
<div key={index}>
<input name='text' value={el.text} onChange={(e) => details_handler(e, index)}/>
<input name='amount' value={el.amount} onChange={(e) => details_handler(e, index)}/>
</div>
);
})
}