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

Commit 0851736

Browse files
devversionThomasBurleson
authored andcommitted
fix(chips): fallback to detect cursor position
* In some cases the browser is not able to use the `selectionStart` propety to detect the cursor position > Figure out whether the current input for the chips buffer is valid for using > the selectionStart / end property to retrieve the cursor position. > Some browsers do not allow the use of those attributes, on different input types. Fixes #9097. Closes #9116
1 parent 1a2b1a8 commit 0851736

File tree

2 files changed

+114
-44
lines changed

2 files changed

+114
-44
lines changed

src/components/chips/chips.spec.js

Lines changed: 98 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -486,57 +486,118 @@ describe('<md-chips>', function() {
486486
expect(scope.items).toEqual(['Apple', 'Banana', 'Orange', 'Test']);
487487
}));
488488

489-
it('should properly cancel the backspace event to select the chip before', inject(function($mdConstant) {
490-
var element = buildChips(BASIC_CHIP_TEMPLATE);
491-
var ctrl = element.controller('mdChips');
492-
var input = element.find('input');
489+
describe('with backspace event', function() {
493490

494-
// Add the element to the document's body, because otherwise we won't be able
495-
// to set the selection of the chip input.
496-
document.body.appendChild(element[0]);
491+
var backspaceEvent, element, input, ctrl, isValidInput;
497492

498-
input.val(' ');
493+
beforeEach(inject(function($mdConstant) {
494+
backspaceEvent = {
495+
type: 'keydown',
496+
keyCode: $mdConstant.KEY_CODE.BACKSPACE,
497+
which: $mdConstant.KEY_CODE.BACKSPACE,
498+
preventDefault: jasmine.createSpy('preventDefault')
499+
};
500+
}));
499501

500-
// We have to trigger the `change` event, because IE11 does not support
501-
// the `input` event to update the ngModel. An alternative for `input` is to use the `change` event.
502-
input.triggerHandler('change');
502+
afterEach(function() {
503+
element && element.remove();
504+
});
503505

504-
// Since the `md-chips` component is testing the backspace select previous chip functionality by
505-
// checking the current caret / cursor position, we have to set the cursor to the end of the current
506-
// value.
507-
input[0].selectionStart = input[0].selectionEnd = input[0].value.length;
506+
function createChips(template) {
507+
element = buildChips(template);
508+
input = element.find('input');
509+
ctrl = element.controller('mdChips');
508510

509-
var backspaceEvent = {
510-
type: 'keydown',
511-
keyCode: $mdConstant.KEY_CODE.BACKSPACE,
512-
which: $mdConstant.KEY_CODE.BACKSPACE,
513-
preventDefault: jasmine.createSpy('preventDefault')
514-
};
511+
$timeout.flush();
515512

516-
input.triggerHandler(backspaceEvent);
513+
// Add the element to the document's body, because otherwise we won't be able
514+
// to set the selection of the chip input.
515+
document.body.appendChild(element[0]);
517516

518-
expect(backspaceEvent.preventDefault).not.toHaveBeenCalled();
517+
/** Detect whether the current input is supporting the `selectionStart` property */
518+
var oldInputValue = input.val();
519+
input.val('2');
520+
isValidInput = angular.isDefined(ctrl.getCursorPosition(input[0]));
521+
input.val(oldInputValue);
522+
}
519523

520-
input.val('');
524+
/**
525+
* Updates the cursor position of the input.
526+
* This is necessary to test the cursor position.
527+
*/
528+
function updateInputCursor() {
529+
if (isValidInput) {
530+
input[0].selectionStart = input[0].selectionEnd = input[0].value.length;
531+
}
532+
}
521533

522-
// Since the `md-chips` component is testing the backspace select previous chip functionality by
523-
// checking the current caret / cursor position, we have to set the cursor to the end of the current
524-
// value.
525-
input[0].selectionStart = input[0].selectionEnd = input[0].value.length;
534+
it('should properly cancel the backspace event to select the chip before', function() {
535+
createChips(BASIC_CHIP_TEMPLATE);
526536

527-
input.triggerHandler(backspaceEvent);
537+
input.val(' ');
538+
updateInputCursor();
539+
input.triggerHandler('change');
528540

529-
// We have to trigger the `change` event, because IE11 does not support
530-
// the `input` event to update the ngModel. An alternative for `input` is to use the `change` event.
531-
input.triggerHandler('change');
541+
input.triggerHandler(backspaceEvent);
542+
expect(backspaceEvent.preventDefault).not.toHaveBeenCalled();
532543

533-
expect(backspaceEvent.preventDefault).toHaveBeenCalledTimes(1);
544+
input.val('');
545+
updateInputCursor();
546+
input.triggerHandler('change');
534547

535-
// Remove the element from the document's body.
536-
document.body.removeChild(element[0]);
537-
}));
538-
});
539548

549+
input.triggerHandler(backspaceEvent);
550+
expect(backspaceEvent.preventDefault).toHaveBeenCalledTimes(1);
551+
});
552+
553+
it('should properly cancel the backspace event to select the chip before', function() {
554+
createChips(BASIC_CHIP_TEMPLATE);
555+
556+
input.val(' ');
557+
updateInputCursor();
558+
input.triggerHandler('change');
559+
560+
561+
input.triggerHandler(backspaceEvent);
562+
expect(backspaceEvent.preventDefault).not.toHaveBeenCalled();
563+
564+
input.val('');
565+
updateInputCursor();
566+
input.triggerHandler('change');
567+
568+
input.triggerHandler(backspaceEvent);
569+
expect(backspaceEvent.preventDefault).toHaveBeenCalledTimes(1);
570+
});
571+
572+
it('should properly handle the cursor position when using a number input', function() {
573+
createChips(
574+
'<md-chips ng-model="items">' +
575+
'<input type="number" placeholder="Enter a number">' +
576+
'</md-chips>'
577+
);
578+
579+
input.val('2');
580+
updateInputCursor();
581+
input.triggerHandler('change');
582+
583+
input.triggerHandler(backspaceEvent);
584+
$timeout.flush();
585+
586+
expect(backspaceEvent.preventDefault).not.toHaveBeenCalled();
587+
588+
input.val('');
589+
updateInputCursor();
590+
input.triggerHandler('change');
591+
592+
input.triggerHandler(backspaceEvent);
593+
$timeout.flush();
594+
595+
expect(backspaceEvent.preventDefault).toHaveBeenCalledTimes(1);
596+
});
597+
598+
});
599+
600+
});
540601

541602
it('focuses/blurs the component when focusing/blurring the input', inject(function() {
542603
var element = buildChips(BASIC_CHIP_TEMPLATE);

src/components/chips/js/chipsController.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ MdChipsCtrl.prototype.inputKeydown = function(event) {
115115
if (event.keyCode === this.$mdConstant.KEY_CODE.BACKSPACE) {
116116
// Only select and focus the previous chip, if the current caret position of the
117117
// input element is at the beginning.
118-
if (getCursorPosition(event.target) !== 0) {
118+
if (this.getCursorPosition(event.target) !== 0) {
119119
return;
120120
}
121121

@@ -149,16 +149,25 @@ MdChipsCtrl.prototype.inputKeydown = function(event) {
149149

150150
/**
151151
* Returns the cursor position of the specified input element.
152-
* If no selection is present it returns -1.
153152
* @param element HTMLInputElement
154153
* @returns {Number} Cursor Position of the input.
155154
*/
156-
function getCursorPosition(element) {
157-
if (element.selectionStart === element.selectionEnd) {
158-
return element.selectionStart;
155+
MdChipsCtrl.prototype.getCursorPosition = function(element) {
156+
/*
157+
* Figure out whether the current input for the chips buffer is valid for using
158+
* the selectionStart / end property to retrieve the cursor position.
159+
* Some browsers do not allow the use of those attributes, on different input types.
160+
*/
161+
try {
162+
if (element.selectionStart === element.selectionEnd) {
163+
return element.selectionStart;
164+
}
165+
} catch (e) {
166+
if (!element.value) {
167+
return 0;
168+
}
159169
}
160-
return -1;
161-
}
170+
};
162171

163172

164173
/**

0 commit comments

Comments
 (0)