How can I make a text box in React which allows only numbers or an empty value, which triggers the number keypad on mobile? - javascript

There are lots of questions like this on StackOverflow, but none of them captures all of my requirements in the same solution. Any help appreciated.
The problem
In my React app, I need a text box with the following characteristics:
It only allows digits to be entered - no minus signs, decimal places, letters, or anything besides just the digits 0-9.
It automatically brings up the number keypad on iOS and Android
I can further restrict the numbers that should be entered, e.g. only allow 4 digits
Leading zeroes are automatically trimmed, e.g. if a user types 02 it should correct to just 2
It allows an empty textbox, and can differentiate between empty and a value of 0
Current code
https://codepen.io/micahrl/pen/RwGeLmo
This code allows typing non-digits, and will just interpret the value as NaN. For instance, the user can type 2f or asdf and the page will say You typed: NaN.
Additionally, while the page loads initially with an empty text box, the user cannot type something and then delete it back to empty. Attempting to delete all text in the input box places a 0 in the box.
Finally, this code doesn't reliably trim leading zeroes, which causes me particular problems because I want to restrict the number to four digits. Typing 01 will not truncate the leading zero; on some browsers, typing 01111 will result in 1111, which is good enough, while on others, typing 01111 will result in 0111, which is a problem.
What I've tried
Because I have set type="number" on the input element, if there is ever a non-number added to the text box, event.target.value in setNumWrapper will be an empty string. This means I can't differentiate between a true empty string (where the user has deleted all text) and invalid input (where the user has typed a non-number, like asdf).
I could set type="text" on the input element, except that I think I need to set it to number to get the number keypad on mobile OSes like iOS and Android.

With further experimentation, and some help from #Keith in a comment, I've solved almost all my problems.
I've forked the codepen in my question and made these changes in the fork: https://codepen.io/micahrl/pen/GRjwqdO.
Checking input validity
#Keith pointed me to validity.badInput (https://developer.mozilla.org/en-US/docs/Web/API/ValidityState/badInput). With this, I can differentiate between empty input, where a user types something then deletes it, and bad input, where the user attempts to add a non-numeric character.
That means I add this to the beginning of setNumWrapper():
if (event.target.value === "") {
if (event.target.validity.badInput) {
// Set the text box and number to the old value - ignore the bad input
inputRef.current.value = String(num);
setNum(num);
} else {
// The data in the text box was deleted - set everything to empty
inputRef.current.value = "";
setNum(NaN);
}
return;
}
I also have to make an inputRef with useRef(), and set it on the <input> element.
This solves #5 most of #1 (but see below for one remaining minor problem).
Trimming leading zeroes
All I had to do for this was use that inputRef to set the value in the <input> element at the end of setNumWrapper():
inputRef.current.value = String(newNum);
The <input> element's value is always a string anyway, and casting the number to a string removed leading zeroes, if there were any.
Remaining problem: invalid input is allowed if the text box is empty
When the text box is empty, the user can type non-numeric characters into it, and setNumWrapper() doesn't even fire. If you put a console.log() at the top of setNumWrapper(), it won't print anything to the log if the user types a letter, but it will print to the log if the user types a number.
This means I cannot use setNumWrapper() to solve this problem.
However, it's also relatively minor. On mobile, the number keypad comes up, preventing non-numeric input. On the desktop nothing stops the user from typing letters with their keyboard, but for my app, it's clear that only numbers are allowed, so this is good enough for now.
Still, if there's a way to fix this, I'd be curious to hear about it.

Related

nativeElement.value is NaN if there is a comma

I'm trying to set the number of decimals at 2 in an input. When I type a comma in it, the value becomes NaN so I would like get my number instead of this.
TS
#ViewChild('number') input;
limitNbOfDecimals() {
var regex =
this.form.number.search(/^(\d+(?:[\.\,]\d{0,2})?)$/) == 0
? true
: false;
if (regex == false) {
// Convert the value to a number
var nb: number = +this.input.nativeElement.value;
//set decimals at 2
this.input.nativeElement.value = nb.toFixed(2);
}
}
HTML
<input class="form-control" type="text" [(ngModel)]="form.number"
#number
name="number"
(input)="limitNbOfDecimals()"
/>
EDIT
I manage to add a comma in the number but if I try to add more than 2 decimals after it removes the numbers after the comma like 1,11 -> 1
This isn't a full answer, in the sense of having a total solution, but hopefully helps you get to one (and it's too long for a comment).
The spec at https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number) states:
This specification does not define what user interface user agents
are to use; user agent vendors are encouraged to consider what would
best serve their users' needs. ..... a user agent designed for the
French market might display the value with apostrophes between
thousands and commas before the decimals, and allow the user to enter
a value in that manner, internally converting it to the submission
format described above.
It would seem that the only sure way - if you don't have control over what browsers your users have - of ensuring they can type numbers in the format they are used to in their local setting is to take input as type text and on each keystroke check that they have typed something valid (as you have defined it) and when they submit it convert to a decimal number.
Searching provides code for doing this, depending on exactly what your requirement is for the number formats though you may be better off coding it from scratch.
To add more than 2 decimal values, you need to tell like .toFixed(4) etc..

Integer/decimal user input: how to serialize/store the value

Our stack is currently: React SPA/Apollo/GraphQL/Rails/PostgreSQL.
We have a web form with a number input that maybe (or not) be used to entry decimal values (dynamic/user-created form).
We want the form to be lenient (it has an autosave feature). If user types ,3 or 17,, that is considered a valid number value. But not too lenient, the number should by parseable as a float and be able to display in graphs...
Also, when the user visit the form again, we want to make sure to init the number input with the exact same value he typed initially (I mean, ,3 does not become magically 0,3)
I'd like to know, for our stack, how to serialize (sending as GraphQL payload) and store (in PostgreSQL) and manipulate (in JS/Ruby) such number without loosing the initial user input value.
I'm thinking it would be simpler to send/store a string, as long as it's validated that it can be parsed.
Just wondering if there is a "proper" solution, for example using number libs, custom GraphQL scalar type, decimal SQL type... that seems complicated to me, anyone did something similar before?
Create two fields. One for user input and other with parsed value. I do not think this will be different for any stack.
The challenge is how to show to a user how you understood entered value. This could be solved with a hint with a parsed value under the text field.
you should choose type of the input as "text" instead of number, so you can create a specific format for your input. first, add a property to your state
state={amount:""}
input section for amount in your form should be like this:
<input
type="text"
placeholder="Amount"
value={this.state.amount}
onChange={this.onAmountChange}
/>
onAmountChange = e => {
const amount = e.target.value;
if (!amount || amount.match(/^-?[0-9]\d*(\.\d{0,2})?$/)) {
this.setState(() => ({ amount }));
}
};
regexp in above function means start with any number with optional -, and optionally u can end with 2 decimal number. you can change it for your form.
if you want to add only positive numbers if your form is for price you can use this regexp:
amount.match(/^\d{1,}(\.\d{0,2})?$/)

AngularJS number input that ignores all keys but numbers

I've been trying to create an input in an AngularJS template and that will only accept whole numbers as input. That is, I don't want it to allow any keys other than 0-9, specifically, I can't stop . from being allowed in the input.
Alexander Puchkov created a directive that achieves this on inputs with type="text" however I want to be able to use type="number" so I can maintain all my other attributes on the field for validation such as min, max, step, etc.
I have an example of this directive not working on a number input here. For example, when type="text" an input of 123. yields a rendering of 123 however when type="number" an input of 123. yeilds 123. since the previous value of 123 is equal when compared numerically.
I'm afraid this simply isn't possible as the following condition is true:
0. == 0
If you perform the following:
setTimeout(() => console.log(element[0].value), 100))
It will always log 0. as 0. This is why ngModelCtrl isnt triggering the parser as no changes are detected.
I would suggest not directly modifying the value of ngModel (as this can also end up leading to users putting in invalid data. ie. pasting 12.00 will resolve to 1200 with your example)
I would add a directive that applies validity depending on if a decimal point is used (technically speaking, typing 0. isn't actually using it. 0.01 is). Set the validity to false if there is a decimal point in the number and display an error message accordingly (via ngMessages). This way the user can correct their own error and can learn from the mistake.

angularjs convert 0 to 1 in number (html5) field

its an odd behavior. m using input field field where type is number
and if i enter 1230 model value remains -> 1230
but as i type 01 its becomes -> 1
where as i can see 01 in input value . so this something to do with angular js
i need 00 in model because its user phone number and number type is to stop user from entering text
any help will be appreciated
https://docs.angularjs.org/api/ng/input/input%5Bnumber%5D
test can be run at angular site
The type number allows you to enter +-,.. So you cannot achieve your goal of preventing the user from entering "invalid" numbers in the first place.
It's also not very user friendly, as telephone numbers are often formatted using spaces and braces. A user can no longer copy and paste such values into the input field. Digits in phone numbers can also be represented by letters btw. 123-HELLO is equal to 12343556.
Please note that there's also an input type tel. It's not particularly useful but semantically more appropriate.
If you only want to save and display the value then use the input as is. It doesn't make much sense to force a user to adhere to your preferred pattern. Adding pattern or ngPattern allows you to use regular expressions to limit the possible characters. If you need the plain number then strip all non-numeric characters - and possibly convert roman letters to numbers - before usage.

Acrobat JavaScript: How to set the field property "exact number of characters”?

Text fields in Acrobat have, besides many others, a property which assigns an exact number of characters (not the maximum number, which is assigned by "charLimit"!).
You can check or uncheck this option in the Field Properties and assign an exact number of characters (it's the last option in the dialog window at the bottom). One consequence is that this number of characters is perfectly aligned in the text field.
However, I couldn't find this property within the Field Properties in the Acrobat JavaScript API. Is this simply missing in the documentation, or is it not accessible via JavaScript?
Thanks, Rudi
Finally, I found it.
You have to set both
f.comb = true;
and
f.charLimit = n; // where n is the intended number of characters
with the intended result.
R

Categories

Resources