From d16d2b6647fa52e46723405fd17c4b8dd7afaa8d Mon Sep 17 00:00:00 2001 From: Robert Messerle Date: Mon, 20 Jul 2015 10:17:06 -0700 Subject: [PATCH] feat(autocomplete): adds a new attribute option `md-select-on-match` When enabled, an exact match in the `searchText` will automatically select a matching item if (a) there is exactly one match found and (b) the `searchText` matches the display value exactly Closes #3324. Closes #3825. --- .../autocomplete/autocomplete.spec.js | 76 +++++++++++++------ .../autocomplete/js/autocompleteController.js | 15 ++++ .../autocomplete/js/autocompleteDirective.js | 3 + 3 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/components/autocomplete/autocomplete.spec.js b/src/components/autocomplete/autocomplete.spec.js index a37fe9a4e04..52b547c8264 100644 --- a/src/components/autocomplete/autocomplete.spec.js +++ b/src/components/autocomplete/autocomplete.spec.js @@ -28,7 +28,7 @@ describe('', function () { } describe('basic functionality', function () { - it('should update selected item and search text', inject(function ($timeout, $mdConstant, $rootElement) { + it('should update selected item and search text', inject(function ($timeout, $mdConstant) { var scope = createScope(); var template = '\ ', function () { expect(scope.selectedItem).toBe(null); element.scope().searchText = 'fo'; - ctrl.keydown({}); - element.scope().$apply(); $timeout.flush(); expect(scope.searchText).toBe('fo'); @@ -65,7 +63,6 @@ describe('', function () { preventDefault: angular.noop, stopPropagation: angular.noop }); - scope.$apply(); $timeout.flush(); expect(scope.searchText).toBe('foo'); @@ -95,8 +92,6 @@ describe('', function () { expect(scope.selectedItem).toBe(null); element.scope().searchText = 'fo'; - ctrl.keydown({}); - element.scope().$apply(); $timeout.flush(); expect(scope.searchText).toBe('fo'); @@ -113,7 +108,6 @@ describe('', function () { preventDefault: angular.noop, stopPropagation: angular.noop }); - scope.$apply(); $timeout.flush(); expect(scope.searchText).toBe('foo'); @@ -136,7 +130,6 @@ describe('', function () { {{item.display}}\ '; var element = compile(template, scope); - var ctrl = element.controller('mdAutocomplete'); var ul = element.find('ul'); expect(scope.searchText).toBe(''); @@ -152,7 +145,7 @@ describe('', function () { }); describe('API access', function () { - it('should clear the selected item', inject(function ($timeout, $mdConstant) { + it('should clear the selected item', inject(function ($timeout) { var scope = createScope(); var template = '\ ', function () { var ctrl = element.controller('mdAutocomplete'); element.scope().searchText = 'fo'; - ctrl.keydown({}); - element.scope().$apply(); $timeout.flush(); ctrl.select(0); - element.scope().$apply(); $timeout.flush(); expect(scope.searchText).toBe('foo'); @@ -187,10 +177,9 @@ describe('', function () { expect(scope.selectedItem).toBe(null); })); - it('should notify selected item watchers', inject(function ($timeout, $mdConstant) { - var scope = createScope(); - var scopeItemChanged = 1; - scope.itemChanged = jasmine.createSpy('itemChanged'); + it('should notify selected item watchers', inject(function ($timeout) { + var scope = createScope(); + scope.itemChanged = jasmine.createSpy('itemChanged'); var registeredWatcher = jasmine.createSpy('registeredWatcher'); @@ -210,12 +199,9 @@ describe('', function () { ctrl.registerSelectedItemWatcher(registeredWatcher); element.scope().searchText = 'fo'; - ctrl.keydown({}); - element.scope().$apply(); $timeout.flush(); ctrl.select(0); - element.scope().$apply(); $timeout.flush(); expect(scope.itemChanged).toHaveBeenCalled(); @@ -237,7 +223,7 @@ describe('', function () { expect(scope.itemChanged.calls.mostRecent().args[ 0 ]).toBeNull(); expect(scope.selectedItem).toBeNull(); })); - it('should pass value to item watcher', inject(function ($timeout, $mdConstant) { + it('should pass value to item watcher', inject(function ($timeout) { var scope = createScope(); var itemValue = null; var template = '\ @@ -257,12 +243,9 @@ describe('', function () { var ctrl = element.controller('mdAutocomplete'); element.scope().searchText = 'fo'; - ctrl.keydown({}); - element.scope().$apply(); $timeout.flush(); ctrl.select(0); - element.scope().$apply(); $timeout.flush(); expect(itemValue).not.toBeNull(); @@ -272,4 +255,51 @@ describe('', function () { element.scope().$apply(); })); }); + + describe('md-select-on-match', function () { + it('should select matching item on exact match when `md-select-on-match` is toggled', inject(function ($timeout) { + var scope = createScope(); + var template = '\ + \ + {{item.display}}\ + '; + var element = compile(template, scope); + + expect(scope.searchText).toBe(''); + expect(scope.selectedItem).toBe(null); + + element.scope().searchText = 'foo'; + $timeout.flush(); + + expect(scope.selectedItem).not.toBe(null); + expect(scope.selectedItem.display).toBe('foo'); + })); + it('should not select matching item on exact match when `md-select-on-match` is NOT toggled', inject(function ($timeout) { + var scope = createScope(); + var template = '\ + \ + {{item.display}}\ + '; + var element = compile(template, scope); + + expect(scope.searchText).toBe(''); + expect(scope.selectedItem).toBe(null); + + element.scope().searchText = 'foo'; + $timeout.flush(); + + expect(scope.selectedItem).toBe(null); + })); + }); }); diff --git a/src/components/autocomplete/js/autocompleteController.js b/src/components/autocomplete/js/autocompleteController.js index d077b7dfdce..cb95d1dd6d5 100644 --- a/src/components/autocomplete/js/autocompleteController.js +++ b/src/components/autocomplete/js/autocompleteController.js @@ -529,6 +529,7 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, if (searchText !== $scope.searchText) return; //-- just cache the results if old request ctrl.matches = matches; ctrl.hidden = shouldHide(); + if ($scope.selectOnMatch) selectItemOnMatch(); updateMessages(); positionDropdown(); } @@ -593,4 +594,18 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, if (hasFocus) ctrl.hidden = shouldHide(); } + /** + * If there is only one matching item and the search text matches its display value exactly, + * automatically select that item. Note: This function is only called if the user uses the + * `md-select-on-match` flag. + */ + function selectItemOnMatch () { + var searchText = $scope.searchText, + matches = ctrl.matches, + item = matches[ 0 ]; + if (matches.length === 1) getDisplayValue(item).then(function (displayValue) { + if (searchText == displayValue) select(0); + }); + } + } diff --git a/src/components/autocomplete/js/autocompleteDirective.js b/src/components/autocomplete/js/autocompleteDirective.js index 387b42fb050..5dd6db3dfa6 100644 --- a/src/components/autocomplete/js/autocompleteDirective.js +++ b/src/components/autocomplete/js/autocompleteDirective.js @@ -58,6 +58,8 @@ angular * FormController * @param {number=} md-input-minlength The minimum length for the input's value for validation * @param {number=} md-input-maxlength The maximum length for the input's value for validation + * @param {boolean=} md-select-on-match When set, autocomplete will automatically select exact + * the item if the search text is an exact match * * @usage * ###Basic Example @@ -128,6 +130,7 @@ function MdAutocomplete () { itemText: '&mdItemText', placeholder: '@placeholder', noCache: '=?mdNoCache', + selectOnMatch: '=?mdSelectOnMatch', itemChange: '&?mdSelectedItemChange', textChange: '&?mdSearchTextChange', minLength: '=?mdMinLength',