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

Commit deae957

Browse files
author
Robert Messerle
committed
fix(autocomplete): locks scrolling while autocomplete menu is visible
Closes #2973
1 parent 33c3397 commit deae957

File tree

2 files changed

+77
-78
lines changed

2 files changed

+77
-78
lines changed

src/components/autocomplete/js/autocompleteController.js

+73-64
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ var ITEM_HEIGHT = 41,
77
MENU_PADDING = 8;
88

99
function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $mdTheming, $window,
10-
$animate, $rootElement) {
10+
$animate, $rootElement, $attrs) {
1111
//-- private variables
12-
var self = this,
12+
var ctrl = this,
1313
itemParts = $scope.itemsExpr.split(/ in /i),
1414
itemExpr = itemParts[1],
1515
elements = null,
@@ -24,28 +24,30 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
2424
defineProperty('hidden', handleHiddenChange, true);
2525

2626
//-- public variables
27-
self.scope = $scope;
28-
self.parent = $scope.$parent;
29-
self.itemName = itemParts[0];
30-
self.matches = [];
31-
self.loading = false;
32-
self.hidden = true;
33-
self.index = null;
34-
self.messages = [];
35-
self.id = $mdUtil.nextUid();
27+
ctrl.scope = $scope;
28+
ctrl.parent = $scope.$parent;
29+
ctrl.itemName = itemParts[0];
30+
ctrl.matches = [];
31+
ctrl.loading = false;
32+
ctrl.hidden = true;
33+
ctrl.index = null;
34+
ctrl.messages = [];
35+
ctrl.id = $mdUtil.nextUid();
36+
ctrl.isDisabled = null;
37+
ctrl.isRequired = null;
3638

3739
//-- public methods
38-
self.keydown = keydown;
39-
self.blur = blur;
40-
self.focus = focus;
41-
self.clear = clearValue;
42-
self.select = select;
43-
self.listEnter = onListEnter;
44-
self.listLeave = onListLeave;
45-
self.mouseUp = onMouseup;
46-
self.getCurrentDisplayValue = getCurrentDisplayValue;
47-
self.registerSelectedItemWatcher = registerSelectedItemWatcher;
48-
self.unregisterSelectedItemWatcher = unregisterSelectedItemWatcher;
40+
ctrl.keydown = keydown;
41+
ctrl.blur = blur;
42+
ctrl.focus = focus;
43+
ctrl.clear = clearValue;
44+
ctrl.select = select;
45+
ctrl.listEnter = onListEnter;
46+
ctrl.listLeave = onListLeave;
47+
ctrl.mouseUp = onMouseup;
48+
ctrl.getCurrentDisplayValue = getCurrentDisplayValue;
49+
ctrl.registerSelectedItemWatcher = registerSelectedItemWatcher;
50+
ctrl.unregisterSelectedItemWatcher = unregisterSelectedItemWatcher;
4951

5052
return init();
5153

@@ -55,6 +57,8 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
5557
* Initialize the controller, setup watchers, gather elements
5658
*/
5759
function init () {
60+
$mdUtil.initOptionalProperties($scope, $attrs, { searchText: null, selectedItem: null } );
61+
$mdTheming($element);
5862
configureWatchers();
5963
$timeout(function () {
6064
gatherElements();
@@ -129,9 +133,9 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
129133
*/
130134
function configureWatchers () {
131135
var wait = parseInt($scope.delay, 10) || 0;
132-
$scope.$watch('searchText', wait
133-
? $mdUtil.debounce(handleSearchText, wait)
134-
: handleSearchText);
136+
$attrs.$observe('disabled', function (value) { ctrl.isDisabled = value; });
137+
$attrs.$observe('required', function (value) { ctrl.isRequired = value !== null; });
138+
$scope.$watch('searchText', wait ? $mdUtil.debounce(handleSearchText, wait) : handleSearchText);
135139
registerSelectedItemWatcher(selectedItemChange);
136140
$scope.$watch('selectedItem', handleSelectedItemChange);
137141
angular.element($window).on('resize', positionDropdown);
@@ -195,6 +199,11 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
195199
*/
196200
function handleHiddenChange (hidden, oldHidden) {
197201
if (!hidden && oldHidden) positionDropdown();
202+
if (!hidden) {
203+
if (elements) $mdUtil.disableScrollAround(elements.ul);
204+
} else {
205+
$mdUtil.enableScrolling();
206+
}
198207
}
199208

200209
/**
@@ -209,7 +218,7 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
209218
*/
210219
function onListLeave () {
211220
noBlur = false;
212-
if (!hasFocus) self.hidden = true;
221+
if (!hasFocus) ctrl.hidden = true;
213222
}
214223

215224
/**
@@ -271,7 +280,7 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
271280
* @param previousSearchText
272281
*/
273282
function handleSearchText (searchText, previousSearchText) {
274-
self.index = getDefaultIndex();
283+
ctrl.index = getDefaultIndex();
275284
//-- do nothing on init
276285
if (searchText === previousSearchText) return;
277286
//-- clear selected item if search text no longer matches it
@@ -282,9 +291,9 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
282291
$scope.textChange(getItemScope($scope.selectedItem));
283292
//-- cancel results if search text is not long enough
284293
if (!isMinLengthMet()) {
285-
self.loading = false;
286-
self.matches = [];
287-
self.hidden = shouldHide();
294+
ctrl.loading = false;
295+
ctrl.matches = [];
296+
ctrl.hidden = shouldHide();
288297
updateMessages();
289298
} else {
290299
handleQuery();
@@ -296,7 +305,7 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
296305
*/
297306
function blur () {
298307
hasFocus = false;
299-
if (!noBlur) self.hidden = true;
308+
if (!noBlur) ctrl.hidden = true;
300309
}
301310

302311
/**
@@ -307,8 +316,8 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
307316
//-- if searchText is null, let's force it to be a string
308317
if (!angular.isString($scope.searchText)) $scope.searchText = '';
309318
if ($scope.minLength > 0) return;
310-
self.hidden = shouldHide();
311-
if (!self.hidden) handleQuery();
319+
ctrl.hidden = shouldHide();
320+
if (!ctrl.hidden) handleQuery();
312321
}
313322

314323
/**
@@ -318,29 +327,29 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
318327
function keydown (event) {
319328
switch (event.keyCode) {
320329
case $mdConstant.KEY_CODE.DOWN_ARROW:
321-
if (self.loading) return;
330+
if (ctrl.loading) return;
322331
event.preventDefault();
323-
self.index = Math.min(self.index + 1, self.matches.length - 1);
332+
ctrl.index = Math.min(ctrl.index + 1, ctrl.matches.length - 1);
324333
updateScroll();
325334
updateMessages();
326335
break;
327336
case $mdConstant.KEY_CODE.UP_ARROW:
328-
if (self.loading) return;
337+
if (ctrl.loading) return;
329338
event.preventDefault();
330-
self.index = self.index < 0 ? self.matches.length - 1 : Math.max(0, self.index - 1);
339+
ctrl.index = ctrl.index < 0 ? ctrl.matches.length - 1 : Math.max(0, ctrl.index - 1);
331340
updateScroll();
332341
updateMessages();
333342
break;
334343
case $mdConstant.KEY_CODE.TAB:
335344
case $mdConstant.KEY_CODE.ENTER:
336-
if (self.hidden || self.loading || self.index < 0 || self.matches.length < 1) return;
345+
if (ctrl.hidden || ctrl.loading || ctrl.index < 0 || ctrl.matches.length < 1) return;
337346
event.preventDefault();
338-
select(self.index);
347+
select(ctrl.index);
339348
break;
340349
case $mdConstant.KEY_CODE.ESCAPE:
341-
self.matches = [];
342-
self.hidden = true;
343-
self.index = getDefaultIndex();
350+
ctrl.matches = [];
351+
ctrl.hidden = true;
352+
ctrl.index = getDefaultIndex();
344353
break;
345354
default:
346355
}
@@ -373,7 +382,7 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
373382
function getItemScope (item) {
374383
if (!item) return;
375384
var locals = {};
376-
if (self.itemName) locals[self.itemName] = item;
385+
if (ctrl.itemName) locals[ctrl.itemName] = item;
377386
return locals;
378387
}
379388

@@ -398,7 +407,7 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
398407
* @returns {*}
399408
*/
400409
function getCurrentDisplayValue () {
401-
return getDisplayValue(self.matches[self.index]);
410+
return getDisplayValue(ctrl.matches[ctrl.index]);
402411
}
403412

404413
/**
@@ -418,7 +427,7 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
418427
* @param value
419428
*/
420429
function defineProperty (key, handler, value) {
421-
Object.defineProperty(self, key, {
430+
Object.defineProperty(ctrl, key, {
422431
get: function () { return value; },
423432
set: function (newValue) {
424433
var oldValue = value;
@@ -433,15 +442,15 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
433442
* @param index
434443
*/
435444
function select (index) {
436-
$scope.selectedItem = self.matches[index];
437-
self.hidden = true;
438-
self.index = 0;
439-
self.matches = [];
445+
$scope.selectedItem = ctrl.matches[index];
446+
ctrl.hidden = true;
447+
ctrl.index = 0;
448+
ctrl.matches = [];
440449
//-- force form to update state for validation
441450
$timeout(function () {
442451
elements.$.input.controller('ngModel').$setViewValue(getDisplayValue($scope.selectedItem) ||
443452
$scope.searchText);
444-
self.hidden = true;
453+
ctrl.hidden = true;
445454
});
446455
}
447456

@@ -470,18 +479,18 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
470479
if (angular.isArray(items)) {
471480
handleResults(items);
472481
} else {
473-
self.loading = true;
482+
ctrl.loading = true;
474483
if (items.success) items.success(handleResults);
475484
if (items.then) items.then(handleResults);
476-
if (items.error) items.error(function () { self.loading = false; });
485+
if (items.error) items.error(function () { ctrl.loading = false; });
477486
}
478487
function handleResults (matches) {
479488
cache[term] = matches;
480489
if (searchText !== $scope.searchText) return; //-- just cache the results if old request
481-
self.loading = false;
490+
ctrl.loading = false;
482491
promise = null;
483-
self.matches = matches;
484-
self.hidden = shouldHide();
492+
ctrl.matches = matches;
493+
ctrl.hidden = shouldHide();
485494
updateMessages();
486495
positionDropdown();
487496
}
@@ -491,29 +500,29 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
491500
* Updates the ARIA messages
492501
*/
493502
function updateMessages () {
494-
self.messages = [ getCountMessage(), getCurrentDisplayValue() ];
503+
ctrl.messages = [ getCountMessage(), getCurrentDisplayValue() ];
495504
}
496505

497506
/**
498507
* Returns the ARIA message for how many results match the current query.
499508
* @returns {*}
500509
*/
501510
function getCountMessage () {
502-
if (lastCount === self.matches.length) return '';
503-
lastCount = self.matches.length;
504-
switch (self.matches.length) {
511+
if (lastCount === ctrl.matches.length) return '';
512+
lastCount = ctrl.matches.length;
513+
switch (ctrl.matches.length) {
505514
case 0: return 'There are no matches available.';
506515
case 1: return 'There is 1 match available.';
507-
default: return 'There are ' + self.matches.length + ' matches available.';
516+
default: return 'There are ' + ctrl.matches.length + ' matches available.';
508517
}
509518
}
510519

511520
/**
512521
* Makes sure that the focused element is within view.
513522
*/
514523
function updateScroll () {
515-
if (!elements.li[self.index]) return;
516-
var li = elements.li[self.index],
524+
if (!elements.li[ctrl.index]) return;
525+
var li = elements.li[ctrl.index],
517526
top = li.offsetTop,
518527
bot = top + li.offsetHeight,
519528
hgt = elements.ul.clientHeight;
@@ -538,12 +547,12 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $
538547
}
539548
//-- if results are cached, pull in cached results
540549
if (!$scope.noCache && cache[term]) {
541-
self.matches = cache[term];
550+
ctrl.matches = cache[term];
542551
updateMessages();
543552
} else {
544553
fetchResults(searchText);
545554
}
546-
if (hasFocus) self.hidden = shouldHide();
555+
if (hasFocus) ctrl.hidden = shouldHide();
547556
}
548557

549558
}

src/components/autocomplete/js/autocompleteDirective.js

+4-14
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ function MdAutocomplete ($mdTheming, $mdUtil) {
106106
return {
107107
controller: 'MdAutocompleteCtrl',
108108
controllerAs: '$mdAutocompleteCtrl',
109-
link: link,
110109
scope: {
111110
inputName: '@mdInputName',
112111
inputMinlength: '@mdInputMinlength',
@@ -142,12 +141,12 @@ function MdAutocomplete ($mdTheming, $mdUtil) {
142141
<ul role="presentation"\
143142
class="md-autocomplete-suggestions md-whiteframe-z1 {{menuClass || \'\'}}"\
144143
id="ul-{{$mdAutocompleteCtrl.id}}"\
144+
ng-hide="$mdAutocompleteCtrl.hidden"\
145145
ng-mouseenter="$mdAutocompleteCtrl.listEnter()"\
146146
ng-mouseleave="$mdAutocompleteCtrl.listLeave()"\
147147
ng-mouseup="$mdAutocompleteCtrl.mouseUp()">\
148148
<li ng-repeat="(index, item) in $mdAutocompleteCtrl.matches"\
149149
ng-class="{ selected: index === $mdAutocompleteCtrl.index }"\
150-
ng-hide="$mdAutocompleteCtrl.hidden"\
151150
ng-click="$mdAutocompleteCtrl.select(index)"\
152151
md-autocomplete-list-item="$mdAutocompleteCtrl.itemName">\
153152
' + itemTemplate + '\
@@ -193,7 +192,7 @@ function MdAutocomplete ($mdTheming, $mdUtil) {
193192
ng-required="isRequired"\
194193
ng-minlength="inputMinlength"\
195194
ng-maxlength="inputMaxlength"\
196-
ng-disabled="isDisabled"\
195+
ng-disabled="$mdAutocompleteCtrl.isDisabled"\
197196
ng-model="$mdAutocompleteCtrl.scope.searchText"\
198197
ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
199198
ng-blur="$mdAutocompleteCtrl.blur()"\
@@ -214,7 +213,7 @@ function MdAutocomplete ($mdTheming, $mdUtil) {
214213
ng-if="!floatingLabel"\
215214
autocomplete="off"\
216215
ng-required="isRequired"\
217-
ng-disabled="isDisabled"\
216+
ng-disabled="$mdAutocompleteCtrl.isDisabled"\
218217
ng-model="$mdAutocompleteCtrl.scope.searchText"\
219218
ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
220219
ng-blur="$mdAutocompleteCtrl.blur()"\
@@ -229,7 +228,7 @@ function MdAutocomplete ($mdTheming, $mdUtil) {
229228
<button\
230229
type="button"\
231230
tabindex="-1"\
232-
ng-if="$mdAutocompleteCtrl.scope.searchText && !isDisabled"\
231+
ng-if="$mdAutocompleteCtrl.scope.searchText && !$mdAutocompleteCtrl.isDisabled"\
233232
ng-click="$mdAutocompleteCtrl.clear()">\
234233
<md-icon md-svg-icon="md-close"></md-icon>\
235234
<span class="md-visually-hidden">Clear</span>\
@@ -239,13 +238,4 @@ function MdAutocomplete ($mdTheming, $mdUtil) {
239238
}
240239
}
241240
};
242-
243-
function link (scope, element, attr) {
244-
attr.$observe('disabled', function (value) { scope.isDisabled = value; });
245-
attr.$observe('required', function (value) { scope.isRequired = value !== null; });
246-
247-
$mdUtil.initOptionalProperties(scope, attr, {searchText:null, selectedItem:null} );
248-
249-
$mdTheming(element);
250-
}
251241
}

0 commit comments

Comments
 (0)