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

Commit 02929f8

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

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
@@ -1943,6 +1943,83 @@ describe('input', function() {
19431943
});
19441944
});
19451945

1946+
['month', 'week', 'time', 'date', 'datetime-local'].forEach(function(inputType) {
1947+
if (jqLite('<input type="' + inputType + '">').prop('type') !== inputType) {
1948+
return;
1949+
}
1950+
1951+
describe(inputType, function() {
1952+
they('should re-validate and dirty when partially editing the input value ($prop event)',
1953+
['keydown', 'wheel', 'mousedown'],
1954+
function(validationEvent) {
1955+
var mockValidity = {valid: true, badInput: false};
1956+
var inputElm = helper.compileInput('<input type="' + inputType + '" ng-model="val" name="alias" />', mockValidity);
1957+
1958+
expect(inputElm).toBeValid();
1959+
expect($rootScope.form.alias.$pristine).toBeTruthy();
1960+
1961+
inputElm.triggerHandler({type: validationEvent});
1962+
mockValidity.valid = false;
1963+
mockValidity.badInput = true;
1964+
$browser.defer.flush();
1965+
expect(inputElm).toBeInvalid();
1966+
expect($rootScope.form.alias.$pristine).toBeFalsy();
1967+
}
1968+
);
1969+
1970+
they('should do nothing when $prop event fired but validity does not change',
1971+
['keydown', 'wheel', 'mousedown'],
1972+
function(validationEvent) {
1973+
var mockValidity = {valid: true, badInput: false};
1974+
var inputElm = helper.compileInput('<input type="' + inputType + '" ng-model="val" name="alias" />', mockValidity);
1975+
1976+
expect(inputElm).toBeValid();
1977+
expect($rootScope.form.alias.$pristine).toBeTruthy();
1978+
1979+
inputElm.triggerHandler({type: validationEvent});
1980+
$browser.defer.flush();
1981+
expect(inputElm).toBeValid();
1982+
expect($rootScope.form.alias.$pristine).toBeTruthy();
1983+
}
1984+
);
1985+
1986+
they('should re-validate dirty when already $invalid and partially editing the input value ($prop event)',
1987+
['keydown', 'wheel', 'mousedown'],
1988+
function(validationEvent) {
1989+
var mockValidity = {valid: false, valueMissing: true, badInput: false};
1990+
var inputElm = helper.compileInput('<input type="' + inputType + '" required ng-model="val" name="alias" />', mockValidity);
1991+
1992+
expect(inputElm).toBeInvalid();
1993+
expect($rootScope.form.alias.$pristine).toBeTruthy();
1994+
1995+
inputElm.triggerHandler({type: validationEvent});
1996+
mockValidity.valid = false;
1997+
mockValidity.valueMissing = true;
1998+
mockValidity.badInput = true;
1999+
$browser.defer.flush();
2000+
expect(inputElm).toBeInvalid();
2001+
expect($rootScope.form.alias.$pristine).toBeFalsy();
2002+
}
2003+
);
2004+
2005+
they('should do nothing when already $invalid and $prop event fired but validity does not change',
2006+
['keydown', 'wheel', 'mousedown'],
2007+
function(validationEvent) {
2008+
var mockValidity = {valid: false, valueMissing: true, badInput: false};
2009+
var inputElm = helper.compileInput('<input type="' + inputType + '" required ng-model="val" name="alias" />', mockValidity);
2010+
2011+
expect(inputElm).toBeInvalid();
2012+
expect($rootScope.form.alias.$pristine).toBeTruthy();
2013+
2014+
inputElm.triggerHandler({type: validationEvent});
2015+
$browser.defer.flush();
2016+
expect(inputElm).toBeInvalid();
2017+
expect($rootScope.form.alias.$pristine).toBeTruthy();
2018+
}
2019+
);
2020+
});
2021+
});
2022+
19462023

19472024
describe('number', function() {
19482025

0 commit comments

Comments
 (0)