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

Commit ab2e83c

Browse files
committedJun 24, 2014
fix(input): improve html5 validation support
This CL improves mocking support for HTML5 validation, fixes the behaviour which invokes validators. Previously, an input would only be revalidated if either its value changed, or if it was the empty string but did not suffer from bad input --- now, it will be revalidated if either the value has changed, or the value is the empty string, there is a ValidityState for the element, and that ValidityState is being tested by one of the validators in the pipeline. Closes #7937 Closes #7957
1 parent e5f454c commit ab2e83c

File tree

5 files changed

+82
-13
lines changed

5 files changed

+82
-13
lines changed
 

‎src/.jshintrc

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"assertNotHasOwnProperty": false,
101101
"getter": false,
102102
"getBlockElements": false,
103+
"VALIDITY_STATE_PROPERTY": false,
103104

104105
/* AngularPublic.js */
105106
"version": false,

‎src/Angular.js

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
-angularModule,
1414
-nodeName_,
1515
-uid,
16+
-VALIDITY_STATE_PROPERTY,
1617
1718
-lowercase,
1819
-uppercase,
@@ -102,6 +103,10 @@
102103
* <div doc-module-components="ng"></div>
103104
*/
104105

106+
// The name of a form control's ValidityState property.
107+
// This is used so that it's possible for internal tests to create mock ValidityStates.
108+
var VALIDITY_STATE_PROPERTY = 'validity';
109+
105110
/**
106111
* @ngdoc function
107112
* @name angular.lowercase

‎src/ng/directive/input.js

+28-11
Original file line numberDiff line numberDiff line change
@@ -435,15 +435,29 @@ function validate(ctrl, validatorName, validity, value){
435435
return validity ? value : undefined;
436436
}
437437

438+
function testFlags(validity, flags) {
439+
var i, flag;
440+
if (flags) {
441+
for (i=0; i<flags.length; ++i) {
442+
flag = flags[i];
443+
if (validity[flag]) {
444+
return true;
445+
}
446+
}
447+
}
448+
return false;
449+
}
438450

439-
function addNativeHtml5Validators(ctrl, validatorName, element) {
440-
var validity = element.prop('validity');
451+
// Pass validity so that behaviour can be mocked easier.
452+
function addNativeHtml5Validators(ctrl, validatorName, badFlags, ignoreFlags, validity) {
441453
if (isObject(validity)) {
454+
ctrl.$$hasNativeValidators = true;
442455
var validator = function(value) {
443456
// Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can
444457
// perform the required validation)
445-
if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError ||
446-
validity.typeMismatch) && !validity.valueMissing) {
458+
if (!ctrl.$error[validatorName] &&
459+
!testFlags(validity, ignoreFlags) &&
460+
testFlags(validity, badFlags)) {
447461
ctrl.$setValidity(validatorName, false);
448462
return;
449463
}
@@ -454,8 +468,9 @@ function addNativeHtml5Validators(ctrl, validatorName, element) {
454468
}
455469

456470
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
457-
var validity = element.prop('validity');
471+
var validity = element.prop(VALIDITY_STATE_PROPERTY);
458472
var placeholder = element[0].placeholder, noevent = {};
473+
ctrl.$$validityState = validity;
459474

460475
// In composition mode, users are still inputing intermediate text buffer,
461476
// hold the listener until composition is done.
@@ -493,11 +508,11 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
493508
value = trim(value);
494509
}
495510

496-
if (ctrl.$viewValue !== value ||
497-
// If the value is still empty/falsy, and there is no `required` error, run validators
498-
// again. This enables HTML5 constraint validation errors to affect Angular validation
499-
// even when the first character entered causes an error.
500-
(validity && value === '' && !validity.valueMissing)) {
511+
// If a control is suffering from bad input, browsers discard its value, so it may be
512+
// necessary to revalidate even if the control's value is the same empty value twice in
513+
// a row.
514+
var revalidate = validity && ctrl.$$hasNativeValidators;
515+
if (ctrl.$viewValue !== value || (value === '' && revalidate)) {
501516
if (scope.$$phase) {
502517
ctrl.$setViewValue(value);
503518
} else {
@@ -603,6 +618,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
603618
}
604619
}
605620

621+
var numberBadFlags = ['badInput'];
622+
606623
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
607624
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
608625

@@ -617,7 +634,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
617634
}
618635
});
619636

620-
addNativeHtml5Validators(ctrl, 'number', element);
637+
addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState);
621638

622639
ctrl.$formatters.push(function(value) {
623640
return ctrl.$isEmpty(value) ? '' : '' + value;

‎test/.jshintrc

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
"assertNotHasOwnProperty": false,
102102
"getter": false,
103103
"getBlockElements": false,
104+
"VALIDITY_STATE_PROPERTY": true,
104105

105106
/* filters.js */
106107
"getFirstThursdayOfYear": false,

‎test/ng/directive/inputSpec.js

+47-2
Original file line numberDiff line numberDiff line change
@@ -410,15 +410,33 @@ describe('ngModel', function() {
410410

411411

412412
describe('input', function() {
413-
var formElm, inputElm, scope, $compile, $sniffer, $browser, changeInputValueTo;
413+
var formElm, inputElm, scope, $compile, $sniffer, $browser, changeInputValueTo, currentSpec;
414414

415-
function compileInput(inputHtml) {
415+
function compileInput(inputHtml, mockValidity) {
416416
inputElm = jqLite(inputHtml);
417+
if (isObject(mockValidity)) {
418+
VALIDITY_STATE_PROPERTY = 'ngMockValidity';
419+
inputElm.prop(VALIDITY_STATE_PROPERTY, mockValidity);
420+
currentSpec.after(function() {
421+
VALIDITY_STATE_PROPERTY = 'validity';
422+
});
423+
}
417424
formElm = jqLite('<form name="form"></form>');
418425
formElm.append(inputElm);
419426
$compile(formElm)(scope);
420427
}
421428

429+
var attrs;
430+
beforeEach(function() { currentSpec = this; });
431+
afterEach(function() { currentSpec = null; });
432+
beforeEach(module(function($compileProvider) {
433+
$compileProvider.directive('attrCapture', function() {
434+
return function(scope, element, $attrs) {
435+
attrs = $attrs;
436+
};
437+
});
438+
}));
439+
422440
beforeEach(inject(function($injector, _$sniffer_, _$browser_) {
423441
$sniffer = _$sniffer_;
424442
$browser = _$browser_;
@@ -844,6 +862,33 @@ describe('input', function() {
844862
});
845863

846864

865+
it('should invalidate number if suffering from bad input', function() {
866+
compileInput('<input type="number" ng-model="age" />', {
867+
valid: false,
868+
badInput: true
869+
});
870+
871+
changeInputValueTo('10a');
872+
expect(scope.age).toBeUndefined();
873+
expect(inputElm).toBeInvalid();
874+
});
875+
876+
877+
it('should validate number if transition from bad input to empty string', function() {
878+
var validity = {
879+
valid: false,
880+
badInput: true
881+
};
882+
compileInput('<input type="number" ng-model="age" />', validity);
883+
changeInputValueTo('10a');
884+
validity.badInput = false;
885+
validity.valid = true;
886+
changeInputValueTo('');
887+
expect(scope.age).toBeNull();
888+
expect(inputElm).toBeValid();
889+
});
890+
891+
847892
describe('min', function() {
848893

849894
it('should validate', function() {

0 commit comments

Comments
 (0)
This repository has been archived.