diff --git a/css/autocomplete.css b/css/autocomplete.css
index b14d5d35..1128e98f 100644
--- a/css/autocomplete.css
+++ b/css/autocomplete.css
@@ -18,10 +18,9 @@
}
.ngTagsInput .autocomplete li.suggestion {
- padding: 3px 20px;
- font-family: Arial, sans-serif;
- font-size: 18px;
- line-height: 22px;
+ padding: 3px 16px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ line-height: 20px;
cursor: pointer;
text-align: left;
white-space: nowrap;
@@ -32,4 +31,9 @@
.ngTagsInput .autocomplete li.selected {
color: #fff;
background-color: #0097cf
+}
+
+.ngTagsInput .autocomplete li em {
+ font-weight: bold;
+ font-style: normal;
}
\ No newline at end of file
diff --git a/css/tags-input.css b/css/tags-input.css
index 3590e12e..28cf9e73 100644
--- a/css/tags-input.css
+++ b/css/tags-input.css
@@ -59,7 +59,7 @@
}
.ngTagsInput .tags span {
- font: 13px Arial, sans-serif;
+ font: 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.ngTagsInput .tags button {
@@ -83,7 +83,7 @@
padding: 0px 0px 0px 4px;
height: 21px;
float: left;
- font: 13px Arial, sans-serif;
+ font: 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.ngTagsInput .tags input::-ms-clear {
diff --git a/src/auto-complete.js b/src/auto-complete.js
index 27c06187..3329860e 100644
--- a/src/auto-complete.js
+++ b/src/auto-complete.js
@@ -8,14 +8,16 @@
* @description
* Provides autocomplete support for the tagsInput directive.
*
- * @param {expression} source Expression to evaluate upon changing the input content. The input value is available as $text.
- * 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
+ * @param {expression} source Expression to evaluate upon changing the input content. The input value is available as
+ * $query. 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=} [minLength=3] Minimum number of characters that must be entered before evaluating the expression
* in the source option.
+ * @param {boolean=} [highlightMatchedText=true] Flag indicating that the matched text will be highlighted in the
+ * suggestions list.
*/
-angular.module('tags-input').directive('autoComplete', function($document, $timeout, configuration) {
+angular.module('tags-input').directive('autoComplete', function($document, $timeout, $sce, configuration) {
function SuggestionList(loadFn, options) {
var self = {}, debouncedLoadId;
@@ -24,6 +26,7 @@ angular.module('tags-input').directive('autoComplete', function($document, $time
self.visible = false;
self.index = -1;
self.selected = null;
+ self.query = null;
$timeout.cancel(debouncedLoadId);
};
@@ -34,15 +37,16 @@ angular.module('tags-input').directive('autoComplete', function($document, $time
self.hide = function() {
self.visible = false;
};
- self.load = function(text) {
- if (text.length < options.minLength) {
+ self.load = function(query) {
+ if (query.length < options.minLength) {
self.reset();
return;
}
$timeout.cancel(debouncedLoadId);
debouncedLoadId = $timeout(function() {
- loadFn({ $text: text }).then(function(items) {
+ self.query = query;
+ loadFn({ $query: query }).then(function(items) {
self.items = items;
if (items.length > 0) {
self.show();
@@ -81,22 +85,36 @@ angular.module('tags-input').directive('autoComplete', function($document, $time
'
{{ item }}' +
+ ' ng-mouseenter="suggestionList.select($index)"' +
+ ' ng-bind-html="highlight(item)">' +
' ' +
'',
link: function(scope, element, attrs, tagsInputCtrl) {
var hotkeys = [KEYS.enter, KEYS.tab, KEYS.escape, KEYS.up, KEYS.down],
- suggestionList, tagsInput, input;
+ suggestionList, tagsInput, input, highlight;
configuration.load(scope, attrs, {
debounceDelay: { type: Number, defaultValue: 100 },
- minLength: { type: Number, defaultValue: 3 }
+ minLength: { type: Number, defaultValue: 3 },
+ highlightMatchedText: { type: Boolean, defaultValue: true }
});
suggestionList = new SuggestionList(scope.source, scope.options);
tagsInput = tagsInputCtrl.registerAutocomplete();
input = tagsInput.input;
+ if (scope.options.highlightMatchedText) {
+ highlight = function(item, text) {
+ var expression = new RegExp(text, 'gi');
+ return item.replace(expression, '$&');
+ };
+ }
+ else {
+ highlight = function(item) {
+ return item;
+ };
+ }
+
scope.suggestionList = suggestionList;
scope.addSuggestion = function() {
@@ -112,6 +130,10 @@ angular.module('tags-input').directive('autoComplete', function($document, $time
return added;
};
+ scope.highlight = function(item) {
+ return $sce.trustAsHtml(highlight(item, suggestionList.query));
+ };
+
input.change(function(value) {
if (value) {
suggestionList.load(value);
diff --git a/test/auto-complete.spec.js b/test/auto-complete.spec.js
index 6013534c..954ffe64 100644
--- a/test/auto-complete.spec.js
+++ b/test/auto-complete.spec.js
@@ -41,7 +41,7 @@ describe('autocomplete-directive', function() {
spyOn(parentCtrl, 'registerAutocomplete').andReturn(tagsInput);
options = jQuery.makeArray(arguments).join(' ');
- element = angular.element('');
+ element = angular.element('');
parent.append(element);
$compile(element)($scope);
@@ -88,8 +88,8 @@ describe('autocomplete-directive', function() {
return !getSuggestionsBox().hasClass('ng-hide');
}
- function loadSuggestions(items) {
- suggestionList.load('foobar');
+ function loadSuggestions(items, text) {
+ suggestionList.load(text || 'foobar');
$timeout.flush();
resolve(items);
}
@@ -629,6 +629,63 @@ describe('autocomplete-directive', function() {
expect(isSuggestionsBoxVisible()).toBe(false);
});
});
+
+ describe('highlight-matched-text option', function() {
+ it('initializes the option to true', function() {
+ // Arrange/Act
+ compile();
+
+ // Assert
+ expect(isolateScope.options.highlightMatchedText).toBe(true);
+ });
+
+ it('sets the option given a static string', function() {
+ // Arrange/Act
+ compile('highlight-matched-text="false"');
+
+ // Assert
+ expect(isolateScope.options.highlightMatchedText).toBe(false);
+ });
+
+ it('sets the option given an interpolated string', function() {
+ // Arrange/Act
+ $scope.value = false;
+ compile('highlight-matched-text="{{ value }}"');
+
+ // Assert
+ expect(isolateScope.options.highlightMatchedText).toBe(false);
+ });
+
+ it('highlights the matched text in the suggestions list', function() {
+ // Arrange
+ compile('highlight-matched-text="true"', 'min-length="1"');
+
+ // Act
+ loadSuggestions(['a', 'ab', 'ba', 'aba', 'bab'], 'a');
+
+ // Assert
+ expect(getSuggestionText(0)).toBe('a');
+ expect(getSuggestionText(1)).toBe('ab');
+ expect(getSuggestionText(2)).toBe('ba');
+ expect(getSuggestionText(3)).toBe('aba');
+ expect(getSuggestionText(4)).toBe('bab');
+ });
+
+ it('doesn\'t highlight the matched text in the suggestions list whe the option is false', function() {
+ // Arrange
+ compile('highlight-matched-text="false"', 'min-length="1"');
+
+ // Act
+ loadSuggestions(['a', 'ab', 'ba', 'aba', 'bab'], 'a');
+
+ // Assert
+ expect(getSuggestionText(0)).toBe('a');
+ expect(getSuggestionText(1)).toBe('ab');
+ expect(getSuggestionText(2)).toBe('ba');
+ expect(getSuggestionText(3)).toBe('aba');
+ expect(getSuggestionText(4)).toBe('bab');
+ });
+ });
});
})();
diff --git a/test/test-page.html b/test/test-page.html
index ebcf5a79..3baa6061 100644
--- a/test/test-page.html
+++ b/test/test-page.html
@@ -8,18 +8,18 @@
-
+