Skip to content

Instantly share code, notes, and snippets.

@aberke
Last active June 20, 2020 05:17
Show Gist options
  • Save aberke/042eef0f37dba1138f9e to your computer and use it in GitHub Desktop.
Save aberke/042eef0f37dba1138f9e to your computer and use it in GitHub Desktop.
AngularJS module for phonenumber inputs - Includes custom directive that formats telephone number input values as well as filter for format them in text.
/***************************************************
----------------------------------------------------
Author: Alexandra Berke (aberke)
Written: Summer 2014
----------------------------------------------------
Formats the phonenumber as (222) 333-4444 within the input element
Binds only the number 2223334444 to the model
Can use either the directive or filter as stand alone items.
jsfiddle example: http://jsfiddle.net/aberke/s0xpkgmq/
INTENDED USE
Example:
--------
<div>
<p>PHONENUMBER: {{ myModel.phonenumber | phonenumber }}</p>
<phonenumber-directive placeholder="'Input phonenumber here'" model='myModel.phonenumber'></phonenumber-directive>
</div>
Attach to your main AngularJS app:
----------------------------------
var MyApp = angular.module('MyApp', ['phonenumberModule',])
Use directive:
----------
pass in {placeholder} and {model} to bind number value to
<phonenumber-directive placeholder="'Input phonenumber here'" model='someModel.phonenumber'></phonenumber-directive>
Use filter:
-------
{{phonenumberValue | phonenumber}}
Does not handle country codes that are not '1' (USA)
****************************************************/
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 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;
};
});
@mdelgadov
Copy link

This humble gist worked much better than ng-mask! thanks!

Suggestion: make it a more general mask and upgrade it to a project!

@C-E-Rios
Copy link

+1
would be good to plug and play this to any kind of validation required

thank you!

@sfrjrs
Copy link

sfrjrs commented Sep 4, 2014

I'm new to Angular and have a request to format a phone number. I've tried using ngOptions with no success and now I've added the directive and filter to my module. I'm not getting any javascript errors in the console and the custom directive html object is not displaying when I view in the browser. Not sure if it's okay to paste code here or not. Any help would be great. Thank you!

@raywu
Copy link

raywu commented Dec 30, 2014

Thanks for this, Alexandra! I've been grappling with adding ng-minlength to the input template. It freezes the input field.

I've updated the fiddle you created to show the problem. http://jsfiddle.net/s0xpkgmq/70/

Anyone knows why?

EDIT: Maybe I misunderstood how min-length works? when I change it to ng-minlength="1" or ng-minlenggth="0" the form input is allowed; however, beyond ng-minlength="1" the input doesn't show up in the field.

@raywu
Copy link

raywu commented Dec 31, 2014

Hi guys, using Alexandra's code, recreated a exact same Module for Credit Cards. Hope some of you'd find it useful. http://jsfiddle.net/y4q61nj3/

var creditCardModule = angular.module('creditCardModule', []);

creditCardModule.directive('creditcardDirective', ['$filter', function($filter) {
  /*
  Intended use:
  
  Where:
  someModel.creditcard: {String} value which to bind only the numeric characters [0-9] entered
  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.creditCardModel;

    scope.$watch('inputValue', function(value, oldValue) {

      value = String(value);
      var number = value.replace(/[^0-9]+/g, '');
      scope.creditCardModel = number;
      scope.inputValue = $filter('creditCard')(number);
    });
  }

  return {
    link: link,
    restrict: 'E',
    scope: {
      creditCardPlaceholder: '=placeholder',
      creditCardModel: '=model',
    },
    // templateUrl: '/static/phonenumberModule/template.html',
    template: ''
  };
}]);

creditCardModule.filter('creditCard', function() {

  // use Regex /^([a-z0-9]{5,})$/.test('abc1'); evaluates true or false

  return function (number) {
    /*
    @param {Number | String} number - Number that will be formatted as telephone number
    Returns formatted number: ####-####-####-#### (VISA, MasterCard, Discover) or ####-######-##### (AMEX)
    */

    if (!number) { return ''; }

    number = String(number);

    // Will return formattedNumber.
    var formattedNumber = number;

    // VISA
    if (/^4[0-9]{6,}$/.test(number)) {
      var visa_firstFour = number.substring(0,4);
      var visa_secondFour = number.substring(4,8);
      var visa_thirdFour = number.substring(8,12);
      var visa_forthFour = number.substring(12,16);

      if (visa_firstFour) {
        formattedNumber = visa_firstFour;
      }
      if (visa_secondFour) {
        formattedNumber += (' ' + visa_secondFour);
      }
      if (visa_thirdFour) {
        formattedNumber += (' ' + visa_thirdFour);
      }
      if (visa_forthFour) {
        formattedNumber += (' ' + visa_forthFour);
      }
    }

    // MasterCard
    if (/^5[1-5][0-9]{5,}$/.test(number)) {
      var masterCard_firstFour = number.substring(0,4);
      var masterCard_secondFour = number.substring(4,8);
      var masterCard_thirdFour = number.substring(8,12);
      var masterCard_forthFour = number.substring(12,16);

      if (masterCard_firstFour) {
        formattedNumber = masterCard_firstFour;
      }
      if (masterCard_secondFour) {
        formattedNumber += (' ' + masterCard_secondFour);
      }
      if (masterCard_thirdFour) {
        formattedNumber += (' ' + masterCard_thirdFour);
      }
      if (masterCard_forthFour) {
        formattedNumber += (' ' + masterCard_forthFour);
      }
    }

    // Discover
    if (/^6(?:011|5[0-9]{2})[0-9]{3,}$/.test(number)) {
      var discoverCard_firstFour = number.substring(0,4);
      var discoverCard_secondFour = number.substring(4,8);
      var discoverCard_thirdFour = number.substring(8,12);
      var discoverCard_forthFour = number.substring(12,16);

      if (discoverCard_firstFour) {
        formattedNumber = discoverCard_firstFour;
      }
      if (discoverCard_secondFour) {
        formattedNumber += (' ' + discoverCard_secondFour);
      }
      if (discoverCard_thirdFour) {
        formattedNumber += (' ' + discoverCard_thirdFour);
      }
      if (discoverCard_forthFour) {
        formattedNumber += (' ' + discoverCard_forthFour);
      }
    }

    // AMEX
    if (/^3[47][0-9]{5,}$/.test(number)) {
      var amex_firstFour = number.substring(0,4);
      var amex_secondSix = number.substring(4,10);
      var amex_thirdFive = number.substring(10,15);

      if (amex_firstFour) {
        formattedNumber = amex_firstFour;
      }
      if (amex_secondSix) {
        formattedNumber += (' ' + amex_secondSix);
      }
      if (amex_thirdFive) {
        formattedNumber += (' ' + amex_thirdFive);
      }
    }

    return formattedNumber;
  };
});

@devtanc
Copy link

devtanc commented Nov 1, 2017

I'd add these lines after line 71:

var inputElement = element[0].childNodes[0];
inputElement.setSelectionRange(scope.inputValue.length, scope.inputValue.length);

This avoids a glitch in mobile browsers where the cursor jumps after the formatting occurs. Seems to be that the formatting updates the value, but the index of the cursor never updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment