Javascript 带有angularjs的有效输入掩码(ui掩码)

Javascript 带有angularjs的有效输入掩码(ui掩码),javascript,html,css,angularjs,Javascript,Html,Css,Angularjs,我在angularJs中使用ui掩码制作输入掩码时遇到了一些问题。我正在检查写入的字符串是否正确,然后textarea必须变为绿色。但在我的情况下,textarea最初是绿色的,然后当您键入某个单词时,它会变为红色,直到您键入正确的单词。我将发布我的代码,如果你能帮我解决这个问题,我不知道如何做更多我尝试了很多事情,但都没有成功。抱歉,伙计们,这么大的脚本。这是我的index.html <!DOCTYPE html> <html ng-app="myApp" lang="en"

我在angularJs中使用ui掩码制作输入掩码时遇到了一些问题。我正在检查写入的字符串是否正确,然后textarea必须变为绿色。但在我的情况下,textarea最初是绿色的,然后当您键入某个单词时,它会变为红色,直到您键入正确的单词。我将发布我的代码,如果你能帮我解决这个问题,我不知道如何做更多我尝试了很多事情,但都没有成功。抱歉,伙计们,这么大的脚本。这是我的index.html

<!DOCTYPE html>
<html ng-app="myApp" lang="en">
  <head>
    <meta charset="utf-8">
    <title>I am Tom</title>
       <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.js"></script>
    <script type="text/javascript" src="js/angularjs/mask.js"></script>
    <script type="text/javascript" src="js/angularjs/module.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css">
  </head>

  <body ng-controller="myController">

    <div class="divito">

        <h3 clas="h3">Please Input following text: I am Tom</h3>

        <textarea class="textarea" style="resize: none" cols="30" row="2" ui-mask="i am tom" ng-model="greeting" data-ng-trim="fasle" ng-trim="false"> 
        </textarea>

    </div>  

  </body>

</html>
这是我的script.js

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

app.controller('myController', ['$scope', function ($scope) {
      $scope.greeting = '123456789';
  }]).directive('uiMask', [
  function () {
    var maskDefinitions = {
      // 'I': /[I-i]/,
      // '2': /[a]/,
      // '3': /[m]/,
      // '4': /[T]/,
      // '5': /[o]/
      'a': /[a]/,   'A': /[A]/, 
      'b': /[b]/,   'B': /[B]/,
      'c': /[c]/,   'C': /[C]/,
      'd': /[d]/,   'D': /[D]/,
      'e': /[e]/,   'E': /[E]/,
      'f': /[f]/,   'F': /[F]/,
      'g': /[g]/,   'G': /[G]/,
      'h': /[h]/,   'H': /[H]/,
      'i': /[i]/,   'I': /[I]/,
      'j': /[j]/,   'J': /[J]/,
      'k': /[h]/,   'K': /[K]/,
      'l': /[l]/,   'L': /[L]/,
      'm': /[m]/,   'M': /[M]/,
      'n': /[n]/,   'N': /[N]/,
      'o': /[o]/,   'O': /[O]/,
      'p': /[p]/,   'P': /[P]/,
      'q': /[q]/,   'Q': /[Q]/,
      'r': /[r]/,   'R': /[R]/,
      's': /[s]/,   'S': /[S]/,
      't': /[t]/,   'T': /[T]/,
      'u': /[u]/,   'U': /[U]/,
      'v': /[v]/,   'V': /[V]/,
      'w': /[w]/,   'W': /[W]/,
      'x': /[x]/,   'X': /[X]/,
      'y': /[y]/,   'Y': /[Y]/,
      'z': /[z]/,   'Z': /[Z]/
    };
    return {
      priority: 100,
      require: 'ngModel',
      restrict: 'A',
      link: function (scope, iElement, iAttrs, controller) {
            //console.log('init');
        var maskProcessed = false, eventsBound = false,
            maskCaretMap, maskPatterns, maskPlaceholder, maskComponents,
            validValue,
            // Minimum required length of the value to be considered valid
            minRequiredLength,
            value, valueMasked, isValid,
            // Vars for initializing/uninitializing
            originalPlaceholder = iAttrs.placeholder,
            originalMaxlength   = iAttrs.maxlength,
            // Vars used exclusively in eventHandler()
            oldValue, oldValueUnmasked, oldCaretPosition, oldSelectionLength;

        function initialize(maskAttr) {
          if (!angular.isDefined(maskAttr)){
            return uninitialize();
          }
          processRawMask(maskAttr);
          if (!maskProcessed){
            return uninitialize();
          }
          initializeElement();
          bindEventListeners();
        }

        function formatter(fromModelValue) {
          if (!maskProcessed){
            return fromModelValue;
          }
          value   = unmaskValue(fromModelValue || '');
          // isValid = validateValue(value);
          // controller.$setValidity('mask', isValid);

          if (isValid) validValue = value;
          //console.log('formatter valid:'+validValue);
          return isValid && value.length ? maskValue(value) : undefined;
       }

        function parser(fromViewValue) {
          if (!maskProcessed){
            return fromViewValue;
          }
          value     = unmaskValue(fromViewValue || '');
          isValid   = validateValue(value);
          viewValue = value.length ? maskValue(value) : '';
          // We have to set viewValue manually as the reformatting of the input
          // value performed by eventHandler() doesn't happen until after
          // this parser is called, which causes what the user sees in the input
          // to be out-of-sync with what the controller's $viewValue is set to.
          controller.$viewValue = viewValue;
          controller.$setValidity('mask', isValid);
          if (value === '' && controller.$error.required !== undefined){
            controller.$setValidity('required', false);
          }
          if (isValid) validValue = value;
          //console.log('parser valid:'+validValue);
          return isValid ? value : undefined;
        }

        iAttrs.$observe('uiMask', initialize);
        controller.$formatters.push(formatter);
        controller.$parsers.push(parser);

        function uninitialize() {
          maskProcessed = false;
          unbindEventListeners();

          if (angular.isDefined(originalPlaceholder)){
            iElement.attr('placeholder', originalPlaceholder);
          }else{
            iElement.removeAttr('placeholder');
          }

          if (angular.isDefined(originalMaxlength)){
            iElement.attr('maxlength', originalMaxlength);
          }else{
            iElement.removeAttr('maxlength');
          }

          iElement.val(controller.$modelValue);
          controller.$viewValue = controller.$modelValue;
          return false;
        }

        function initializeElement() {
          value       = oldValueUnmasked = unmaskValue(controller.$modelValue || '');
          valueMasked = oldValue         = maskValue(value);
          isValid     = validateValue(value);
          viewValue   = isValid && value.length ? valueMasked : '';
          if (iAttrs.maxlength){ // Double maxlength to allow pasting new val at end of mask
            iElement.attr('maxlength', maskCaretMap[maskCaretMap.length-1]*2);
          }
          iElement.attr('placeholder', maskPlaceholder);
          iElement.val(viewValue);
          controller.$viewValue = viewValue;
          // Not using $setViewValue so we don't clobber the model value and dirty the form
          // without any kind of user interaction.
        }

        function bindEventListeners() {
          if (eventsBound){
            return true;
          }
          iElement.bind('blur',              blurHandler);
          iElement.bind('mousedown mouseup', mouseDownUpHandler);
          iElement.bind('input keyup click', eventHandler);
          eventsBound = true;
        }

        // function unbindEventListeners() {
        //   if (!eventsBound){
        //     return true;
        //   }
        //   iElement.unbind('blur',      blurHandler);
        //   iElement.unbind('mousedown', mouseDownUpHandler);
        //   iElement.unbind('mouseup',   mouseDownUpHandler);
        //   iElement.unbind('input',     eventHandler);
        //   iElement.unbind('keyup',     eventHandler);
        //   iElement.unbind('click',     eventHandler);
        //   eventsBound = false;
        // }

        function validateValue(value) {
          // Zero-length value validity is ngRequired's determination
          return value.length ? value.length >= minRequiredLength : true;
        }

        function unmaskValue(value) {
          var valueUnmasked    = '',
              maskPatternsCopy = maskPatterns.slice();
          // Preprocess by stripping mask components from value
          value = value.toString();
          angular.forEach(maskComponents, function(component, i) {
            value = value.replace(component, '');
          });
          angular.forEach(value.split(''), function(chr, i) {
            if (maskPatternsCopy.length && maskPatternsCopy[0].test(chr)) {
              valueUnmasked += chr;
              maskPatternsCopy.shift();
            }
          });
          return valueUnmasked;
        }

        function maskValue(unmaskedValue) {
          var valueMasked      = '',
              maskCaretMapCopy = maskCaretMap.slice();
          angular.forEach(maskPlaceholder.split(''), function(chr, i) {
            if (unmaskedValue.length && i === maskCaretMapCopy[0]) {
              valueMasked  += unmaskedValue.charAt(0) || '_';
              unmaskedValue = unmaskedValue.substr(1);
              maskCaretMapCopy.shift(); }
            else{
              valueMasked += chr;
            }
          });
          return valueMasked;
        }

        function processRawMask(mask) {
          var characterCount = 0;
          maskCaretMap       = [];
          maskPatterns       = [];
          maskPlaceholder    = '';

          // No complex mask support for now...
          // if (mask instanceof Array) {
          //   angular.forEach(mask, function(item, i) {
          //     if (item instanceof RegExp) {
          //       maskCaretMap.push(characterCount++);
          //       maskPlaceholder += '_';
          //       maskPatterns.push(item);
          //     }
          //     else if (typeof item == 'string') {
          //       angular.forEach(item.split(''), function(chr, i) {
          //         maskPlaceholder += chr;
          //         characterCount++;
          //       });
          //     }
          //   });
          // }
          // Otherwise it's a simple mask
          // else

          if (typeof mask === 'string') {
            minRequiredLength = 0;
            var isOptional = false;

            angular.forEach(mask.split(''), function(chr, i) {
              if (maskDefinitions[chr]) {
                maskCaretMap.push(characterCount);
                maskPlaceholder += '_';
                maskPatterns.push(maskDefinitions[chr]);

                characterCount++;
                if (!isOptional) {
                  minRequiredLength++;
                }
              }
              else if (chr === "?") {
                isOptional = true;
              }
              else{
                maskPlaceholder += chr;
                characterCount++;
              }
            });
          }
          // Caret position immediately following last position is valid.
          maskCaretMap.push(maskCaretMap.slice().pop() + 1);
          // Generate array of mask components that will be stripped from a masked value
          // before processing to prevent mask components from being added to the unmasked value.
          // E.g., a mask pattern of '+7 9999' won't have the 7 bleed into the unmasked value.
                                                                // If a maskable char is followed by a mask char and has a mask
                                                                // char behind it, we'll split it into it's own component so if
                                                                // a user is aggressively deleting in the input and a char ahead
                                                                // of the maskable char gets deleted, we'll still be able to strip
                                                                // it in the unmaskValue() preprocessing.
          maskComponents = maskPlaceholder.replace(/[_]+/g,'_').replace(/([^_]+)([a-zA-Z0-9])([^_])/g, '$1$2_$3').split('_');
          maskProcessed  = maskCaretMap.length > 1 ? true : false;
        }

        function blurHandler(e) {
          oldCaretPosition   = 0;
          oldSelectionLength = 0;
          if (!isValid || value.length === 0) {
            valueMasked = '';
            iElement.val('');
            scope.$apply(function() {
              controller.$setViewValue('');
            });
          }
        }

        function mouseDownUpHandler(e) {
          if (e.type === 'mousedown'){
            iElement.bind('mouseout', mouseoutHandler);
          }else{
            iElement.unbind('mouseout', mouseoutHandler);
          }
        }

        iElement.bind('mousedown mouseup', mouseDownUpHandler);

        function mouseoutHandler(e) {
          oldSelectionLength = getSelectionLength(this);
          iElement.unbind('mouseout', mouseoutHandler);
        }

        function eventHandler(e) {
          e = e || {};
          // Allows more efficient minification
          var eventWhich = e.which,
              eventType  = e.type;
          // Prevent shift and ctrl from mucking with old values
          if (eventWhich === 16 || eventWhich === 91){ return true;}

          var val             = iElement.val(),
              valOld          = oldValue,
              valMasked,
              valUnmasked     = unmaskValue(val),
              valUnmaskedOld  = oldValueUnmasked,
              valAltered      = false,

              caretPos        = getCaretPosition(this) || 0,
              caretPosOld     = oldCaretPosition || 0,
              caretPosDelta   = caretPos - caretPosOld,
              caretPosMin     = maskCaretMap[0],
              caretPosMax     = maskCaretMap[valUnmasked.length] || maskCaretMap.slice().shift(),

              selectionLen    = getSelectionLength(this),
              selectionLenOld = oldSelectionLength || 0,
              isSelected      = selectionLen > 0,
              wasSelected     = selectionLenOld > 0,

                                                                // Case: Typing a character to overwrite a selection
              isAddition      = (val.length > valOld.length) || (selectionLenOld && val.length >  valOld.length - selectionLenOld),
                                                                // Case: Delete and backspace behave identically on a selection
              isDeletion      = (val.length < valOld.length) || (selectionLenOld && val.length === valOld.length - selectionLenOld),
              isSelection     = (eventWhich >= 37 && eventWhich <= 40) && e.shiftKey, // Arrow key codes

              isKeyLeftArrow  = eventWhich === 37,
                                                    // Necessary due to "input" event not providing a key code
              isKeyBackspace  = eventWhich === 8  || (eventType !== 'keyup' && isDeletion && (caretPosDelta === -1)),
              isKeyDelete     = eventWhich === 46 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === 0 ) && !wasSelected),

              // Handles cases where caret is moved and placed in front of invalid maskCaretMap position. Logic below
              // ensures that, on click or leftward caret placement, caret is moved leftward until directly right of
              // non-mask character. Also applied to click since users are (arguably) more likely to backspace
              // a character when clicking within a filled input.
              caretBumpBack   = (isKeyLeftArrow || isKeyBackspace || eventType === 'click') && caretPos > caretPosMin;

          oldSelectionLength  = selectionLen;

          // These events don't require any action
          if (isSelection || (isSelected && (eventType === 'click' || eventType === 'keyup'))){
            return true;
          }

          // Value Handling
          // ==============

          // User attempted to delete but raw value was unaffected--correct this grievous offense

          if ((eventType === 'input') && isDeletion && !wasSelected && valUnmasked === valUnmaskedOld) {
            while (isKeyBackspace && caretPos > caretPosMin && !isValidCaretPosition(caretPos)){
              caretPos--;
            }
            while (isKeyDelete && caretPos < caretPosMax && maskCaretMap.indexOf(caretPos) === -1){
              caretPos++;
            }
            var charIndex = maskCaretMap.indexOf(caretPos);
            // Strip out non-mask character that user would have deleted if mask hadn't been in the way.
            valUnmasked = valUnmasked.substring(0, charIndex) + valUnmasked.substring(charIndex + 1);
            valAltered  = true;
          }

          // Update values
          console.log(e);
          console.log(String.fromCharCode(e.keyCode));
          //console.log(String.fromCodePoint(e.keyCode));
          //console.log("---> update values start");
          //console.log("valUnmasked:" + valUnmasked);
          //console.log("valMasked:" + valMasked);
          valMasked        = maskValue(valUnmasked);
          oldValue         = valMasked;
          oldValueUnmasked = valUnmasked;
          iElement.val(valMasked);



          // Make sure caret is within min and max position limits
          caretPos = caretPos > caretPosMax ? caretPosMax : caretPos < caretPosMin ? caretPosMin : caretPos;

          // Scoot the caret back or forth until it's in a non-mask position and within min/max position limits
          while (!isValidCaretPosition(caretPos) && caretPos > caretPosMin && caretPos < caretPosMax){
            caretPos += caretBumpBack ? -1 : 1;
          }

          if ((caretBumpBack && caretPos < caretPosMax) || (isAddition && !isValidCaretPosition(caretPosOld))){
            caretPos++;
          }
          oldCaretPosition = caretPos;
          setCaretPosition(this, caretPos);
        }

        function isValidCaretPosition(pos) { return maskCaretMap.indexOf(pos) > -1; }

        function getCaretPosition(input) {
          if (input.selectionStart !== undefined){
            return input.selectionStart;
          }else if (document.selection) {
            // Curse you IE
            input.focus();
            var selection = document.selection.createRange();
            selection.moveStart('character', -input.value.length);
            return selection.text.length;
          }
        }

        function setCaretPosition(input, pos) {
          if (input.offsetWidth === 0 || input.offsetHeight === 0){
            return true; // Input's hidden
          }
          if (input.setSelectionRange) {
            input.focus();
            input.setSelectionRange(pos,pos); }
          else if (input.createTextRange) {
            // Curse you IE
            var range = input.createTextRange();
            range.collapse(true);
            range.moveEnd('character', pos);
            range.moveStart('character', pos);
            range.select();
          }
        }

        function getSelectionLength(input) {
          if (input.selectionStart !== undefined){
            return (input.selectionEnd - input.selectionStart);
          }
          if (document.selection){
            return (document.selection.createRange().text.length);
          }
        }


      }
    };
  }
]);
var-app=angular.module('myApp',[]);
app.controller('myController',['$scope',函数($scope){
$scope.greeting='123456789';
}]).指令(“uiMask”[
函数(){
变量maskDefinitions={
//“I”:/[I-I]/,
//“2”:/[a]/,
//‘3’:/[m]/,
//'4':/[T]/,,
//‘5’:/[o]/
“a”:/[a]/,“a”:/[a]/,
“b”:/[b]/,“b”:/[b]/,
‘c’:/[c]/,‘c’:/[c]/,
“d”:/[d]/,“d”:/[d]/,
“e”:/[e]/,“e”:/[e]/,
‘f’:/[f]/,‘f’:/[f]/,
‘g’:/[g]/,‘g’:/[g]/,
‘h’:/[h]/,‘h’:/[h]/,
“i”:/[i]/,“i”:/[i]/,
“j”:/[j]/,“j”:/[j]/,
“k”:/[h]/,“k”:/[k]/,
‘l’:/[l]/,‘l’:/[l]/,
'm':/[m]/,'m':/[m]/,
‘n’:/[n]/,‘n’:/[n]/,
“o”:/[o]/,“o”:/[o]/,
‘p’:/[p]/,‘p’:/[p]/,
‘q’:/[q]/,‘q’:/[q]/,
'r':/[r]/,'r':/[r]/,
's':/[s]/,'s':/[s]/,
‘t’:/[t]/,‘t’:/[t]/,
‘u’:/[u]/,‘u’:/[u]/,
‘v’:/[v]/,‘v’:/[v]/,
‘w’:/[w]/,‘w’:/[w]/,
'x':/[x]/,'x':/[x]/,
“y”:/[y]/,“y”:/[y]/,
‘z’:/[z]/,‘z’:/[z]/
};
返回{
优先:100,
要求:'ngModel',
限制:“A”,
链接:功能(范围、IELENT、iAttrs、控制器){
//console.log('init');
var maskProcessed=false,eventsBound=false,
maskCaretMap、maskPatterns、maskPlaceholder、maskComponents、,
有效值,
//被视为有效值的最小所需长度
最小所需长度,
值,valueMasked,isValid,
//用于初始化/取消初始化的变量
originalPlaceholder=iAttrs.placeholder,
原始最大长度=iAttrs.maxlength,
//仅在eventHandler()中使用的变量
oldValue、oldValueUnmasked、oldCaretPosition、oldSelectionLength;
函数初始化(maskAttr){
如果(!angular.isDefined(maskAttr)){
返回uninitialize();
}
processRawMask(maskAttr);
如果(!maskProcessed){
返回uninitialize();
}
初始化元素();
bindEventListeners();
}
函数格式化程序(fromModelValue){
如果(!maskProcessed){
从模型值返回;
}
value=unmaskValue(fromModelValue | |“”);
//isValid=validateValue(值);
//控制器.$setValidity('mask',isValid);
如果(isValid)validValue=值;
//log('格式化程序有效:'+有效值);
return isValid&&value.length?maskValue(value):未定义;
}
函数解析器(fromViewValue){
如果(!maskProcessed){
返回视图值;
}
value=unmaskValue(fromViewValue | |“”);
isValid=validateValue(值);
viewValue=value.length?maskValue(值):“”;
//我们必须手动设置viewValue作为输入的重新格式化
//eventHandler()执行的值直到
//这个解析器被调用,这会导致用户在输入中看到什么
//与控制器的$viewValue设置不同步。
控制器。$viewValue=viewValue;
控制器.$setValidity('mask',isValid);
如果(值==''&&controller.$error.required!==未定义){
控制器.$setValidity('required',false);
}
如果(isValid)validValue=值;
//log('parser valid:'+validValue);
返回值是否有效?值:未定义;
}
iAttrs.$observe('uiMask',初始化);
控制器.$formatters.push(格式化程序);
控制器.$parsers.push(解析器);
函数取消初始化(){
maskProcessed=false;
unbindEventListeners();
如果(角度已定义(原始位置保持架)){
iElement.attr(“占位符”,原始占位符);
}否则{
element.removeAttr(‘占位符’);
}
如果(定义了角度(原始轴长度)){
IELENT.attr(“最大长度”,原始最大长度);
}否则{
iElement.removeAttr('maxlength');
}
iElement.val(控制器$modelValue);
控制器。$viewValue=控制器。$modelValue;
返回false;
}
函数初始化元素(){
value=oldValueUnmasked=unmaskValue(控制器$modelValue | | |“”);
valueMasked=oldValue=maskValue(值);
isValid=validateValue(值);
viewValue=isValid&&value.length?值掩码:“”;
if(iAttrs.maxlength){//将maxlength加倍,以允许在掩码末尾粘贴新val
iElement.attr('maxlength',maskCaretMap[maskCaretMap.length-1]*2);
}
属性(“占位符”,maskPlaceholder);
iElement.val(视图值);
控制器。$viewValue=viewValue;
//不使用$setViewValue,因此我们不会破坏模型值并弄脏表单
//没有任何用户交互。
}
函数bindEventListeners(){
if(eventsBound){
返回true;
}
iElement.bind('blur',blurHandler);
绑定('mousedown mouseup',mousedown-uphandler);
var app = angular.module('myApp',[]); 

app.controller('myController', ['$scope', function ($scope) {
      $scope.greeting = '123456789';
  }]).directive('uiMask', [
  function () {
    var maskDefinitions = {
      // 'I': /[I-i]/,
      // '2': /[a]/,
      // '3': /[m]/,
      // '4': /[T]/,
      // '5': /[o]/
      'a': /[a]/,   'A': /[A]/, 
      'b': /[b]/,   'B': /[B]/,
      'c': /[c]/,   'C': /[C]/,
      'd': /[d]/,   'D': /[D]/,
      'e': /[e]/,   'E': /[E]/,
      'f': /[f]/,   'F': /[F]/,
      'g': /[g]/,   'G': /[G]/,
      'h': /[h]/,   'H': /[H]/,
      'i': /[i]/,   'I': /[I]/,
      'j': /[j]/,   'J': /[J]/,
      'k': /[h]/,   'K': /[K]/,
      'l': /[l]/,   'L': /[L]/,
      'm': /[m]/,   'M': /[M]/,
      'n': /[n]/,   'N': /[N]/,
      'o': /[o]/,   'O': /[O]/,
      'p': /[p]/,   'P': /[P]/,
      'q': /[q]/,   'Q': /[Q]/,
      'r': /[r]/,   'R': /[R]/,
      's': /[s]/,   'S': /[S]/,
      't': /[t]/,   'T': /[T]/,
      'u': /[u]/,   'U': /[U]/,
      'v': /[v]/,   'V': /[V]/,
      'w': /[w]/,   'W': /[W]/,
      'x': /[x]/,   'X': /[X]/,
      'y': /[y]/,   'Y': /[Y]/,
      'z': /[z]/,   'Z': /[Z]/
    };
    return {
      priority: 100,
      require: 'ngModel',
      restrict: 'A',
      link: function (scope, iElement, iAttrs, controller) {
            //console.log('init');
        var maskProcessed = false, eventsBound = false,
            maskCaretMap, maskPatterns, maskPlaceholder, maskComponents,
            validValue,
            // Minimum required length of the value to be considered valid
            minRequiredLength,
            value, valueMasked, isValid,
            // Vars for initializing/uninitializing
            originalPlaceholder = iAttrs.placeholder,
            originalMaxlength   = iAttrs.maxlength,
            // Vars used exclusively in eventHandler()
            oldValue, oldValueUnmasked, oldCaretPosition, oldSelectionLength;

        function initialize(maskAttr) {
          if (!angular.isDefined(maskAttr)){
            return uninitialize();
          }
          processRawMask(maskAttr);
          if (!maskProcessed){
            return uninitialize();
          }
          initializeElement();
          bindEventListeners();
        }

        function formatter(fromModelValue) {
          if (!maskProcessed){
            return fromModelValue;
          }
          value   = unmaskValue(fromModelValue || '');
          // isValid = validateValue(value);
          // controller.$setValidity('mask', isValid);

          if (isValid) validValue = value;
          //console.log('formatter valid:'+validValue);
          return isValid && value.length ? maskValue(value) : undefined;
       }

        function parser(fromViewValue) {
          if (!maskProcessed){
            return fromViewValue;
          }
          value     = unmaskValue(fromViewValue || '');
          isValid   = validateValue(value);
          viewValue = value.length ? maskValue(value) : '';
          // We have to set viewValue manually as the reformatting of the input
          // value performed by eventHandler() doesn't happen until after
          // this parser is called, which causes what the user sees in the input
          // to be out-of-sync with what the controller's $viewValue is set to.
          controller.$viewValue = viewValue;
          controller.$setValidity('mask', isValid);
          if (value === '' && controller.$error.required !== undefined){
            controller.$setValidity('required', false);
          }
          if (isValid) validValue = value;
          //console.log('parser valid:'+validValue);
          return isValid ? value : undefined;
        }

        iAttrs.$observe('uiMask', initialize);
        controller.$formatters.push(formatter);
        controller.$parsers.push(parser);

        function uninitialize() {
          maskProcessed = false;
          unbindEventListeners();

          if (angular.isDefined(originalPlaceholder)){
            iElement.attr('placeholder', originalPlaceholder);
          }else{
            iElement.removeAttr('placeholder');
          }

          if (angular.isDefined(originalMaxlength)){
            iElement.attr('maxlength', originalMaxlength);
          }else{
            iElement.removeAttr('maxlength');
          }

          iElement.val(controller.$modelValue);
          controller.$viewValue = controller.$modelValue;
          return false;
        }

        function initializeElement() {
          value       = oldValueUnmasked = unmaskValue(controller.$modelValue || '');
          valueMasked = oldValue         = maskValue(value);
          isValid     = validateValue(value);
          viewValue   = isValid && value.length ? valueMasked : '';
          if (iAttrs.maxlength){ // Double maxlength to allow pasting new val at end of mask
            iElement.attr('maxlength', maskCaretMap[maskCaretMap.length-1]*2);
          }
          iElement.attr('placeholder', maskPlaceholder);
          iElement.val(viewValue);
          controller.$viewValue = viewValue;
          // Not using $setViewValue so we don't clobber the model value and dirty the form
          // without any kind of user interaction.
        }

        function bindEventListeners() {
          if (eventsBound){
            return true;
          }
          iElement.bind('blur',              blurHandler);
          iElement.bind('mousedown mouseup', mouseDownUpHandler);
          iElement.bind('input keyup click', eventHandler);
          eventsBound = true;
        }

        // function unbindEventListeners() {
        //   if (!eventsBound){
        //     return true;
        //   }
        //   iElement.unbind('blur',      blurHandler);
        //   iElement.unbind('mousedown', mouseDownUpHandler);
        //   iElement.unbind('mouseup',   mouseDownUpHandler);
        //   iElement.unbind('input',     eventHandler);
        //   iElement.unbind('keyup',     eventHandler);
        //   iElement.unbind('click',     eventHandler);
        //   eventsBound = false;
        // }

        function validateValue(value) {
          // Zero-length value validity is ngRequired's determination
          return value.length ? value.length >= minRequiredLength : true;
        }

        function unmaskValue(value) {
          var valueUnmasked    = '',
              maskPatternsCopy = maskPatterns.slice();
          // Preprocess by stripping mask components from value
          value = value.toString();
          angular.forEach(maskComponents, function(component, i) {
            value = value.replace(component, '');
          });
          angular.forEach(value.split(''), function(chr, i) {
            if (maskPatternsCopy.length && maskPatternsCopy[0].test(chr)) {
              valueUnmasked += chr;
              maskPatternsCopy.shift();
            }
          });
          return valueUnmasked;
        }

        function maskValue(unmaskedValue) {
          var valueMasked      = '',
              maskCaretMapCopy = maskCaretMap.slice();
          angular.forEach(maskPlaceholder.split(''), function(chr, i) {
            if (unmaskedValue.length && i === maskCaretMapCopy[0]) {
              valueMasked  += unmaskedValue.charAt(0) || '_';
              unmaskedValue = unmaskedValue.substr(1);
              maskCaretMapCopy.shift(); }
            else{
              valueMasked += chr;
            }
          });
          return valueMasked;
        }

        function processRawMask(mask) {
          var characterCount = 0;
          maskCaretMap       = [];
          maskPatterns       = [];
          maskPlaceholder    = '';

          // No complex mask support for now...
          // if (mask instanceof Array) {
          //   angular.forEach(mask, function(item, i) {
          //     if (item instanceof RegExp) {
          //       maskCaretMap.push(characterCount++);
          //       maskPlaceholder += '_';
          //       maskPatterns.push(item);
          //     }
          //     else if (typeof item == 'string') {
          //       angular.forEach(item.split(''), function(chr, i) {
          //         maskPlaceholder += chr;
          //         characterCount++;
          //       });
          //     }
          //   });
          // }
          // Otherwise it's a simple mask
          // else

          if (typeof mask === 'string') {
            minRequiredLength = 0;
            var isOptional = false;

            angular.forEach(mask.split(''), function(chr, i) {
              if (maskDefinitions[chr]) {
                maskCaretMap.push(characterCount);
                maskPlaceholder += '_';
                maskPatterns.push(maskDefinitions[chr]);

                characterCount++;
                if (!isOptional) {
                  minRequiredLength++;
                }
              }
              else if (chr === "?") {
                isOptional = true;
              }
              else{
                maskPlaceholder += chr;
                characterCount++;
              }
            });
          }
          // Caret position immediately following last position is valid.
          maskCaretMap.push(maskCaretMap.slice().pop() + 1);
          // Generate array of mask components that will be stripped from a masked value
          // before processing to prevent mask components from being added to the unmasked value.
          // E.g., a mask pattern of '+7 9999' won't have the 7 bleed into the unmasked value.
                                                                // If a maskable char is followed by a mask char and has a mask
                                                                // char behind it, we'll split it into it's own component so if
                                                                // a user is aggressively deleting in the input and a char ahead
                                                                // of the maskable char gets deleted, we'll still be able to strip
                                                                // it in the unmaskValue() preprocessing.
          maskComponents = maskPlaceholder.replace(/[_]+/g,'_').replace(/([^_]+)([a-zA-Z0-9])([^_])/g, '$1$2_$3').split('_');
          maskProcessed  = maskCaretMap.length > 1 ? true : false;
        }

        function blurHandler(e) {
          oldCaretPosition   = 0;
          oldSelectionLength = 0;
          if (!isValid || value.length === 0) {
            valueMasked = '';
            iElement.val('');
            scope.$apply(function() {
              controller.$setViewValue('');
            });
          }
        }

        function mouseDownUpHandler(e) {
          if (e.type === 'mousedown'){
            iElement.bind('mouseout', mouseoutHandler);
          }else{
            iElement.unbind('mouseout', mouseoutHandler);
          }
        }

        iElement.bind('mousedown mouseup', mouseDownUpHandler);

        function mouseoutHandler(e) {
          oldSelectionLength = getSelectionLength(this);
          iElement.unbind('mouseout', mouseoutHandler);
        }

        function eventHandler(e) {
          e = e || {};
          // Allows more efficient minification
          var eventWhich = e.which,
              eventType  = e.type;
          // Prevent shift and ctrl from mucking with old values
          if (eventWhich === 16 || eventWhich === 91){ return true;}

          var val             = iElement.val(),
              valOld          = oldValue,
              valMasked,
              valUnmasked     = unmaskValue(val),
              valUnmaskedOld  = oldValueUnmasked,
              valAltered      = false,

              caretPos        = getCaretPosition(this) || 0,
              caretPosOld     = oldCaretPosition || 0,
              caretPosDelta   = caretPos - caretPosOld,
              caretPosMin     = maskCaretMap[0],
              caretPosMax     = maskCaretMap[valUnmasked.length] || maskCaretMap.slice().shift(),

              selectionLen    = getSelectionLength(this),
              selectionLenOld = oldSelectionLength || 0,
              isSelected      = selectionLen > 0,
              wasSelected     = selectionLenOld > 0,

                                                                // Case: Typing a character to overwrite a selection
              isAddition      = (val.length > valOld.length) || (selectionLenOld && val.length >  valOld.length - selectionLenOld),
                                                                // Case: Delete and backspace behave identically on a selection
              isDeletion      = (val.length < valOld.length) || (selectionLenOld && val.length === valOld.length - selectionLenOld),
              isSelection     = (eventWhich >= 37 && eventWhich <= 40) && e.shiftKey, // Arrow key codes

              isKeyLeftArrow  = eventWhich === 37,
                                                    // Necessary due to "input" event not providing a key code
              isKeyBackspace  = eventWhich === 8  || (eventType !== 'keyup' && isDeletion && (caretPosDelta === -1)),
              isKeyDelete     = eventWhich === 46 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === 0 ) && !wasSelected),

              // Handles cases where caret is moved and placed in front of invalid maskCaretMap position. Logic below
              // ensures that, on click or leftward caret placement, caret is moved leftward until directly right of
              // non-mask character. Also applied to click since users are (arguably) more likely to backspace
              // a character when clicking within a filled input.
              caretBumpBack   = (isKeyLeftArrow || isKeyBackspace || eventType === 'click') && caretPos > caretPosMin;

          oldSelectionLength  = selectionLen;

          // These events don't require any action
          if (isSelection || (isSelected && (eventType === 'click' || eventType === 'keyup'))){
            return true;
          }

          // Value Handling
          // ==============

          // User attempted to delete but raw value was unaffected--correct this grievous offense

          if ((eventType === 'input') && isDeletion && !wasSelected && valUnmasked === valUnmaskedOld) {
            while (isKeyBackspace && caretPos > caretPosMin && !isValidCaretPosition(caretPos)){
              caretPos--;
            }
            while (isKeyDelete && caretPos < caretPosMax && maskCaretMap.indexOf(caretPos) === -1){
              caretPos++;
            }
            var charIndex = maskCaretMap.indexOf(caretPos);
            // Strip out non-mask character that user would have deleted if mask hadn't been in the way.
            valUnmasked = valUnmasked.substring(0, charIndex) + valUnmasked.substring(charIndex + 1);
            valAltered  = true;
          }

          // Update values
          console.log(e);
          console.log(String.fromCharCode(e.keyCode));
          //console.log(String.fromCodePoint(e.keyCode));
          //console.log("---> update values start");
          //console.log("valUnmasked:" + valUnmasked);
          //console.log("valMasked:" + valMasked);
          valMasked        = maskValue(valUnmasked);
          oldValue         = valMasked;
          oldValueUnmasked = valUnmasked;
          iElement.val(valMasked);



          // Make sure caret is within min and max position limits
          caretPos = caretPos > caretPosMax ? caretPosMax : caretPos < caretPosMin ? caretPosMin : caretPos;

          // Scoot the caret back or forth until it's in a non-mask position and within min/max position limits
          while (!isValidCaretPosition(caretPos) && caretPos > caretPosMin && caretPos < caretPosMax){
            caretPos += caretBumpBack ? -1 : 1;
          }

          if ((caretBumpBack && caretPos < caretPosMax) || (isAddition && !isValidCaretPosition(caretPosOld))){
            caretPos++;
          }
          oldCaretPosition = caretPos;
          setCaretPosition(this, caretPos);
        }

        function isValidCaretPosition(pos) { return maskCaretMap.indexOf(pos) > -1; }

        function getCaretPosition(input) {
          if (input.selectionStart !== undefined){
            return input.selectionStart;
          }else if (document.selection) {
            // Curse you IE
            input.focus();
            var selection = document.selection.createRange();
            selection.moveStart('character', -input.value.length);
            return selection.text.length;
          }
        }

        function setCaretPosition(input, pos) {
          if (input.offsetWidth === 0 || input.offsetHeight === 0){
            return true; // Input's hidden
          }
          if (input.setSelectionRange) {
            input.focus();
            input.setSelectionRange(pos,pos); }
          else if (input.createTextRange) {
            // Curse you IE
            var range = input.createTextRange();
            range.collapse(true);
            range.moveEnd('character', pos);
            range.moveStart('character', pos);
            range.select();
          }
        }

        function getSelectionLength(input) {
          if (input.selectionStart !== undefined){
            return (input.selectionEnd - input.selectionStart);
          }
          if (document.selection){
            return (document.selection.createRange().text.length);
          }
        }


      }
    };
  }
]);
textarea.ng-dirty.ng-invalid {
    border:2px solid #EA4335;
}
textarea.ng-dirty.ng-valid {
    background-color:lightgreen;
}