2

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>
Jim Cooper
  • 5,113
  • 5
  • 30
  • 35
  • Jim, I have attempted almost the exact solution and I am facing the same issue of caret jumping. Did you ever solve your issue? – ArjaaAine Jul 31 '14 at 17:46

2 Answers2

1

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.

Community
  • 1
  • 1
klambiguit
  • 527
  • 1
  • 3
  • 14
0

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;
        }
      });
    }
  };
});
Community
  • 1
  • 1
CalM
  • 542
  • 6
  • 14
  • If you look at the code I am using the built in validation as discussed in that link. The directive is simply to add classes and error messages for me. – Jim Cooper Feb 19 '14 at 23:57
  • I am curious about what you're saying about element manipulation in the compile stage. I thought the whole point of the compile stage was to do DOM manipulation. – Jim Cooper Feb 19 '14 at 23:57
  • The compile stage is really just used for adding or removing elements from the DOM and cloning elements, not for adding or changing attributes. For example, compile is used in ng-repeat to clone the element for each item in a list. The clone fn runs once for all the cloned items - but each cloned item has its own link fn run. You nearly always need the link fn instead of compile - which you can access using the `link` property instead of `compile`. – CalM Feb 20 '14 at 00:06
  • Thanks CalM, typically I do use a Link function and I started using compile function while experimenting with this directive. But that aside, even if I do this all in the link statement, I'll have the same problem. – Jim Cooper Feb 21 '14 at 01:33
  • I'm trying to set a class on the parent element, not the invalid element. – Jim Cooper Feb 21 '14 at 20:27