-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for (XXX) XXX-XXXX format and ability to remove country code #61
base: master
Are you sure you want to change the base?
Changes from all commits
b9b3c94
c26368f
93c0b6e
19f82d4
954fd8f
3b3607f
83e4738
22dd8c2
db4eaac
0621919
8bd677e
4da6646
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,9 +30,9 @@ | |
|
||
By default, the phone number is considered to be a US phone number, and | ||
will be validated according to the pattern `XXX-XXX-XXXX`, where `X` is a | ||
digit, and `-` is the separating dash. If you want to customize the input | ||
digit, and `-` is the separating dash. Formatting also supports single spaces. If you want to customize the input | ||
for a different area code or number pattern, use the `country-code` and | ||
`phone-number-pattern` attributes | ||
`phone-number-pattern` attributes. | ||
|
||
<gold-phone-input | ||
country-code="33" | ||
|
@@ -77,6 +77,10 @@ | |
@apply(--gold-phone-input-country-code); | ||
} | ||
|
||
.label-no-country-code { | ||
left: 0 !important; | ||
} | ||
|
||
.container { | ||
@apply(--layout-horizontal); | ||
} | ||
|
@@ -101,15 +105,16 @@ | |
<label hidden$="[[!label]]">[[label]]</label> | ||
|
||
<div class="container"> | ||
<span class="country-code">+[[countryCode]]</span> | ||
|
||
|
||
<span hidden$="[[noCountryCode]]" class="country-code">+[[countryCode]]</span> | ||
|
||
<input is="iron-input" id="input" | ||
aria-labelledby$="[[_ariaLabelledBy]]" | ||
aria-describedby$="[[_ariaDescribedBy]]" | ||
required$="[[required]]" | ||
bind-value="{{value}}" | ||
name$="[[name]]" | ||
allowed-pattern="[0-9\-]" | ||
allowed-pattern="[()\s0-9\-]" | ||
autocomplete="tel" | ||
type="tel" | ||
prevent-invalid-input | ||
|
@@ -157,6 +162,14 @@ | |
value: '1' | ||
}, | ||
|
||
/* | ||
* Set this to true to disable the country code | ||
*/ | ||
noCountryCode: { | ||
type: Boolean, | ||
value: false | ||
}, | ||
|
||
/* | ||
* The format of a valid phone number, including formatting but excluding | ||
* the country code. Use 'X' to denote the digits separated by dashes. | ||
|
@@ -181,51 +194,122 @@ | |
// If there's an initial input, validate it. | ||
if (this.value) | ||
this._handleAutoValidate(); | ||
|
||
}, | ||
|
||
attached: function() { | ||
|
||
if(this.noCountryCode) { | ||
this.set('countryCode', ''); | ||
Polymer.dom(this).node.querySelector('label').classList.add('label-no-country-code'); | ||
} | ||
|
||
this.set('maxlength', this.phoneNumberPattern.length); | ||
|
||
}, | ||
|
||
_phoneNumberPatternChanged: function() { | ||
// Transform the pattern into a regex the iron-input understands. | ||
var regex = ''; | ||
regex = this.phoneNumberPattern.replace(/\s/g, '\\s'); | ||
regex = regex.replace(/\(/gi, '\\('); | ||
regex = regex.replace(/\)/gi, '\\)'); | ||
regex = regex.replace(/X/gi, '\\d'); | ||
regex = regex.replace(/\+/g, '\\+'); | ||
this.$.input.pattern = regex; | ||
|
||
this.regex = regex; | ||
}, | ||
|
||
/** | ||
* A handler that is called on input | ||
*/ | ||
_onValueChanged: function(value, oldValue) { | ||
|
||
// The initial property assignment is handled by `ready`. | ||
if (oldValue == undefined || value === oldValue) | ||
if (oldValue == undefined || value == undefined) { | ||
this.value = ''; | ||
return; | ||
} | ||
|
||
//Ensure value is a string | ||
value = value ? value.toString() : ''; | ||
|
||
// Keep track of how many dashes the original value has. After | ||
// reformatting the value, we might gain or lose some of them, which | ||
// means we have to correctly move the caret to account for the difference. | ||
var start = this.$.input.selectionStart; | ||
|
||
var initialDashesBeforeCaret = value.substr(0, start).split('-').length - 1; | ||
var initialSpacesBeforeCaret = value.substr(0, start).split(' ').length - 1; | ||
var initialLeftParenBeforeCaret = value.substr(0, start).split('(').length - 1; | ||
var initialRightParenBeforeCaret = value.substr(0, start).split(')').length - 1; | ||
|
||
var formattedValue = ''; | ||
|
||
// Remove any already-applied formatting. | ||
value = value.replace(/-/g, ''); | ||
var shouldFormat = value.length <= this.phoneNumberPattern.replace(/-/g, '').length; | ||
var formattedValue = ''; | ||
value = value.replace(/\(/g, ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think all of this would be enormously simplified if you force the format, including the country code to be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This normalization is needed if we're going to support parentheses and spaces There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but this code if very repetitive, which makes it brittle, which makes it hard to maintain. I would much rather lose formatting flexibility than have everything copy pasted 3 times :( Can we somehow generalize the code? Maybe we don't need to count all the different kinds of characters added? |
||
value = value.replace(/\)/g, ''); | ||
value = value.replace(/\s/g, ''); | ||
|
||
oldValue = oldValue.replace(/-/g, ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to do this twice, for both value and oldValue? |
||
oldValue = oldValue.replace(/\(/g, ''); | ||
oldValue = oldValue.replace(/\)/g, ''); | ||
oldValue = oldValue.replace(/\s/g, ''); | ||
|
||
if(value === oldValue) { | ||
return; | ||
} | ||
|
||
if(value.length > 10) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 10 is completely arbitrary, and doesn't work for numbers that aren't US phone numbers. The previous code calculated this based on the pattern |
||
this.updateValueAndPreserveCaret(value.trim()); | ||
this._handleAutoValidate(); | ||
return; | ||
} | ||
|
||
// Fill in the dashes according to the specified pattern. | ||
var leftParenIndex = this.phoneNumberPattern.indexOf('('); | ||
var leftParenAdded = false; | ||
|
||
var rightParenIndex = this.phoneNumberPattern.indexOf(')') - 1; | ||
var rightParenAdded = false; | ||
|
||
var currentSpaceIndex = 0; | ||
var currentDashIndex = 0; | ||
var totalDashesAdded = 0; | ||
|
||
var totalCharactersAdded = 0; | ||
|
||
for (var i = 0; i < value.length; i++) { | ||
|
||
currentDashIndex = this.phoneNumberPattern.indexOf('-', currentDashIndex); | ||
|
||
// Since we remove any formatting first, we need to account added dashes | ||
currentSpaceIndex = this.phoneNumberPattern.indexOf(' ', currentSpaceIndex); | ||
|
||
// Since we remove any formatting first, we need to account added dashes, etc. | ||
// when counting the position of new dashes in the pattern. | ||
if (shouldFormat && i == (currentDashIndex - totalDashesAdded)) { | ||
|
||
if (i == leftParenIndex) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be abstracted in a function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be abstracted in a function? |
||
formattedValue += '('; | ||
leftParenAdded = true; | ||
totalCharactersAdded++; | ||
} | ||
|
||
if (i == rightParenIndex) { | ||
formattedValue += ')'; | ||
rightParenAdded = true; | ||
totalCharactersAdded++; | ||
} | ||
|
||
if (i == (currentSpaceIndex - totalCharactersAdded)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs a lot of comments. At the moment, it's very hard to understand what this code is doing |
||
formattedValue += ' '; | ||
currentSpaceIndex++; | ||
totalCharactersAdded++; | ||
} | ||
|
||
if (i == (currentDashIndex - totalCharactersAdded)) { | ||
formattedValue += '-'; | ||
currentDashIndex++; | ||
totalDashesAdded++; | ||
totalCharactersAdded++; | ||
} | ||
|
||
formattedValue += value[i]; | ||
|
@@ -234,12 +318,21 @@ | |
var updatedDashesBeforeCaret = formattedValue.substr(0, start).split('-').length - 1; | ||
var dashesDifference = updatedDashesBeforeCaret - initialDashesBeforeCaret; | ||
|
||
var updatedSpacesBeforeCaret = formattedValue.substr(0, start+1).split(' ').length - 1; | ||
var spacesDifference = updatedSpacesBeforeCaret - initialSpacesBeforeCaret; | ||
|
||
var updatedLeftParenBeforeCaret = formattedValue.substr(0, start).split('(').length - 1; | ||
var leftParenDifference = updatedLeftParenBeforeCaret - initialLeftParenBeforeCaret; | ||
|
||
var updatedRightParenBeforeCaret = formattedValue.substr(0, start).split(')').length - 1; | ||
var rightParenDifference = updatedRightParenBeforeCaret - initialRightParenBeforeCaret; | ||
|
||
// Note: this will call _onValueChanged again, which will move the | ||
// cursor to the end of the value. Correctly adjust the caret afterwards. | ||
this.updateValueAndPreserveCaret(formattedValue.trim()); | ||
|
||
// Advance or back up the caret based on the change that happened before it. | ||
this.$.input.selectionStart = this.$.input.selectionEnd = start + dashesDifference; | ||
this.$.input.selectionStart = this.$.input.selectionEnd = start + spacesDifference + dashesDifference + leftParenDifference + rightParenDifference; | ||
|
||
this._handleAutoValidate(); | ||
}, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,14 @@ | |
</template> | ||
</test-fixture> | ||
|
||
<test-fixture id="parensFormat"> | ||
<template> | ||
<gold-phone-input | ||
phone-number-pattern="(XXX) XXX-XXXX" | ||
></gold-phone-input> | ||
</template> | ||
</test-fixture> | ||
|
||
<script> | ||
|
||
suite('basic', function() { | ||
|
@@ -101,6 +109,13 @@ | |
assert.notEqual(getComputedStyle(error).visibility, 'hidden', 'error is not visibility:hidden'); | ||
assert.isTrue(container.invalid); | ||
}); | ||
|
||
test('input format respects parentheses and spaces', function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add tests for incomplete matching too. Like, if I start with |
||
var input = fixture('parensFormat'); | ||
input.value ='1231112222'; | ||
assert.equal(input.value, '(123) 111-2222'); | ||
}); | ||
|
||
}); | ||
|
||
suite('a11y', function() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "setting the value to undefined resets it to the empty string" test fails without this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But isn't the testing passing on master, without this change?