2

I'm trying to format a phone number as a user types, and I've almost got it, but I just can't seem to bring it home.

Here's what I'm looking for:

+12345678900 --> +1 (234) 567 89-00
+1234567890  --> +1 (234) 567 89-0
+123456789   --> +1 (234) 567 89-
+12345       --> +1 (234) 5
+123         --> +1 (23

Here's my approach:

'+12345678900'
    .replace(
        /^(\+\d{1})?(\d{1,3})?(\d{1,3})?(\d{1,2})?(\d{1,2})?$/,
        "$1 ($2) $3 $4-$5"
    ); // +1 (234) 567 89-00 ✔️

It works with a full number, but fails for other cases, here are some of them:

+12345678 // expected '+1 (234) 567 8', received '+1 (234) 567 8-'
+1234567  // expected '+1 (234) 567',   received '+1 (234) 567 -'
+123      // expected '+1 (23',         received '+1 (23)  -'

What am I missing here?

Mike K
  • 7,621
  • 14
  • 60
  • 120
  • Use a [replacer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_function_as_the_replacement) to replace the match conditionally: `.replace(/.../, (match, g1, g2, g3, g4, g5) => { /* your code */ })`. – InSync Mar 26 '23 at 12:31
  • See also [Implement an input with a mask](https://stackoverflow.com/questions/12578507/implement-an-input-with-a-mask/55010378#55010378) – trincot Mar 26 '23 at 13:43

2 Answers2

1

Don't reinvent the wheel. Look into Google's LibPhonenumber, which has a javascript implementation and does 'As you type' formatting. You can try the demo here.

Output for your example:

****Parsing Result:****
{"country_code":1,"national_number":2345678900,"raw_input":"+12345678900 ","country_code_source":1}

****Validation Results:****
Result from isPossibleNumber(): true
Result from isValidNumber(): true
Result from isValidNumberForRegion(): false
Phone Number region: US
Result from getNumberType(): FIXED_LINE_OR_MOBILE

****Formatting Results:**** 
E164 format: +12345678900
Original format: +1 234-567-8900
National format: (234) 567-8900
International format: +1 234-567-8900
Out-of-country format from US: 1 (234) 567-8900
Out-of-country format from Switzerland: 00 1 234-567-8900
Format for mobile dialing (calling from US): +1 234-567-8900
Format for national dialing with preferred carrier code and empty fallback carrier code: (234) 567-8900

****AsYouTypeFormatter Results****
Char entered: + Output: +
Char entered: 1 Output: +1
Char entered: 2 Output: +1 2
Char entered: 3 Output: +1 23
Char entered: 4 Output: +1 234
Char entered: 5 Output: +1 234-5
Char entered: 6 Output: +1 234-56
Char entered: 7 Output: +1 234-567
Char entered: 8 Output: +1 234-567-8
Char entered: 9 Output: +1 234-567-89
Char entered: 0 Output: +1 234-567-890
Char entered: 0 Output: +1 234-567-8900
Char entered:   Output: +12345678900 

This works for all (most?) countries and ensures you use a formatting that is globally used. If I'm not mistaken it is used in Android phones and most certainly in lots of other projects and I think it can be considered a de facto standard for formatting numbers.

RobIII
  • 8,488
  • 2
  • 43
  • 93
0

You can use a replacement function in your replace method. With this you can build your string based on your required conditions:

function numberFormatter(number) {
  return number.replace(
    /^(\+\d{1})(\d{1,3})?(\d{1,3})?(\d{1,2})?(\d{1,2})?$/,
    (match, p1, p2, p3, p4, p5) => p1 + ' ' + (p2 ? '(' + p2 + (p2.length > 2 ? ')' : '') : '') + (p3 ? ' ' + p3 : '') + (p4 ? ' ' + p4 + (p4.length > 1 ? '-' : '') : '') + (p5 ? p5 : '')
  )
}

// Utils

// Tests which worked from your code
expect("+12345678900", "+1 (234) 567 89-00")
expect("+1234567890", "+1 (234) 567 89-0")
expect("+123456789", "+1 (234) 567 89-")
expect("+12345", "+1 (234) 5")

// Tests which work now
expect("+12345678", "+1 (234) 567 8")
expect("+1234567", "+1 (234) 567")
expect("+123", "+1 (23")

function expect(input, expectedResult) {
  const formattedNumber = numberFormatter(input)
  if (formattedNumber === expectedResult) {
    console.log(`PASSED: "${input}" -> "${expectedResult}"`);
  } else {
    console.error(`FAILED: "${input}" -> Expected: "${expectedResult}", Received: "${formattedNumber}"`);
  }
}

Off-topic: I was a little confused by your requirements. I think the replacements will make more sense:

+1 (234) 567 89     -> +1 (234) 567 89  instead of +1 (234) 567 89-
+1 (23)             -> +1 (23)          instead of +1 (23
Lars Flieger
  • 2,421
  • 1
  • 12
  • 34