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

Commit

Permalink
feat(autocomplete): Implemented min-length option
Browse files Browse the repository at this point in the history
Added a minimum length option to the autocomplete directive so it only
evaluates the source expression when a certain number of characters has
been entered.

Closes #21.
  • Loading branch information
mbenford committed Nov 30, 2013
1 parent a69b628 commit c17d7a4
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 34 deletions.
10 changes: 9 additions & 1 deletion src/auto-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* The result of the expression must be a promise that resolves to an array of strings.
* @param {number=} [debounceDelay=100] Amount of time, in milliseconds, to wait after the last keystroke before evaluating
* the expression in the source option.
* @param {number=3} [minLength=3] Minimum number of characters that must be entered before evaluating the expression
* in the source option.
*/
angular.module('tags-input').directive('autoComplete', function($document, $timeout, configuration) {
function SuggestionList(loadFn, options) {
Expand All @@ -33,6 +35,11 @@ angular.module('tags-input').directive('autoComplete', function($document, $time
self.visible = false;
};
self.load = function(text) {
if (text.length < options.minLength) {
self.reset();
return;
}

$timeout.cancel(debouncedLoadId);
debouncedLoadId = $timeout(function() {
loadFn({ $text: text }).then(function(items) {
Expand Down Expand Up @@ -82,7 +89,8 @@ angular.module('tags-input').directive('autoComplete', function($document, $time
suggestionList, tagsInput, input;

configuration.load(scope, attrs, {
debounceDelay: { type: Number, defaultValue: 100 }
debounceDelay: { type: Number, defaultValue: 100 },
minLength: { type: Number, defaultValue: 3 }
});

suggestionList = new SuggestionList(scope.source, scope.options);
Expand Down
133 changes: 101 additions & 32 deletions test/auto-complete.spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
(function() {
'use strict';

describe('autocomplete-directive', function () {
describe('autocomplete-directive', function() {
var $compile, $scope, $q, $timeout,
parentCtrl, element, isolateScope, input, suggestionList, deferred, inputChangeHandler, onTagAddedHandler;

beforeEach(function () {
beforeEach(function() {
module('tags-input');

inject(function($rootScope, _$compile_, _$q_, _$timeout_) {
Expand Down Expand Up @@ -89,7 +89,7 @@ describe('autocomplete-directive', function () {
}

function loadSuggestions(items) {
suggestionList.load('');
suggestionList.load('foobar');
$timeout.flush();
resolve(items);
}
Expand Down Expand Up @@ -210,7 +210,7 @@ describe('autocomplete-directive', function () {
expect(input.changeValue).toHaveBeenCalledWith('Item1');
});

it('does not change the input value when the enter key is pressed and there is nothing selected', function () {
it('does not change the input value when the enter key is pressed and there is nothing selected', function() {
// Arrange
loadSuggestions(['Item1', 'Item2']);

Expand All @@ -221,7 +221,7 @@ describe('autocomplete-directive', function () {
expect(input.changeValue).not.toHaveBeenCalled();
});

it('sets the selected suggestion to null after adding it to the input field', function () {
it('sets the selected suggestion to null after adding it to the input field', function() {
// Arrange
loadSuggestions(['Item1', 'Item2']);
suggestionList.select(0);
Expand Down Expand Up @@ -259,7 +259,7 @@ describe('autocomplete-directive', function () {
expect(getSuggestion(2).hasClass('selected')).toBe(false);
});

it('selects no suggestion after the suggestion box is shown', function () {
it('selects no suggestion after the suggestion box is shown', function() {
// Arrange/Act
loadSuggestions(['Item1', 'Item2']);

Expand Down Expand Up @@ -360,12 +360,12 @@ describe('autocomplete-directive', function () {
});
});

describe('hotkeys propagation handling - suggestion box is visible', function () {
beforeEach(function () {
describe('hotkeys propagation handling - suggestion box is visible', function() {
beforeEach(function() {
suggestionList.show();
});

it('prevents the down arrow keydown event from being propagated', function () {
it('prevents the down arrow keydown event from being propagated', function() {
// Act
var event = sendKeyDown(KEYS.down);

Expand All @@ -374,7 +374,7 @@ describe('autocomplete-directive', function () {
expect(event.isPropagationStopped()).toBe(true);
});

it('prevents the up arrow keydown event from being propagated', function () {
it('prevents the up arrow keydown event from being propagated', function() {
// Act
var event = sendKeyDown(KEYS.up);

Expand All @@ -383,7 +383,7 @@ describe('autocomplete-directive', function () {
expect(event.isPropagationStopped()).toBe(true);
});

it('prevents the enter keydown event from being propagated if there is a suggestion selected', function () {
it('prevents the enter keydown event from being propagated if there is a suggestion selected', function() {
// Arrange
suggestionList.selected = 'suggestion';

Expand All @@ -395,7 +395,7 @@ describe('autocomplete-directive', function () {
expect(event.isPropagationStopped()).toBe(true);
});

it('does not prevent the enter keydown event from begin propagated if there is no suggestion selected', function () {
it('does not prevent the enter keydown event from begin propagated if there is no suggestion selected', function() {
// Arrange
suggestionList.selected = null;

Expand All @@ -407,7 +407,7 @@ describe('autocomplete-directive', function () {
expect(event.isPropagationStopped()).toBe(false);
});

it('prevents the tab keydown event from being propagated if there is a suggestion selected', function () {
it('prevents the tab keydown event from being propagated if there is a suggestion selected', function() {
// Arrange
suggestionList.selected = 'suggestion';

Expand All @@ -419,7 +419,7 @@ describe('autocomplete-directive', function () {
expect(event.isPropagationStopped()).toBe(true);
});

it('does not prevent the tab keydown event from being propagated if there is no suggestion selected', function () {
it('does not prevent the tab keydown event from being propagated if there is no suggestion selected', function() {
// Arrange
suggestionList.selected = null;

Expand All @@ -431,7 +431,7 @@ describe('autocomplete-directive', function () {
expect(event.isPropagationStopped()).toBe(false);
});

it('prevents the escape keydown event from being propagated', function () {
it('prevents the escape keydown event from being propagated', function() {
// Act
var event = sendKeyDown(KEYS.escape);

Expand All @@ -441,12 +441,12 @@ describe('autocomplete-directive', function () {
});
});

describe('hotkeys propagation handling - suggestion box is hidden', function () {
beforeEach(function () {
describe('hotkeys propagation handling - suggestion box is hidden', function() {
beforeEach(function() {
suggestionList.reset();
});

it('does not prevent the down arrow keydown event from being propagated', function () {
it('does not prevent the down arrow keydown event from being propagated', function() {
// Act
var event = sendKeyDown(KEYS.down);

Expand All @@ -455,7 +455,7 @@ describe('autocomplete-directive', function () {
expect(event.isPropagationStopped()).toBe(false);
});

it('does not prevent the up arrow keydown event from being propagated', function () {
it('does not prevent the up arrow keydown event from being propagated', function() {
// Act
var event = sendKeyDown(KEYS.up);

Expand All @@ -464,7 +464,7 @@ describe('autocomplete-directive', function () {
expect(event.isPropagationStopped()).toBe(false);
});

it('does not prevent the enter keydown event from being propagated', function () {
it('does not prevent the enter keydown event from being propagated', function() {
// Act
var event = sendKeyDown(KEYS.enter);

Expand All @@ -473,7 +473,7 @@ describe('autocomplete-directive', function () {
expect(event.isPropagationStopped()).toBe(false);
});

it('does not prevent the tab keydown event from being propagated', function () {
it('does not prevent the tab keydown event from being propagated', function() {
// Act
var event = sendKeyDown(KEYS.tab);

Expand All @@ -482,7 +482,7 @@ describe('autocomplete-directive', function () {
expect(event.isPropagationStopped()).toBe(false);
});

it('does not prevent the escape keydown event from being propagated', function () {
it('does not prevent the escape keydown event from being propagated', function() {
// Act
var event = sendKeyDown(KEYS.escape);

Expand All @@ -492,8 +492,33 @@ describe('autocomplete-directive', function () {
});
});

describe('debounce-delay option', function () {
it('doesn\'t call the load function immediately', function () {
describe('debounce-delay option', function() {
it('initializes the option to 100 milliseconds', function() {
// Arrange/Act
compile();

// Assert
expect(isolateScope.options.debounceDelay).toBe(100);
});

it('sets the option given a static string', function() {
// Arrange/Act
compile('debounce-delay="1000"');

// Assert
expect(isolateScope.options.debounceDelay).toBe(1000);
});

it('sets the option given an interpolated string', function() {
// Arrange/Act
$scope.value = 1000;
compile('debounce-delay="{{ value }}"');

// Assert
expect(isolateScope.options.debounceDelay).toBe(1000);
});

it('doesn\'t call the load function immediately', function() {
// Arrange
compile('debounce-delay="100"');

Expand All @@ -515,7 +540,7 @@ describe('autocomplete-directive', function () {
changeInputValue('AB');
changeInputValue('ABC');

$timeout.flush();
$timeout.flush(100);

// Assert
expect($scope.loadItems).toHaveBeenCalledWith('ABC');
Expand All @@ -534,30 +559,74 @@ describe('autocomplete-directive', function () {
expect($scope.loadItems).not.toHaveBeenCalled();

});
});

it('initializes the option to 100 milliseconds', function () {
describe('min-length option', function() {
it('initializes the option to 3', function() {
// Arrange/Act
compile();

// Assert
expect(isolateScope.options.debounceDelay).toBe(100);
expect(isolateScope.options.minLength).toBe(3);
});

it('sets the option given a static string', function() {
// Arrange/Act
compile('debounce-delay="1000"');
compile('min-length="5"');

// Assert
expect(isolateScope.options.debounceDelay).toBe(1000);
expect(isolateScope.options.minLength).toBe(5);
});

it('sets the option given an interpolated string', function() {
// Arrange/Act
$scope.value = 1000;
compile('debounce-delay="{{ value }}"');
$scope.value = 5;
compile('min-length="{{ value }}"');

// Assert
expect(isolateScope.options.debounceDelay).toBe(1000);
expect(isolateScope.options.minLength).toBe(5);
});

it('calls the load function only after the minimum amount of characters has been entered', function() {
// Arrange
compile('min-length="3"');

// Act
changeInputValue('A');
changeInputValue('AB');
changeInputValue('ABC');

$timeout.flush();

// Assert
expect($scope.loadItems.calls.length).toBe(1);
expect($scope.loadItems.calls[0].args[0]).toBe('ABC');
});

it('doesn\'t call the load function when the minimum amount of characters isn\'t entered', function() {
// Arrange
compile('min-length="3"');

// Act
changeInputValue('A');
changeInputValue('AB');

$timeout.flush();

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

it('hides the suggestion box when the number of entered characters is less than the option value', function() {
// Arrange
compile('min-length="5"');
suggestionList.show();

// Act
changeInputValue('ABCD');

// Assert
expect(isSuggestionsBoxVisible()).toBe(false);
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion test/test-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</head>
<body ng-controller="Ctrl">
<tags-input ng-model="tags" placeholder="{{ placeholder.value }}">
<auto-complete source="loadItems($text)" debounce-delay="100"></auto-complete>
<auto-complete source="loadItems($text)" debounce-delay="100" min-length="10"></auto-complete>
</tags-input>

<script type="text/javascript">
Expand Down

0 comments on commit c17d7a4

Please sign in to comment.