Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

Commit

Permalink
feat(autocomplete): allow developers to specify amount of dropdown it…
Browse files Browse the repository at this point in the history
…ems.

* Adds an attribute, which allows developer to overwrite the amount of items shown in the autocomplete dropdown.
  - When the dropdown doesn't fit into the possible space it will shrink accordingly (as same as before)
  - When the specified amount of items is higher than the current matches then it will shrink (as same as before)

* Fixes the item height deviations between CSS and JavaScript.
* Removed the unnecessary CSS for the dropdown height, because everything will be handled from the JS (same as before)
* Added tests for the dropdown height calculation, which make sure that everything is now calculated properly.

Fixes #9306. Closes #8751.
  • Loading branch information
devversion committed Sep 4, 2016
1 parent a415550 commit b107ee8
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 13 deletions.
12 changes: 6 additions & 6 deletions src/components/autocomplete/autocomplete.scss
@@ -1,4 +1,5 @@
$autocomplete-option-height: 48px !default;
// The default item height is also specified in the JavaScript.
$md-autocomplete-item-height: 48px !default;

@keyframes md-autocomplete-list-out {
0% {
Expand Down Expand Up @@ -201,25 +202,24 @@ md-autocomplete {
.md-virtual-repeat-container.md-autocomplete-suggestions-container {
position: absolute;
box-shadow: 0 2px 5px rgba(black, 0.25);
height: 41px * 5.5;
max-height: 41px * 5.5;
z-index: $z-index-tooltip;
}

.md-virtual-repeat-container.md-not-found {
height: 48px;
height: $md-autocomplete-item-height;
}

.md-autocomplete-suggestions {
margin: 0;
list-style: none;
padding: 0;

li {
font-size: 14px;
overflow: hidden;
padding: 0 15px;
line-height: $autocomplete-option-height;
height: $autocomplete-option-height;
line-height: $md-autocomplete-item-height;
height: $md-autocomplete-item-height;
transition: background 0.15s linear;
margin: 0;
white-space: nowrap;
Expand Down
118 changes: 118 additions & 0 deletions src/components/autocomplete/autocomplete.spec.js
Expand Up @@ -1736,6 +1736,26 @@ describe('<md-autocomplete>', function() {

describe('dropdown position', function() {

var DEFAULT_MAX_ITEMS = 5;
var DEFAULT_ITEM_HEIGHT = 48;

var dropdownItems = DEFAULT_MAX_ITEMS;

/**
* Function to create fake matches with the given dropdown items.
* Useful when running tests against the dropdown max items calculations.
* @returns {Array} Fake matches.
*/
function fakeItemMatch() {
var matches = [];

for (var i = 0; i < dropdownItems; i++) {
matches.push('Item ' + i);
}

return matches;
}

it('should adjust the width when the window resizes', inject(function($timeout, $window) {
var scope = createScope();

Expand Down Expand Up @@ -1936,6 +1956,104 @@ describe('<md-autocomplete>', function() {
document.body.removeChild(parent[0]);
}));

it('should calculate the height from the default max items', inject(function($timeout) {
var scope = createScope();

scope.match = fakeItemMatch;

var template =
'<div>' +
'<md-autocomplete ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item" ' +
'md-min-length="0" ' +
'placeholder="placeholder">' +
'<span md-highlight-text="searchText">{{item}}</span>' +
'</md-autocomplete>' +
'</div>';

var parent = compile(template, scope);
var element = parent.find('md-autocomplete');
var ctrl = element.controller('mdAutocomplete');

// Add container to the DOM to be able to test the rect calculations.
document.body.appendChild(parent[0]);

$timeout.flush();

// Focus the autocomplete and trigger a query to be able to open the dropdown.
ctrl.focus();
scope.$apply('searchText = "Query 1"');
waitForVirtualRepeat(element);

var scrollContainer = document.body.querySelector('.md-virtual-repeat-container');

expect(scrollContainer).toBeTruthy();
expect(scrollContainer.style.maxHeight).toBe(DEFAULT_MAX_ITEMS * DEFAULT_ITEM_HEIGHT + 'px');

dropdownItems = 6;

// Trigger a new query to request an update of the items and dropdown.
scope.$apply('searchText = "Query 2"');

// The dropdown should not increase its height because of the new extra item.
expect(scrollContainer.style.maxHeight).toBe(DEFAULT_MAX_ITEMS * DEFAULT_ITEM_HEIGHT + 'px');

document.body.removeChild(parent[0]);
}));

it('should calculate its height from the specified max items', inject(function($timeout) {
var scope = createScope();
var maxDropdownItems = 2;

// Set the current dropdown items to the new maximum.
dropdownItems = maxDropdownItems;
scope.match = fakeItemMatch;

var template =
'<div>' +
'<md-autocomplete ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item" ' +
'md-min-length="0" ' +
'md-dropdown-items="' + maxDropdownItems +'"' +
'placeholder="placeholder">' +
'<span md-highlight-text="searchText">{{item}}</span>' +
'</md-autocomplete>' +
'</div>';

var parent = compile(template, scope);
var element = parent.find('md-autocomplete');
var ctrl = element.controller('mdAutocomplete');

// Add container to the DOM to be able to test the rect calculations.
document.body.appendChild(parent[0]);

$timeout.flush();

// Focus the autocomplete and trigger a query to be able to open the dropdown.
ctrl.focus();
scope.$apply('searchText = "Query 1"');
waitForVirtualRepeat(element);

var scrollContainer = document.body.querySelector('.md-virtual-repeat-container');

expect(scrollContainer).toBeTruthy();
expect(scrollContainer.style.maxHeight).toBe(maxDropdownItems * DEFAULT_ITEM_HEIGHT + 'px');

dropdownItems = 6;

// Trigger a new query to request an update of the items and dropdown.
scope.$apply('searchText = "Query 2"');

// The dropdown should not increase its height because of the new extra item.
expect(scrollContainer.style.maxHeight).toBe(maxDropdownItems * DEFAULT_ITEM_HEIGHT + 'px');

document.body.removeChild(parent[0]);
}));

});

describe('md-highlight-text', function() {
Expand Down
17 changes: 11 additions & 6 deletions src/components/autocomplete/js/autocompleteController.js
Expand Up @@ -2,8 +2,8 @@ angular
.module('material.components.autocomplete')
.controller('MdAutocompleteCtrl', MdAutocompleteCtrl);

var ITEM_HEIGHT = 41,
MAX_HEIGHT = 5.5 * ITEM_HEIGHT,
var ITEM_HEIGHT = 48,
MAX_ITEMS = 5,
MENU_PADDING = 8,
INPUT_PADDING = 2; // Padding provided by `md-input-container`

Expand Down Expand Up @@ -92,7 +92,12 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
* @returns {*}
*/
function positionDropdown () {
if (!elements) return $mdUtil.nextTick(positionDropdown, false, $scope);
if (!elements) {
return $mdUtil.nextTick(positionDropdown, false, $scope);
}

var dropdownHeight = ($scope.dropdownItems || MAX_ITEMS) * ITEM_HEIGHT;

var hrect = elements.wrap.getBoundingClientRect(),
vrect = elements.snap.getBoundingClientRect(),
root = elements.root.getBoundingClientRect(),
Expand All @@ -112,14 +117,14 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
minWidth: width + 'px',
maxWidth: Math.max(hrect.right - root.left, root.right - hrect.left) - MENU_PADDING + 'px'
};
if (top > bot && root.height - hrect.bottom - MENU_PADDING < MAX_HEIGHT) {
if (top > bot && root.height - hrect.bottom - MENU_PADDING < dropdownHeight) {
styles.top = 'auto';
styles.bottom = bot + 'px';
styles.maxHeight = Math.min(MAX_HEIGHT, hrect.top - root.top - MENU_PADDING) + 'px';
styles.maxHeight = Math.min(dropdownHeight, hrect.top - root.top - MENU_PADDING) + 'px';
} else {
styles.top = (top - offset) + 'px';
styles.bottom = 'auto';
styles.maxHeight = Math.min(MAX_HEIGHT, root.bottom + $mdUtil.scrollTop() - hrect.bottom - MENU_PADDING) + 'px';
styles.maxHeight = Math.min(dropdownHeight, root.bottom + $mdUtil.scrollTop() - hrect.bottom - MENU_PADDING) + 'px';
}

elements.$.scrollContainer.css(styles);
Expand Down
7 changes: 6 additions & 1 deletion src/components/autocomplete/js/autocompleteDirective.js
Expand Up @@ -82,6 +82,10 @@ angular
* will select on case-insensitive match
* @param {string=} md-escape-options Override escape key logic. Default is `blur clear`.<br/>
* Options: `blur | clear`, `none`
* @param {string=} md-dropdown-items Specifies the maximum amount of items to be shown in
* the dropdown.<br/><br/>
* When the dropdown doesn't fit into the viewport, the dropdown will shrink
* as less as possible.
*
* @usage
* ### Basic Example
Expand Down Expand Up @@ -165,7 +169,8 @@ function MdAutocomplete ($$mdSvgRegistry) {
autoselect: '=?mdAutoselect',
menuClass: '@?mdMenuClass',
inputId: '@?mdInputId',
escapeOptions: '@?mdEscapeOptions'
escapeOptions: '@?mdEscapeOptions',
dropdownItems: '=?mdDropdownItems'
},
link: function(scope, element, attrs, controller) {
// Retrieve the state of using a md-not-found template by using our attribute, which will
Expand Down

0 comments on commit b107ee8

Please sign in to comment.