From 581161d5777e29f2cce440896e0c0ecca09771d6 Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Tue, 11 Aug 2015 07:35:09 -0700 Subject: [PATCH] chore(typeahead): unify code style --- .../test/typeahead-highlight.spec.js | 19 +- src/typeahead/test/typeahead-parser.spec.js | 15 +- src/typeahead/test/typeahead-popup.spec.js | 14 +- src/typeahead/test/typeahead.spec.js | 246 +++--- src/typeahead/typeahead.js | 698 +++++++++--------- 5 files changed, 466 insertions(+), 526 deletions(-) diff --git a/src/typeahead/test/typeahead-highlight.spec.js b/src/typeahead/test/typeahead-highlight.spec.js index 1a5aafc48d..1a5d8b1c5d 100644 --- a/src/typeahead/test/typeahead-highlight.spec.js +++ b/src/typeahead/test/typeahead-highlight.spec.js @@ -1,39 +1,38 @@ -describe('typeaheadHighlight', function () { - +describe('typeaheadHighlight', function() { var highlightFilter; beforeEach(module('ui.bootstrap.typeahead')); - beforeEach(inject(function (typeaheadHighlightFilter) { + beforeEach(inject(function(typeaheadHighlightFilter) { highlightFilter = typeaheadHighlightFilter; })); - it('should higlight a match', function () { + it('should higlight a match', function() { expect(highlightFilter('before match after', 'match')).toEqual('before match after'); }); - it('should higlight a match with mixed case', function () { + it('should higlight a match with mixed case', function() { expect(highlightFilter('before MaTch after', 'match')).toEqual('before MaTch after'); }); - it('should higlight all matches', function () { + it('should higlight all matches', function() { expect(highlightFilter('before MaTch after match', 'match')).toEqual('before MaTch after match'); }); - it('should do nothing if no match', function () { + it('should do nothing if no match', function() { expect(highlightFilter('before match after', 'nomatch')).toEqual('before match after'); }); - it('should do nothing if no or empty query', function () { + it('should do nothing if no or empty query', function() { expect(highlightFilter('before match after', '')).toEqual('before match after'); expect(highlightFilter('before match after', null)).toEqual('before match after'); expect(highlightFilter('before match after', undefined)).toEqual('before match after'); }); - it('issue 316 - should work correctly for regexp reserved words', function () { + it('issue 316 - should work correctly for regexp reserved words', function() { expect(highlightFilter('before (match after', '(match')).toEqual('before (match after'); }); - it('issue 1777 - should work correctly with numeric values', function () { + it('issue 1777 - should work correctly with numeric values', function() { expect(highlightFilter(123, '2')).toEqual('123'); }); }); diff --git a/src/typeahead/test/typeahead-parser.spec.js b/src/typeahead/test/typeahead-parser.spec.js index e6a7e536cd..f19aec5cfb 100644 --- a/src/typeahead/test/typeahead-parser.spec.js +++ b/src/typeahead/test/typeahead-parser.spec.js @@ -1,15 +1,14 @@ -describe('syntax parser', function () { - +describe('syntax parser', function() { var typeaheadParser, scope, filterFilter; beforeEach(module('ui.bootstrap.typeahead')); - beforeEach(inject(function (_$rootScope_, _filterFilter_, _typeaheadParser_) { + beforeEach(inject(function(_$rootScope_, _filterFilter_, _typeaheadParser_) { typeaheadParser = _typeaheadParser_; scope = _$rootScope_; filterFilter = _filterFilter_; })); - it('should parse the simplest array-based syntax', function () { + it('should parse the simplest array-based syntax', function() { scope.states = ['Alabama', 'California', 'Delaware']; var result = typeaheadParser.parse('state for state in states | filter:$viewValue'); @@ -22,8 +21,8 @@ describe('syntax parser', function () { expect(result.modelMapper(scope, locals)).toEqual('Alabama'); }); - it('should parse the simplest function-based syntax', function () { - scope.getStates = function ($viewValue) { + it('should parse the simplest function-based syntax', function() { + scope.getStates = function($viewValue) { return filterFilter(['Alabama', 'California', 'Delaware'], $viewValue); }; var result = typeaheadParser.parse('state for state in getStates($viewValue)'); @@ -38,7 +37,6 @@ describe('syntax parser', function () { }); it('should allow to specify custom model mapping that is used as a label as well', function () { - scope.states = [ {code:'AL', name:'Alabama'}, {code:'CA', name:'California'}, @@ -59,8 +57,7 @@ describe('syntax parser', function () { expect(result.modelMapper(scope, locals)).toEqual('Alabama'); }); - it('should allow to specify custom view and model mappers', function () { - + it('should allow to specify custom view and model mappers', function() { scope.states = [ {code:'AL', name:'Alabama'}, {code:'CA', name:'California'}, diff --git a/src/typeahead/test/typeahead-popup.spec.js b/src/typeahead/test/typeahead-popup.spec.js index 89c9f75e83..5b2b94ae3c 100644 --- a/src/typeahead/test/typeahead-popup.spec.js +++ b/src/typeahead/test/typeahead-popup.spec.js @@ -1,18 +1,16 @@ -describe('typeaheadPopup - result rendering', function () { - +describe('typeaheadPopup - result rendering', function() { var scope, $rootScope, $compile; beforeEach(module('ui.bootstrap.typeahead')); beforeEach(module('template/typeahead/typeahead-popup.html')); beforeEach(module('template/typeahead/typeahead-match.html')); - beforeEach(inject(function (_$rootScope_, _$compile_) { + beforeEach(inject(function(_$rootScope_, _$compile_) { $rootScope = _$rootScope_; scope = $rootScope.$new(); $compile = _$compile_; })); - it('should render initial results', function () { - + it('should render initial results', function() { scope.matches = ['foo', 'bar', 'baz']; scope.active = 1; @@ -26,8 +24,7 @@ describe('typeaheadPopup - result rendering', function () { expect(liElems.eq(2)).not.toHaveClass('active'); }); - it('should change active item on mouseenter', function () { - + it('should change active item on mouseenter', function() { scope.matches = ['foo', 'bar', 'baz']; scope.active = 1; @@ -44,8 +41,7 @@ describe('typeaheadPopup - result rendering', function () { expect(liElems.eq(2)).toHaveClass('active'); }); - it('should select an item on mouse click', function () { - + it('should select an item on mouse click', function() { scope.matches = ['foo', 'bar', 'baz']; scope.active = 1; $rootScope.select = angular.noop; diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index 259f8feb1e..92a75de3f4 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -1,5 +1,4 @@ -describe('typeahead tests', function () { - +describe('typeahead tests', function() { var $scope, $compile, $document, $timeout; var changeInputValueTo; @@ -7,17 +6,17 @@ describe('typeahead tests', function () { beforeEach(module('template/typeahead/typeahead-popup.html')); beforeEach(module('template/typeahead/typeahead-match.html')); beforeEach(module(function($compileProvider) { - $compileProvider.directive('formatter', function () { + $compileProvider.directive('formatter', function() { return { require: 'ngModel', link: function (scope, elm, attrs, ngModelCtrl) { - ngModelCtrl.$formatters.unshift(function (viewVal) { + ngModelCtrl.$formatters.unshift(function(viewVal) { return 'formatted' + viewVal; }); } }; }); - $compileProvider.directive('childDirective', function () { + $compileProvider.directive('childDirective', function() { return { restrict: 'A', require: '^parentDirective', @@ -25,7 +24,7 @@ describe('typeahead tests', function () { }; }); })); - beforeEach(inject(function (_$rootScope_, _$compile_, _$document_, _$timeout_, $sniffer) { + beforeEach(inject(function(_$rootScope_, _$compile_, _$document_, _$timeout_, $sniffer) { $scope = _$rootScope_; $scope.source = ['foo', 'bar', 'baz']; $scope.states = [ @@ -35,7 +34,7 @@ describe('typeahead tests', function () { $compile = _$compile_; $document = _$document_; $timeout = _$timeout_; - changeInputValueTo = function (element, value) { + changeInputValueTo = function(element, value) { var inputEl = findInput(element); inputEl.val(value); inputEl.trigger($sniffer.hasEvent('input') ? 'input' : 'change'); @@ -44,25 +43,25 @@ describe('typeahead tests', function () { })); //utility functions - var prepareInputEl = function (inputTpl) { + var prepareInputEl = function(inputTpl) { var el = $compile(angular.element(inputTpl))($scope); $scope.$digest(); return el; }; - var findInput = function (element) { + var findInput = function(element) { return element.find('input'); }; - var findDropDown = function (element) { + var findDropDown = function(element) { return element.find('ul.dropdown-menu'); }; - var findMatches = function (element) { + var findMatches = function(element) { return findDropDown(element).find('li'); }; - var triggerKeyDown = function (element, keyCode) { + var triggerKeyDown = function(element, keyCode) { var inputEl = findInput(element); var e = $.Event('keydown'); e.which = keyCode; @@ -117,20 +116,18 @@ describe('typeahead tests', function () { }); }); - afterEach(function () { + afterEach(function() { findDropDown($document.find('body')).remove(); }); //coarse grained, "integration" tests - describe('initial state and model changes', function () { - - it('should be closed by default', function () { + describe('initial state and model changes', function() { + it('should be closed by default', function() { var element = prepareInputEl('
'); expect(element).toBeClosed(); }); - it('should correctly render initial state if the "as" keyword is used', function () { - + it('should correctly render initial state if the "as" keyword is used', function() { $scope.result = $scope.states[0]; var element = prepareInputEl('
'); @@ -139,8 +136,7 @@ describe('typeahead tests', function () { expect(inputEl.val()).toEqual('Alaska'); }); - it('should default to bound model for initial rendering if there is not enough info to render label', function () { - + it('should default to bound model for initial rendering if there is not enough info to render label', function() { $scope.result = $scope.states[0].code; var element = prepareInputEl('
'); @@ -149,7 +145,7 @@ describe('typeahead tests', function () { expect(inputEl.val()).toEqual('AL'); }); - it('should not get open on model change', function () { + it('should not get open on model change', function() { var element = prepareInputEl('
'); $scope.$apply(function () { $scope.result = 'foo'; @@ -158,9 +154,8 @@ describe('typeahead tests', function () { }); }); - describe('basic functionality', function () { - - it('should open and close typeahead based on matches', function () { + describe('basic functionality', function() { + it('should open and close typeahead based on matches', function() { var element = prepareInputEl('
'); var inputEl = findInput(element); var ownsId = inputEl.attr('aria-owns'); @@ -182,7 +177,7 @@ describe('typeahead tests', function () { expect(inputEl.attr('aria-activedescendant')).toBeUndefined(); }); - it('should allow expressions over multiple lines', function () { + it('should allow expressions over multiple lines', function() { var element = prepareInputEl('
'); changeInputValueTo(element, 'ba'); @@ -192,14 +187,14 @@ describe('typeahead tests', function () { expect(element).toBeClosed(); }); - it('should not open typeahead if input value smaller than a defined threshold', function () { + it('should not open typeahead if input value smaller than a defined threshold', function() { var element = prepareInputEl('
'); changeInputValueTo(element, 'b'); expect(element).toBeClosed(); }); - it('should support custom model selecting function', function () { - $scope.updaterFn = function (selectedItem) { + it('should support custom model selecting function', function() { + $scope.updaterFn = function(selectedItem) { return 'prefix' + selectedItem; }; var element = prepareInputEl('
'); @@ -208,8 +203,8 @@ describe('typeahead tests', function () { expect($scope.result).toEqual('prefixfoo'); }); - it('should support custom label rendering function', function () { - $scope.formatterFn = function (sourceItem) { + it('should support custom label rendering function', function() { + $scope.formatterFn = function(sourceItem) { return 'prefix' + sourceItem; }; @@ -219,20 +214,19 @@ describe('typeahead tests', function () { expect(matchHighlight).toEqual('prefixfoo'); }); - it('should by default bind view value to model even if not part of matches', function () { + it('should by default bind view value to model even if not part of matches', function() { var element = prepareInputEl('
'); changeInputValueTo(element, 'not in matches'); expect($scope.result).toEqual('not in matches'); }); - it('should support the editable property to limit model bindings to matches only', function () { + it('should support the editable property to limit model bindings to matches only', function() { var element = prepareInputEl('
'); changeInputValueTo(element, 'not in matches'); expect($scope.result).toEqual(undefined); }); - it('should set validation errors for non-editable inputs', function () { - + it('should set validation errors for non-editable inputs', function() { var element = prepareInputEl( '
' + '' + @@ -248,7 +242,7 @@ describe('typeahead tests', function () { expect($scope.form.input.$error.editable).toBeFalsy(); }); - it('should not set editable validation error for empty input', function () { + it('should not set editable validation error for empty input', function() { var element = prepareInputEl( '
' + '' + @@ -262,11 +256,10 @@ describe('typeahead tests', function () { expect($scope.form.input.$error.editable).toBeFalsy(); }); - it('should bind loading indicator expression', inject(function ($timeout) { - + it('should bind loading indicator expression', inject(function($timeout) { $scope.isLoading = false; - $scope.loadMatches = function (viewValue) { - return $timeout(function () { + $scope.loadMatches = function(viewValue) { + return $timeout(function() { return []; }, 1000); }; @@ -279,8 +272,7 @@ describe('typeahead tests', function () { expect($scope.isLoading).toBeFalsy(); })); - it('should support timeout before trying to match $viewValue', inject(function ($timeout) { - + it('should support timeout before trying to match $viewValue', inject(function($timeout) { var element = prepareInputEl('
'); changeInputValueTo(element, 'foo'); expect(element).toBeClosed(); @@ -289,7 +281,7 @@ describe('typeahead tests', function () { expect(element).toBeOpenWithActive(1, 0); })); - it('should cancel old timeouts when something is typed within waitTime', inject(function ($timeout) { + it('should cancel old timeouts when something is typed within waitTime', inject(function($timeout) { var values = []; $scope.loadMatches = function(viewValue) { values.push(viewValue); @@ -304,7 +296,7 @@ describe('typeahead tests', function () { expect(values).not.toContain('first'); })); - it('should allow timeouts when something is typed after waitTime has passed', inject(function ($timeout) { + it('should allow timeouts when something is typed after waitTime has passed', inject(function($timeout) { var values = []; $scope.loadMatches = function(viewValue) { @@ -324,8 +316,7 @@ describe('typeahead tests', function () { expect(values).toContain('second'); })); - it('should support custom templates for matched items', inject(function ($templateCache) { - + it('should support custom templates for matched items', inject(function($templateCache) { $templateCache.put('custom.html', '

{{ index }} {{ match.label }}

'); var element = prepareInputEl('
'); @@ -335,8 +326,7 @@ describe('typeahead tests', function () { expect(findMatches(element).eq(0).find('p').text()).toEqual('0 Alaska'); })); - it('should support directives which require controllers in custom templates for matched items', inject(function ($templateCache) { - + it('should support directives which require controllers in custom templates for matched items', inject(function($templateCache) { $templateCache.put('custom.html', '

{{ index }} {{ match.label }}

'); var element = prepareInputEl('
'); @@ -348,18 +338,16 @@ describe('typeahead tests', function () { expect(findMatches(element).eq(0).find('p').text()).toEqual('0 Alaska'); })); - it('should throw error on invalid expression', function () { - var prepareInvalidDir = function () { + it('should throw error on invalid expression', function() { + var prepareInvalidDir = function() { prepareInputEl('
'); }; expect(prepareInvalidDir).toThrow(); }); }); - describe('selecting a match', function () { - - it('should select a match on enter', function () { - + describe('selecting a match', function() { + it('should select a match on enter', function() { var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -371,8 +359,7 @@ describe('typeahead tests', function () { expect(element).toBeClosed(); }); - it('should select a match on tab', function () { - + it('should select a match on tab', function() { var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -384,8 +371,7 @@ describe('typeahead tests', function () { expect(element).toBeClosed(); }); - it('should not select any match on blur without \'select-on-blur=true\' option', function () { - + it('should not select any match on blur without \'select-on-blur=true\' option', function() { var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -397,8 +383,7 @@ describe('typeahead tests', function () { expect(inputEl.val()).toEqual('b'); }); - it('should select a match on blur with \'select-on-blur=true\' option', function () { - + it('should select a match on blur with \'select-on-blur=true\' option', function() { var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -410,8 +395,7 @@ describe('typeahead tests', function () { expect(inputEl.val()).toEqual('bar'); }); - it('should select match on click', function () { - + it('should select match on click', function() { var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -426,9 +410,8 @@ describe('typeahead tests', function () { expect(element).toBeClosed(); }); - it('should invoke select callback on select', function () { - - $scope.onSelect = function ($item, $model, $label) { + it('should invoke select callback on select', function() { + $scope.onSelect = function($item, $model, $label) { $scope.$item = $item; $scope.$model = $model; $scope.$label = $label; @@ -444,8 +427,7 @@ describe('typeahead tests', function () { expect($scope.$label).toEqual('Alaska'); }); - it('should correctly update inputs value on mapping where label is not derived from the model', function () { - + it('should correctly update inputs value on mapping where label is not derived from the model', function() { var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -456,11 +438,10 @@ describe('typeahead tests', function () { expect(inputEl.val()).toEqual('AL'); }); - it('should bind no results indicator as true when no matches returned', inject(function ($timeout) { - + 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 () { + $scope.loadMatches = function(viewValue) { + return $timeout(function() { return []; }, 1000); }; @@ -473,11 +454,10 @@ describe('typeahead tests', function () { expect($scope.isNoResults).toBeTruthy(); })); - it('should bind no results indicator as false when matches are returned', inject(function ($timeout) { - + 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 () { + $scope.loadMatches = function(viewValue) { + return $timeout(function() { return [viewValue]; }, 1000); }; @@ -491,10 +471,8 @@ describe('typeahead tests', function () { })); }); - describe('select on exact match', function(){ - - it('should select on an exact match when set', function () { - + describe('select on exact match', function() { + it('should select on an exact match when set', function() { $scope.onSelect = jasmine.createSpy('onSelect'); var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -507,8 +485,7 @@ describe('typeahead tests', function () { expect($scope.onSelect).toHaveBeenCalled(); }); - it('should not select on an exact match by default', function () { - + it('should not select on an exact match by default', function() { $scope.onSelect = jasmine.createSpy('onSelect'); var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -520,8 +497,7 @@ describe('typeahead tests', function () { expect($scope.onSelect.calls.any()).toBe(false); }); - it('should not be case sensitive when select on an exact match', function () { - + it('should not be case sensitive when select on an exact match', function() { $scope.onSelect = jasmine.createSpy('onSelect'); var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -534,8 +510,7 @@ describe('typeahead tests', function () { expect($scope.onSelect).toHaveBeenCalled(); }); - it('should not auto select when not a match with one potential result left', function () { - + it('should not auto select when not a match with one potential result left', function() { $scope.onSelect = jasmine.createSpy('onSelect'); var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -548,14 +523,14 @@ describe('typeahead tests', function () { }); }); - describe('pop-up interaction', function () { + describe('pop-up interaction', function() { var element; - beforeEach(function () { + beforeEach(function() { element = prepareInputEl('
'); }); - it('should activate prev/next matches on up/down keys', function () { + it('should activate prev/next matches on up/down keys', function() { changeInputValueTo(element, 'b'); expect(element).toBeOpenWithActive(2, 0); @@ -576,7 +551,7 @@ describe('typeahead tests', function () { expect(element).toBeOpenWithActive(2, 0); }); - it('should close popup on escape key', function () { + it('should close popup on escape key', function() { changeInputValueTo(element, 'b'); expect(element).toBeOpenWithActive(2, 0); @@ -585,28 +560,27 @@ describe('typeahead tests', function () { expect(element).toBeClosed(); }); - it('should highlight match on mouseenter', function () { + it('should highlight match on mouseenter', function() { changeInputValueTo(element, 'b'); expect(element).toBeOpenWithActive(2, 0); findMatches(element).eq(1).trigger('mouseenter'); expect(element).toBeOpenWithActive(2, 1); }); - }); - describe('promises', function () { + describe('promises', function() { var element, deferred; - beforeEach(inject(function ($q) { + beforeEach(inject(function($q) { deferred = $q.defer(); - $scope.source = function () { + $scope.source = function() { return deferred.promise; }; element = prepareInputEl('
'); })); - it('should display matches from promise', function () { + it('should display matches from promise', function() { changeInputValueTo(element, 'c'); expect(element).toBeClosed(); @@ -615,7 +589,7 @@ describe('typeahead tests', function () { expect(element).toBeOpenWithActive(2, 0); }); - it('should not display anything when promise is rejected', function () { + it('should not display anything when promise is rejected', function() { changeInputValueTo(element, 'c'); expect(element).toBeClosed(); @@ -624,7 +598,7 @@ describe('typeahead tests', function () { expect(element).toBeClosed(); }); - it('PR #3178, resolves #2999 - should not return property "length" of undefined for undefined matches', function () { + it('PR #3178, resolves #2999 - should not return property "length" of undefined for undefined matches', function() { changeInputValueTo(element, 'c'); expect(element).toBeClosed(); @@ -632,12 +606,11 @@ describe('typeahead tests', function () { $scope.$digest(); expect(element).toBeClosed(); }); - }); - describe('non-regressions tests', function () { + describe('non-regressions tests', function() { - it('issue 231 - closes matches popup on click outside typeahead', function () { + it('issue 231 - closes matches popup on click outside typeahead', function() { var element = prepareInputEl('
'); changeInputValueTo(element, 'b'); @@ -648,13 +621,12 @@ describe('typeahead tests', function () { expect(element).toBeClosed(); }); - it('issue 591 - initial formatting for un-selected match and complex label expression', function () { - + it('issue 591 - initial formatting for un-selected match and complex label expression', function() { var inputEl = findInput(prepareInputEl('
')); expect(inputEl.val()).toEqual(''); }); - it('issue 786 - name of internal model should not conflict with scope model name', function () { + it('issue 786 - name of internal model should not conflict with scope model name', function() { $scope.state = $scope.states[0]; var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -662,8 +634,7 @@ describe('typeahead tests', function () { expect(inputEl.val()).toEqual('Alaska'); }); - it('issue 863 - it should work correctly with input type="email"', function () { - + it('issue 863 - it should work correctly with input type="email"', function() { $scope.emails = ['foo@host.com', 'bar@host.com']; var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -677,10 +648,9 @@ describe('typeahead tests', function () { expect(inputEl.val()).toEqual('bar@host.com'); }); - it('issue 964 - should not show popup with matches if an element is not focused', function () { - + it('issue 964 - should not show popup with matches if an element is not focused', function() { $scope.items = function(viewValue) { - return $timeout(function(){ + return $timeout(function() { return [viewValue]; }); }; @@ -696,8 +666,7 @@ describe('typeahead tests', function () { expect(element).toBeClosed(); }); - it('should properly update loading callback if an element is not focused', function () { - + it('should properly update loading callback if an element is not focused', function() { $scope.items = function(viewValue) { return $timeout(function(){ return [viewValue]; @@ -715,10 +684,9 @@ describe('typeahead tests', function () { expect($scope.isLoading).toBeFalsy(); }); - it('issue 1140 - should properly update loading callback when deleting characters', function () { - + it('issue 1140 - should properly update loading callback when deleting characters', function() { $scope.items = function(viewValue) { - return $timeout(function(){ + return $timeout(function() { return [viewValue]; }); }; @@ -736,7 +704,7 @@ describe('typeahead tests', function () { expect($scope.isLoading).toBeFalsy(); }); - it('should cancel old timeout when deleting characters', inject(function ($timeout) { + it('should cancel old timeout when deleting characters', inject(function($timeout) { var values = []; $scope.loadMatches = function(viewValue) { values.push(viewValue); @@ -755,7 +723,7 @@ describe('typeahead tests', function () { // Dummy describe to be able to create an after hook for this tests var element; - it('does not close matches popup on click in input', function () { + it('does not close matches popup on click in input', function() { element = prepareInputEl('
'); var inputEl = findInput(element); @@ -770,7 +738,7 @@ describe('typeahead tests', function () { expect(element).toBeOpenWithActive(2, 0); }); - it('issue #1773 - should not trigger an error when used with ng-focus', function () { + it('issue #1773 - should not trigger an error when used with ng-focus', function() { element = prepareInputEl('
'); var inputEl = findInput(element); @@ -789,8 +757,7 @@ describe('typeahead tests', function () { }); }); - it('issue #1238 - allow names like "query" to be used inside "in" expressions ', function () { - + it('issue #1238 - allow names like "query" to be used inside "in" expressions ', function() { $scope.query = function() { return ['foo', 'bar']; }; @@ -801,15 +768,14 @@ describe('typeahead tests', function () { expect(element).toBeOpenWithActive(2, 0); }); - it('issue #3318 - should set model validity to true when set manually', function () { - + it('issue #3318 - should set model validity to true when set manually', function() { var element = prepareInputEl( '
' + '' + '
'); changeInputValueTo(element, 'not in matches'); - $scope.$apply(function () { + $scope.$apply(function() { $scope.result = 'manually set'; }); @@ -817,7 +783,7 @@ describe('typeahead tests', function () { expect($scope.form.input.$valid).toBeTruthy(); }); - it('issue #3166 - should set \'parse\' key as valid when selecting a perfect match and not editable', function () { + it('issue #3166 - should set \'parse\' key as valid when selecting a perfect match and not editable', function() { var element = prepareInputEl('
'); var inputEl = findInput(element); @@ -828,10 +794,8 @@ describe('typeahead tests', function () { }); }); - describe('input formatting', function () { - - it('should co-operate with existing formatters', function () { - + describe('input formatting', function() { + it('should co-operate with existing formatters', function() { $scope.result = $scope.states[0]; var element = prepareInputEl('
'), @@ -840,8 +804,7 @@ describe('typeahead tests', function () { expect(inputEl.val()).toEqual('formatted' + $scope.result.name); }); - it('should support a custom input formatting function', function () { - + it('should support a custom input formatting function', function() { $scope.result = $scope.states[0]; $scope.formatInput = function($model) { return $model.code; @@ -853,18 +816,16 @@ describe('typeahead tests', function () { expect(inputEl.val()).toEqual('AL'); expect($scope.result).toEqual($scope.states[0]); }); - - }); - describe('append to body', function () { - it('append typeahead results to body', function () { + describe('append to body', function() { + it('append typeahead results to body', function() { var element = prepareInputEl('
'); changeInputValueTo(element, 'ba'); expect($document.find('body')).toBeOpenWithActive(2, 0); }); - it('should not append to body when value of the attribute is false', function () { + it('should not append to body when value of the attribute is false', function() { var element = prepareInputEl('
'); changeInputValueTo(element, 'ba'); expect(findDropDown($document.find('body')).length).toEqual(0); @@ -891,12 +852,12 @@ describe('typeahead tests', function () { window.scroll(0, 500); body.triggerHandler('scroll'); $timeout.flush(); - expect(dropdown.css('top') ).toEqual('500px'); + expect(dropdown.css('top')).toEqual('500px'); }); }); - describe('focus first', function () { - it('should focus the first element by default', function () { + describe('focus first', function() { + it('should focus the first element by default', function() { var element = prepareInputEl('
'); changeInputValueTo(element, 'b'); expect(element).toBeOpenWithActive(2, 0); @@ -918,7 +879,7 @@ describe('typeahead tests', function () { expect(element).toBeOpenWithActive(2, 0); }); - it('should not focus the first element until keys are pressed', function () { + it('should not focus the first element until keys are pressed', function() { var element = prepareInputEl('
'); changeInputValueTo(element, 'b'); expect(element).toBeOpenWithActive(2, -1); @@ -954,9 +915,9 @@ describe('typeahead tests', function () { }); }); - it('should not capture enter or tab when an item is not focused', function () { + it('should not capture enter or tab when an item is not focused', function() { $scope.select_count = 0; - $scope.onSelect = function ($item, $model, $label) { + $scope.onSelect = function($item, $model, $label) { $scope.select_count = $scope.select_count + 1; }; var element = prepareInputEl('
'); @@ -974,9 +935,9 @@ describe('typeahead tests', function () { expect(element).toBeClosed(); }); - it('should capture enter or tab when an item is focused', function () { + it('should capture enter or tab when an item is focused', function() { $scope.select_count = 0; - $scope.onSelect = function ($item, $model, $label) { + $scope.onSelect = function($item, $model, $label) { $scope.select_count = $scope.select_count + 1; }; var element = prepareInputEl('
'); @@ -993,13 +954,12 @@ describe('typeahead tests', function () { expect($scope.select_count).toEqual(1); }); - describe('minLength set to 0', function () { - it('should open typeahead if input is changed to empty string if defined threshold is 0', function () { + describe('minLength set to 0', function() { + it('should open typeahead if input is changed to empty string if defined threshold is 0', function() { var element = prepareInputEl('
'); changeInputValueTo(element, ''); expect(element).toBeOpenWithActive(3, 0); }); }); - }); diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 3fa0c2a813..db661ba0ca 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -4,14 +4,13 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap * A helper service that can parse typeahead's syntax (string provided by users) * Extracted to a separate service for ease of unit testing */ - .factory('typeaheadParser', ['$parse', function ($parse) { + .factory('typeaheadParser', ['$parse', function($parse) { // 00000111000000000000022200000000000000003333333333333330000000000044000 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; return { - parse:function (input) { - + parse: function(input) { var match = input.match(TYPEAHEAD_REGEXP); if (!match) { throw new Error( @@ -30,461 +29,451 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap }]) .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$position', 'typeaheadParser', - function ($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) { - - var HOT_KEYS = [9, 13, 27, 38, 40]; - var eventDebounceTime = 200; - - return { - require:'ngModel', - link:function (originalScope, element, attrs, modelCtrl) { + function($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) { + var HOT_KEYS = [9, 13, 27, 38, 40]; + var eventDebounceTime = 200; - //SUPPORTED ATTRIBUTES (OPTIONS) - - //minimal no of characters that needs to be entered before typeahead kicks-in - var minLength = originalScope.$eval(attrs.typeaheadMinLength); - if (!minLength && minLength !== 0) { - minLength = 1; - } - - //minimal wait time after last character typed before typeahead kicks-in - var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + return { + require: 'ngModel', + link: function(originalScope, element, attrs, modelCtrl) { - //should it restrict model values to the ones selected from the popup only? - var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + //SUPPORTED ATTRIBUTES (OPTIONS) - //binding to a variable that indicates if matches are being retrieved asynchronously - var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + //minimal no of characters that needs to be entered before typeahead kicks-in + var minLength = originalScope.$eval(attrs.typeaheadMinLength); + if (!minLength && minLength !== 0) { + minLength = 1; + } - //a callback executed when a match is selected - var onSelectCallback = $parse(attrs.typeaheadOnSelect); + //minimal wait time after last character typed before typeahead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; - //should it select highlighted popup value when losing focus? - var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false; + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== 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; + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; - var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); - var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; + //should it select highlighted popup value when losing focus? + var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false; - var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== 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; - //If input matches an item of the list exactly, select it automatically - var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false; + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; - //INTERNAL VARIABLES + var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; - //model setter executed upon match selection - var $setModelValue = $parse(attrs.ngModel).assign; + var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false; - //expressions used by typeahead - var parserResult = typeaheadParser.parse(attrs.typeahead); + //If input matches an item of the list exactly, select it automatically + var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false; - var hasFocus; + //INTERNAL VARIABLES - //Used to avoid bug in iOS webview where iOS keyboard does not fire - //mousedown & mouseup events - //Issue #3699 - var selected; + //model setter executed upon match selection + var $setModelValue = $parse(attrs.ngModel).assign; - //create a child scope for the typeahead directive so we are not polluting original scope - //with typeahead-specific data (matches, query etc.) - var scope = originalScope.$new(); - originalScope.$on('$destroy', function(){ - scope.$destroy(); - }); + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); - // WAI-ARIA - var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); - element.attr({ - 'aria-autocomplete': 'list', - 'aria-expanded': false, - 'aria-owns': popupId - }); + var hasFocus; - //pop-up element used to display matches - var popUpEl = angular.element('
'); - popUpEl.attr({ - id: popupId, - matches: 'matches', - active: 'activeIdx', - select: 'select(activeIdx)', - 'move-in-progress': 'moveInProgress', - query: 'query', - position: 'position' - }); - //custom item template - if (angular.isDefined(attrs.typeaheadTemplateUrl)) { - popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); - } + //Used to avoid bug in iOS webview where iOS keyboard does not fire + //mousedown & mouseup events + //Issue #3699 + var selected; - var resetMatches = function() { - scope.matches = []; - scope.activeIdx = -1; - element.attr('aria-expanded', false); - }; + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function() { + scope.$destroy(); + }); - var getMatchId = function(index) { - return popupId + '-option-' + index; - }; + // WAI-ARIA + var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + element.attr({ + 'aria-autocomplete': 'list', + 'aria-expanded': false, + 'aria-owns': popupId + }); - // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. - // This attribute is added or removed automatically when the `activeIdx` changes. - scope.$watch('activeIdx', function(index) { - if (index < 0) { - element.removeAttr('aria-activedescendant'); - } else { - element.attr('aria-activedescendant', getMatchId(index)); + //pop-up element used to display matches + var popUpEl = angular.element('
'); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + 'move-in-progress': 'moveInProgress', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); } - }); - var inputIsExactMatch = function(inputValue, index) { - - if (scope.matches.length > index && inputValue) { - return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase(); - } + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; - return false; - }; + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; - var getMatchesAsync = function(inputValue) { - - 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 - //but we are interested only in responses that correspond to the current view value - var onCurrentRequest = (inputValue === modelCtrl.$viewValue); - if (onCurrentRequest && hasFocus) { - if (matches && matches.length > 0) { - - scope.activeIdx = focusFirst ? 0 : -1; - isNoResultsSetter(originalScope, false); - scope.matches.length = 0; - - //transform labels - for(var i=0; i index && inputValue) { + return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase(); + } - element.attr('aria-expanded', true); + return false; + }; - //Select the single remaining option if user input matches - if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { - scope.select(0); + var getMatchesAsync = function(inputValue) { + 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 + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches && matches.length > 0) { + + scope.activeIdx = focusFirst ? 0 : -1; + isNoResultsSetter(originalScope, false); + scope.matches.length = 0; + + //transform labels + for(var i=0; i= minLength) { - if (waitTime > 0) { + if (minLength === 0 || inputValue && inputValue.length >= minLength) { + if (waitTime > 0) { + cancelPreviousTimeout(); + scheduleSearchWithTimeout(inputValue); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); cancelPreviousTimeout(); - scheduleSearchWithTimeout(inputValue); + resetMatches(); + } + + if (isEditable) { + return inputValue; } else { - getMatchesAsync(inputValue); + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return null; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } } - } else { - isLoadingSetter(originalScope, false); - cancelPreviousTimeout(); - resetMatches(); - } + }); - if (isEditable) { - return inputValue; - } else { - if (!inputValue) { - // Reset in case user had typed something previously. + modelCtrl.$formatters.push(function(modelValue) { + var candidateViewValue, emptyViewValue; + var locals = {}; + + // The validity may be set to false via $parsers (see above) if + // the model is restricted to selected values. If the model + // is set manually it is considered to be valid. + if (!isEditable) { modelCtrl.$setValidity('editable', true); - return null; - } else { - modelCtrl.$setValidity('editable', false); - return undefined; } - } - }); - modelCtrl.$formatters.push(function (modelValue) { + if (inputFormatter) { + locals.$model = modelValue; + return inputFormatter(originalScope, locals); + } else { + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + } + }); - var candidateViewValue, emptyViewValue; - var locals = {}; + scope.select = function(activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; - // The validity may be set to false via $parsers (see above) if - // the model is restricted to selected values. If the model - // is set manually it is considered to be valid. - if (!isEditable) { + selected = true; + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); modelCtrl.$setValidity('editable', true); - } + modelCtrl.$setValidity('parse', true); - if (inputFormatter) { - - locals.$model = modelValue; - return inputFormatter(originalScope, locals); + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); - } else { + resetMatches(); - //it might happen that we don't have enough info to properly render input value - //we need to check for this situation and simply return model value if we can't apply custom formatting - locals[parserResult.itemName] = modelValue; - candidateViewValue = parserResult.viewMapper(originalScope, locals); - locals[parserResult.itemName] = undefined; - emptyViewValue = parserResult.viewMapper(originalScope, locals); + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + $timeout(function() { element[0].focus(); }, 0, false); + }; - return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; - } - }); - - scope.select = function (activeIdx) { - //called from within the $digest() cycle - var locals = {}; - var model, item; - - selected = true; - locals[parserResult.itemName] = item = scope.matches[activeIdx].model; - model = parserResult.modelMapper(originalScope, locals); - $setModelValue(originalScope, model); - modelCtrl.$setValidity('editable', true); - modelCtrl.$setValidity('parse', true); - - onSelectCallback(originalScope, { - $item: item, - $model: model, - $label: parserResult.viewMapper(originalScope, locals) - }); + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function(evt) { - resetMatches(); + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } - //return focus to the input element if a match was selected via a mouse click event - // use timeout to avoid $rootScope:inprog error - $timeout(function() { element[0].focus(); }, 0, false); - }; + // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results + if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) { + resetMatches(); + scope.$digest(); + return; + } - //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) - element.bind('keydown', function (evt) { + evt.preventDefault(); - //typeahead is open and an "interesting" key was pressed - if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { - return; - } + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); - // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results - if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) { - resetMatches(); - scope.$digest(); - return; - } + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); - evt.preventDefault(); + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); - if (evt.which === 40) { - scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; - scope.$digest(); + } else if (evt.which === 27) { + evt.stopPropagation(); - } else if (evt.which === 38) { - scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; - scope.$digest(); + resetMatches(); + scope.$digest(); + } + }); - } else if (evt.which === 13 || evt.which === 9) { - scope.$apply(function () { - scope.select(scope.activeIdx); - }); + element.bind('blur', function() { + if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) { + selected = true; + scope.$apply(function() { + scope.select(scope.activeIdx); + }); + } + hasFocus = false; + selected = false; + }); - } else if (evt.which === 27) { - evt.stopPropagation(); + // Keep reference to click handler to unbind it. + var dismissClickHandler = function(evt) { + // Issue #3973 + // Firefox treats right click as a click on document + if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) { + resetMatches(); + if (!$rootScope.$$phase) { + scope.$digest(); + } + } + }; - resetMatches(); - scope.$digest(); - } - }); + $document.bind('click', dismissClickHandler); - element.bind('blur', function () { - if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) { - selected = true; - scope.$apply(function () { - scope.select(scope.activeIdx); - }); - } - hasFocus = false; - selected = false; - }); - - // Keep reference to click handler to unbind it. - var dismissClickHandler = function (evt) { - // Issue #3973 - // Firefox treats right click as a click on document - if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) { - resetMatches(); - if (!$rootScope.$$phase) { - scope.$digest(); + originalScope.$on('$destroy', function() { + $document.unbind('click', dismissClickHandler); + if (appendToBody) { + $popup.remove(); } - } - }; + // Prevent jQuery cache memory leak + popUpEl.remove(); + }); - $document.bind('click', dismissClickHandler); + var $popup = $compile(popUpEl)(scope); - originalScope.$on('$destroy', function(){ - $document.unbind('click', dismissClickHandler); if (appendToBody) { - $popup.remove(); + $document.find('body').append($popup); + } else { + element.after($popup); } - // Prevent jQuery cache memory leak - popUpEl.remove(); - }); - - var $popup = $compile(popUpEl)(scope); - - if (appendToBody) { - $document.find('body').append($popup); - } else { - element.after($popup); } - } - }; + }; -}]) + }]) - .directive('typeaheadPopup', function () { + .directive('typeaheadPopup', function() { return { - restrict:'EA', - scope:{ - matches:'=', - query:'=', - active:'=', - position:'&', - moveInProgress:'=', - select:'&' + restrict: 'EA', + scope: { + matches: '=', + query: '=', + active: '=', + position: '&', + moveInProgress: '=', + select: '&' }, - replace:true, - templateUrl:'template/typeahead/typeahead-popup.html', - link:function (scope, element, attrs) { - + replace: true, + templateUrl: 'template/typeahead/typeahead-popup.html', + link: function(scope, element, attrs) { scope.templateUrl = attrs.templateUrl; - scope.isOpen = function () { + scope.isOpen = function() { return scope.matches.length > 0; }; - scope.isActive = function (matchIdx) { + scope.isActive = function(matchIdx) { return scope.active == matchIdx; }; - scope.selectActive = function (matchIdx) { + scope.selectActive = function(matchIdx) { scope.active = matchIdx; }; - scope.selectMatch = function (activeIdx) { + scope.selectMatch = function(activeIdx) { scope.select({activeIdx:activeIdx}); }; } }; }) - .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', function ($templateRequest, $compile, $parse) { + .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) { return { - restrict:'EA', - scope:{ - index:'=', - match:'=', - query:'=' + restrict: 'EA', + scope: { + index: '=', + match: '=', + query: '=' }, - link:function (scope, element, attrs) { + link:function(scope, element, attrs) { var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; $templateRequest(tplUrl).then(function(tplContent) { - $compile(tplContent.trim())(scope, function(clonedElement){ + $compile(tplContent.trim())(scope, function(clonedElement) { element.replaceWith(clonedElement); }); }); @@ -493,7 +482,6 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap }]) .filter('typeaheadHighlight', function() { - function escapeRegexp(queryToEscape) { return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); }