Reputation: 6541
I'm using this Angular Directive to format phone numbers in an input to (999) 999-9999
. This works great until a user makes a mistake and modifies the entered phone number.
You can replicate this issue by running the code below and doing the following:
• Enter the phone number (555) 123-4567
• Place your cursor after the 4
character and delete it.
• Type in 0
twice.
You can see that the 0
is added twice and the 7
character is dropped.
Another issue is if a user attempts to delete and change the 1
character. Their cursor is pushed to the very end of the input.
I'm sure this is due to an issue with the phonenumber filter, but I'm not sure how to approach this.
function MyCntl($scope) {
$scope.myModel = {};
$scope.myPrompt = "Input your phonenumber here!";
}
var phonenumberModule = angular.module('phonenumberModule', [])
.directive('phonenumberDirective', ['$filter', function($filter) {
/*
Intended use:
<phonenumber-directive placeholder='prompt' model='someModel.phonenumber'></phonenumber-directive>
Where:
someModel.phonenumber: {String} value which to bind only the numeric characters [0-9] entered
ie, if user enters 617-2223333, value of 6172223333 will be bound to model
prompt: {String} text to keep in placeholder when no numeric input entered
*/
function link(scope, element, attributes) {
// scope.inputValue is the value of input element used in template
scope.inputValue = scope.phonenumberModel;
scope.$watch('inputValue', function(value, oldValue) {
value = String(value);
var number = value.replace(/[^0-9]+/g, '');
scope.phonenumberModel = number;
scope.inputValue = $filter('phonenumber')(number);
});
}
return {
link: link,
restrict: 'E',
scope: {
phonenumberPlaceholder: '=placeholder',
phonenumberModel: '=model',
},
//templateUrl: '/static/phonenumberModule/template.html',
template: '<input name="phonenumber" ng-model="inputValue" type="tel" class="phonenumber" placeholder="{{phonenumberPlaceholder}}" title="Phonenumber (Format: (999) 9999-9999)">',
};
}])
.filter('phonenumber', function() {
/*
Format phonenumber as: c (xxx) xxx-xxxx
or as close as possible if phonenumber length is not 10
if c is not '1' (country code not USA), does not use country code
*/
return function (number) {
/*
@param {Number | String} number - Number that will be formatted as telephone number
Returns formatted number: (###) ###-####
if number.length < 4: ###
else if number.length < 7: (###) ###
Does not handle country codes that are not '1' (USA)
*/
if (!number) { return ''; }
number = String(number);
// Will return formattedNumber.
// If phonenumber isn't longer than an area code, just show number
var formattedNumber = number;
// if the first character is '1', strip it out and add it back
var c = (number[0] == '1') ? '1 ' : '';
number = number[0] == '1' ? number.slice(1) : number;
// # (###) ###-#### as c (area) front-end
var area = number.substring(0,3);
var front = number.substring(3, 6);
var end = number.substring(6, 10);
if (front) {
formattedNumber = (c + "(" + area + ") " + front);
}
if (end) {
formattedNumber += ("-" + end);
}
return formattedNumber;
};
});
.phonenumber {
min-width: 200px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="phonenumberModule" ng-controller="MyCntl">
<p>phonenumber value: {{ myModel.phonenumber }}</p>
<p>formatted phonenumber: {{ myModel.phonenumber | phonenumber }}</p>
<form name="phoneForm">
<phonenumber-directive placeholder="myPrompt" model='myModel.phonenumber'></phonenumber-directive>
<div ng-show="phoneForm.phonenumber.$error.minlength">
<p>Enter a valid phone number</p>
</div>
</form>
</div>
Upvotes: 24
Views: 1014
Reputation: 496
Modifying a user's input as they type can be distracting and always leads to problems like those you describe.
In general, it is easier to implement and less frustrating to the user to modify the UI instead. A common solution for the case of phone numbers is to put three input fields side-by-side, with the formatting characters displayed in-between.
Using Angular, a single directive can be created to wrap this multi-part input mechanism and expose a single concatenated result to the rest of the app.
Upvotes: 5
Reputation: 346
I think you should use an editable field (like input of type text) to enter the number and a read-only field (like a label) to show it formatted, because the formatted value is just a display concern, so it should not be editable.
So I modified your snippet to do that.
function MyCntl($scope) {
$scope.myModel = {};
$scope.myPrompt = "Input your phonenumber here!";
}
var phonenumberModule = angular.module('phonenumberModule', [])
.directive('phonenumberDirective', ['$filter', function($filter) {
/*
Intended use:
<phonenumber-directive placeholder='prompt' model='someModel.phonenumber'></phonenumber-directive>
Where:
someModel.phonenumber: {String} value which to bind only the numeric characters [0-9] entered
ie, if user enters 617-2223333, value of 6172223333 will be bound to model
prompt: {String} text to keep in placeholder when no numeric input entered
*/
function link(scope, element, attributes) {
// scope.inputValue is the value of input element used in template
scope.inputValue = scope.phonenumberModel;
scope.$watch('inputValue', function(value, oldValue) {
value = String(value);
oldValue = String(oldValue);
// get rid of input non digits chars
var number = value.replace(/[^0-9]+/g, '');
var oldNumber = oldValue.replace(/[^0-9]+/g, '');
var filteredNumber = $filter('phonenumber')(number);
// get rid of filter non digits chars
scope.phonenumberModel = filteredNumber.replace(/[^0-9]+/g, '');
inputValue = scope.phonenumberModel;
var filteredOldNumber = $filter('phonenumber')(oldNumber);
if(filteredNumber.length === filteredOldNumber.length) {
scope.maxlength = filteredNumber.length;
}
else {
scope.maxlength = Math.max(number.length, 11);
}
});
}
return {
link: link,
restrict: 'E',
scope: {
phonenumberPlaceholder: '=placeholder',
phonenumberModel: '=model',
},
//templateUrl: '/static/phonenumberModule/template.html',
template: '<input name="phonenumber" ng-model="inputValue" type="tel" maxlength="{{maxlength || 11}}" class="phonenumber" placeholder="{{phonenumberPlaceholder}}" title="Phonenumber (Format: (999) 9999-9999)"> <label>Formatted:{{inputValue | phonenumber}}</label>',
};
}])
.filter('phonenumber', function() {
/*
Format phonenumber as: c (xxx) xxx-xxxx
or as close as possible if phonenumber length is not 10
if c is not '1' (country code not USA), does not use country code
*/
return function (number) {
/*
@param {Number | String} number - Number that will be formatted as telephone number
Returns formatted number: (###) ###-####
if number.length < 4: ###
else if number.length < 7: (###) ###
Does not handle country codes that are not '1' (USA)
*/
if (!number) { return ''; }
number = String(number);
// Will return formattedNumber.
// If phonenumber isn't longer than an area code, just show number
var formattedNumber = number;
// if the first character is '1', strip it out and add it back
var c = (number[0] == '1') ? '1 ' : '';
number = number[0] == '1' ? number.slice(1) : number;
// # (###) ###-#### as c (area) front-end
var area = number.substring(0,3);
var front = number.substring(3, 6);
var end = number.substring(6, 10);
if (front) {
formattedNumber = (c + "(" + area + ") " + front);
}
if (end) {
formattedNumber += ("-" + end);
}
return formattedNumber;
};
});
.phonenumber {
min-width: 200px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="phonenumberModule" ng-controller="MyCntl">
<p>phonenumber value: {{ myModel.phonenumber }}</p>
<p>formatted phonenumber: {{ myModel.phonenumber | phonenumber }}</p>
<form name="phoneForm">
<phonenumber-directive placeholder="myPrompt" model='myModel.phonenumber'></phonenumber-directive>
<div ng-show="phoneForm.phonenumber.$error.minlength">
<p>Enter a valid phone number</p>
</div>
</form>
</div>
Upvotes: 2
Reputation: 1159
Can you check plunker please ... i hope this is what u want ..
http://plnkr.co/edit/0IBJBRb2JtvZnq6PtBiV?p=preview
supporting masking and regex .. (is beta) Usage : # for both , a for chars , 9 is for numbers ... ( mask filter is not working as accepted will check it ..
<maskinput ng-mask-model="value" ng-mask="9 (999) 99-9999" ng-mask-regex="[^0-9]+"></maskinput>
Upvotes: 1