Skip to content

Commit 70cecda

Browse files
devversionjelbourn
authored andcommitted
feat(autocomplete): option to toggle the clear button (angular#9892)
* Adds a new attribute, which allows developers to explicitly show the clear button for all types of autocomplete's. Closes angular#4841. Closes angular#2727
1 parent b3b8fab commit 70cecda

File tree

4 files changed

+139
-23
lines changed

4 files changed

+139
-23
lines changed

src/components/autocomplete/autocomplete.scss

+22-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// The default item height is also specified in the JavaScript.
22
$md-autocomplete-item-height: 48px !default;
3+
$md-autocomplete-clear-size: 30px !default;
4+
$md-autocomplete-input-offset: 20px !default;
35

46
md-autocomplete {
57
border-radius: 2px;
@@ -24,14 +26,24 @@ md-autocomplete {
2426
md-autocomplete-wrap {
2527
height: auto;
2628
}
27-
button {
28-
position: absolute;
29-
top: auto;
30-
bottom: 0;
31-
right: 0;
32-
width: 30px;
33-
height: 30px;
29+
30+
.md-show-clear-button {
31+
32+
button {
33+
display: block;
34+
position: absolute;
35+
right: 0;
36+
top: $md-autocomplete-input-offset;
37+
width: $md-autocomplete-clear-size;
38+
height: $md-autocomplete-clear-size;
39+
}
40+
41+
input {
42+
// Add padding to the end of the input to avoid overlapping with the clear button.
43+
@include rtl-prop(padding-right, padding-left, $md-autocomplete-clear-size, 0);
44+
}
3445
}
46+
3547
}
3648
md-autocomplete-wrap {
3749

@@ -99,12 +111,12 @@ md-autocomplete {
99111
line-height: 40px;
100112
height: 40px;
101113
}
102-
button {
114+
.md-show-clear-button button {
103115
position: relative;
104116
line-height: 20px;
105117
text-align: center;
106-
width: 30px;
107-
height: 30px;
118+
width: $md-autocomplete-clear-size;
119+
height: $md-autocomplete-clear-size;
108120
cursor: pointer;
109121
border: none;
110122
border-radius: 50%;

src/components/autocomplete/autocomplete.spec.js

+74
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,80 @@ describe('<md-autocomplete>', function() {
13371337

13381338
});
13391339

1340+
describe('clear button', function() {
1341+
1342+
it('should show the clear button for inset autocomplete', function() {
1343+
var scope = createScope();
1344+
1345+
var template =
1346+
'<md-autocomplete ' +
1347+
'md-selected-item="selectedItem" ' +
1348+
'md-search-text="searchText" ' +
1349+
'md-items="item in match(searchText)" ' +
1350+
'md-item-text="item.display" ' +
1351+
'placeholder="placeholder"> ' +
1352+
'<span md-highlight-text="searchText">{{item.display}}</span>' +
1353+
'</md-autocomplete>';
1354+
1355+
var element = compile(template, scope);
1356+
var ctrl = element.controller('mdAutocomplete');
1357+
var wrapEl = element.find('md-autocomplete-wrap');
1358+
1359+
expect(ctrl.scope.clearButton).toBe(true);
1360+
expect(wrapEl).toHaveClass('md-show-clear-button');
1361+
});
1362+
1363+
it('should not show the clear button for floating label autocomplete', function() {
1364+
var scope = createScope();
1365+
1366+
var template =
1367+
'<md-autocomplete ' +
1368+
'md-selected-item="selectedItem" ' +
1369+
'md-search-text="searchText" ' +
1370+
'md-items="item in match(searchText)" ' +
1371+
'md-item-text="item.display" ' +
1372+
'md-floating-label="Label"> ' +
1373+
'<span md-highlight-text="searchText">{{item.display}}</span>' +
1374+
'</md-autocomplete>';
1375+
1376+
var element = compile(template, scope);
1377+
var ctrl = element.controller('mdAutocomplete');
1378+
var wrapEl = element.find('md-autocomplete-wrap');
1379+
1380+
expect(ctrl.scope.clearButton).toBe(false);
1381+
expect(wrapEl).not.toHaveClass('md-show-clear-button');
1382+
});
1383+
1384+
it('should allow developers to toggle the clear button', function() {
1385+
1386+
var scope = createScope();
1387+
1388+
var template =
1389+
'<md-autocomplete ' +
1390+
'md-selected-item="selectedItem" ' +
1391+
'md-search-text="searchText" ' +
1392+
'md-items="item in match(searchText)" ' +
1393+
'md-item-text="item.display" ' +
1394+
'md-floating-label="Label" ' +
1395+
'md-clear-button="showButton">' +
1396+
'<span md-highlight-text="searchText">{{item.display}}</span>' +
1397+
'</md-autocomplete>';
1398+
1399+
var element = compile(template, scope);
1400+
var ctrl = element.controller('mdAutocomplete');
1401+
var wrapEl = element.find('md-autocomplete-wrap');
1402+
1403+
expect(ctrl.scope.clearButton).toBeFalsy();
1404+
expect(wrapEl).not.toHaveClass('md-show-clear-button');
1405+
1406+
scope.$apply('showButton = true');
1407+
1408+
expect(ctrl.scope.clearButton).toBe(true);
1409+
expect(wrapEl).toHaveClass('md-show-clear-button');
1410+
});
1411+
1412+
});
1413+
13401414
describe('xss prevention', function() {
13411415

13421416
it('should not allow html to slip through', inject(function($timeout, $material) {

src/components/autocomplete/js/autocompleteController.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,13 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
7373
* Initialize the controller, setup watchers, gather elements
7474
*/
7575
function init () {
76-
$mdUtil.initOptionalProperties($scope, $attrs, { searchText: '', selectedItem: null });
76+
77+
$mdUtil.initOptionalProperties($scope, $attrs, {
78+
searchText: '',
79+
selectedItem: null,
80+
clearButton: false
81+
});
82+
7783
$mdTheming($element);
7884
configureWatchers();
7985
$mdUtil.nextTick(function () {

src/components/autocomplete/js/autocompleteDirective.js

+36-12
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ angular
8787
* make suggestions
8888
* @param {number=} md-delay Specifies the amount of time (in milliseconds) to wait before looking
8989
* for results
90+
* @param {boolean=} md-clear-button Whether the clear button for the autocomplete input should show up or not.
9091
* @param {boolean=} md-autofocus If true, the autocomplete will be automatically focused when a `$mdDialog`,
9192
* `$mdBottomsheet` or `$mdSidenav`, which contains the autocomplete, is opening. <br/><br/>
9293
* Also the autocomplete will immediately focus the input element.
@@ -151,6 +152,17 @@ angular
151152
* In this example, our code utilizes `md-item-template` and `md-not-found` to specify the
152153
* different parts that make up our component.
153154
*
155+
* ### Clear button for the input
156+
* By default, for floating label autocomplete's the clear button is not showing up
157+
* ([See specs](https://material.google.com/components/text-fields.html#text-fields-auto-complete-text-field))
158+
*
159+
* Nevertheless, developers are able to explicitly toggle the clear button for all types of autocomplete's.
160+
*
161+
* <hljs lang="html">
162+
* <md-autocomplete ... md-clear-button="true"></md-autocomplete>
163+
* <md-autocomplete ... md-clear-button="false"></md-autocomplete>
164+
* </hljs>
165+
*
154166
* ### Example with validation
155167
* <hljs lang="html">
156168
* <form name="autocompleteForm">
@@ -232,7 +244,8 @@ function MdAutocomplete ($$mdSvgRegistry) {
232244
inputId: '@?mdInputId',
233245
escapeOptions: '@?mdEscapeOptions',
234246
dropdownItems: '=?mdDropdownItems',
235-
dropdownPosition: '@?mdDropdownPosition'
247+
dropdownPosition: '@?mdDropdownPosition',
248+
clearButton: '=?mdClearButton'
236249
},
237250
compile: function(tElement, tAttrs) {
238251
var attributes = ['md-select-on-focus', 'md-no-asterisk', 'ng-trim', 'ng-pattern'];
@@ -250,6 +263,11 @@ function MdAutocomplete ($$mdSvgRegistry) {
250263
// Retrieve the state of using a md-not-found template by using our attribute, which will
251264
// be added to the element in the template function.
252265
ctrl.hasNotFound = !!element.attr('md-has-not-found');
266+
267+
// By default the inset autocomplete should show the clear button when not explicitly overwritten.
268+
if (!angular.isDefined(attrs.mdClearButton) && !scope.floatingLabel) {
269+
scope.clearButton = true;
270+
}
253271
}
254272
},
255273
template: function (element, attr) {
@@ -269,8 +287,11 @@ function MdAutocomplete ($$mdSvgRegistry) {
269287

270288
return '\
271289
<md-autocomplete-wrap\
272-
ng-class="{ \'md-whiteframe-z1\': !floatingLabel, \'md-menu-showing\': !$mdAutocompleteCtrl.hidden }">\
290+
ng-class="{ \'md-whiteframe-z1\': !floatingLabel, \
291+
\'md-menu-showing\': !$mdAutocompleteCtrl.hidden, \
292+
\'md-show-clear-button\': !!clearButton }">\
273293
' + getInputElement() + '\
294+
' + getClearButton() + '\
274295
<md-progress-linear\
275296
class="' + (attr.mdFloatingLabel ? 'md-inline' : '') + '"\
276297
ng-if="$mdAutocompleteCtrl.loadingIsVisible()"\
@@ -366,18 +387,21 @@ function MdAutocomplete ($$mdSvgRegistry) {
366387
role="combobox"\
367388
aria-haspopup="true"\
368389
aria-activedescendant=""\
369-
aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>\
370-
<button\
371-
type="button"\
372-
tabindex="-1"\
373-
ng-if="$mdAutocompleteCtrl.scope.searchText && !$mdAutocompleteCtrl.isDisabled"\
374-
ng-click="$mdAutocompleteCtrl.clear($event)">\
375-
<md-icon md-svg-src="' + $$mdSvgRegistry.mdClose + '"></md-icon>\
376-
<span class="md-visually-hidden">Clear</span>\
377-
</button>\
378-
';
390+
aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>';
379391
}
380392
}
393+
394+
function getClearButton() {
395+
return '' +
396+
'<button ' +
397+
'type="button" ' +
398+
'aria-label="Clear Input" ' +
399+
'tabindex="-1" ' +
400+
'ng-if="clearButton && $mdAutocompleteCtrl.scope.searchText && !$mdAutocompleteCtrl.isDisabled" ' +
401+
'ng-click="$mdAutocompleteCtrl.clear($event)">' +
402+
'<md-icon md-svg-src="' + $$mdSvgRegistry.mdClose + '"></md-icon>' +
403+
'</button>';
404+
}
381405
}
382406
};
383407
}

0 commit comments

Comments
 (0)