AngularJS ngMask Sample

In this example below you will see how to do a AngularJS ngMask Sample with some HTML / CSS and Javascript

Thumbnail
This awesome code was written by hspindler, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright hspindler ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>AngularJS ngMask Sample</title>
  
  
  
      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <form ng-app="form-example" name="form" class="row form-horizontal" novalidate>
  <div class="control-group">
    <label class="control-label" for="inputDate">Date</label>
    <div class="controls">
      <input type="text" id="inputDate" name="inputDate" placeholder="Date" ng-model="date" required mask='39/19/2999'>
      <div class="input-help">
        <h4>Invalid Date</h4>
      </div>
    </div>
  </div>
  <div class="control-group">
    <label class="control-label" for="inputEmail">Email</label>
    <div class="controls">
      <input type="email" id="inputEmail" name="inputEmail" placeholder="Email" ng-model="email" required>
      <div class="input-help">
        <h4>Invalid Email</h4>
      </div>
    </div>
  </div>
  <div class="control-group">
    <label class="control-label" for="inputPassword">Password</label>
    <div class="controls">
      <input ng-model="password" name="inputPassword" class="immediate-help" password-validate required type="password" id="inputPassword" placeholder="Password">
      <div class="input-help">
        <h4>Password must meet the following requirements:</h4>
        <ul>
          <li ng-class="pwdHasLetter">At least <strong>one letter</strong></li>
          <li ng-class="pwdHasNumber">At least <strong>one number</strong></li>
          <li ng-class="pwdValidLength">At least <strong>8 characters long</strong></li>
        </ul>
      </div>
    </div>
  </div>
  <div class="control-group">
    <div class="controls">
      <button type="submit" class="btn">Create Account</button>
      <button class="btn" disabled>Create Account</button>
    </div>
  </div>
 
  <pre class="container">Password:{{form.inputPassword.$error|json}}
Form: {{form.$error|json}}
        </pre>
</form>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/hspindler/angularjs-ngmask-sample-NRmQrd */
.input-help {
  display: none;
  position: absolute;
  z-index: 100;
  top: -6px;
  left: 160px;
  width: 200px;
  padding: 10px;
  background: #fefefe;
  font-size: .875em;
  border-radius: 5px;
  box-shadow: 0 1px 3px #aaa;
  border: 1px solid #ddd;
  opacity: 0.9;
}

.input-help::before {
  content: "\25C0";
  position: absolute;
  top: 10px;
  left: -12px;
  font-size: 16px;
  line-height: 16px;
  color: #ddd;
  text-shadow: none;
}

.input-help h4 {
  margin: 0;
  padding: 0;
  font-weight: normal;
  font-size: 1.1em;
}


/* Always hide the input help when it's pristine */

input.ng-pristine + .input-help {
  display: none;
}


/* Hide the invalid box while the input has focus */

.ng-invalid:focus + .input-help {
  display: none;
}


/* Show a blue border while an input has focus, make sure it overrides everything else */


/* Overriding Twitter Bootstrap cuz I don't agree we need to alarm the user while they're typing */

input:focus {
  color: black !important;
  border-color: rgba(82, 168, 236, 0.8) !important;
  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6) !important;
  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6) !important;
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6) !important;
}


/* Show green border when stuff has been typed in, and its valid */

.ng-dirty.ng-valid {
  border-color: #3a7d34;
}


/* Show red border when stuff has been typed in, but its invalid */

.ng-dirty.ng-invalid {
  border-color: #ec3f41;
}


/* Show the help box once it has focus */

.immediate-help:focus + .input-help {
  display: block;
}


/* Immediate help should be red when pristine */

.immediate-help.ng-pristine:focus + .input-help {
  border-color: #ec3f41;
}

.immediate-help.ng-pristine:focus + .input-help::before {
  color: #ec3f41;
}


/* Help hould be green when input is valid */

.ng-valid + .input-help {
  border-color: #3a7d34;
}

.ng-valid + .input-help::before {
  color: #3a7d34;
}


/* Help should show and be red when invalid */

.ng-invalid + .input-help {
  display: block;
  border-color: #ec3f41;
}

.ng-invalid + .input-help::before {
  color: #ec3f41;
}


/* Style input help requirement bullets */

.input-help ul {
  list-style: none;
  margin: 10px 0 0 0;
}


/* Default each bullet to be invalid with a red cross and text */

.input-help li {
  padding-left: 22px;
  line-height: 24px;
  color: #ec3f41;
  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAA1CAYAAABIkmvkAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAJwAAACcBKgmRTwAAABZ0RVh0Q3JlYXRpb24gVGltZQAxMC8wOS8xMlhq+BkAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzVxteM2AAAEA0lEQVRIie2WW2jbVRzHv//zT5rkn0ub61zaNdEiPqTC9EFRhtgJk63qg3Wr0806pswNiYgDUXxwyryCEB8UBevtaSCCDHQoboKyoVvVzfRmL2napU0mrdbl8s//dy4+dM1M28n64FsPnIdz+XzO75zfOXA0pRRWU7o/uS9FxOc+3/vlIQBgq4F3fHxvKuIPJ9cFwi9uTXU8BwDa1Uaw/aN7UusCkWRbPI5yxcTI2Bgy49kXrkrwwIedqYg/nGyLXwsJiYHBYWTGs7Cq5Kpt4cA3PXft+2rX40vhrt7OVLgplIzHYuBKoH9gCKMjGVE1LdfJl86YDAAOfN2ziZP4NODyv9/z2fanFuH7P9iWCjcFk/FYK4QSGLgEk0WeUy/3mQCgPXFs9xbBRW883NrssDvQN3hWcOLPEPGWiD94MBaPQymBoaERjI9mBSfu+fHwL+biItpjR3e6JFfloDeAaGQ9SpUycvlp6ExHJBKGYsDvgyMYH81KTsL90yuX4VoWdh3pMqSQpWBjAC3RZkgpYEkCFDA8NIqJ0UlFxI3Tr/5aB9elsau305BcloKBAFpjLeBSYGRwDBNjk4oTN06/dnYZXCcAgK1vbzYkl6VwOATihOzYlOLEjTOvn1sRXiYAgDsP32YIKUuWaXFOwtP3xrnqleAVBQBwy/M3GZy4+PnN3/4TvqJgNWVVj2lNsCZYE6wJ1gRrgv9dYAMAHHw2Bl2fUEpBVavtLPVW/78nVR/Zk4CupzVHA6zChSOK0yHv0S8GFyK4BMPhAJxOgLE03/9kYhE2dz+agKaldY8bDaEQ7D5ft7Roy+UIlCooy5LQdaZ5vVBEgGmmrT172yVxaIylmdcDm9cHc2oK1Zm8kETvLAo0pRRk8mmnEqKouVw68zVCzP8F/uccFHHoXi/sjT6Y53Mw83mhOHn8J7416wQAwPftd0ouiswwdJu/CRASkBKQAmYuBzNfWIC/O173W6llwfbeu6Yi8tDsrAQJYGICyGQAIWDO5KUkaxlcJwAASdSmaWAQHCACOAc4h6YzJi1qWymNNUHlwYcT0JDWXQbACYhGgeh6gHM4Ghuh2/R0YePNiaUCTSmFcvdDCY1paZvhht3nQ2VmGmahICSR5vQHmDt6DcozeZSnp2FdLLZHhwdq94SVd+xMaJqWtrkM2L1uVHILpy0t8igidymXExfHMzBCQbhCIdga7Onz8etqkdgkUYTZbYCSqORmULlQEIq4J3jyexMA8jdu9BRzuaKyLN3udkNjDEqICID+2hbm797Wwez24/T3vJTE3aFTP9Sd9vT1NziVEMUGr1c35+Y2b5jKnqgNKqWglMLspjs6/rj1dudie2mdao07J5s3dCzt/werJTyI1yYqpQAAAABJRU5ErkJggg==) no-repeat 2px -34px;
}


/* Set to green check and text when valid */

.input-help li.valid {
  color: #3a7d34;
  background-position: 2px 6px;
}


/* Set submit button */

form .btn,
form.ng-valid .btn[disabled] {
  display: none;
}

form.ng-invalid .btn[disabled],
form.ng-valid .btn {
  display: inline-block;
}

body {
  padding: 20px 0;
}

input {
  width: 166px
}

.form-horizontal .control-label {
  width: 100px;
}

.form-horizontal .controls {
  position: relative;
  margin-left: 120px;
}


/*Downloaded from https://www.codeseek.co/hspindler/angularjs-ngmask-sample-NRmQrd */
var app = angular.module('form-example', ['ngMask']);

app.directive('passwordValidate', function() {
  return {
    require: 'ngModel',
    link: function(scope, elm, attrs, ctrl) {
      ctrl.$parsers.unshift(function(viewValue) {

        scope.pwdValidLength = (viewValue && viewValue.length >= 8 ? 'valid' : undefined);
        scope.pwdHasLetter = (viewValue && /[A-z]/.test(viewValue)) ? 'valid' : undefined;
        scope.pwdHasNumber = (viewValue && /\d/.test(viewValue)) ? 'valid' : undefined;
 
        if (scope.pwdValidLength && scope.pwdHasLetter && scope.pwdHasNumber) {
          ctrl.$setValidity('pwd', true);
          //elm.$setValidity('pwd', true); //<-- I WANT THIS TO WORK! (or something like it)
 
          return viewValue;
        } else {
          ctrl.$setValidity('pwd', false);
          //elm.$setValidity('pwd', false); //<-- I WANT THIS TO WORK! (or something like it)

          return undefined;
        }

      });
    }
  };
});


(function() {
  'use strict';
  angular.module('ngMask', []);
})();
(function() {
  'use strict';
  angular.module('ngMask')
    .directive('mask', ['$log', '$timeout', 'MaskService', function($log, $timeout, MaskService) {
      return {
        restrict: 'A',
        require: 'ngModel',
        compile: function($element, $attrs) {
          if (!$attrs.mask || !$attrs.ngModel) {
            $log.info('Mask and ng-model attributes are required!');
            return;
          }

          var maskService = MaskService.create();
          var timeout;
          var promise;

          function setSelectionRange(selectionStart) {
            if (typeof selectionStart !== 'number') {
              return;
            }

            // using $timeout:
            // it should run after the DOM has been manipulated by Angular
            // and after the browser renders (which may cause flicker in some cases)
            $timeout.cancel(timeout);
            timeout = $timeout(function() {
              var selectionEnd = selectionStart + 1;
              var input = $element[0];

              if (input.setSelectionRange) {
                input.focus();
                input.setSelectionRange(selectionStart, selectionEnd);
              } else if (input.createTextRange) {
                var range = input.createTextRange();

                range.collapse(true);
                range.moveEnd('character', selectionEnd);
                range.moveStart('character', selectionStart);
                range.select();
              }
            });
          }

          return {
            pre: function($scope, $element, $attrs, controller) {
              promise = maskService.generateRegex({
                mask: $attrs.mask,
                // repeat mask expression n times
                repeat: ($attrs.repeat || $attrs.maskRepeat),
                // clean model value - without divisors
                clean: (($attrs.clean || $attrs.maskClean) === 'true'),
                // limit length based on mask length
                limit: (($attrs.limit || $attrs.maskLimit || 'true') === 'true'),
                // how to act with a wrong value
                restrict: ($attrs.restrict || $attrs.maskRestrict || 'select'), //select, reject, accept
                // set validity mask
                validate: (($attrs.validate || $attrs.maskValidate || 'true') === 'true'),
                // default model value
                model: $attrs.ngModel,
                // default input value
                value: $attrs.ngValue
              });
            },
            post: function($scope, $element, $attrs, controller) {
              var timeout;
              var options = maskService.getOptions();

              function parseViewValue(value) {
                var untouchedValue = value;
                options = maskService.getOptions();
                // set default value equal 0
                value = value || '';

                // get view value object
                var viewValue = maskService.getViewValue(value);

                // get mask without question marks
                var maskWithoutOptionals = options['maskWithoutOptionals'] || '';

                // get view values capped
                // used on view
                var viewValueWithDivisors = viewValue.withDivisors(true);
                // used on model
                var viewValueWithoutDivisors = viewValue.withoutDivisors(true);

                try {
                  // get current regex
                  var regex = maskService.getRegex(viewValueWithDivisors.length - 1);
                  var fullRegex = maskService.getRegex(maskWithoutOptionals.length - 1);

                  // current position is valid
                  var validCurrentPosition = regex.test(viewValueWithDivisors) || fullRegex.test(viewValueWithDivisors);

                  // difference means for select option
                  var diffValueAndViewValueLengthIsOne = (value.length - viewValueWithDivisors.length) === 1;
                  var diffMaskAndViewValueIsGreaterThanZero = (maskWithoutOptionals.length - viewValueWithDivisors.length) > 0;

                  if (options.restrict !== 'accept') {
                    if (options.restrict === 'select' && (!validCurrentPosition || diffValueAndViewValueLengthIsOne)) {
                      var lastCharInputed = value[(value.length - 1)];
                      var lastCharGenerated = viewValueWithDivisors[(viewValueWithDivisors.length - 1)];

                      if ((lastCharInputed !== lastCharGenerated) && diffMaskAndViewValueIsGreaterThanZero) {
                        viewValueWithDivisors = viewValueWithDivisors + lastCharInputed;
                      }

                      var wrongPosition = maskService.getFirstWrongPosition(viewValueWithDivisors);
                      if (angular.isDefined(wrongPosition)) {
                        setSelectionRange(wrongPosition);
                      }
                    } else if (options.restrict === 'reject' && !validCurrentPosition) {
                      viewValue = maskService.removeWrongPositions(viewValueWithDivisors);
                      viewValueWithDivisors = viewValue.withDivisors(true);
                      viewValueWithoutDivisors = viewValue.withoutDivisors(true);

                      // setSelectionRange(viewValueWithDivisors.length);
                    }
                  }

                  if (!options.limit) {
                    viewValueWithDivisors = viewValue.withDivisors(false);
                    viewValueWithoutDivisors = viewValue.withoutDivisors(false);
                  }

                  // Set validity
                  if (options.validate && controller.$dirty) {
                    if (fullRegex.test(viewValueWithDivisors) || controller.$isEmpty(untouchedValue)) {
                      controller.$setValidity('mask', true);
                    } else {
                      controller.$setValidity('mask', false);
                    }
                  }

                  // Update view and model values
                  if (value !== viewValueWithDivisors) {
                    controller.$setViewValue(angular.copy(viewValueWithDivisors), 'input');
                    controller.$render();
                  }
                } catch (e) {
                  $log.error('[mask - parseViewValue]');
                  throw e;
                }

                // Update model, can be different of view value
                if (options.clean) {
                  return viewValueWithoutDivisors;
                } else {
                  return viewValueWithDivisors;
                }
              }

              var callParseViewValue = function() {
                parseViewValue();

                controller.$parsers.push(parseViewValue);

                // $evalAsync from a directive
                // it should run after the DOM has been manipulated by Angular
                // but before the browser renders
                if (options.value) {
                  $scope.$evalAsync(function($scope) {
                    controller.$setViewValue(angular.copy(options.value), 'input');
                    controller.$render();
                  });
                }
              }

              $element.on('click input paste keyup', function() {
                timeout = $timeout(function() {
                  // Manual debounce to prevent multiple execution
                  $timeout.cancel(timeout);

                  parseViewValue($element.val());
                  $scope.$apply();
                }, 100);
              });

              // Register the watch to observe remote loading or promised data
              // Deregister calling returned function
              var watcher = $scope.$watch($attrs.ngModel, function(newValue, oldValue) {
                if (angular.isDefined(newValue)) {
                  parseViewValue(newValue);
                  watcher();
                }
              });

              $scope.$watch(function() {
                return [$attrs.mask];
              }, function() {
                promise = maskService.generateRegex({
                  mask: $attrs.mask,
                  // repeat mask expression n times
                  repeat: ($attrs.repeat || $attrs.maskRepeat),
                  // clean model value - without divisors
                  clean: (($attrs.clean || $attrs.maskClean) === 'true'),
                  // limit length based on mask length
                  limit: (($attrs.limit || $attrs.maskLimit || 'true') === 'true'),
                  // how to act with a wrong value
                  restrict: ($attrs.restrict || $attrs.maskRestrict || 'select'), //select, reject, accept
                  // set validity mask
                  validate: (($attrs.validate || $attrs.maskValidate || 'true') === 'true'),
                  // default model value
                  model: $attrs.ngModel,
                  // default input value
                  value: $attrs.ngValue
                }).then(function() {
                  $element.triggerHandler('click');
                });

                promise.then(callParseViewValue);
              }, true);

              promise.then(callParseViewValue);
            }
          }
        }
      }
    }]);
})();
(function() {
  'use strict';
  angular.module('ngMask')
    .factory('MaskService', ['$q', 'OptionalService', 'UtilService', function($q, OptionalService, UtilService) {
      function create() {
        var options;
        var maskWithoutOptionals;
        var maskWithoutOptionalsLength = 0;
        var maskWithoutOptionalsAndDivisorsLength = 0;
        var optionalIndexes = [];
        var optionalDivisors = {};
        var optionalDivisorsCombinations = [];
        var divisors = [];
        var divisorElements = {};
        var regex = [];
        var patterns = {
          '9': /[0-9]/,
          '8': /[0-8]/,
          '7': /[0-7]/,
          '6': /[0-6]/,
          '5': /[0-5]/,
          '4': /[0-4]/,
          '3': /[0-3]/,
          '2': /[0-2]/,
          '1': /[0-1]/,
          '0': /[0]/,
          '*': /./,
          'w': /\w/,
          'W': /\W/,
          'd': /\d/,
          'D': /\D/,
          's': /\s/,
          'S': /\S/,
          'b': /\b/,
          'A': /[A-Z]/,
          'a': /[a-z]/,
          'Z': /[A-ZÇÀÁÂÃÈÉÊẼÌÍÎĨÒÓÔÕÙÚÛŨ]/,
          'z': /[a-zçáàãâéèêẽíìĩîóòôõúùũüû]/,
          '@': /[a-zA-Z]/,
          '#': /[a-zA-ZçáàãâéèêẽíìĩîóòôõúùũüûÇÀÁÂÃÈÉÊẼÌÍÎĨÒÓÔÕÙÚÛŨ]/,
          '%': /[0-9a-zA-ZçáàãâéèêẽíìĩîóòôõúùũüûÇÀÁÂÃÈÉÊẼÌÍÎĨÒÓÔÕÙÚÛŨ]/
        };

        // REGEX

        function generateIntermetiateElementRegex(i, forceOptional) {
          var charRegex;
          try {
            var element = maskWithoutOptionals[i];
            var elementRegex = patterns[element];
            var hasOptional = isOptional(i);

            if (elementRegex) {
              charRegex = '(' + elementRegex.source + ')';
            } else { // is a divisor
              if (!isDivisor(i)) {
                divisors.push(i);
                divisorElements[i] = element;
              }

              charRegex = '(' + '\\' + element + ')';
            }
          } catch (e) {
            throw e;
          }

          if (hasOptional || forceOptional) {
            charRegex += '?';
          }

          return new RegExp(charRegex);
        }

        function generateIntermetiateRegex(i, forceOptional) {


          var elementRegex
          var elementOptionalRegex;
          try {
            var intermetiateElementRegex = generateIntermetiateElementRegex(i, forceOptional);
            elementRegex = intermetiateElementRegex;

            var hasOptional = isOptional(i);
            var currentRegex = intermetiateElementRegex.source;

            if (hasOptional && ((i + 1) < maskWithoutOptionalsLength)) {
              var intermetiateRegex = generateIntermetiateRegex((i + 1), true).elementOptionalRegex();
              currentRegex += intermetiateRegex.source;
            }

            elementOptionalRegex = new RegExp(currentRegex);
          } catch (e) {
            throw e;
          }
          return {
            elementRegex: function() {
              return elementRegex;
            },
            elementOptionalRegex: function() {
              // from element regex, gets the flow of regex until first not optional
              return elementOptionalRegex;
            }
          };
        }

        function generateRegex(opts) {
          var deferred = $q.defer();
          maskWithoutOptionals = null;
          maskWithoutOptionalsLength = 0;
          maskWithoutOptionalsAndDivisorsLength = 0;
          optionalIndexes = [];
          optionalDivisors = {};
          optionalDivisorsCombinations = [];
          divisors = [];
          divisorElements = {};
          regex = [];
          options = opts;

          try {
            var mask = opts['mask'];
            var repeat = opts['repeat'];

            if (!mask)
              return;

            if (repeat) {
              mask = Array((parseInt(repeat) + 1)).join(mask);
            }

            optionalIndexes = OptionalService.getOptionals(mask).fromMaskWithoutOptionals();
            options['maskWithoutOptionals'] = maskWithoutOptionals = OptionalService.removeOptionals(mask);
            maskWithoutOptionalsLength = maskWithoutOptionals.length;

            var cumulativeRegex;
            for (var i = 0; i < maskWithoutOptionalsLength; i++) {
              var charRegex = generateIntermetiateRegex(i);
              var elementRegex = charRegex.elementRegex();
              var elementOptionalRegex = charRegex.elementOptionalRegex();

              var newRegex = cumulativeRegex ? cumulativeRegex.source + elementOptionalRegex.source : elementOptionalRegex.source;
              newRegex = new RegExp(newRegex);
              cumulativeRegex = cumulativeRegex ? cumulativeRegex.source + elementRegex.source : elementRegex.source;
              cumulativeRegex = new RegExp(cumulativeRegex);

              regex.push(newRegex);
            }

            generateOptionalDivisors();
            maskWithoutOptionalsAndDivisorsLength = removeDivisors(maskWithoutOptionals).length;

            deferred.resolve({
              options: options,
              divisors: divisors,
              divisorElements: divisorElements,
              optionalIndexes: optionalIndexes,
              optionalDivisors: optionalDivisors,
              optionalDivisorsCombinations: optionalDivisorsCombinations
            });
          } catch (e) {
            deferred.reject(e);
            throw e;
          }

          return deferred.promise;
        }

        function getRegex(index) {
          var currentRegex;

          try {
            currentRegex = regex[index] ? regex[index].source : '';
          } catch (e) {
            throw e;
          }

          return (new RegExp('^' + currentRegex + '$'));
        }

        // DIVISOR

        function isOptional(currentPos) {
          return UtilService.inArray(currentPos, optionalIndexes);
        }

        function isDivisor(currentPos) {
          return UtilService.inArray(currentPos, divisors);
        }

        function generateOptionalDivisors() {
          function sortNumber(a, b) {
            return a - b;
          }

          var sortedDivisors = divisors.sort(sortNumber);
          var sortedOptionals = optionalIndexes.sort(sortNumber);
          for (var i = 0; i < sortedDivisors.length; i++) {
            var divisor = sortedDivisors[i];
            for (var j = 1; j <= sortedOptionals.length; j++) {
              var optional = sortedOptionals[(j - 1)];
              if (optional >= divisor) {
                break;
              }

              if (optionalDivisors[divisor]) {
                optionalDivisors[divisor] = optionalDivisors[divisor].concat(divisor - j);
              } else {
                optionalDivisors[divisor] = [(divisor - j)];
              }

              // get the original divisor for alternative divisor
              divisorElements[(divisor - j)] = divisorElements[divisor];
            }
          }
        }

        function removeDivisors(value) {
          value = value.toString();
          try {
            if (divisors.length > 0 && value) {
              var keys = Object.keys(divisorElements);
              var elments = [];

              for (var i = keys.length - 1; i >= 0; i--) {
                var divisor = divisorElements[keys[i]];
                if (divisor) {
                  elments.push(divisor);
                }
              }

              elments = UtilService.uniqueArray(elments);

              // remove if it is not pattern
              var regex = new RegExp(('[' + '\\' + elments.join('\\') + ']'), 'g');
              return value.replace(regex, '');
            } else {
              return value;
            }
          } catch (e) {
            throw e;
          }
        }

        function insertDivisors(array, combination) {
          function insert(array, output) {
            var out = output;
            for (var i = 0; i < array.length; i++) {
              var divisor = array[i];
              if (divisor < out.length) {
                out.splice(divisor, 0, divisorElements[divisor]);
              }
            }
            return out;
          }

          var output = array;
          var divs = divisors.filter(function(it) {
            var optionalDivisorsKeys = Object.keys(optionalDivisors).map(function(it) {
              return parseInt(it);
            });

            return !UtilService.inArray(it, combination) && !UtilService.inArray(it, optionalDivisorsKeys);
          });

          if (!angular.isArray(array) || !angular.isArray(combination)) {
            return output;
          }

          // insert not optional divisors
          output = insert(divs, output);

          // insert optional divisors
          output = insert(combination, output);

          return output;
        }

        function tryDivisorConfiguration(value) {
          var output = value.split('');
          var defaultDivisors = true;

          // has optional?
          if (optionalIndexes.length > 0) {
            var lazyArguments = [];
            var optionalDivisorsKeys = Object.keys(optionalDivisors);

            // get all optional divisors as array of arrays [[], [], []...]
            for (var i = 0; i < optionalDivisorsKeys.length; i++) {
              var val = optionalDivisors[optionalDivisorsKeys[i]];
              lazyArguments.push(val);
            }

            // generate all possible configurations
            if (optionalDivisorsCombinations.length === 0) {
              UtilService.lazyProduct(lazyArguments, function() {
                // convert arguments to array
                optionalDivisorsCombinations.push(Array.prototype.slice.call(arguments));
              });
            }

            for (var i = optionalDivisorsCombinations.length - 1; i >= 0; i--) {
              var outputClone = angular.copy(output);
              outputClone = insertDivisors(outputClone, optionalDivisorsCombinations[i]);

              // try validation
              var viewValueWithDivisors = outputClone.join('');
              var regex = getRegex(maskWithoutOptionals.length - 1);

              if (regex.test(viewValueWithDivisors)) {
                defaultDivisors = false;
                output = outputClone;
                break;
              }
            }
          }

          if (defaultDivisors) {
            output = insertDivisors(output, divisors);
          }

          return output.join('');
        }

        // MASK

        function getOptions() {
          return options;
        }

        function getViewValue(value) {
          try {
            var outputWithoutDivisors = removeDivisors(value);
            var output = tryDivisorConfiguration(outputWithoutDivisors);

            return {
              withDivisors: function(capped) {
                if (capped) {
                  return output.substr(0, maskWithoutOptionalsLength);
                } else {
                  return output;
                }
              },
              withoutDivisors: function(capped) {
                if (capped) {
                  return outputWithoutDivisors.substr(0, maskWithoutOptionalsAndDivisorsLength);
                } else {
                  return outputWithoutDivisors;
                }
              }
            };
          } catch (e) {
            throw e;
          }
        }

        // SELECTOR

        function getWrongPositions(viewValueWithDivisors, onlyFirst) {
          var pos = [];

          if (!viewValueWithDivisors) {
            return 0;
          }

          for (var i = 0; i < viewValueWithDivisors.length; i++) {
            var pattern = getRegex(i);
            var value = viewValueWithDivisors.substr(0, (i + 1));

            if (pattern && !pattern.test(value)) {
              pos.push(i);

              if (onlyFirst) {
                break;
              }
            }
          }

          return pos;
        }

        function getFirstWrongPosition(viewValueWithDivisors) {
          return getWrongPositions(viewValueWithDivisors, true)[0];
        }

        function removeWrongPositions(viewValueWithDivisors) {
          var wrongPositions = getWrongPositions(viewValueWithDivisors, false);
          var newViewValue = viewValueWithDivisors;

          for (var i = 0; i < wrongPositions.length; i++) {
            var wrongPosition = wrongPositions[i];
            var viewValueArray = viewValueWithDivisors.split('');
            viewValueArray.splice(wrongPosition, 1);
            newViewValue = viewValueArray.join('');
          }

          return getViewValue(newViewValue);
        }

        return {
          getViewValue: getViewValue,
          generateRegex: generateRegex,
          getRegex: getRegex,
          getOptions: getOptions,
          removeDivisors: removeDivisors,
          getFirstWrongPosition: getFirstWrongPosition,
          removeWrongPositions: removeWrongPositions
        }
      }

      return {
        create: create
      }
    }]);
})();
(function() {
  'use strict';
  angular.module('ngMask')
    .factory('OptionalService', [function() {
      function getOptionalsIndexes(mask) {
        var indexes = [];

        try {
          var regexp = /\?/g;
          var match = [];

          while ((match = regexp.exec(mask)) != null) {
            // Save the optional char
            indexes.push((match.index - 1));
          }
        } catch (e) {
          throw e;
        }

        return {
          fromMask: function() {
            return indexes;
          },
          fromMaskWithoutOptionals: function() {
            return getOptionalsRelativeMaskWithoutOptionals(indexes);
          }
        };
      }

      function getOptionalsRelativeMaskWithoutOptionals(optionals) {
        var indexes = [];
        for (var i = 0; i < optionals.length; i++) {
          indexes.push(optionals[i] - i);
        }
        return indexes;
      }

      function removeOptionals(mask) {
        var newMask;

        try {
          newMask = mask.replace(/\?/g, '');
        } catch (e) {
          throw e;
        }

        return newMask;
      }

      return {
        removeOptionals: removeOptionals,
        getOptionals: getOptionalsIndexes
      }
    }]);
})();
(function() {
  'use strict';
  angular.module('ngMask')
    .factory('UtilService', [function() {

      // sets: an array of arrays
      // f: your callback function
      // context: [optional] the `this` to use for your callback
      // http://phrogz.net/lazy-cartesian-product
      function lazyProduct(sets, f, context) {
        if (!context) {
          context = this;
        }

        var p = [];
        var max = sets.length - 1;
        var lens = [];

        for (var i = sets.length; i--;) {
          lens[i] = sets[i].length;
        }

        function dive(d) {
          var a = sets[d];
          var len = lens[d];

          if (d === max) {
            for (var i = 0; i < len; ++i) {
              p[d] = a[i];
              f.apply(context, p);
            }
          } else {
            for (var i = 0; i < len; ++i) {
              p[d] = a[i];
              dive(d + 1);
            }
          }

          p.pop();
        }

        dive(0);
      }

      function inArray(i, array) {
        var output;

        try {
          output = array.indexOf(i) > -1;
        } catch (e) {
          throw e;
        }

        return output;
      }

      function uniqueArray(array) {
        var u = {};
        var a = [];

        for (var i = 0, l = array.length; i < l; ++i) {
          if (u.hasOwnProperty(array[i])) {
            continue;
          }

          a.push(array[i]);
          u[array[i]] = 1;
        }

        return a;
      }

      return {
        lazyProduct: lazyProduct,
        inArray: inArray,
        uniqueArray: uniqueArray
      }
    }]);
})();

Comments