Compiling Element Causes Input Caret Position to M

2019-07-18 02:25发布

I am having a problem with a directive. The purpose of the directive is to easily add validation without having to manually add ng-class (among other things) to elements in order to get the error state to show up. I just simply want to put a "validation" directive on my element and have the appropriate classes (and error messages) be generated when there is an error state.

As far as the validation goes, it is working great, but it causes an odd side effect. Whenever I am editing a value in an input box that has the validation directive on it, it moves the caret to the end of the text in the input field. It appears to be the fact that I'm compiling the element (in this case the parent element which contains this element).

Here is a jsbin showing the problem. To reproduce, type a value in the field, then put the caret in the middle of the value you just typed and try typing another character. Notice it moves you to the end of the field. Notice that if you delete the value, the field label turns red as expected to show a validation error (the field is required).

Here is the directive (from the jsbin):

angular.module('app', [])
.directive('validation', function($compile) {
  return {
    require: 'ngModel',
    restrict: 'A',
    compile: function(compileElement, attrs) {
      var formName = compileElement[0].form.name;
      compileElement.removeAttr('validation');
      compileElement.parent().attr('ng-class', formName + "['" + attrs.name + "'].$invalid && " + formName + "['" + attrs.name + "'].$dirty ? 'error' : ''");

      return function(scope, element) {
        $compile(element.parent())(scope);
      }
    }
  };
});

And here is the html:

<html>
  <head>
  <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.min.js"></script>
  </head>
  <body ng-app="app">
    <form name="subscribeForm">
    <label>
      First Name
      <input type="text" 
             id="firstName" 
             name="firstName" 
             ng-model="userInfo.FirstName" 
             required 
             validation/>
    </label>
    </form>
  </body>
</html>

2条回答
对你真心纯属浪费
2楼-- · 2019-07-18 02:41

not sure if you've figured this out but I encountered a similar problem. found a solution at Preserving cursor position with angularjs. for convenience, below is the directive snippet that would solve this issue.

app.directive('cleanInput', function() {
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, ngModelController) {
      var el = element[0];

      function clean(x) {
        return x && x.toUpperCase().replace(/[^A-Z\d]/g, '');
      }

      ngModelController.$parsers.push(function(val) {
        var cleaned = clean(val);

        // Avoid infinite loop of $setViewValue <-> $parsers
        if (cleaned === val) return val;

        var start = el.selectionStart;
        var end = el.selectionEnd + cleaned.length - val.length;

        // element.val(cleaned) does not behave with
        // repeated invalid elements
        ngModelController.$setViewValue(cleaned);
        ngModelController.$render();

        el.setSelectionRange(start, end);
        return cleaned;
      });
    }
  }
});

the directive had a different purpose though so modify it according to your requirements.

查看更多
Summer. ? 凉城
3楼-- · 2019-07-18 02:52

If you're not using the built in validation model/process you're doing it wrong. Check out the tutorial on the angular-js website:

http://code.angularjs.org/1.2.13/docs/guide/forms

Also, you shouldn't be doing element manipulation in the compile stage.

Update

You need to have a look at the section called Custom Validation.

Use the ctrl.$parsers approach. You add your parser on to the list of parsers and your fn will run any time the model changes. You then use the ctrl.$setValidity('strNameOfValidation', true) to set the validity. Angular will then add a class for you - called .ng-valid-float or .ng-invalid-float.

var FLOAT_REGEXP = /^\-?\d+((\.|\,)\d+)?$/;
app.directive('smartFloat', function() {
  return {
    require: 'ngModel',
    link: function(scope, elm, attrs, ctrl) {
      ctrl.$parsers.unshift(function(viewValue) {
        if (FLOAT_REGEXP.test(viewValue)) {
          ctrl.$setValidity('float', true);
          return parseFloat(viewValue.replace(',', '.'));
        } else {
          ctrl.$setValidity('float', false);
          return undefined;
        }
      });
    }
  };
});
查看更多
登录 后发表回答