From 647cdd931c1eecb8c0de6e260363ae76c8f542e6 Mon Sep 17 00:00:00 2001 From: Jeff Treuting Date: Mon, 6 Oct 2014 16:12:16 -0700 Subject: [PATCH] feat(typeahead): add `noResults` indicator binding - Add ability to configure no results message Closes #2016 Closes #2792 Closes #4068 --- src/typeahead/docs/demo.html | 5 +++- src/typeahead/docs/readme.md | 6 ++++- src/typeahead/test/typeahead.spec.js | 34 ++++++++++++++++++++++++++++ src/typeahead/typeahead.js | 17 ++++++++++---- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/typeahead/docs/demo.html b/src/typeahead/docs/demo.html index b0d4b1967d..3808bc854d 100644 --- a/src/typeahead/docs/demo.html +++ b/src/typeahead/docs/demo.html @@ -12,8 +12,11 @@

Static arrays

Asynchronous results

Model: {{asyncSelected | json}}
- + +
+ No Results Found +

Custom templates for results

Model: {{customSelected | json}}
diff --git a/src/typeahead/docs/readme.md b/src/typeahead/docs/readme.md index ccbff4d140..8415064b1b 100644 --- a/src/typeahead/docs/readme.md +++ b/src/typeahead/docs/readme.md @@ -43,7 +43,11 @@ The typeahead directives provide several attributes: * `typeahead-min-length` _(Defaults: 1)_ : Minimal no of characters that needs to be entered before typeahead kicks-in - + +* `typeahead-no-results` + _(Defaults: angular.noop)_ : + Binding to a variable that indicates if no matching results were found + * `typeahead-on-select($item, $model, $label)` _(Defaults: null)_ : A callback executed when a match is selected diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index a8b38f6492..e8e4411ece 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -455,6 +455,40 @@ describe('typeahead tests', function () { expect($scope.result).toEqual('AL'); expect(inputEl.val()).toEqual('AL'); }); + + it('should bind no results indicator as true when no matches returned', inject(function ($timeout) { + + $scope.isNoResults = false; + $scope.loadMatches = function (viewValue) { + return $timeout(function () { + return []; + }, 1000); + }; + + var element = prepareInputEl('
'); + changeInputValueTo(element, 'foo'); + + expect($scope.isNoResults).toBeFalsy(); + $timeout.flush(); + expect($scope.isNoResults).toBeTruthy(); + })); + + it('should bind no results indicator as false when matches are returned', inject(function ($timeout) { + + $scope.isNoResults = false; + $scope.loadMatches = function (viewValue) { + return $timeout(function () { + return [viewValue]; + }, 1000); + }; + + var element = prepareInputEl('
'); + changeInputValueTo(element, 'foo'); + + expect($scope.isNoResults).toBeFalsy(); + $timeout.flush(); + expect($scope.isNoResults).toBeFalsy(); + })); }); describe('select on exact match', function(){ diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index f719c79a06..bc6c57146d 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -62,12 +62,15 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap //should it select highlighted popup value when losing focus? var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false; + //binding to a variable that indicates if there were no results after the query is completed + var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop; + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false; - + //If input matches an item of the list exactly, select it automatically var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false; @@ -136,13 +139,13 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap element.attr('aria-activedescendant', getMatchId(index)); } }); - + var inputIsExactMatch = function(inputValue, index) { - + if (scope.matches.length > index && inputValue) { return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase(); } - + return false; }; @@ -150,6 +153,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap var locals = {$viewValue: inputValue}; isLoadingSetter(originalScope, true); + isNoResultsSetter(originalScope, false); $q.when(parserResult.source(originalScope, locals)).then(function(matches) { //it might happen that several async queries were in progress if a user were typing fast @@ -159,6 +163,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap if (matches && matches.length > 0) { scope.activeIdx = focusFirst ? 0 : -1; + isNoResultsSetter(originalScope, false); scope.matches.length = 0; //transform labels @@ -178,13 +183,14 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap recalculatePosition(); element.attr('aria-expanded', true); - + //Select the single remaining option if user input matches if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { scope.select(0); } } else { resetMatches(); + isNoResultsSetter(originalScope, true); } } if (onCurrentRequest) { @@ -193,6 +199,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap }, function(){ resetMatches(); isLoadingSetter(originalScope, false); + isNoResultsSetter(originalScope, true); }); };