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

Commit e383804

Browse files
jbedardgkalpak
authored andcommitted
fix(input): re-validate when partially editing date-family inputs
Fixes #12207 Closes #13886
1 parent 2a7d4c1 commit e383804

File tree

2 files changed

+105
-2
lines changed

2 files changed

+105
-2
lines changed

src/ng/directive/input.js

+28-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/;
3232
var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/;
3333
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
3434

35+
var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown';
36+
var PARTIAL_VALIDATION_TYPES = createMap();
37+
forEach('date,datetime-local,month,time,week'.split(','), function(type) {
38+
PARTIAL_VALIDATION_TYPES[type] = true;
39+
});
40+
3541
var inputType = {
3642

3743
/**
@@ -1118,6 +1124,8 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
11181124
});
11191125
}
11201126

1127+
var timeout;
1128+
11211129
var listener = function(ev) {
11221130
if (timeout) {
11231131
$browser.defer.cancel(timeout);
@@ -1147,8 +1155,6 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
11471155
if ($sniffer.hasEvent('input')) {
11481156
element.on('input', listener);
11491157
} else {
1150-
var timeout;
1151-
11521158
var deferListener = function(ev, input, origValue) {
11531159
if (!timeout) {
11541160
timeout = $browser.defer(function() {
@@ -1180,6 +1186,26 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
11801186
// or form autocomplete on newer browser, we need "change" event to catch it
11811187
element.on('change', listener);
11821188

1189+
// Some native input types (date-family) have the ability to change validity without
1190+
// firing any input/change events.
1191+
// For these event types, when native validators are present and the browser supports the type,
1192+
// check for validity changes on various DOM events.
1193+
if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) {
1194+
element.on(PARTIAL_VALIDATION_EVENTS, function(ev) {
1195+
if (!timeout) {
1196+
var validity = this[VALIDITY_STATE_PROPERTY];
1197+
var origBadInput = validity.badInput;
1198+
var origTypeMismatch = validity.typeMismatch;
1199+
timeout = $browser.defer(function() {
1200+
timeout = null;
1201+
if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) {
1202+
listener(ev);
1203+
}
1204+
});
1205+
}
1206+
});
1207+
}
1208+
11831209
ctrl.$render = function() {
11841210
// Workaround for Firefox validation #12102.
11851211
var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;

test/ng/directive/inputSpec.js

+77
Original file line numberDiff line numberDiff line change
@@ -2103,6 +2103,83 @@ describe('input', function() {
21032103
});
21042104
});
21052105

2106+
['month', 'week', 'time', 'date', 'datetime-local'].forEach(function(inputType) {
2107+
if (jqLite('<input type="' + inputType + '">').prop('type') !== inputType) {
2108+
return;
2109+
}
2110+
2111+
describe(inputType, function() {
2112+
they('should re-validate and dirty when partially editing the input value ($prop event)',
2113+
['keydown', 'wheel', 'mousedown'],
2114+
function(validationEvent) {
2115+
var mockValidity = {valid: true, badInput: false};
2116+
var inputElm = helper.compileInput('<input type="' + inputType + '" ng-model="val" name="alias" />', mockValidity);
2117+
2118+
expect(inputElm).toBeValid();
2119+
expect($rootScope.form.alias.$pristine).toBeTruthy();
2120+
2121+
inputElm.triggerHandler({type: validationEvent});
2122+
mockValidity.valid = false;
2123+
mockValidity.badInput = true;
2124+
$browser.defer.flush();
2125+
expect(inputElm).toBeInvalid();
2126+
expect($rootScope.form.alias.$pristine).toBeFalsy();
2127+
}
2128+
);
2129+
2130+
they('should do nothing when $prop event fired but validity does not change',
2131+
['keydown', 'wheel', 'mousedown'],
2132+
function(validationEvent) {
2133+
var mockValidity = {valid: true, badInput: false};
2134+
var inputElm = helper.compileInput('<input type="' + inputType + '" ng-model="val" name="alias" />', mockValidity);
2135+
2136+
expect(inputElm).toBeValid();
2137+
expect($rootScope.form.alias.$pristine).toBeTruthy();
2138+
2139+
inputElm.triggerHandler({type: validationEvent});
2140+
$browser.defer.flush();
2141+
expect(inputElm).toBeValid();
2142+
expect($rootScope.form.alias.$pristine).toBeTruthy();
2143+
}
2144+
);
2145+
2146+
they('should re-validate dirty when already $invalid and partially editing the input value ($prop event)',
2147+
['keydown', 'wheel', 'mousedown'],
2148+
function(validationEvent) {
2149+
var mockValidity = {valid: false, valueMissing: true, badInput: false};
2150+
var inputElm = helper.compileInput('<input type="' + inputType + '" required ng-model="val" name="alias" />', mockValidity);
2151+
2152+
expect(inputElm).toBeInvalid();
2153+
expect($rootScope.form.alias.$pristine).toBeTruthy();
2154+
2155+
inputElm.triggerHandler({type: validationEvent});
2156+
mockValidity.valid = false;
2157+
mockValidity.valueMissing = true;
2158+
mockValidity.badInput = true;
2159+
$browser.defer.flush();
2160+
expect(inputElm).toBeInvalid();
2161+
expect($rootScope.form.alias.$pristine).toBeFalsy();
2162+
}
2163+
);
2164+
2165+
they('should do nothing when already $invalid and $prop event fired but validity does not change',
2166+
['keydown', 'wheel', 'mousedown'],
2167+
function(validationEvent) {
2168+
var mockValidity = {valid: false, valueMissing: true, badInput: false};
2169+
var inputElm = helper.compileInput('<input type="' + inputType + '" required ng-model="val" name="alias" />', mockValidity);
2170+
2171+
expect(inputElm).toBeInvalid();
2172+
expect($rootScope.form.alias.$pristine).toBeTruthy();
2173+
2174+
inputElm.triggerHandler({type: validationEvent});
2175+
$browser.defer.flush();
2176+
expect(inputElm).toBeInvalid();
2177+
expect($rootScope.form.alias.$pristine).toBeTruthy();
2178+
}
2179+
);
2180+
});
2181+
});
2182+
21062183

21072184
describe('number', function() {
21082185

0 commit comments

Comments
 (0)