From 73efc782e98123b6def5df85ec2875ab583cade2 Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Tue, 3 Nov 2015 05:35:05 -0800 Subject: [PATCH] chore(debounce): create debounce helper - Create internal debounce helper - Move debounce logic from typeahead to use helper Closes #3966 Closes #4813 --- src/debounce/debounce.js | 21 ++++++++++++++++ src/debounce/test/debounce.spec.js | 40 ++++++++++++++++++++++++++++++ src/typeahead/typeahead.js | 33 +++++++++++------------- 3 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 src/debounce/debounce.js create mode 100644 src/debounce/test/debounce.spec.js diff --git a/src/debounce/debounce.js b/src/debounce/debounce.js new file mode 100644 index 0000000000..751795702c --- /dev/null +++ b/src/debounce/debounce.js @@ -0,0 +1,21 @@ +angular.module('ui.bootstrap.debounce', []) +/** + * A helper, internal service that debounces a function + */ + .factory('$$debounce', ['$timeout', function($timeout) { + return function(callback, debounceTime) { + var timeoutPromise; + + return function() { + var self = this; + var args = Array.prototype.slice(arguments); + if (timeoutPromise) { + $timeout.cancel(timeoutPromise); + } + + timeoutPromise = $timeout(function() { + callback.apply(self, args); + }, debounceTime); + }; + }; + }]); \ No newline at end of file diff --git a/src/debounce/test/debounce.spec.js b/src/debounce/test/debounce.spec.js new file mode 100644 index 0000000000..6b67f61627 --- /dev/null +++ b/src/debounce/test/debounce.spec.js @@ -0,0 +1,40 @@ +describe('$$debounce', function() { + var $$debounce, $timeout, debouncedFunction, i; + + beforeEach(module('ui.bootstrap.debounce')); + beforeEach(inject(function(_$$debounce_, _$timeout_) { + $$debounce = _$$debounce_; + $timeout = _$timeout_; + i = 0; + debouncedFunction = $$debounce(function() { + i++; + }, 100); + })); + + it('should function like a $timeout when called once during timeout', function() { + debouncedFunction(); + $timeout.flush(50); + + expect(i).toBe(0); + + $timeout.flush(50); + + expect(i).toBe(1); + }); + + it('should only execute 100ms after last call when called twice', function() { + debouncedFunction(); + $timeout.flush(50); + + expect(i).toBe(0); + + debouncedFunction(); + $timeout.flush(50); + + expect(i).toBe(0); + + $timeout.flush(50); + + expect(i).toBe(1); + }); +}); diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 6ac2a6775f..323f6bc3a6 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -1,4 +1,4 @@ -angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position']) /** * A helper service that can parse typeahead's syntax (string provided by users) @@ -26,8 +26,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) }; }]) - .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$uibPosition', 'uibTypeaheadParser', - function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) { + .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser', + function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) { var HOT_KEYS = [9, 13, 27, 38, 40]; var eventDebounceTime = 200; var modelCtrl, ngModelOptions; @@ -264,8 +264,16 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) $document.find('body').bind('scroll', fireRecalculating); } - // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later - var timeoutEventPromise; + // Declare the debounced function outside recalculating for + // proper debouncing + var debouncedRecalculate = $$debounce(function() { + // if popup is visible + if (scope.matches.length) { + recalculatePosition(); + } + + scope.moveInProgress = false; + }, eventDebounceTime); // Default progress type scope.moveInProgress = false; @@ -276,20 +284,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) scope.$digest(); } - // Cancel previous timeout - if (timeoutEventPromise) { - $timeout.cancel(timeoutEventPromise); - } - - // Debounced executing recalculate after events fired - timeoutEventPromise = $timeout(function() { - // if popup is visible - if (scope.matches.length) { - recalculatePosition(); - } - - scope.moveInProgress = false; - }, eventDebounceTime); + debouncedRecalculate(); } // recalculate actual position and set new values to scope