Skip to content
This repository has been archived by the owner on Nov 22, 2021. It is now read-only.

Commit

Permalink
fix(autocomplete): Prevent pending promises from executing
Browse files Browse the repository at this point in the history
Prevent pending promises from executing and showing the suggestion
list after the user has already added a tag or cancelled autocomplete.

Closes #36.
  • Loading branch information
mbenford committed Dec 15, 2013
1 parent 6e4f7c7 commit 710d33a
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 7 deletions.
14 changes: 12 additions & 2 deletions build/ng-tags-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ tagsInput.directive('tagsInput', ["$timeout","$document","tagsInputConfig", func
*/
tagsInput.directive('autoComplete', ["$document","$timeout","$sce","tagsInputConfig", function($document, $timeout, $sce, tagsInputConfig) {
function SuggestionList(loadFn, options) {
var self = {}, debouncedLoadId, getDifference;
var self = {}, debouncedLoadId, getDifference, lastPromise;

getDifference = function(array1, array2) {
var result = [];
Expand All @@ -283,6 +283,8 @@ tagsInput.directive('autoComplete', ["$document","$timeout","$sce","tagsInputCon
};

self.reset = function() {
lastPromise = null;

self.items = [];
self.visible = false;
self.index = -1;
Expand All @@ -307,7 +309,15 @@ tagsInput.directive('autoComplete', ["$document","$timeout","$sce","tagsInputCon
$timeout.cancel(debouncedLoadId);
debouncedLoadId = $timeout(function() {
self.query = query;
loadFn({ $query: query }).then(function(items) {

var promise = loadFn({ $query: query });
lastPromise = promise;

promise.then(function(items) {
if (promise !== lastPromise) {
return;
}

self.items = getDifference(items, tags);
if (self.items.length > 0) {
self.show();
Expand Down
Binary file modified build/ng-tags-input.min.zip
Binary file not shown.
Binary file modified build/ng-tags-input.zip
Binary file not shown.
14 changes: 12 additions & 2 deletions src/auto-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*/
tagsInput.directive('autoComplete', function($document, $timeout, $sce, tagsInputConfig) {
function SuggestionList(loadFn, options) {
var self = {}, debouncedLoadId, getDifference;
var self = {}, debouncedLoadId, getDifference, lastPromise;

getDifference = function(array1, array2) {
var result = [];
Expand All @@ -35,6 +35,8 @@ tagsInput.directive('autoComplete', function($document, $timeout, $sce, tagsInpu
};

self.reset = function() {
lastPromise = null;

self.items = [];
self.visible = false;
self.index = -1;
Expand All @@ -59,7 +61,15 @@ tagsInput.directive('autoComplete', function($document, $timeout, $sce, tagsInpu
$timeout.cancel(debouncedLoadId);
debouncedLoadId = $timeout(function() {
self.query = query;
loadFn({ $query: query }).then(function(items) {

var promise = loadFn({ $query: query });
lastPromise = promise;

promise.then(function(items) {
if (promise !== lastPromise) {
return;
}

self.items = getDifference(items, tags);
if (self.items.length > 0) {
self.show();
Expand Down
48 changes: 47 additions & 1 deletion test/auto-complete.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,53 @@ describe('autocomplete-directive', function() {
// Assert
expect(suggestionList.selected).toBeNull();
});

it('discards all load calls but the last one', function() {
// Arrange
var deferred1 = $q.defer(), deferred2 = $q.defer(), deferred3 = $q.defer();
var promises = [deferred1.promise, deferred2.promise, deferred3.promise];

$scope.loadItems = jasmine.createSpy().andCallFake(function() {
return promises.shift();
});
spyOn(suggestionList, 'show');

// Act
// First we need to register all promises
suggestionList.load('foobar', tagsInput.getTags());
$timeout.flush();

suggestionList.load('foobar', tagsInput.getTags());
$timeout.flush();

suggestionList.load('foobar', tagsInput.getTags());
$timeout.flush();

// Now we resolve each promise which was previously created
deferred1.resolve(['Item1']);
deferred2.resolve(['Item2']);
deferred3.resolve(['Item3']);

$scope.$digest();

// Assert
expect(suggestionList.show.calls.length).toBe(1);
});

it('discards all load calls after the suggestion list is reset', function() {
// Arrange
spyOn(suggestionList, 'show');
suggestionList.load('foobar', tagsInput.getTags());
$timeout.flush();

// Act
suggestionList.reset();

resolve(['Item3']);

// Assert
expect(suggestionList.show).not.toHaveBeenCalled();
});
});

describe('navigation through suggestions', function() {
Expand Down Expand Up @@ -555,7 +602,6 @@ describe('autocomplete-directive', function() {

// Assert
expect($scope.loadItems).not.toHaveBeenCalled();

});
});

Expand Down
14 changes: 12 additions & 2 deletions test/test-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,23 @@

<script type="text/javascript">
angular.module('app', ['ngTagsInput'])
.controller('Ctrl', function($scope, $q) {
.controller('Ctrl', function($scope, $q, $timeout) {
var superheroes = ['Batman <[email protected]>', 'Superman', 'Flash', 'Iron Man', 'Hulk', 'Wolverine', "Green Lantern", "Green Arrow", "Spiderman"];

$scope.tags = ['Batman', 'Superman', 'Flash'];
$scope.placeholder = {value: "New tag" };
$scope.loadItems = function(query) {
console.log(query);
var deferred = $q.defer();
deferred.resolve(['Batman <[email protected]>', 'Superman', 'Flash', 'Iron Man', 'Hulk', 'Wolverine', "Green Lantern", "Green Arrow", "Spiderman"]);
var items = [];
for (var i = 0; i < superheroes.length; i++) {
if (superheroes[i].indexOf(query) > -1) {
items.push(superheroes[i]);
}
}
$timeout(function() {
deferred.resolve(items);
}, 2000);
return deferred.promise;
};
});
Expand Down

0 comments on commit 710d33a

Please sign in to comment.