5

I have failed to convert the input value to currency format. I want to automaticly add thousands and decimal separators when the user types the number (5,000.00; 125,000.00).

Here's my code :

$('input.CurrencyInput').on('blur, focus, keyup',
    function() {
        $(this).val().toLocaleString('en-US', {
            style: 'decimal',
            maximumFractionDigits: 2,
            minimumFractionDigits: 2
        });
    });
AlonsoCT
  • 177
  • 1
  • 4
  • 18
  • `.val()` returns a `string`, but `.toLocaleString` is a method of a `number`. – Tyler Roper Jun 06 '18 at 14:09
  • Possible duplicate of [How can I format numbers as dollars currency string in JavaScript?](https://stackoverflow.com/questions/149055/how-can-i-format-numbers-as-dollars-currency-string-in-javascript) – admcfajn Jun 06 '18 at 14:10
  • Also, you're not *setting* the value anywhere. You're fetching the current value and attempting to manipulate it, but not doing anything with the result. – Tyler Roper Jun 06 '18 at 14:13
  • You cannot use oninput nor keyup since you do not know what they are planning to type. Would I be able to type 1,123,123.123 and have it formate to 1,123,123.12 or type 1123123.123 (which is a valid float) and have it format to 1,123,123.12 while I type? – mplungjan Jun 06 '18 at 14:20
  • You may want to take a look at some external libraries/plugins. I've used [this one](http://robinherbots.github.io/Inputmask/) in the past and it works well. (Click on the *demo* button - there's a currency input there) – Tyler Roper Jun 06 '18 at 14:35

2 Answers2

10

There are a couple of problems with your code:

  1. You're using comma when binding multiple event handlers to the input box.
  2. You're not converting the received value to a number before applying toLocaleString on it.
  3. You're not setting the value of the textbox again after conversion.

Correcting these, here is a working demo. For the sake of simplicity, I've removed the other event handlers, except blur, as keyup was causing problems.

$('input.CurrencyInput').on('blur', function() {
  const value = this.value.replace(/,/g, '');
  this.value = parseFloat(value).toLocaleString('en-US', {
    style: 'decimal',
    maximumFractionDigits: 2,
    minimumFractionDigits: 2
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input class="CurrencyInput">
31piy
  • 23,323
  • 6
  • 47
  • 67
  • Did you try this? – mplungjan Jun 06 '18 at 14:13
  • @mplungjan -- yes, you can run the snippet. It correctly formats the number on blur. – 31piy Jun 06 '18 at 14:14
  • Not if I enter `1,123,123.123` – mplungjan Jun 06 '18 at 14:14
  • Also try .on("input" instead `adding automatic when the user types the number` – mplungjan Jun 06 '18 at 14:15
  • I'd imagine that it would only work once. After you repopulate the `.val()` with the formatted string, `parseFloat()` won't return what's expected in sequential calls. – Tyler Roper Jun 06 '18 at 14:15
  • @mplungjan -- I believe OP has used such number as desired output. The input must be a number, otherwise `toLocaleString` won't work correctly. – 31piy Jun 06 '18 at 14:15
  • Hence toLocaleString might not be what they actually want – mplungjan Jun 06 '18 at 14:16
  • I agree with @mplungjan . I suppose to get your specific example working you could do a `.replace(',','')` before parsing the value, but even then, I'm not sure how that might handle cases of other locales. – Tyler Roper Jun 06 '18 at 14:18
  • @TylerRoper -- In that case, I'm not sure how to get this to work correctly. :D I understand that patches can be made, but it depends on what OP requires. – 31piy Jun 06 '18 at 14:20
  • Me neither, hence I neither wrote an answer nor hammerclosed as duplicate. – mplungjan Jun 06 '18 at 14:21
  • When I've got jQuery available to me, I personally just use one of the many robust masking libraries that are available, however I'm not sure that's much of an answer. In any case I don't see a valid solution for the `keyup` events. – Tyler Roper Jun 06 '18 at 14:21
  • 1
    We can wait for OP's comment. If this suits the requirement, then no need to go further. – 31piy Jun 06 '18 at 14:22
  • 1
    I think this is a good answer, just make it work for re-entry,. eg. typing `1000`, will give you `1,000.00`.. but now if you alter the last 0 before the decimal, you will get `1.00`, instead of `1,001.00` stripping the commas before re-parse like @TylerRoper says would help. But `.replace(/,/g,'')` instead, as the none regex only replace the first `,`.. :) – Keith Jun 06 '18 at 14:27
  • @Keith Agreed. For something a bit more robust I'd still lean on a plugin, but this answer should satisfy OP's immediate question. – Tyler Roper Jun 06 '18 at 14:36
  • @31piy Thank you! It's a good answer but I used the keyup event, and it doesn't allow me to enter more than 3 digits in a row and doesn't allow me to delete the value either. – AlonsoCT Jun 06 '18 at 15:05
  • How to add money separator as user types? Your code only add separators when user leaves the input. – saeed khalafinejad Oct 11 '22 at 04:52
2

Parse value to specific Locale Currency

The below function will try to parse any gibberish input value to a specific currency.
Useful if you don't want to force users to input values in a specific locale format.

Caveat:

Since the input can literally be gibberish like 1,2.3.45,5 and still give a valid output (like i.e USD: "12,345.50"), there's a small but user friendly caveat:

// Example: conversion to HRK (Format: n.nnn,nn)
INPUT: "0.575"   OUTPUT: "0,58"      // smart guessed: decimals
INPUT: "1.575"   OUTPUT: "1.575,00"  // smart guessed: separator

Example:

/**
 * Parse value to currency
 * @param {number|string} input
 * @param {string} locale - Desired locale i.e: "en-US" "hr-HR"
 * @param {string} currency - Currency to use "USD" "EUR" "HRK"  
 * @return {object} 
 */
const parse = (input, locale = "en-US", currency = "USD") => {
  let fmt = String(input)
    .replace(/(?<=\d)[.,](?!\d+$)/g, "")
    .replace(",", ".");
  const pts = fmt.split(".");
  if (pts.length > 1) {
    if (+pts[0] === 0) fmt = pts.join(".");
    else if (pts[1].length === 3) fmt = pts.join("");
  }
  const number = Number(fmt);
  const isValid = isFinite(number);
  const string = number.toFixed(2);
  const intlNFOpts = new Intl.NumberFormat(locale, {
    style: "currency",
    currency: currency,
  }).resolvedOptions();
  const output = number.toLocaleString(locale, {
    ...intlNFOpts,
    style: "decimal",
  });
  return {
    input,
    isValid,
    string,
    number,
    currency,
    output,
  };
};

Example test:

/**
 * Parse value to currency
 * @param {number|string} input
 * @param {string} locale - Desired locale i.e: "en-US" "hr-HR"
 * @param {string} currency - Currency to use "USD" "EUR" "HRK"  
 * @return {object} 
 */
const parse = (input, locale = "en-US", currency = "USD") => {
  let fmt = String(input)
    .replace(/(?<=\d)[.,](?!\d+$)/g, "")
    .replace(",", ".");
  const pts = fmt.split(".");
  if (pts.length > 1) {
    if (+pts[0] === 0) fmt = pts.join(".");
    else if (pts[1].length === 3) fmt = pts.join("");
  }
  const number = Number(fmt);
  const isValid = isFinite(number);
  const string = number.toFixed(2);
  const intlNFOpts = new Intl.NumberFormat(locale, {
    style: "currency",
    currency: currency,
  }).resolvedOptions();
  const output = number.toLocaleString(locale, {
    ...intlNFOpts,
    style: "decimal",
  });
  return {
    input,
    isValid,
    string,
    number,
    currency,
    output,
  };
};

// TEST:
[
  // Valid values
  "2e5",
  "1,2.3,10,6",
  "0.575",
  "1.575",
  "2.30",
  "1.000.00",
  "1.000",
  1000,
  1,
  ".4",
  "4.",
  "0.25",
  "0.076",
  "1.076",
  0.3478,
  "0.05",
  "123,123",
  "3.000,333.444,009",
  "123,123.80",
  "23.123,80",
  "23.123,8",
  "23.4",
  
  // Invalid values
  null,
  NaN,
  Infinity,
  "a",
].forEach((val) => {
  const p = parse(val, "hr-HR", "HRK");
  console.log(`INP: ${p.input}\t OUT: ${p.output}`);
});
.as-console-wrapper {min-height: 100vh;}

Documentaion:

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313