/
githubmirror
/
angular.js
Обзор
Документация
Войти
/
githubmirror
/
angular.js
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/ng/directive/validators.js
439 строк
16 KB
Martin Staffa
fix(required): correctly validate required on non-input element surrounded by ngIf
06 мар 2019, 20:08
Не верифицирован
06 мар 2019, 20:08
6959bc2
Код
Авторство
О чём код?
'use strict'; /** * @ngdoc directive * @name ngRequired * @restrict A * * @param {expression} ngRequired AngularJS expression. If it evaluates to `true`, it sets the * `required` attribute to the element and adds the `required` * {@link ngModel.NgModelController#$validators `validator`}. * * @description * * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be * applied to custom controls. * * The directive sets the `required` attribute on the element if the AngularJS expression inside * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide} * for more info. * * The validator will set the `required` error key to true if the `required` attribute is set and * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. * * @example * <example name="ngRequiredDirective" module="ngRequiredExample"> * <file name="index.html"> * <script> * angular.module('ngRequiredExample', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.required = true; * }]); * </script> * <div ng-controller="ExampleController"> * <form name="form"> * <label for="required">Toggle required: </label> * <input type="checkbox" ng-model="required" id="required" /> * <br> * <label for="input">This input must be filled if `required` is true: </label> * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br> * <hr> * required error set? = <code>{{form.input.$error.required}}</code><br> * model = <code>{{model}}</code> * </form> * </div> * </file> * <file name="protractor.js" type="protractor"> var required = element(by.binding('form.input.$error.required')); var model = element(by.binding('model')); var input = element(by.id('input')); it('should set the required error', function() { expect(required.getText()).toContain('true'); input.sendKeys('123'); expect(required.getText()).not.toContain('true'); expect(model.getText()).toContain('123'); }); * </file> * </example> */ var requiredDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; // For boolean attributes like required, presence means true var value = attr.hasOwnProperty('required') || $parse(attr.ngRequired)(scope); if (!attr.ngRequired) { // force truthy in case we are on non input element // (input elements do this automatically for boolean attributes like required) attr.required = true; } ctrl.$validators.required = function(modelValue, viewValue) { return !value || !ctrl.$isEmpty(viewValue); }; attr.$observe('required', function(newVal) { if (value !== newVal) { value = newVal; ctrl.$validate(); } }); } }; }]; /** * @ngdoc directive * @name ngPattern * @restrict A * * @param {expression|RegExp} ngPattern AngularJS expression that must evaluate to a `RegExp` or a `String` * parsable into a `RegExp`, or a `RegExp` literal. See above for * more details. * * @description * * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. * * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} * does not match a RegExp which is obtained from the `ngPattern` attribute value: * - the value is an AngularJS expression: * - If the expression evaluates to a RegExp object, then this is used directly. * - If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. * - If the value is a RegExp literal, e.g. `ngPattern="/^\d+$/"`, it is used directly. * * <div class="alert alert-info"> * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * </div> * * <div class="alert alert-info"> * **Note:** This directive is also added when the plain `pattern` attribute is used, with two * differences: * <ol> * <li> * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is * not available. * </li> * <li> * The `ngPattern` attribute must be an expression, while the `pattern` value must be * interpolated. * </li> * </ol> * </div> * * @example * <example name="ngPatternDirective" module="ngPatternExample"> * <file name="index.html"> * <script> * angular.module('ngPatternExample', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.regex = '\\d+'; * }]); * </script> * <div ng-controller="ExampleController"> * <form name="form"> * <label for="regex">Set a pattern (regex string): </label> * <input type="text" ng-model="regex" id="regex" /> * <br> * <label for="input">This input is restricted by the current pattern: </label> * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br> * <hr> * input valid? = <code>{{form.input.$valid}}</code><br> * model = <code>{{model}}</code> * </form> * </div> * </file> * <file name="protractor.js" type="protractor"> var model = element(by.binding('model')); var input = element(by.id('input')); it('should validate the input with the default pattern', function() { input.sendKeys('aaa'); expect(model.getText()).not.toContain('aaa'); input.clear().then(function() { input.sendKeys('123'); expect(model.getText()).toContain('123'); }); }); * </file> * </example> */ var patternDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', compile: function(tElm, tAttr) { var patternExp; var parseFn; if (tAttr.ngPattern) { patternExp = tAttr.ngPattern; // ngPattern might be a scope expression, or an inlined regex, which is not parsable. // We get value of the attribute here, so we can compare the old and the new value // in the observer to avoid unnecessary validations if (tAttr.ngPattern.charAt(0) === '/' && REGEX_STRING_REGEXP.test(tAttr.ngPattern)) { parseFn = function() { return tAttr.ngPattern; }; } else { parseFn = $parse(tAttr.ngPattern); } } return function(scope, elm, attr, ctrl) { if (!ctrl) return; var attrVal = attr.pattern; if (attr.ngPattern) { attrVal = parseFn(scope); } else { patternExp = attr.pattern; } var regexp = parsePatternAttr(attrVal, patternExp, elm); attr.$observe('pattern', function(newVal) { var oldRegexp = regexp; regexp = parsePatternAttr(newVal, patternExp, elm); if ((oldRegexp && oldRegexp.toString()) !== (regexp && regexp.toString())) { ctrl.$validate(); } }); ctrl.$validators.pattern = function(modelValue, viewValue) { // HTML5 pattern constraint validates the input value, so we validate the viewValue return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); }; }; } }; }]; /** * @ngdoc directive * @name ngMaxlength * @restrict A * * @param {expression} ngMaxlength AngularJS expression that must evaluate to a `Number` or `String` * parsable into a `Number`. Used as value for the `maxlength` * {@link ngModel.NgModelController#$validators validator}. * * @description * * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. * * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} * is longer than the integer obtained by evaluating the AngularJS expression given in the * `ngMaxlength` attribute value. * * <div class="alert alert-info"> * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two * differences: * <ol> * <li> * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint * validation is not available. * </li> * <li> * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be * interpolated. * </li> * </ol> * </div> * * @example * <example name="ngMaxlengthDirective" module="ngMaxlengthExample"> * <file name="index.html"> * <script> * angular.module('ngMaxlengthExample', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.maxlength = 5; * }]); * </script> * <div ng-controller="ExampleController"> * <form name="form"> * <label for="maxlength">Set a maxlength: </label> * <input type="number" ng-model="maxlength" id="maxlength" /> * <br> * <label for="input">This input is restricted by the current maxlength: </label> * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br> * <hr> * input valid? = <code>{{form.input.$valid}}</code><br> * model = <code>{{model}}</code> * </form> * </div> * </file> * <file name="protractor.js" type="protractor"> var model = element(by.binding('model')); var input = element(by.id('input')); it('should validate the input with the default maxlength', function() { input.sendKeys('abcdef'); expect(model.getText()).not.toContain('abcdef'); input.clear().then(function() { input.sendKeys('abcde'); expect(model.getText()).toContain('abcde'); }); }); * </file> * </example> */ var maxlengthDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; var maxlength = attr.maxlength || $parse(attr.ngMaxlength)(scope); var maxlengthParsed = parseLength(maxlength); attr.$observe('maxlength', function(value) { if (maxlength !== value) { maxlengthParsed = parseLength(value); maxlength = value; ctrl.$validate(); } }); ctrl.$validators.maxlength = function(modelValue, viewValue) { return (maxlengthParsed < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlengthParsed); }; } }; }]; /** * @ngdoc directive * @name ngMinlength * @restrict A * * @param {expression} ngMinlength AngularJS expression that must evaluate to a `Number` or `String` * parsable into a `Number`. Used as value for the `minlength` * {@link ngModel.NgModelController#$validators validator}. * * @description * * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. * * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} * is shorter than the integer obtained by evaluating the AngularJS expression given in the * `ngMinlength` attribute value. * * <div class="alert alert-info"> * **Note:** This directive is also added when the plain `minlength` attribute is used, with two * differences: * <ol> * <li> * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint * validation is not available. * </li> * <li> * The `ngMinlength` value must be an expression, while the `minlength` value must be * interpolated. * </li> * </ol> * </div> * * @example * <example name="ngMinlengthDirective" module="ngMinlengthExample"> * <file name="index.html"> * <script> * angular.module('ngMinlengthExample', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.minlength = 3; * }]); * </script> * <div ng-controller="ExampleController"> * <form name="form"> * <label for="minlength">Set a minlength: </label> * <input type="number" ng-model="minlength" id="minlength" /> * <br> * <label for="input">This input is restricted by the current minlength: </label> * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br> * <hr> * input valid? = <code>{{form.input.$valid}}</code><br> * model = <code>{{model}}</code> * </form> * </div> * </file> * <file name="protractor.js" type="protractor"> var model = element(by.binding('model')); var input = element(by.id('input')); it('should validate the input with the default minlength', function() { input.sendKeys('ab'); expect(model.getText()).not.toContain('ab'); input.sendKeys('abc'); expect(model.getText()).toContain('abc'); }); * </file> * </example> */ var minlengthDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; var minlength = attr.minlength || $parse(attr.ngMinlength)(scope); var minlengthParsed = parseLength(minlength) || -1; attr.$observe('minlength', function(value) { if (minlength !== value) { minlengthParsed = parseLength(value) || -1; minlength = value; ctrl.$validate(); } }); ctrl.$validators.minlength = function(modelValue, viewValue) { return ctrl.$isEmpty(viewValue) || viewValue.length >= minlengthParsed; }; } }; }]; function parsePatternAttr(regex, patternExp, elm) { if (!regex) return undefined; if (isString(regex)) { regex = new RegExp('^' + regex + '$'); } if (!regex.test) { throw minErr('ngPattern')('noregexp', 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, regex, startingTag(elm)); } return regex; } function parseLength(val) { var intVal = toInt(val); return isNumberNaN(intVal) ? -1 : intVal; }