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

Commit 2fa2e4d

Browse files
topherfangioThomasBurleson
authored andcommitted
fix(autocomplete): Fix autocomplete items with spaces.
If an autocomplete's item had spaces at the end, it would cause the autocomplete to show only the selected option when attempting to clear the value. This was caused because the `oninput` event that we fire was somehow resetting the value in the scope (after we had cleared it). Fix by resetting the scope back to an empty value after we fire the `oninput` event. Additionally, fix a few issues in the Virtual Repeat that was affecting the autocomplete and some test descriptions: - Add unwatcher in Virtual Repeat to fix #8178. - Use cached values in `$mdUtil.waitTransitionEnd()` to speed up performance and fix sizing issue in Virtual Repeat affecting the autocomplete. - Fix a few descriptions in the tests to be shorter/clearer and fix typos. Fixes #7655. Fixes #8178. Closes #8580
1 parent 92e932c commit 2fa2e4d

File tree

6 files changed

+122
-33
lines changed

6 files changed

+122
-33
lines changed

src/components/autocomplete/autocomplete.spec.js

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe('<md-autocomplete>', function() {
5858
}
5959

6060
describe('basic functionality', function() {
61-
it('should update selected item and search text', inject(function($timeout, $mdConstant, $material) {
61+
it('updates selected item and search text', inject(function($timeout, $mdConstant, $material) {
6262
var scope = createScope();
6363
var template = '\
6464
<md-autocomplete\
@@ -103,8 +103,7 @@ describe('<md-autocomplete>', function() {
103103
element.remove();
104104
}));
105105

106-
107-
it('should allow you to set an input id without floating label', inject(function() {
106+
it('allows you to set an input id without floating label', inject(function() {
108107
var scope = createScope(null, {inputId: 'custom-input-id'});
109108
var template = '\
110109
<md-autocomplete\
@@ -124,7 +123,7 @@ describe('<md-autocomplete>', function() {
124123
element.remove();
125124
}));
126125

127-
it('should allow allow using ng-readonly', inject(function() {
126+
it('allows using ng-readonly', inject(function() {
128127
var scope = createScope(null, {inputId: 'custom-input-id'});
129128
var template = '\
130129
<md-autocomplete\
@@ -153,7 +152,7 @@ describe('<md-autocomplete>', function() {
153152
element.remove();
154153
}));
155154

156-
it('should allow allow using an empty readonly attribute', inject(function() {
155+
it('allows using an empty readonly attribute', inject(function() {
157156
var scope = createScope(null, {inputId: 'custom-input-id'});
158157
var template = '\
159158
<md-autocomplete\
@@ -174,7 +173,7 @@ describe('<md-autocomplete>', function() {
174173
element.remove();
175174
}));
176175

177-
it('should allow you to set an input id with floating label', inject(function() {
176+
it('allows you to set an input id with floating label', inject(function() {
178177
var scope = createScope(null, {inputId: 'custom-input-id'});
179178
var template = '\
180179
<md-autocomplete\
@@ -195,7 +194,7 @@ describe('<md-autocomplete>', function() {
195194
element.remove();
196195
}));
197196

198-
it('should forward the `md-select-on-focus` attribute to the input', inject(function() {
197+
it('forwards the `md-select-on-focus` attribute to the input', inject(function() {
199198
var scope = createScope(null, {inputId: 'custom-input-id'});
200199
var template =
201200
'<md-autocomplete ' +
@@ -218,7 +217,7 @@ describe('<md-autocomplete>', function() {
218217
element.remove();
219218
}));
220219

221-
it('should forward the tabindex to the input', inject(function() {
220+
it('forwards the tabindex to the input', inject(function() {
222221
var scope = createScope(null, {inputId: 'custom-input-id'});
223222
var template =
224223
'<md-autocomplete ' +
@@ -240,7 +239,7 @@ describe('<md-autocomplete>', function() {
240239
element.remove();
241240
}));
242241

243-
it('should always set the tabindex of the autcomplete to `-1`', inject(function() {
242+
it('always sets the tabindex of the autcomplete to `-1`', inject(function() {
244243
var scope = createScope(null, {inputId: 'custom-input-id'});
245244
var template =
246245
'<md-autocomplete ' +
@@ -291,7 +290,7 @@ describe('<md-autocomplete>', function() {
291290
element.remove();
292291
}));
293292

294-
it('should clear value when hitting escape', inject(function($mdConstant, $timeout) {
293+
it('clears the value when hitting escape', inject(function($mdConstant, $timeout) {
295294
var scope = createScope();
296295
var template = '\
297296
<md-autocomplete\
@@ -380,7 +379,7 @@ describe('<md-autocomplete>', function() {
380379
});
381380

382381
describe('basic functionality with template', function() {
383-
it('should update selected item and search text', inject(function($timeout, $material, $mdConstant) {
382+
it('updates selected item and search text', inject(function($timeout, $material, $mdConstant) {
384383
var scope = createScope();
385384
var template = '\
386385
<md-autocomplete\
@@ -423,7 +422,60 @@ describe('<md-autocomplete>', function() {
423422
element.remove();
424423
}));
425424

426-
it('should compile the template against the parent scope', inject(function($timeout, $material) {
425+
it('properly clears values when the item ends in a space character', inject(function($timeout, $material, $mdConstant) {
426+
var myItems = ['foo ', 'bar', 'baz'].map(function(item) {
427+
return {display: item};
428+
});
429+
var scope = createScope(myItems);
430+
431+
var template = '\
432+
<md-autocomplete\
433+
md-selected-item="selectedItem"\
434+
md-search-text="searchText"\
435+
md-items="item in match(searchText)"\
436+
md-item-text="item.display"\
437+
placeholder="placeholder">\
438+
<md-item-template>\
439+
<span md-highlight-text="searchText">{{item.display}}</span>\
440+
</md-item-template>\
441+
</md-autocomplete>';
442+
var element = compile(template, scope);
443+
var ctrl = element.controller('mdAutocomplete');
444+
var ul = element.find('ul');
445+
446+
expect(scope.searchText).toBe('');
447+
expect(scope.selectedItem).toBe(null);
448+
449+
$material.flushInterimElement();
450+
451+
// Focus the input
452+
ctrl.focus();
453+
454+
element.scope().searchText = 'fo';
455+
waitForVirtualRepeat(element);
456+
457+
expect(scope.searchText).toBe('fo');
458+
expect(scope.match(scope.searchText).length).toBe(1);
459+
expect(ul.find('li').length).toBe(1);
460+
461+
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.DOWN_ARROW));
462+
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ENTER));
463+
464+
$timeout.flush();
465+
466+
expect(scope.searchText).toBe('foo ');
467+
expect(scope.selectedItem).toBe(scope.match(scope.searchText)[0]);
468+
469+
ctrl.clear();
470+
$timeout.flush();
471+
472+
expect(scope.searchText).toBe('');
473+
expect(scope.selectedItem).toBe(null);
474+
475+
element.remove();
476+
}));
477+
478+
it('compiles the template against the parent scope', inject(function($timeout, $material) {
427479
var scope = createScope(null, {bang: 'boom'});
428480
var template =
429481
'<md-autocomplete' +
@@ -470,7 +522,7 @@ describe('<md-autocomplete>', function() {
470522
element.remove();
471523
}));
472524

473-
it('should remove the md-scroll-mask on cleanup', inject(function($mdUtil, $timeout, $material) {
525+
it('removes the md-scroll-mask on cleanup', inject(function($mdUtil, $timeout, $material) {
474526
spyOn($mdUtil, 'enableScrolling');
475527

476528
var scope = createScope();
@@ -513,7 +565,7 @@ describe('<md-autocomplete>', function() {
513565
expect($mdUtil.enableScrolling).toHaveBeenCalled();
514566
}));
515567

516-
it('should ensure the parent scope digests along with the current scope', inject(function($timeout, $material) {
568+
it('ensures the parent scope digests along with the current scope', inject(function($timeout, $material) {
517569
var scope = createScope(null, {bang: 'boom'});
518570
var template =
519571
'<md-autocomplete' +
@@ -695,7 +747,7 @@ describe('<md-autocomplete>', function() {
695747
expect(ctrl2.hasNotFound).toBe(false);
696748
}));
697749

698-
it('should even show the md-not-found template if we have lost focus', inject(function($timeout) {
750+
it('shows the md-not-found template even if we have lost focus', inject(function($timeout) {
699751
var scope = createScope();
700752
var template =
701753
'<md-autocomplete' +
@@ -807,7 +859,7 @@ describe('<md-autocomplete>', function() {
807859

808860
describe('Async matching', function() {
809861

810-
it('should probably stop the loading indicator when clearing', inject(function($timeout, $material) {
862+
it('properly stops the loading indicator when clearing', inject(function($timeout, $material) {
811863
var scope = createScope();
812864
var template =
813865
'<md-autocomplete ' +
@@ -837,7 +889,7 @@ describe('<md-autocomplete>', function() {
837889
});
838890

839891
describe('API access', function() {
840-
it('should clear the selected item', inject(function($timeout) {
892+
it('clears the selected item', inject(function($timeout) {
841893
var scope = createScope();
842894
var template = '\
843895
<md-autocomplete\
@@ -871,7 +923,7 @@ describe('<md-autocomplete>', function() {
871923
element.remove();
872924
}));
873925

874-
it('should notify selected item watchers', inject(function($timeout) {
926+
it('notifies selected item watchers', inject(function($timeout) {
875927
var scope = createScope();
876928
scope.itemChanged = jasmine.createSpy('itemChanged');
877929

@@ -919,7 +971,7 @@ describe('<md-autocomplete>', function() {
919971

920972
element.remove();
921973
}));
922-
it('should pass value to item watcher', inject(function($timeout) {
974+
it('passes the value to the item watcher', inject(function($timeout) {
923975
var scope = createScope();
924976
var itemValue = null;
925977
var template = '\
@@ -955,7 +1007,7 @@ describe('<md-autocomplete>', function() {
9551007
});
9561008

9571009
describe('md-select-on-match', function() {
958-
it('should select matching item on exact match when `md-select-on-match` is toggled', inject(function($timeout) {
1010+
it('selects matching item on exact match when `md-select-on-match` is toggled', inject(function($timeout) {
9591011
var scope = createScope();
9601012
var template = '\
9611013
<md-autocomplete\
@@ -1004,7 +1056,7 @@ describe('<md-autocomplete>', function() {
10041056
element.remove();
10051057
}));
10061058

1007-
it('should select matching item using case insensitive', inject(function($timeout) {
1059+
it('selects matching item using case insensitive', inject(function($timeout) {
10081060
var scope = createScope(null, null, true);
10091061
var template =
10101062
'<md-autocomplete ' +
@@ -1063,7 +1115,7 @@ describe('<md-autocomplete>', function() {
10631115
element.remove();
10641116
});
10651117

1066-
it('should validate an empty `required` as true', function() {
1118+
it('validates an empty `required` as true', function() {
10671119
var scope = createScope();
10681120
var template = '\
10691121
<md-autocomplete\
@@ -1082,7 +1134,7 @@ describe('<md-autocomplete>', function() {
10821134
expect(ctrl.isRequired).toBe(true);
10831135
});
10841136

1085-
it('should correctly validate an interpolated `ng-required` value', function() {
1137+
it('correctly validates an interpolated `ng-required` value', function() {
10861138
var scope = createScope();
10871139
var template = '\
10881140
<md-autocomplete\
@@ -1111,7 +1163,7 @@ describe('<md-autocomplete>', function() {
11111163
expect(ctrl.isRequired).toBe(true);
11121164
});
11131165

1114-
it('should forward the md-no-asterisk attribute', function() {
1166+
it('forwards the md-no-asterisk attribute', function() {
11151167
var scope = createScope();
11161168
var template = '\
11171169
<md-autocomplete\
@@ -1133,7 +1185,7 @@ describe('<md-autocomplete>', function() {
11331185
});
11341186

11351187
describe('md-highlight-text', function() {
1136-
it('should update when content is modified', inject(function() {
1188+
it('updates when content is modified', inject(function() {
11371189
var template = '<div md-highlight-text="query">{{message}}</div>';
11381190
var scope = createScope(null, {message: 'some text', query: 'some'});
11391191
var element = compile(template, scope);

src/components/autocomplete/js/autocompleteController.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
421421
/**
422422
* Handles input focus event, determines if the dropdown should show.
423423
*/
424-
function focus () {
424+
function focus($event) {
425425
hasFocus = true;
426426
//-- if searchText is null, let's force it to be a string
427427
if (!angular.isString($scope.searchText)) $scope.searchText = '';
@@ -639,9 +639,9 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
639639
/**
640640
* Clears the searchText value and selected item.
641641
*/
642-
function clearValue () {
642+
function clearValue ($event) {
643643
// Set the loading to true so we don't see flashes of content.
644-
// The flashing will only occour when an async request is running.
644+
// The flashing will only occur when an async request is running.
645645
// So the loading process will stop when the results had been retrieved.
646646
setLoading(true);
647647

@@ -652,9 +652,14 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
652652

653653
// Per http://www.w3schools.com/jsref/event_oninput.asp
654654
var eventObj = document.createEvent('CustomEvent');
655-
eventObj.initCustomEvent('input', true, true, { value: $scope.searchText });
655+
eventObj.initCustomEvent('input', true, true, { value: '' });
656656
elements.input.dispatchEvent(eventObj);
657657

658+
// For some reason, firing the above event resets the value of $scope.searchText if
659+
// $scope.searchText has a space character at the end, so we blank it one more time and then
660+
// focus.
661+
elements.input.blur();
662+
$scope.searchText = '';
658663
elements.input.focus();
659664
}
660665

src/components/autocomplete/js/autocompleteDirective.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ function MdAutocomplete () {
245245
ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
246246
ng-blur="$mdAutocompleteCtrl.blur()"\
247247
' + (attr.mdNoAsterisk != null ? 'md-no-asterisk="' + attr.mdNoAsterisk + '"' : '') + '\
248-
ng-focus="$mdAutocompleteCtrl.focus()"\
248+
ng-focus="$mdAutocompleteCtrl.focus($event)"\
249249
aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
250250
' + (attr.mdSelectOnFocus != null ? 'md-select-on-focus=""' : '') + '\
251251
aria-label="{{floatingLabel}}"\
@@ -270,7 +270,7 @@ function MdAutocomplete () {
270270
ng-model="$mdAutocompleteCtrl.scope.searchText"\
271271
ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
272272
ng-blur="$mdAutocompleteCtrl.blur()"\
273-
ng-focus="$mdAutocompleteCtrl.focus()"\
273+
ng-focus="$mdAutocompleteCtrl.focus($event)"\
274274
placeholder="{{placeholder}}"\
275275
aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
276276
' + (attr.mdSelectOnFocus != null ? 'md-select-on-focus=""' : '') + '\
@@ -284,7 +284,7 @@ function MdAutocomplete () {
284284
type="button"\
285285
tabindex="-1"\
286286
ng-if="$mdAutocompleteCtrl.scope.searchText && !$mdAutocompleteCtrl.isDisabled"\
287-
ng-click="$mdAutocompleteCtrl.clear()">\
287+
ng-click="$mdAutocompleteCtrl.clear($event)">\
288288
<md-icon md-svg-icon="md-close"></md-icon>\
289289
<span class="_md-visually-hidden">Clear</span>\
290290
</button>\

src/components/showHide/showHide.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,19 @@ function createDirective(name, targetValue) {
2222
var unregister = $scope.$on('$md-resize-enable', function() {
2323
unregister();
2424

25+
var cachedTransitionStyles = window.getComputedStyle($element[0]);
26+
2527
$scope.$watch($attr[name], function(value) {
2628
if (!!value === targetValue) {
2729
$mdUtil.nextTick(function() {
2830
$scope.$broadcast('$md-resize');
2931
});
30-
$mdUtil.dom.animator.waitTransitionEnd($element).then(function() {
32+
33+
var opts = {
34+
cachedTransitionStyles: cachedTransitionStyles
35+
};
36+
37+
$mdUtil.dom.animator.waitTransitionEnd($element, opts).then(function() {
3138
$scope.$broadcast('$md-resize');
3239
});
3340
}

src/components/virtualRepeat/virtual-repeater.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,10 @@ VirtualRepeatController.prototype.repeatListExpression_ = function(scope) {
612612
VirtualRepeatController.prototype.containerUpdated = function() {
613613
// If itemSize is unknown, attempt to measure it.
614614
if (!this.itemSize) {
615+
// Make sure to clean up watchers if we can (see #8178)
616+
if(this.unwatchItemSize_ && this.unwatchItemSize_ !== angular.noop){
617+
this.unwatchItemSize_();
618+
}
615619
this.unwatchItemSize_ = this.$scope.$watchCollection(
616620
this.repeatListExpression,
617621
angular.bind(this, function(items) {

0 commit comments

Comments
 (0)