diff --git a/src/components/autocomplete/autocomplete.spec.js b/src/components/autocomplete/autocomplete.spec.js index f6863babc0f..bea6aa38fe4 100644 --- a/src/components/autocomplete/autocomplete.spec.js +++ b/src/components/autocomplete/autocomplete.spec.js @@ -1248,6 +1248,51 @@ describe('', function() { })); }); + describe('when requiring a match', function() { + + it('should correctly update the validity', inject(function($timeout) { + var scope = createScope(); + var template = '\ +
\ + \ + {{item.display}}\ + \ + '; + var element = compile(template, scope); + var ctrl = element.find('md-autocomplete').controller('mdAutocomplete'); + + element.scope().searchText = 'fo'; + $timeout.flush(); + + ctrl.select(0); + $timeout.flush(); + + expect(scope.searchText).toBe('foo'); + expect(scope.selectedItem).not.toBeNull(); + expect(scope.selectedItem.display).toBe('foo'); + expect(scope.match(scope.searchText).length).toBe(1); + + expect(scope.form.autocomplete.$error['md-require-match']).toBeFalsy(); + + ctrl.clear(); + + scope.$apply(); + + expect(scope.searchText).toBe(''); + expect(scope.selectedItem).toBe(null); + expect(scope.form.autocomplete.$error['md-require-match']).toBeTruthy(); + + })); + + }); + describe('when required', function() { it('properly handles md-min-length="0" and undefined searchText', function() { var scope = createScope(); diff --git a/src/components/autocomplete/demoFloatingLabel/index.html b/src/components/autocomplete/demoFloatingLabel/index.html index ac51f2104c3..84872098bed 100644 --- a/src/components/autocomplete/demoFloatingLabel/index.html +++ b/src/components/autocomplete/demoFloatingLabel/index.html @@ -16,12 +16,14 @@ md-search-text="ctrl.searchText" md-items="item in ctrl.querySearch(ctrl.searchText)" md-item-text="item.display" + md-require-match="" md-floating-label="Favorite state"> {{item.display}}
You must have a favorite state.
+
Please select an existing state.
Your entry is not long enough.
Your entry is too long.
diff --git a/src/components/autocomplete/js/autocompleteController.js b/src/components/autocomplete/js/autocompleteController.js index d7ada42c3cb..eacdf8b79ab 100644 --- a/src/components/autocomplete/js/autocompleteController.js +++ b/src/components/autocomplete/js/autocompleteController.js @@ -20,7 +20,8 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, hasFocus = false, lastCount = 0, fetchesInProgress = 0, - enableWrapScroll = null; + enableWrapScroll = null, + inputModelCtrl = null; //-- public variables with handlers defineProperty('hidden', handleHiddenChange, true); @@ -74,6 +75,12 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, }); } + function updateModelValidators() { + if (!$scope.requireMatch || !inputModelCtrl) return; + + inputModelCtrl.$setValidity('md-require-match', !!$scope.selectedItem); + } + /** * Calculates the dropdown's position and applies the new styles to the menu element * @returns {*} @@ -205,9 +212,12 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, wrap: $element.find('md-autocomplete-wrap')[0], root: document.body }; + elements.li = elements.ul.getElementsByTagName('li'); elements.snap = getSnapTarget(); elements.$ = getAngularElements(elements); + + inputModelCtrl = elements.$.input.controller('ngModel'); } /** @@ -310,6 +320,9 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, * @param previousSelectedItem */ function selectedItemChange (selectedItem, previousSelectedItem) { + + updateModelValidators(); + if (selectedItem) { getDisplayValue(selectedItem).then(function (val) { $scope.searchText = val; @@ -372,9 +385,12 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming, */ function handleSearchText (searchText, previousSearchText) { ctrl.index = getDefaultIndex(); + // do nothing on init if (searchText === previousSearchText) return; + updateModelValidators(); + getDisplayValue($scope.selectedItem).then(function (val) { // clear selected item if search text no longer matches it if (searchText !== val) { diff --git a/src/components/autocomplete/js/autocompleteDirective.js b/src/components/autocomplete/js/autocompleteDirective.js index 16192241b52..fa15a6cd069 100644 --- a/src/components/autocomplete/js/autocompleteDirective.js +++ b/src/components/autocomplete/js/autocompleteDirective.js @@ -45,6 +45,8 @@ angular * @param {string=} placeholder Placeholder text that will be forwarded to the input. * @param {boolean=} md-no-cache Disables the internal caching that happens in autocomplete * @param {boolean=} ng-disabled Determines whether or not to disable the input field + * @param {boolean=} md-require-match When set to true, the autocomplete will add a validator, + * which will evaluate to false, when no item is currently selected. * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will * make suggestions * @param {number=} md-delay Specifies the amount of time (in milliseconds) to wait before looking @@ -143,6 +145,7 @@ function MdAutocomplete ($$mdSvgRegistry) { itemText: '&mdItemText', placeholder: '@placeholder', noCache: '=?mdNoCache', + requireMatch: '=?mdRequireMatch', selectOnMatch: '=?mdSelectOnMatch', matchInsensitive: '=?mdMatchCaseInsensitive', itemChange: '&?mdSelectedItemChange',