I'm making an admin dashboard with NextJS and MaterialUI (mui), and I need to mask some values (eg.: phone numbers) that comes from back-end without mask.
I'm using react-input-mask for input elements, but I don't know how to mask any element (for example, a span inside a table cell).
For example, my back-end gives me a phone number like 5511912345678 and a document number like 12345678900, and I need to display it in a table this way:
First name
Last name
Phone number
Document
John
Doe
+55 (11) 9.1234-5678
123.456.789-00
Lorem
Ipsum
+55 (11) 1234-5678
234.567.891-00
How can I mask that values on a table-cell without an <input> element? I assume that will not be possible using react-input-mask, so there's no problem adding another package. I just don't know what package to install to achieve this result.
Thanks!
You can either use a library from NPM or build your own masking function. Here is a naïve implementation of the masking functions. Here is a working example and following are the important snippets from it.
digitsExtractor will return a wrapped object which will help in pulling out digits from the number.
function digitsExtractor(numberString) {
const digitsInReverse = numberString.split('').reverse();
const extractDigits = (count = 0) => {
const digits = [];
while (count-- > 0) digits.push(digitsInReverse.pop());
return digits.join('');
};
return {
extractDigits,
getRemainingDigitsCount: () => digitsInReverse.length,
};
}
maskPhoneNumber will mask the phone number
function maskPhoneNumber(phoneNumber) {
const { extractDigits, getRemainingDigitsCount } =
digitsExtractor(phoneNumber);
return `+${extractDigits(2)} (${extractDigits(2)}) ${
getRemainingDigitsCount() === 8
? [extractDigits(4), extractDigits(4)].join('-')
: getRemainingDigitsCount() === 9
? `${extractDigits(1)}.${extractDigits(4)}-${extractDigits(4)}`
: ''
}`;
}
maskDocumentNumber will mask the document number
function maskDocumentNumber(documentNumber) {
const { extractDigits } = digitsExtractor(documentNumber);
return [
[extractDigits(3), extractDigits(3), extractDigits(3)].join('.'),
extractDigits(2),
].join('-');
}
You can make it more generic based on your requirements.
Related
I am posting this question with my answer so far but would like to invite other solutions as I am not 100% sure about mine.
It will:
Automatically place the dashes in the right place so the user only has to enter the digits.
Can be any size. You can set a maxlength attribute on your input and it will continue to apply dashes intil it runs out of space. It will default to 8 characters max
Allowsuser to delete digits without the need to delete the dashes too.
Why am I posting a this?
I could not find the answer myself on StackOverflow and when you search this question on Google, it keeps returning a PHP answer for StackOverflow instead! There are even answers in there for Javascript.
Hopefully this question can produce other solutions too!
How does it work?
This is designed to work with a real-time input.
It works out the maximum length
It captures the event and works out if the delete key was pressed
The 'regex' part is saying to replace ever 2nd character with itself
plus a dash.
The next line first replaces anything that's not a number, then uses the regex to inject dashes and finally the string is sliced to remove any trailing slash
You would apply this function to your onkeyup or onpaste events, passing 'this' in.
function checkSortCode(el,ev){
var len = el.maxLength || 8;
ev = ev || window.event;
if(ev.keyCode == 8 && el.value.slice(-1) == "-"){
el.value = el.value.slice(0,-1);
} else {
var regex = new RegExp("(\\S{" + (2 - 1) + "}\\S)", "g");
el.value = el.value.replace(/[^0-9]/g,"").replace(regex,("$1"+"-")).slice(0,len);
}
}
.sortcode::placeholder{color:#eeeeee;}
body{font-family:arial,sans-serif;font-size:1.4em;}
input{font-size:1.4em;}
<label>Sort Code</label><br>
<input type="text" name="sortcode" onkeyup="checkSortCode(this,event)" onpaste="checkSortCode(this,event)" class="sortcode" size="8" maxlength="8" placeholder="00-00-00" />
Ideally, I wanted it to show the 00-00-00 format all the time and then the user would fill it in but have padded zeros where they hadn't. That's not easy as the cursor wants to go to the end of the input.
What you're looking for is called Input Masking. You can implement it yourself but I would recommend using a library to separate the actual input value and the mask.
Here an implementation using native js, you'll notice it's a bit janky.
<html>
<body>
<input id="input">
<script>
const pattern = '00-00-00-00'
const patternRegex = /^[0-9]{2}\-[0-9]{2}\-[0-9]{2}\-[0-9]{2}$/
const separator = '-'
/* returns current value completed by the pattern (filled with 0) */
const fill = value => {
return `${value}${pattern.substring(value.length)}`
}
/* format the input on keyup */
const format = event => {
/* only format the input at cursor position (to ignore filled pattern) */
const position = event.target.selectionStart
const value = event.target.value.substring(0, position)
/* rollback invalid inputs */
if (!patternRegex.test(fill(value))) {
event.target.value = event.target.value.substring(0, position - 1)
return
}
/* change target valuer to include pattern and restore carret position */
event.target.value = fill(value)
const newPosition = event.target.value[position] === separator ? position + 1 : position
event.target.setSelectionRange(newPosition, newPosition)
}
const input = document.getElementById('input')
input.addEventListener('keyup', format)
</script>
</body>
</html>
You can check some other implementation here : https://css-tricks.com/input-masking/
The reason why it's janky is because we format the input after a change occured. When using a library (or React), you can control the input value before it's rendered.
In this code, I have the value 1000000 (one million) which I format with toLocaleString('en-gb') to get 1,000,000.
Then I print that value as text and it works as expected, also when I use that const as the value of a text input.
But when using the value in a numeric input, it just doesn't render. It works though when the value is < 1 million.
Inspecting the html, I see the value is correct:
In addition, when trying to type the value in that numeric input, it doesn't register values after 4 digits.
Any ideas what's going on? I wonder if it could be that after 999.000 the number will have two thousand-separators
teh codez (also in this playground https://stackblitz.com/edit/react-ts-8ufbe1?file=App.tsx):
export default function App() {
const value = (1000000).toLocaleString('en-gb');
const [inputValue, setInputValue] = React.useState(value);
return (
<div>
<h1>{value}</h1>
<input
type="number"
value={inputValue}
onChange={(e) => setInputValue(Number(e.target.value).toLocaleString('en-gb'))}
/>
<input type="text" value={inputValue} />
</div>
);
}
I see there are libraries like react-number-format but seems like the native way should do what I need.
Thank you very much in advance.
It works before reaching 1,000 because 1,000 is where you have to add a comma. At 999 you still satisfy the requirements of an input with type number you're inserting only a number 999. Since HTML only really allows strings they let you pass with "999" vs 999 the pure numeric version.
In any case. I don't think you want to use the type='number' it works pretty poorly and shows the ticker up and down. If this is a mobile focused decision consider using type='tel'.
What you can also do is attempt to manually mask the behavior by putting 2 inputs on top of each other. Or you can use an input type='tel' onFocus={() => Number(inputValue)} and onBlur={() => inputValue.toLocaleString('en-gb')} and that will be the start to getting where you want to get.
This type of stuff usually is slightly messy and will take a bit to perfect. The UI can easily get wonky, and the problem is not so perfectly solved:
We want to enforce that users only type numbers and make it easy for them to do so (especially mobile)
We want the numbers to appear as strings.
Those 2 priorities are at odds with each other.
If you don't want to directly import a library which contains this I'd strongly suggest you read their source code.
Some potential resources:
https://cchanxzy.github.io/react-currency-input-field/ (Note that just the single component is 600 lines long)
including this excerpt:
const handleOnFocus = (event: React.FocusEvent<HTMLInputElement>): number => {
onFocus && onFocus(event);
return stateValue ? stateValue.length : 0;
};
/**
* Handle blur event
*
* Format value by padding/trimming decimals if required by
*/
const handleOnBlur = (event: React.FocusEvent<HTMLInputElement>): void => {
const {
target: { value },
} = event;
const valueOnly = cleanValue({ value, ...cleanValueOptions });
if (valueOnly === '-' || !valueOnly) {
setStateValue('');
onBlur && onBlur(event);
return;
}
const fixedDecimals = fixedDecimalValue(valueOnly, decimalSeparator, fixedDecimalLength);
const newValue = padTrimValue(
fixedDecimals,
decimalSeparator,
decimalScale !== undefined ? decimalScale : fixedDecimalLength
);
const numberValue = parseFloat(newValue.replace(decimalSeparator, '.'));
const formattedValue = formatValue({
...formatValueOptions,
value: newValue,
});
if (onValueChange) {
onValueChange(newValue, name, {
float: numberValue,
formatted: formattedValue,
value: newValue,
});
}
setStateValue(formattedValue);
onBlur && onBlur(event);
};
```
I use this function to format every number input I have :
export const parseToNumber = (value: string) => {
const numberRegex = /^[0-9\b]+$/;
if (numberRegex.test(value)) {
return value;
} else {
return value.replace(/\D/g, "");
}
};
And I use it with another function to have a an empty space between every 4 characters like this :
onChangeText={(e) => {
setFieldValue(
"phone",
parseToNumber(e).replace(/(.{4})/g, "$1 ")
);
}}
The problem is the Backspace won't work because of the parseToNumber . How can I exclude the functions when the user presses the Backspace and use the simple way like this setFieldValue("phone", (e)); so the backspace works ?
Or is there another way to fix the Backspace disabled problem with these functions in place possibly modify the parseToNumber function ?
If Problem is parseToNumber, you can make sure only take numbers by opening numbers only keyboard by passing prop
keyboardType = 'numeric'
or to simplify the use and support multiple spacing for input you can use this library - react-native-mask-input, its js only library so you can also just copy the specific implementation on need to use basis.
This is my first time posting in SO, and I need help formatting numbers correctly in a calculator app that I've made using ReactJS.
Here is the link on StackBlitz.
Now, I want to achieve the formatting effect after numbers are pressed and shown in the display and arithmetic signs are added, especially when multiple arithmetics are used.
To illustrate my point, below is a sample of the current display:
123456 + 7890123 * 11111
And what I want to achieve is this:
123,456 + 7,890,123 * 11,111
I could only do this when displaying the result using the toLocaleString() function. Even then, if I pressed number/numbers and then clicking the result button twice, it will be crashed (as the display contains a comma, and the evaluation function will not process it properly).
Hopefully, someone can point me out in the right direction. Thanks.
Quick Fix
You can remove ',' before evaluating the result.
Change line 65 of Input.js to
setDisplay(evaluate(display.replace(/,/g, '')).toLocaleString());
Better Solution
Keep separate variables for Internal logical state and External Display state, where the former is valid for code and the latter is its visual representation.
You can achieve this by useEffect like this
/* --- Display.js --- */
const Display = ({ display }) => {
const [printValue, setPrintValue] = useState('')
useEffect(() => {
setPrintValue(`${display}`.replace(/[0-9]+/g, num => (+num).toLocaleString()))
}, [display])
return (
<StyledDisplay>
{' '}
<span>{printValue}</span>{' '}
</StyledDisplay>
);
};
Also, in Input.js, update line 65 in handleResult to
setDisplay(evaluate(display));
For this kind of situations, I like to use regex. Here what you can do is to use a regex that matches 3 digits and add the comma as wanted. To simplify the regex I usually reverse the string:
const original = "123456 / 98765 * 22222"
function format(str) {
const reversed = str.split('').reverse().join('')
const formatted = reversed.replace(/(\d{3})(?=\d)/gm, `$1,`)
return formatted.split('').reverse().join('')
}
console.log('original string : ', original)
console.log('result string : ',format(original))
You can use this function in your Display component, just before injecting the display prop like this
function format(str){
const reversed = str.split('').reverse().join('')
const formatted = reversed.replace(/(\d{3})(?=\d)/gm, `$1,`)
return formatted.split('').reverse().join('')
}
const Display = ({ display }) => {
return (
<StyledDisplay>
{' '}
<span>{format(display)}</span>{' '}
</StyledDisplay>
);
};
If I do:
var number = 3500;
alert(number.toLocaleString("hi-IN"));
I will get ३,५०० in Hindi.
But how can I convert it back to 3500.
I want something like:
var str='३,५००';
alert(str.toLocaleNumber("en-US"));
So, that it can give 3500.
Is it possible by javascript or jquery?
I think you are looking for something like:
https://github.com/jquery/globalize
Above link will take you to git project page. This is a js library contributed by Microsoft.
You should give it one try and try to use formt method of that plugin. If you want to study this plugin, here is the link for the same:
http://weblogs.asp.net/scottgu/jquery-globalization-plugin-from-microsoft
I hope this is what you are looking for and will resolve your problem soon. If it doesn't work, let me know.
Recently I've been struggling with the same problem of converting stringified number formatted in any locale back to the number.
I've got inspired by the solution implemented in NG Prime InputNumber component. They use Intl.NumberFormat.prototype.format() (which I recommend) to format the value to locale string, and then create set of RegExp expressions based on simple samples so they can cut off particular expressions from formatted string.
This solution can be simplified with using Intl.Numberformat.prototype.formatToParts(). This method returns information about grouping/decimal/currency and all the other separators used to format your value in particular locale, so you can easily clear them out of previously formatted string. It seems to be the easiest solution, that will cover all cases, but you must know in what locale the value has been previously formatted.
Why Ng Prime didn't go this way? I think its because Intl.Numberformat.prototype.formatToParts() does not support IE11, or perhaps there is something else I didn't notice.
A complete code example using this solution can be found here.
Unfortunately you will have to tackle the localisation manually. Inspired by this answer , I created a function that will manually replace the Hindi numbers:
function parseHindi(str) {
return Number(str.replace(/[०१२३४५६७८९]/g, function (d) {
return d.charCodeAt(0) - 2406;
}).replace(/[०१२३४५६७८९]/g, function (d) {
return d.charCodeAt(0) - 2415;
}));
}
alert(parseHindi("३५००"));
Fiddle here: http://jsfiddle.net/yyxgxav4/
You can try this out
function ConvertDigits(input, source, target) {
var systems = {
arabic: 48, english: 48, tamil: 3046, kannada: 3302, telugu: 3174, hindi: 2406,
malayalam: 3430, oriya: 2918, gurmukhi: 2662, nagari: 2534, gujarati: 2790,
},
output = [], offset = 0, zero = 0, nine = 0, char = 0;
source = source.toLowerCase();
target = target.toLowerCase();
if (!(source in systems && target in systems) || input == null || typeof input == "undefined" || typeof input == "object") {
return input;
}
input = input.toString();
offset = systems[target] - systems[source];
zero = systems[source];
nine = systems[source] + 9;
for (var i = 0 ; i < input.length; i++) {
var char = input.charCodeAt(i);
if (char >= zero && char <= nine) {
output.push(String.fromCharCode(char + offset));
} else {
output.push(input[i]);
}
}
return output.join("");
}
var res = ConvertDigits('१२३४५६७८९', 'hindi', 'english');
I got it from here
If you need a jquery thing then please try this link
Use the Globalize library.
Install it
npm install globalize cldr-data --save
then
var cldr = require("cldr-data");
var Globalize = require("globalize");
Globalize.load(cldr("supplemental/likelySubtags"));
Globalize.load(cldr("supplemental/numberingSystems"));
Globalize.load(cldr("supplemental/currencyData"));
//replace 'hi' with appropriate language tag
Globalize.load(cldr("main/hi/numbers"));
Globalize.load(cldr("main/hi/currencies"));
//You may replace the above locale-specific loads with the following line,
// which will load every type of CLDR language data for every available locale
// and may consume several hundred megs of memory!
//Use with caution.
//Globalize.load(cldr.all());
//Set the locale
//We use the extention u-nu-native to indicate that Devanagari and
// not Latin numerals should be used.
// '-u' means extension
// '-nu' means number
// '-native' means use native script
//Without -u-nu-native this example will not work
//See
// https://en.wikipedia.org/wiki/IETF_language_tag#Extension_U_.28Unicode_Locale.29
// for more details on the U language code extension
var hindiGlobalizer = Globalize('hi-IN-u-nu-native');
var parseHindiNumber = hindiGlobalizer.numberParser();
var formatHindiNumber = hindiGlobalizer.numberFormatter();
var formatRupeeCurrency = hindiGlobalizer.currencyFormatter("INR");
console.log(parseHindiNumber('३,५००')); //3500
console.log(formatHindiNumber(3500)); //३,५००
console.log(formatRupeeCurrency(3500)); //₹३,५००.००
https://github.com/codebling/globalize-example
A common scenario for this problem is to display a float number to the user and then want it back as a numerical value.
In that case, javascript has the number in the first place and looses it when formatting it for display. A simple workaround for the parsing is to store the real float value along with the formatted value:
var number = 3500;
div.innerHTML = number.toLocaleString("hi-IN");
div.dataset.value = number;
Then get it back by parsing the data attribute:
var number = parseFloat(div.dataset.value);
This is a Columbus's egg style answer. It works provided the problem is an egg.
var number = 3500;
var toLocaleString = number.toLocaleString("hi-IN")
var formatted = toLocaleString.replace(',','')
var converted = parseInt(formatted)