I have a html <input> element that I want to accept only numbers and to be recognised on mobile devices as a number field. I also want invalid characters to be swallowed, just like for standard type=number swallowing disallowed characters.
I've tried the obvious type=number but it has a number of shortcomings. Specifically it allows 'e', '+' and '-' (at least in chrome), but these were easy to fix with some JS. The real problem is with the '.' character, I want to be able to enter floating point numbers e.g. '0.10', '5.5054', but don't want to be able to enter invalid strings like '0.10.1' for instance. I tried to resolve this by allowing only 1 '.' at a time but this failed as the input.value gets massaged by the browser e.g. '5.' becomes '5', '5..' becomes null (!) and it seems impossible to get the raw string value typed in the input. The above means checking for existing '.'s and taking action appears to be a dead end...
Core questions:
Is there a way I missing to inspect and conform the input?
'Is there a way of marking an input as a number without the logistical baggage of type=number?
Note:
* I realise that you can paste whatever you want in, I consider that behaviour pathological and shouldn't be covered by input prevention.
Update
To clarify, I have already tried keypress, keydown etc events and they aren't adequate as I want to see how many '.'s exist in the input currently to choose whether or not to allow another. At this point the input.value has been massaged by the browser to remove '.'s. I want to conditionally allow characters based on the current number of '.'s that have been entered.
Example
HTML (angular style binding for brevity)
<input type="number" (keydown)="keyDown()">
JS
function keyDown($event: KeyboardEvent) {
const inputField = // obtain reference to input element
const value = inputField.value;
if ( value.indexOf('.') !== -1 && $event.key === '.') { // disallow another . if one is present
// ! input field prunes . so this check isn't sufficient
$event.preventDefault();
return;
}
// This is the crux of the problem e.g.
// type 5. into input field, value === 5
// type 5.. into the input field, value === null
// Since the . char is removed by the input element there's no way to know how many are present!
console.log(value);
}
Summary
Is there a way to signal that an <input> is of type number without using the type=number attribute setting.
i.e. mobile devices recognise and display number pad etc
For an <input> that has type=number is there a way to swallow all key input that doesn't result in a valid number
Before the character is added to the input by the browser, no janky deletion on keyup
Is there a way to signal that an <input> is of type number without using the type=number attribute setting.
i.e. mobile devices recognise and display number pad etc
Use inputmode="decimal" instead of type="number" to signal a mobile device to use a number pad keyboard input. This way you can continue to use type="text" and process the input as needed.
See MDN for more info and inputtypes.com to test on a device.
A slightly different approach. It allows digits, only 1 period, and backspace. All the rest of KeyboardEvent.keys including ctrl + v and ctrl + c are ignored. But if wish to allow them, you can do so.
To check if the character is one of the 10 digits, I am using event.key since they can have two different codes: Digits[0-9] and Numpad[0-9]. But for the period and backspace, I am using event.code since they have only one code.
const input = document.querySelector("#number_input");
const App = {
isDigit: function(key) {
const digits = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
];
return digits.includes(key);
},
isPeriod: function(code) {
return code === "Period";
},
isBackSpace: function(code) {
return code === "Backspace";
},
handleEvent: function(event) {
const key = event.key;
const code = event.code;
const value = input.value;
if (App.isDigit(key) || App.isPeriod(code) || App.isBackSpace(code)) {
if (App.isPeriod(code) && value.indexOf(key) !== -1) {
event.preventDefault();
}
} else {
event.preventDefault();
}
}
};
input.onkeydown = App.handleEvent
<input id="number_input" />
A clever hack
Since you insist to use a number input. First use, a dummy text input which you can hide it using either CSS or Js and validate its value instead of the number input.
const input = document.querySelector("#number_input");
const dummyInput = document.querySelector("#dummy_input")
const App = {
isDigit: function(key) {
const digits = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
];
return digits.includes(key);
},
isPeriod: function(code) {
return code === "Period";
},
isBackSpace: function(code) {
return code === "Backspace";
},
handleEvent: function(event) {
const key = event.key;
const code = event.code;
const dummyValue = dummyInput.value;
if (App.isBackSpace(code)) {
dummyInput.value = dummyValue.substring(0, dummyValue.length - 1)
} else {
if (App.isDigit(key) || App.isPeriod(code)) {
if (App.isPeriod(code) && dummyValue.indexOf(key) !== -1) {
event.preventDefault();
} else {
dummyInput.value += event.key
}
} else {
event.preventDefault();
}
}
}
};
input.onkeydown = App.handleEvent
<input type="number" id="number_input" />
<input type="text" id="dummy_input" />
Update
All of the answers that use input[type="number"] have a problem. You can change the input's value to a negative number by mouse wheel/spinner. To fix the issue, set a minimum value for the input.
<input type="number" min="1" id="number_input" />
You need to listen for onchange events and then change value of the dummy input.
const input = document.querySelector("#number_input");
const dummyInput = document.querySelector("#dummy_input")
const App = {
isDigit: function(key) {
const digits = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
];
return digits.includes(key);
},
isPeriod: function(code) {
return code === "Period";
},
isBackSpace: function(code) {
return code === "Backspace";
},
handleEvent: function(event) {
const key = event.key;
const code = event.code;
const dummyValue = dummyInput.value;
if (App.isBackSpace(code)) {
dummyInput.value = dummyValue.substring(0, dummyValue.length - 1)
} else {
if (App.isDigit(key) || App.isPeriod(code)) {
if (App.isPeriod(code) && dummyValue.indexOf(key) !== -1) {
event.preventDefault();
} else {
dummyInput.value += event.key
}
} else {
event.preventDefault();
}
}
},
handleChange: function(event) {
dummyInput.value = event.target.value
}
};
input.onkeydown = App.handleEvent;
input.onchange = App.handleChange;
<input type="number" min="1" id="number_input" />
<input type="text" id="dummy_input" />
trigger special function for onkeypress and check if your allowed character are typed or not. If not - prevent the default behavior.
let special = document.getElementById('special_input');
special.addEventListener("keypress", function(e) {
let dot = 46;
// allowed char: 0-9, .
let allow_char = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, dot];
if (allow_char.indexOf(e.which) !== -1) {
// only 1 dot
if (e.which == 46 && special.value.indexOf('.') !== -1)
e.preventDefault();
} else {
e.preventDefault();
}
});
<input id='special_input'>
updated
The answer is updated as per requirement changes.
okay I try to fix one more problem and that is paste
according to following code you can not paste anything, you can only paste numbers, if you try to paste string, the input box will become empty automatically :)
let special = document.getElementById('inputField');
special.addEventListener("keypress", function(e) {
let dot = 46;
// allowed char: 0-9, .
let allow_char = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, dot];
if (allow_char.indexOf(e.which) !== -1) {
// only 1 dot
if (e.which == 46 && special.value.indexOf('.') !== -1)
e.preventDefault();
} else {
e.preventDefault();
}
});
function checkString()
{
setTimeout(() => {
var value = document.getElementById("inputField").value;
value = parseInt(value);
if(isNaN(value))
document.getElementById("inputField").value = "";
}, 100);
}
<input type="number" id="inputField" onpaste="checkString()"/>
Check truthy with
value == parseFloat(value)
parseFloat() will capture the first decimal point and remove any additional ones.
Using '==' instead of '===' will make sure the reference value isn't changed.
function keyDown($event: KeyboardEvent) {
const inputField = // obtain reference to input element
const value = inputField.value;
if ( value == parseFloat(value)) { // disallow another . if one is present
// ! input field prunes . so this check isn't sufficient
$event.preventDefault();
return;
}
// This is the crux of the problem e.g.
// type 5. into input field, value === 5
// type 5.. into the input field, value === null
// Since the . char is removed by the input element there's no way to know how many are present!
console.log(value);
}
Here is a similar test you can run in node.js to verify it accomplishes what you are going for.
function numCheck (value) {
if (value == parseFloat(value)) {
console.log(value)
} else {
console.log('not a number')
}
}
numCheck("5");
numCheck("5.5");
numCheck("5.5.5");
numCheck("A");
For restrictions, Regex is best way to go. Register event listener on keypress
numbersOnly(event) {
const numbers = /^([0-9])$/;
const result = numbers.test(event.key);
return result;
}
Above function will swallow charset, if it doesn't match regex
Javascript Regex: validating a double/float
Above link has regex, who allows double/float. Hope it helps :)
This may be what your looking for:
This input eats all caracters exept for numbers and "."
let prev = "";
function keyUp(event) {
if ((!event.target.value && event.keyCode !== 8) || event.target.value.indexOf('e') !== -1 || parseFloat(event.target.value) == NaN) {
event.target.value = prev;
} else {
prev = event.target.value;
}
}
<input type="number" onkeyup="keyUp(event)">
Perhaps an indirect solution to your problem. You can look into validity property to determine if the input is valid.
You can check the validity without the need to access the value, which may not be returned in case of invalid input.
HTML:
<input type="number" id="num" step="0.1"/>
JS:
var numField = document.getElementById('num');
numField.addEventListener("keyup", event => {
if(!numField.validity.valid)
{
alert('invalid input');
}
});
Some tests:
34 -> valid
3.4 -> valid
e -> invalid
+4 -> valid
3.4. -> invalid
3.4.. -> invalid
.4 -> valid (evaluates to 0.4)
3e+2 -> valid
1e -> invalid
eee -> invalid
-0.3e+1 -> valid
..32e.. -> invalid
This works both with pasting and inputting the values.
UPDATE:
The original question is a bit verbose. I am trying to break it down and answer part by part.
Input[type="number"] allows access to the value only if it is valid and returns empty otherwise. Thus, you can know if value is correct or not, however you cannot get the invalid value.
You could get around this by pushing individual keypresses into a buffer. However, maintaining sync between the buffer and actual value is challenging due to copy/paste, input field swallowing certain keys, moving the cursor around and inserting keys, etc. Implementing reliable cross-platform support for all of this seems like overkill. A better approach would be to implement other means to input the number, e.g. a slider. This resource covers the basics of designing number inputs
So, you are left with knowing if the current input is valid and the last key pressed. The problem is that certain invalid inputs, e.g. "4." may lead to a valid input "4.1". Thus, just swallowing the last key in case the input turns invalid is out of the question.
You could implement a rule that if invalid input is not turned into a valid one within 2 keystrokes (or 1 second) the input returns to last valid state swallowing the invalid input. However, I would argue that this creates UX problems because users may not be used to such behavior and it opens doors for users submitting technically valid, yet unwanted values.
Core questions:
Is there a way I missing to inspect and conform the input?
Is there a way of marking an input as a number without the logistical baggage of type=number?
Apparently not.
Yes, there is by rethinking the way how you enter numeric values.
I'm doing input validation on the following field and I want to set a default value. What I mean by default value is I want to set some value to input field if the field is empty.
var inputBox = document.getElementById("inputBox");
inputBox.addEventListener("input", function() {
validateInput(this);
});
inputBox.addEventListener("keydown", function(e) {
validateInput(this, e);
});
function validateInput(elm, e) {
// handle keydown event
if (e) {
// do not allow floating-point numbers, if it's not expected
if (!isFloat(elm.step)) {
if (e.key == ".") {
e.preventDefault();
}
}
}
// handle input event
else {
// do not allow leading zeros
while (elm.value.length > 1 && elm.value[0] === "0") {
elm.value = elm.value.toString().slice(1);
}
// input should be between min and max
if (elm.validity.rangeUnderflow) {
elm.value = elm.min;
} else if (elm.validity.rangeOverflow) {
elm.value = elm.max;
}
}
}
function isFloat(x) {
var f = parseFloat(x);
var floor = Math.floor(f);
var fraction = f - floor;
if (fraction > 0) {
return true;
}
return false;
}
<input type="number" id="inputBox" min="0" max="16581375" step="1" value="0" />
In a very straightforward approach, I can add the following to the end of validateInput()
// set default value, empty field isn't allowed
if (elm.value == "") {
elm.value = elm.min;
}
but this breaks some functionality. For example, I can't enter exponential values (e.g. 1e+5).
Input field does its own check at input. The moment I enter 1e, it evaluates to NaN and the value property of the element is set to "", but visually, 1e is still entered on the field. So, you see, the entered value and the HTMLInputElement.value might differ.
To be able to set a default value, I should be able to check the entered value, not the parsed one by the element. Something like this would probably work:
// set default value, empty field isn't allowed
if (elm.value == "" && !elm.stringValue.includes(e)) {
elm.value = elm.min;
}
But of course, there's no such stringValue property. One way I can think of to get around this problem is to use a text input field and do an extra validation for number.
Is there a way to make this work with number input field?
Looks like you need to parse exponential values into numbers.
I think, this solution should help you: https://stackoverflow.com/a/18719988/6420563
This solution made for text field but i think it will also work for number input.
I'm using the following function to remove non-digit characters from input fields onkeyup (as posted in numerous places). The function works fine for text inputs but when linked to a number input, the number input is completely erased when a non-digit character is typed.
Thoughts?
function RemoveNonNumeric(e)
{
var el = e.target;
el.value = el.value.replace(/\D/g,"");
return;
}//end RemoveNonNumeric
What happens is as soon as there is a non-numeric character in a number input field... the value becomes empty. One way to solve this would be to save the last true numeric value and replace the current value with that if it becomes an empty value. Seems kind of hacky, but see if it works for you:
JSFiddle
var lastNumericValue = 0;
function RemoveNonNumeric(e)
{
if(e.keyCode === 8)
return;
var el = e.target;
if(el.value === "") {
el.value = lastNumericValue;
} else {
lastNumericValue = el.value;
}
}
on second thought... how about preventing the non-numeric values from ever being placed instead of removing them after the fact? Run the following onkeypress
JSFiddle
function RemoveNonNumeric(e) {
if (e.which < 48 || e.which > 57) {
e.preventDefault();
}
};
If you debug, you will find that when the value of a number input is invalid, el.value returns an empty string. I am unaware of a workaround.
I called a class called test for my textbox. When I entered the first value for e.g. the first value as 4., then suddenly the output coming as 4.00. I just want to restrict entry only for two decimal places.
$(".test").keyup(function (event) {
debugger;
this.value = parseFloat(this.value).toFixed(2);
});
This small change to your code may suffice:
this.value = this.value.replace (/(\.\d\d)\d+|([\d.]*)[^\d.]/, '$1$2');
Essentially replace the decimal point followed by any number of digits by a decimal point and the first two digits only. Or if a non digit is entered removes it.
What about something like this:
$(".test").keyup(function (event) {
if ((pointPos = this.value.indexOf('.')) >= 0)
$(this).attr("maxLength", pointPos+3);
else
$(this).removeAttr("maxLength");
});
Here is a working fiddle.
you can use the maxLength attribute for that, try
$(".test").keyup(function (event) {
var last = $(this).val()[$(this).val().length - 1];
if (last == '.') {
$(".test").attr("maxlength", $(this).val().length+2);
}
});
You shouldn't worry about what the user has in the input until they submit the form. You really don't care what's in there before then. However, if you want to warn about invalid input, you can put a message on the screen if you detect non–conforming input, e.g.
<script>
function validate(element) {
var re = /^\s*\d*\.?\d{0,2}\s*$/;
var errMsg = "Number must have a maximum of 2 decimal places";
var errNode = document.getElementById(element.name + '-error')
if (errNode) {
errNode.innerHTML = re.test(element.value)? '' : errMsg;
}
}
</script>
You should probably also put a listener on the change handler too to account for values that get there by other means.
$(document).on("keyup", ".ctc", function ()
{
if (!this.value.match(/^\s*\d*\.?\d{0,2}\s*$/) && this.value != "") {
this.value = "";
this.focus();
alert("Please Enter only alphabets in text");
}
});
I have a JavaScript function that validates an input field and prevents the user from typing anything that doesn't match the condition. This function is based on event.keyCode.
I'm trying to modify the function to use a RegExp and validates not "per character" but "per whole input" so that it does the same, but with different conditions:
numeric only
allowed decimal "." or ","
Here is the function in its current form, using event.keyCode:
function isNumeric(evt, alertDIVid, alertMsg) {
var charCode = (evt.which) ? evt.which : event.keyCode
if (charCode >= 48 && charCode <= 57) {
document.getElementById(alertDIVid).innerHTML = '';
return true;
}
else {
document.getElementById(alertDIVid).innerHTML = alertMsg;
return false;
}
}
document.getElementById('AMNT').onkeypress = function(event) {
event = event || window.event;
return isNumeric(event, 'numericalert', 'Numeric values only!')
};
In order to do the kind of validation you want, you need to listen to the keyup event instead. This event fires after the field is changed, so that you know the new value of the field. You also need to know the previous value of the field so you can "reset" it if what the user typed turns out to be invalid.
For example:
(function() {
var previousValue = document.getElementById('myInput').value;
var pattern = /^\d*((\.|,)\d*)?$/;
function validateInput(event) {
event = event || window.event;
var newValue = event.target.value || '';
if (newValue.match(pattern)) {
// Valid input; update previousValue:
previousValue = newValue;
} else {
// Invalid input; reset field value:
event.target.value = previousValue;
}
}
document.getElementById('myInput').onkeyup = validateInput;
}());
Working demo: http://jsfiddle.net/8kUdG/
It's worth noting that this will also validate empty strings, as well as unfinished numbers, like 5, or 42. (otherwise the user would have to insert the decimal sign after typing the decimals, which would be... weird).
And finally, keep in mind that this might not be a cross-browser safe solution. If you need a pure-JavaScript solution, you will need to refine it (i.e., this might not work in IE).
Edit: of course, showing an error message instead of resetting the input field to the previous value is also perfectly possible (updated JSFiddle):
(function() {
var pattern = /^(?=.)\d*(?:[.,]\d+)?$/;
var error = document.getElementById('error');
document.getElementById('myInput').onkeyup = function(event) {
event = event || window.event;
var newValue = event.target.value || '';
if (newValue.match(pattern)) {
error.innerHTML = '';
} else {
error.innerHTML = 'Not a valid number!';
}
};
}());
I leave it up to you to replace the alert with something more user-friendly.
The easiest solution would be something like this
// Returns true on valid, false on invalid
function myInputFilter(input)
{
var value = input.value;
var regex = /^[\d\,\.]*$/;
if(!regex.test(value))
return false;
return true;
}
You could edit the function to just take a string argument, but I've chosen to have it accept the text input element instead. The RegEx can be replaced by anything, I've made a simple one for this example. I would refine it a bit if I were you (You can use the excellent online tool RegExr)
Here is an example of the filter implemented
http://jsfiddle.net/kVV77/
You can use following regular expression:
/^[+-]?(?=.)(?:\d+,)*\d*(?:\.\d+)?$/
to allow only any number of comma and only one dot . with the condition that number cannot start with a comma. Number can have optional + or - at the start.