richbai90
richbai90

Reputation: 5204

Angularjs $setValidity via a service not working

I am building a validations module in angularjs to improve some of the basic html5 validations that come out of the box. My work is based on the jquery validations plugin. The issue I am having is that even though everything seems to be working correctly, the form isn't validating correctly. Specifically I am currently testing an email validation that is less generous than the standard html5 version in that it requires a complete email with a top level domain, but when I call $setValidity('email',false); the form is still valid anyways. My code is below

Validations Service

'use strict';

/* Services */

function validations() {
  /**
   * Created by rich on 8/14/15.
   *
   * Most of these regex statements were kifed from jquery validation plugin with perhaps a little bit of sugar
   * added to some by yours truly.
   *
   * Thanks to jzaeffer for his efforts.
   * http://jqueryvalidation.org
   * https://github.com/jzaefferer/jquery-validation
   */


  var tests = {
    //validates full email address with a valid domain. [string]@[string].[string]
    email: function(value, param, elem) {
      return /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])+\.+(?:[a-zA-Z0-9\.](?:[a-zA-Z0-9-\.]{0,61}[a-zA-Z0-9])?)$/.test(value);
    },
    url: function(value, param, elem) {
      return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(value)
    },
    integer: function(value, param, elem) {
      return /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value);
    },
    creditcard: function(value, param, elem) {
      // accept only spaces, digits and dashes
      if (/[^0-9 \-]+/.test(value)) {
        return false;
      }
      var nCheck = 0,
        nDigit = 0,
        bEven = false,
        n, cDigit;
      value = value.replace(/\D/g, "");
      // Basing min and max length on
      // http://developer.ean.com/general_info/Valid_Credit_Card_Types
      if (value.length < 13 || value.length > 19) {
        return false;
      }
      for (n = value.length - 1; n >= 0; n--) {
        cDigit = value.charAt(n);
        nDigit = parseInt(cDigit, 10);
        if (bEven) {
          if ((nDigit *= 2) > 9) {
            nDigit -= 9;
          }
        }
        nCheck += nDigit;
        bEven = !bEven;
      }
      return (nCheck % 10) === 0;
    },
    min: function(value, param, elem) {
      return value >= param
    },
    max: function(value, param, elem) {
      return value <= param
    },
    between: function(value, param, elem) {
      return (value >= param[0] && value <= param[1]);
    },
    equalTo: function(value, param, elem) {
      return value === elem.val();
    },
    //Accept specific mime type
    accept: function(value, param, elem) {
      // Split mime on commas in case we have multiple types we can accept
      var typeParam = typeof param === "string" ? param.replace(/\s/g, "").replace(/,/g, "|") : "image/*",
        i, file;
      // If we are using a wildcard, make it regex friendly
      typeParam = typeParam.replace(/\*/g, ".*");
      // Check if the element has a FileList before checking each file
      if (elem.files && elem.files.length) {
        for (i = 0; i < elem.files.length; i++) {
          file = elem.files[i];
          // Grab the mimetype from the loaded file, verify it matches
          if (!file.type.match(new RegExp("\\.?(" + typeParam + ")$", "i"))) {
            return false;
          }
        }
      }
      // Either return true because we've validated each file, or because the
      // browser does not support element.files and the FileList feature
      return true;
    },
    alphanumeric: function(value, param, elem) {
      /^\w+$/i.test(value);
    },
    ipv4: function(value, param, elem) {
      return /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test(value);
    },
    ipv6: function(value, param, elem) {
      return /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test(value)
    },
    phone: function(value, param, elem) {
      return (value.length > 9 && value.match(/^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]([02-9]\d|1[02-9])-?\d{4}$/));
    },
    zip: function(value, param, elem) {
      return /^\d{5}(-\d{4})?$/.test(value)
    }
  };

  function addTest(name, test) {
    tests[name] = test;
  }

  function isValid(test, value, params, elem) {
    var valid = tests[test](value, params, elem);
    return valid
  }

  return {
    addTest: function(name, test) {
      addTest(name, test);
    },
    test: function(testName, options) {
      if (typeof options === 'object') {
        return isValid(testName, options.value, options.param, options.elem);
      } else {
        //options is actually just the value to be tested
        return isValid(testName, options);
      }
    },
    validate: function(name, ngModel) {
      var validator;
      var self = this;
      if (self.isType(name)) {
        validator = function(ngModelValue) {
          //Custom Property used for debugging
          ngModel.$setValiditityTo = this.test(name, ngModelValue);
          //Set validity based on results of test
          ngModel.$setValidity(name, this.test(name, ngModelValue));
          return ngModelValue
        };
        //Bind validator to current this object so that it can run the required tests.
        ngModel.$parsers.push(validator.bind(self));
      }
    },
    isType: function(name) {
      var types = ['email', 'url', 'creditcard', 'integer', 'alphanumeric', 'ipv4', 'ipv6', 'phone', 'zip'];
      return types.indexOf(name) > -1;
    }
  }
}

app.factory('validations', validations);

Input Directive

function input(validations) {
  return {
    restrict: 'E',
    require: 'ngModel',
    scope: {
      type: '@'
    },
    link: function(scope, elem, attr, ctrl) {
      validations.validate(scope.type, ctrl);
    }
  };
}
app.directive('input', input);

In the plunker below you can see that the custom $setValidityTo object is set correctly, but the errors object follows the normal behavior of an email validation.

http://plnkr.co/edit/68ItZrGSlN9Yf080ctBe?p=preview

Upvotes: 0

Views: 255

Answers (1)

Sunil D.
Sunil D.

Reputation: 18193

The problem is what your validation function returns when the field is invalid:

validator = function(ngModelValue) {
  //Custom Property used for debugging
  ngModel.$setValiditityTo = this.test(name, ngModelValue);
  //Set validity based on results of test
  ngModel.$setValidity(name, this.test(name, ngModelValue));
  return ngModelValue
}

Above you always return the model value.

Recall that the $parsers/$formatters work as a "pipeline". The validation functions are executed in the order they are added, and if one of the validation functions deems the value to be invalid it returns undefined.

When a validation function returns undefined, the remaining of the validator functions in the pipeline are not executed.

Working plunkr, and the code that was changed:

validator = function(ngModelValue) {
  var result = this.test(name, ngModelValue); 
  ngModel.$setValiditityTo = result;
  ngModel.$setValidity(name, result);
  return result ? ngModelValue : undefined;

Upvotes: 1

Related Questions