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

Commit 5a591f7

Browse files
jbedardcaitp
authored andcommitted
fix(inputs): ignoring input events in IE caused by placeholder changes or focus/blur on inputs with placeholders
1 parent 2240c11 commit 5a591f7

File tree

5 files changed

+185
-26
lines changed

5 files changed

+185
-26
lines changed

src/ng/directive/input.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,6 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
944944

945945
function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
946946
var validity = element.prop(VALIDITY_STATE_PROPERTY);
947-
var placeholder = element[0].placeholder, noevent = {};
948947
var type = lowercase(element[0].type);
949948

950949
// In composition mode, users are still inputing intermediate text buffer,
@@ -968,15 +967,6 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
968967
var value = element.val(),
969968
event = ev && ev.type;
970969

971-
// IE (11 and under) seem to emit an 'input' event if the placeholder value changes.
972-
// We don't want to dirty the value when this happens, so we abort here. Unfortunately,
973-
// IE also sends input events for other non-input-related things, (such as focusing on a
974-
// form control), so this change is not entirely enough to solve this.
975-
if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) {
976-
placeholder = element[0].placeholder;
977-
return;
978-
}
979-
980970
// By default we will trim the value
981971
// If the attribute ng-trim exists we will avoid trimming
982972
// If input type is 'password', the value is never trimmed

src/ng/sniffer.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ function $SnifferProvider() {
6767
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
6868
// it. In particular the event is not fired when backspace or delete key are pressed or
6969
// when cut operation is performed.
70-
if (event == 'input' && msie == 9) return false;
70+
// IE10+ implements 'input' event but it erroneously fires under various situations,
71+
// e.g. when placeholder changes, or a form is focused.
72+
if (event === 'input' && msie <= 11) return false;
7173

7274
if (isUndefined(eventSupport[event])) {
7375
var divElm = document.createElement('div');

src/ngScenario/dsl.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ angular.scenario.dsl('binding', function() {
199199
*/
200200
angular.scenario.dsl('input', function() {
201201
var chain = {};
202-
var supportInputEvent = 'oninput' in document.createElement('div') && msie != 9;
202+
var supportInputEvent = 'oninput' in document.createElement('div') && !(msie <= 11)
203203

204204
chain.enter = function(value, event) {
205205
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'",

test/ng/directive/inputSpec.js

Lines changed: 179 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,22 +1370,188 @@ describe('input', function() {
13701370
expect(scope.name).toEqual('caitp');
13711371
});
13721372

1373-
it('should not dirty the model on an input event in response to a placeholder change', inject(function($sniffer) {
1374-
if (msie && $sniffer.hasEvent('input')) {
1375-
compileInput('<input type="text" ng-model="name" name="name" />');
1376-
inputElm.attr('placeholder', 'Test');
1377-
browserTrigger(inputElm, 'input');
1373+
describe("IE placeholder input events", function() {
1374+
//IE fires an input event whenever a placeholder visually changes, essentially treating it as a value
1375+
//Events:
1376+
// placeholder attribute change: *input*
1377+
// focus (which visually removes the placeholder value): focusin focus *input*
1378+
// blur (which visually creates the placeholder value): focusout *input* blur
1379+
//However none of these occur if the placeholder is not visible at the time of the event.
1380+
//These tests try simulate various scenerios which do/do-not fire the extra input event
1381+
1382+
it('should not dirty the model on an input event in response to a placeholder change', function() {
1383+
if (msie) {
1384+
compileInput('<input type="text" placeholder="Test" attr-capture ng-model="unsetValue" name="name" />');
1385+
browserTrigger(inputElm, 'input');
1386+
expect(inputElm.attr('placeholder')).toBe('Test');
1387+
expect(inputElm).toBePristine();
1388+
1389+
attrs.$set('placeholder', '');
1390+
browserTrigger(inputElm, 'input');
1391+
expect(inputElm.attr('placeholder')).toBe('');
1392+
expect(inputElm).toBePristine();
1393+
1394+
attrs.$set('placeholder', 'Test Again');
1395+
browserTrigger(inputElm, 'input');
1396+
expect(inputElm.attr('placeholder')).toBe('Test Again');
1397+
expect(inputElm).toBePristine();
1398+
1399+
attrs.$set('placeholder', undefined);
1400+
browserTrigger(inputElm, 'input');
1401+
expect(inputElm.attr('placeholder')).toBe(undefined);
1402+
expect(inputElm).toBePristine();
1403+
1404+
changeInputValueTo('foo');
1405+
expect(inputElm).toBeDirty();
1406+
}
1407+
});
13781408

1379-
expect(inputElm.attr('placeholder')).toBe('Test');
1380-
expect(inputElm).toBePristine();
1409+
it('should not dirty the model on an input event in response to a interpolated placeholder change', inject(function($rootScope) {
1410+
if (msie) {
1411+
compileInput('<input type="text" placeholder="{{ph}}" ng-model="unsetValue" name="name" />');
1412+
browserTrigger(inputElm, 'input');
1413+
expect(inputElm).toBePristine();
13811414

1382-
inputElm.attr('placeholder', 'Test Again');
1383-
browserTrigger(inputElm, 'input');
1415+
$rootScope.ph = 1;
1416+
$rootScope.$digest();
1417+
browserTrigger(inputElm, 'input');
1418+
expect(inputElm).toBePristine();
13841419

1385-
expect(inputElm.attr('placeholder')).toBe('Test Again');
1386-
expect(inputElm).toBePristine();
1387-
}
1388-
}));
1420+
$rootScope.ph = "";
1421+
$rootScope.$digest();
1422+
browserTrigger(inputElm, 'input');
1423+
expect(inputElm).toBePristine();
1424+
1425+
changeInputValueTo('foo');
1426+
expect(inputElm).toBeDirty();
1427+
}
1428+
}));
1429+
1430+
it('should dirty the model on an input event in response to a placeholder change while in focus', inject(function($rootScope) {
1431+
if (msie) {
1432+
$rootScope.ph = 'Test';
1433+
compileInput('<input type="text" ng-attr-placeholder="{{ph}}" ng-model="unsetValue" name="name" />');
1434+
expect(inputElm).toBePristine();
1435+
1436+
browserTrigger(inputElm, 'focusin');
1437+
browserTrigger(inputElm, 'focus');
1438+
browserTrigger(inputElm, 'input');
1439+
expect(inputElm.attr('placeholder')).toBe('Test');
1440+
expect(inputElm).toBePristine();
1441+
1442+
$rootScope.ph = 'Test Again';
1443+
$rootScope.$digest();
1444+
expect(inputElm).toBePristine();
1445+
1446+
changeInputValueTo('foo');
1447+
expect(inputElm).toBeDirty();
1448+
}
1449+
}));
1450+
1451+
it('should not dirty the model on an input event in response to a ng-attr-placeholder change', inject(function($rootScope) {
1452+
if (msie) {
1453+
compileInput('<input type="text" ng-attr-placeholder="{{ph}}" ng-model="unsetValue" name="name" />');
1454+
expect(inputElm).toBePristine();
1455+
1456+
$rootScope.ph = 1;
1457+
$rootScope.$digest();
1458+
browserTrigger(inputElm, 'input');
1459+
expect(inputElm).toBePristine();
1460+
1461+
$rootScope.ph = "";
1462+
$rootScope.$digest();
1463+
browserTrigger(inputElm, 'input');
1464+
expect(inputElm).toBePristine();
1465+
1466+
changeInputValueTo('foo');
1467+
expect(inputElm).toBeDirty();
1468+
}
1469+
}));
1470+
1471+
it('should not dirty the model on an input event in response to a focus', inject(function($sniffer) {
1472+
if (msie) {
1473+
compileInput('<input type="text" placeholder="Test" ng-model="unsetValue" name="name" />');
1474+
browserTrigger(inputElm, 'input');
1475+
expect(inputElm.attr('placeholder')).toBe('Test');
1476+
expect(inputElm).toBePristine();
1477+
1478+
browserTrigger(inputElm, 'focusin');
1479+
browserTrigger(inputElm, 'focus');
1480+
browserTrigger(inputElm, 'input');
1481+
expect(inputElm.attr('placeholder')).toBe('Test');
1482+
expect(inputElm).toBePristine();
1483+
1484+
changeInputValueTo('foo');
1485+
expect(inputElm).toBeDirty();
1486+
}
1487+
}));
1488+
1489+
it('should not dirty the model on an input event in response to a blur', inject(function($sniffer) {
1490+
if (msie) {
1491+
compileInput('<input type="text" placeholder="Test" ng-model="unsetValue" name="name" />');
1492+
browserTrigger(inputElm, 'input');
1493+
expect(inputElm.attr('placeholder')).toBe('Test');
1494+
expect(inputElm).toBePristine();
1495+
1496+
browserTrigger(inputElm, 'focusin');
1497+
browserTrigger(inputElm, 'focus');
1498+
browserTrigger(inputElm, 'input');
1499+
expect(inputElm).toBePristine();
1500+
1501+
browserTrigger(inputElm, 'focusout');
1502+
browserTrigger(inputElm, 'input');
1503+
browserTrigger(inputElm, 'blur');
1504+
expect(inputElm).toBePristine();
1505+
1506+
changeInputValueTo('foo');
1507+
expect(inputElm).toBeDirty();
1508+
}
1509+
}));
1510+
1511+
it('should dirty the model on an input event if there is a placeholder and value', inject(function($rootScope) {
1512+
if (msie) {
1513+
$rootScope.name = 'foo';
1514+
compileInput('<input type="text" placeholder="Test" ng-model="name" value="init" name="name" />');
1515+
expect(inputElm.val()).toBe($rootScope.name);
1516+
expect(inputElm).toBePristine();
1517+
1518+
changeInputValueTo('bar');
1519+
expect(inputElm).toBeDirty();
1520+
}
1521+
}));
1522+
1523+
it('should dirty the model on an input event if there is a placeholder and value after focusing', inject(function($rootScope) {
1524+
if (msie) {
1525+
$rootScope.name = 'foo';
1526+
compileInput('<input type="text" placeholder="Test" ng-model="name" value="init" name="name" />');
1527+
expect(inputElm.val()).toBe($rootScope.name);
1528+
expect(inputElm).toBePristine();
1529+
1530+
browserTrigger(inputElm, 'focusin');
1531+
browserTrigger(inputElm, 'focus');
1532+
changeInputValueTo('bar');
1533+
expect(inputElm).toBeDirty();
1534+
}
1535+
}));
1536+
1537+
it('should dirty the model on an input event if there is a placeholder and value after bluring', inject(function($rootScope) {
1538+
if (msie) {
1539+
$rootScope.name = 'foo';
1540+
compileInput('<input type="text" placeholder="Test" ng-model="name" value="init" name="name" />');
1541+
expect(inputElm.val()).toBe($rootScope.name);
1542+
expect(inputElm).toBePristine();
1543+
1544+
browserTrigger(inputElm, 'focusin');
1545+
browserTrigger(inputElm, 'focus');
1546+
expect(inputElm).toBePristine();
1547+
1548+
browserTrigger(inputElm, 'focusout');
1549+
browserTrigger(inputElm, 'blur');
1550+
changeInputValueTo('bar');
1551+
expect(inputElm).toBeDirty();
1552+
}
1553+
}));
1554+
});
13891555

13901556

13911557
it('should interpolate input names', function() {

test/ng/snifferSpec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ describe('$sniffer', function() {
6464

6565
it('should claim that IE9 doesn\'t have support for "oninput"', function() {
6666
// IE9 implementation is fubared, so it's better to pretend that it doesn't have the support
67+
// IE10+ implementation is fubared when mixed with placeholders
6768
mockDivElement = {oninput: noop};
6869

69-
expect($sniffer.hasEvent('input')).toBe((msie == 9) ? false : true);
70+
expect($sniffer.hasEvent('input')).toBe(!(msie && msie <= 11));
7071
});
7172
});
7273

0 commit comments

Comments
 (0)