Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix(ngModel): let aliased validator directives work on any element
Browse files Browse the repository at this point in the history
`ng(Pattern|Minlength|Maxlength)` directives will now validate the
`ngModel` when on an element that is not an `input` or
a `textarea`. Previously, only their HTML5 counterparts worked on
every element.

This is because the three validators were extracted
into  separate directives (see 26d91b6
and 1be9bb9), whereas the aliased
attribute handling assumes the validators will only exist on
`input|textarea` (see d9b90d7 and
25541c1).

Since `ngMin` and `ngMax` are also aliased attributes, this means
observers of `min` and `max` will be fired if `ngMin` and `ngMax`
change. This will happen on any element, even if it does not have
an `ngModel`. However, since min/max validators are only ever added
as part of the `input[number|textarea]` types, even if the element
has an `ngModel`, no validators will be added.

Finally the commit also tests that `ng-required` works on any element,
although that validator worked on all elements before this fix.

Fixes #12158
Closes #12658
  • Loading branch information
Narretz committed Aug 31, 2015
1 parent 170cd96 commit 43769fb
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 4 deletions.
5 changes: 2 additions & 3 deletions src/jqLite.js
Expand Up @@ -556,9 +556,8 @@ function getBooleanAttrName(element, name) {
return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
}

function getAliasedAttrName(element, name) {
var nodeName = element.nodeName;
return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name];
function getAliasedAttrName(name) {
return ALIASED_ATTR[name];
}

forEach({
Expand Down
2 changes: 1 addition & 1 deletion src/ng/compile.js
Expand Up @@ -1077,7 +1077,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {

var node = this.$$element[0],
booleanKey = getBooleanAttrName(node, key),
aliasedKey = getAliasedAttrName(node, key),
aliasedKey = getAliasedAttrName(key),
observer = key,
nodeName;

Expand Down
73 changes: 73 additions & 0 deletions test/ng/directive/validatorsSpec.js
Expand Up @@ -219,6 +219,24 @@ describe('validators', function() {
expect($rootScope.form.test.$modelValue).toBe('12340');
expect(inputElm).toBeValid();
});


it('should validate on non-input elements', inject(function($compile) {
$rootScope.pattern = '\\d{4}';
var elm = $compile('<span ng-model="value" pattern="\\d{4}"></span>')($rootScope);
var elmNg = $compile('<span ng-model="value" ng-pattern="pattern"></span>')($rootScope);
var ctrl = elm.controller('ngModel');
var ctrlNg = elmNg.controller('ngModel');

expect(ctrl.$error.pattern).not.toBe(true);
expect(ctrlNg.$error.pattern).not.toBe(true);

ctrl.$setViewValue('12');
ctrlNg.$setViewValue('12');

expect(ctrl.$error.pattern).toBe(true);
expect(ctrlNg.$error.pattern).toBe(true);
}));
});


Expand Down Expand Up @@ -283,6 +301,24 @@ describe('validators', function() {
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});


it('should validate on non-input elements', inject(function($compile) {
$rootScope.min = 3;
var elm = $compile('<span ng-model="value" minlength="{{min}}"></span>')($rootScope);
var elmNg = $compile('<span ng-model="value" ng-minlength="min"></span>')($rootScope);
var ctrl = elm.controller('ngModel');
var ctrlNg = elmNg.controller('ngModel');

expect(ctrl.$error.minlength).not.toBe(true);
expect(ctrlNg.$error.minlength).not.toBe(true);

ctrl.$setViewValue('12');
ctrlNg.$setViewValue('12');

expect(ctrl.$error.minlength).toBe(true);
expect(ctrlNg.$error.minlength).toBe(true);
}));
});


Expand Down Expand Up @@ -453,6 +489,24 @@ describe('validators', function() {
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});


it('should validate on non-input elements', inject(function($compile) {
$rootScope.max = 3;
var elm = $compile('<span ng-model="value" maxlength="{{max}}"></span>')($rootScope);
var elmNg = $compile('<span ng-model="value" ng-maxlength="max"></span>')($rootScope);
var ctrl = elm.controller('ngModel');
var ctrlNg = elmNg.controller('ngModel');

expect(ctrl.$error.maxlength).not.toBe(true);
expect(ctrlNg.$error.maxlength).not.toBe(true);

ctrl.$setViewValue('1234');
ctrlNg.$setViewValue('1234');

expect(ctrl.$error.maxlength).toBe(true);
expect(ctrlNg.$error.maxlength).toBe(true);
}));
});


Expand Down Expand Up @@ -547,6 +601,7 @@ describe('validators', function() {
expect(inputElm).toBeValid();
});


it('should validate emptiness against the viewValue', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" required />');

Expand All @@ -560,5 +615,23 @@ describe('validators', function() {
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});


it('should validate on non-input elements', inject(function($compile) {
$rootScope.value = '12';
var elm = $compile('<span ng-model="value" required></span>')($rootScope);
var elmNg = $compile('<span ng-model="value" ng-required="true"></span>')($rootScope);
var ctrl = elm.controller('ngModel');
var ctrlNg = elmNg.controller('ngModel');

expect(ctrl.$error.required).not.toBe(true);
expect(ctrlNg.$error.required).not.toBe(true);

ctrl.$setViewValue('');
ctrlNg.$setViewValue('');

expect(ctrl.$error.required).toBe(true);
expect(ctrlNg.$error.required).toBe(true);
}));
});
});

0 comments on commit 43769fb

Please sign in to comment.