├── .gitignore ├── .npmignore ├── .jshintrc ├── bower.json ├── test ├── configs │ └── unit.conf.js └── debounce.js ├── dist ├── angular-debounce.min.js └── angular-debounce.js ├── .jshintrc-test ├── LICENSE ├── package.json ├── src └── debounce.js ├── Gruntfile.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | /node_modules 3 | /bower_components 4 | /libpeerconnection.log 5 | /unit-results.xml 6 | /e2e-results.xml 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /Gruntfile.coffee 2 | /.travis.yml 3 | /genplurals.py 4 | /test 5 | /src 6 | /node_modules 7 | /bower_components 8 | /libpeerconnection.log 9 | /*-results.xml 10 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "white": true, 13 | "proto": true, 14 | "predef": [ 15 | "$", 16 | "angular", 17 | "console" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-debounce", 3 | "version": "1.1.1", 4 | "main": "dist/angular-debounce.js", 5 | "ignore": [ 6 | "**/.*", 7 | "src", 8 | "node_modules", 9 | "bower_components", 10 | "test", 11 | "Gruntfile.coffee" 12 | ], 13 | "dependencies": { 14 | "angular": "*" 15 | }, 16 | "devDependencies": { 17 | "angular-mocks": "*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/configs/unit.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | return config.set({ 3 | basePath: '../..', 4 | frameworks: ['mocha', 'chai'], 5 | files: [ 6 | 'bower_components/angular/angular.js', 7 | 'bower_components/angular-mocks/angular-mocks.js', 8 | 'dist/angular-debounce.js', 9 | 'test/*.js' 10 | ], 11 | port: 9877 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /dist/angular-debounce.min.js: -------------------------------------------------------------------------------- 1 | angular.module("rt.debounce",[]).factory("debounce",["$timeout",function(a){return function(b,c,d,e){function f(){l=c.apply(k||this,j||[]),k=j=null,n=!0}function g(){m&&(a.cancel(m),m=null)}function h(){k=this,j=arguments,d?n&&(n=!1,m=a(f,b,!e)):(g(),m=a(f,b,!e))}function i(){var a=!!k;return a&&(g(),f()),a}var j,k,l,m,n=!0;return h.flush=function(){return i()||m||f(),l},h.flushPending=function(){return i(),l},h.cancel=g,h}}]); -------------------------------------------------------------------------------- /.jshintrc-test: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true, 13 | "white": true, 14 | "predef": [ 15 | "angular", 16 | "describe", 17 | "it", 18 | "beforeEach", 19 | "afterEach", 20 | "module", 21 | "inject", 22 | "assert" 23 | ] 24 | } 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 by Ruben Vermeersch 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-debounce", 3 | "version": "1.1.1", 4 | "description": "Tiny debouncing function for Angular.JS.", 5 | "main": "dist/angular-debounce.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "grunt ci", 11 | "prepublish": "grunt build" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "filter" 16 | ], 17 | "author": { 18 | "name": "Ruben Vermeersch", 19 | "email": "ruben@rocketeer.be", 20 | "url": "http://rocketeer.be/" 21 | }, 22 | "license": "MIT", 23 | "devDependencies": { 24 | "chai": "~1.9.0", 25 | "grunt": "^1.0.0", 26 | "grunt-bump": "~0.8.0", 27 | "grunt-contrib-clean": "^1.0.0", 28 | "grunt-contrib-concat": "^1.0.0", 29 | "grunt-contrib-jshint": "^3.2.0", 30 | "grunt-contrib-uglify": "^1.0.0", 31 | "grunt-contrib-watch": "^1.0.0", 32 | "grunt-karma": "^4.0.2", 33 | "grunt-ngmin": "0.0.3", 34 | "karma": "^6.3.14", 35 | "karma-chai": "~0.1.0", 36 | "karma-chrome-launcher": "^1.0.0", 37 | "karma-firefox-launcher": "^1.0.0", 38 | "karma-mocha": "^1.0.0", 39 | "karma-phantomjs-launcher": "^1.0.0", 40 | "mocha": "~10.1.0", 41 | "phantomjs-prebuilt": "^2.1.7" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git://github.com/rubenv/angular-debounce.git" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /dist/angular-debounce.js: -------------------------------------------------------------------------------- 1 | angular.module('rt.debounce', []).factory('debounce', [ 2 | '$timeout', 3 | function ($timeout) { 4 | return function (wait, fn, no_postpone, skipApply) { 5 | var args, context, result, timeout; 6 | var executed = true; 7 | // Execute the callback function 8 | function ping() { 9 | result = fn.apply(context || this, args || []); 10 | context = args = null; 11 | executed = true; 12 | } 13 | // Cancel the timeout (for rescheduling afterwards). 14 | function cancel() { 15 | if (timeout) { 16 | $timeout.cancel(timeout); 17 | timeout = null; 18 | } 19 | } 20 | // This is the actual result of the debounce call. It is a 21 | // wrapper function which you can invoke multiple times and 22 | // which will only be called once every "wait" milliseconds. 23 | function wrapper() { 24 | context = this; 25 | args = arguments; 26 | if (!no_postpone) { 27 | cancel(); 28 | timeout = $timeout(ping, wait, !skipApply); 29 | } else if (executed) { 30 | executed = false; 31 | timeout = $timeout(ping, wait, !skipApply); 32 | } 33 | } 34 | // Forces the execution of pending calls 35 | function flushPending() { 36 | var pending = !!context; 37 | if (pending) { 38 | // Call pending, do it now. 39 | cancel(); 40 | ping(); 41 | } 42 | return pending; 43 | } 44 | // The wrapper also has a flush method, which you can use to 45 | // force the execution of the last scheduled call to happen 46 | // immediately (if any). It will also return the result of that 47 | // call. Note that for asynchronous operations, you'll need to 48 | // return a promise and wait for that one to resolve. 49 | wrapper.flush = function () { 50 | if (!flushPending() && !timeout) { 51 | // Never been called. 52 | ping(); 53 | } 54 | return result; 55 | }; 56 | // Flushes pending calls if any 57 | wrapper.flushPending = function () { 58 | flushPending(); 59 | return result; 60 | }; 61 | // Cancels the queued execution if any 62 | wrapper.cancel = cancel; 63 | return wrapper; 64 | }; 65 | } 66 | ]); -------------------------------------------------------------------------------- /src/debounce.js: -------------------------------------------------------------------------------- 1 | angular.module('rt.debounce', []).factory('debounce', ['$timeout', function ($timeout) { 2 | return function (wait, fn, no_postpone, skipApply) { 3 | var args, context, result, timeout; 4 | var executed = true; 5 | 6 | // Execute the callback function 7 | function ping() { 8 | result = fn.apply(context || this, args || []); 9 | context = args = null; 10 | executed = true; 11 | } 12 | 13 | // Cancel the timeout (for rescheduling afterwards). 14 | function cancel() { 15 | if (timeout) { 16 | $timeout.cancel(timeout); 17 | timeout = null; 18 | } 19 | } 20 | 21 | // This is the actual result of the debounce call. It is a 22 | // wrapper function which you can invoke multiple times and 23 | // which will only be called once every "wait" milliseconds. 24 | function wrapper() { 25 | context = this; 26 | args = arguments; 27 | if (!no_postpone) { 28 | cancel(); 29 | timeout = $timeout(ping, wait, !skipApply); 30 | } else if (executed) { 31 | executed = false; 32 | timeout = $timeout(ping, wait, !skipApply); 33 | } 34 | } 35 | 36 | // Forces the execution of pending calls 37 | function flushPending() { 38 | var pending = !!context; 39 | if (pending) { 40 | // Call pending, do it now. 41 | cancel(); 42 | ping(); 43 | } 44 | return pending; 45 | } 46 | 47 | // The wrapper also has a flush method, which you can use to 48 | // force the execution of the last scheduled call to happen 49 | // immediately (if any). It will also return the result of that 50 | // call. Note that for asynchronous operations, you'll need to 51 | // return a promise and wait for that one to resolve. 52 | wrapper.flush = function () { 53 | if (!flushPending() && !timeout) { 54 | // Never been called. 55 | ping(); 56 | } 57 | return result; 58 | }; 59 | 60 | // Flushes pending calls if any 61 | wrapper.flushPending = function () { 62 | flushPending(); 63 | return result; 64 | }; 65 | 66 | // Cancels the queued execution if any 67 | wrapper.cancel = cancel; 68 | 69 | return wrapper; 70 | }; 71 | }]); -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.loadNpmTasks("grunt-bump"); 3 | grunt.loadNpmTasks("grunt-contrib-clean"); 4 | grunt.loadNpmTasks("grunt-contrib-concat"); 5 | grunt.loadNpmTasks("grunt-contrib-jshint"); 6 | grunt.loadNpmTasks("grunt-contrib-uglify"); 7 | grunt.loadNpmTasks("grunt-contrib-watch"); 8 | grunt.loadNpmTasks("grunt-karma"); 9 | grunt.loadNpmTasks("grunt-ngmin"); 10 | 11 | grunt.initConfig({ 12 | config: { 13 | name: "angular-debounce", 14 | e2ePort: 9000 15 | }, 16 | 17 | jshint: { 18 | lib: { 19 | options: { 20 | jshintrc: ".jshintrc" 21 | }, 22 | files: { 23 | src: ["src/**.js"] 24 | } 25 | }, 26 | test: { 27 | options: { 28 | jshintrc: ".jshintrc-test" 29 | }, 30 | files: { 31 | src: ["test/*{,/*}.js"] 32 | } 33 | } 34 | }, 35 | 36 | concat: { 37 | dist: { 38 | files: { 39 | "dist/<%= config.name %>.js": ["src/*.js"] 40 | } 41 | } 42 | }, 43 | 44 | uglify: { 45 | dist: { 46 | files: { 47 | "dist/<%= config.name %>.min.js": "dist/<%= config.name %>.js" 48 | } 49 | } 50 | }, 51 | 52 | clean: { 53 | all: ["dist"] 54 | }, 55 | 56 | watch: { 57 | all: { 58 | files: ["src/**.js", "test/*{,/*}"], 59 | tasks: ["build", "karma:unit:run"] 60 | } 61 | }, 62 | 63 | ngmin: { 64 | dist: { 65 | files: { 66 | "dist/<%= config.name %>.js": "dist/<%= config.name %>.js" 67 | } 68 | } 69 | }, 70 | 71 | bump: { 72 | options: { 73 | files: ["package.json", "bower.json"], 74 | commitFiles: ["-a"], 75 | pushTo: "origin" 76 | } 77 | }, 78 | 79 | karma: { 80 | unit: { 81 | configFile: "test/configs/unit.conf.js", 82 | browsers: ["Chrome"], 83 | background: true 84 | }, 85 | unitci_firefox: { 86 | configFile: "test/configs/unit.conf.js", 87 | browsers: ["Firefox", "PhantomJS"], 88 | singleRun: true 89 | } 90 | } 91 | }); 92 | 93 | grunt.registerTask("default", ["test"]); 94 | grunt.registerTask("build", ["clean", "jshint", "concat", "ngmin", "uglify"]); 95 | grunt.registerTask("test", ["build", "karma:unit", "watch:all"]); 96 | grunt.registerTask("ci", ["build", "karma:unitci_firefox"]); 97 | }; 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-debounce 2 | 3 | > Tiny debouncing function for Angular.JS 4 | 5 | Debouncing is a form of rate-limiting: it prevents rapid-firing of events. You 6 | can use this to throttle calls to an autocomplete API: call a function multiple 7 | times and it won't get called more than once during the time interval you 8 | specify. 9 | 10 | ## Usage 11 | Add angular-debounce to your project: 12 | 13 | ``` 14 | bower install --save angular-debounce 15 | ``` 16 | 17 | Add it to your HTML file: 18 | 19 | ```html 20 | 21 | ``` 22 | 23 | Reference it as a dependency for your app module: 24 | 25 | ```js 26 | angular.module('myApp', ['rt.debounce']); 27 | ``` 28 | 29 | Use it: 30 | 31 | ```js 32 | angular.module('myApp').controller('testCtrl', function (debounce) { 33 | // Inject through "debounce". 34 | 35 | // Creates a function that will only get called once every 2 seconds: 36 | var fn = debounce(2000, function () { 37 | // Do things here. 38 | }); 39 | 40 | // Call it a couple of times, will only invoke the wrapped function 41 | // 2 seconds after the last invocation: 42 | fn(); 43 | fn(); 44 | fn(); 45 | 46 | // Want to stop waiting and send out the call immediately? Flush it! 47 | fn.flush(); 48 | 49 | // Want to stop waiting and send out the call immediately if any was made? 50 | // Flush pending calls! 51 | fn.flushPending(); 52 | 53 | // No longer care about it? Cancel is supported. 54 | fn.cancel(); 55 | }); 56 | ``` 57 | 58 | Repeatedly calling `fn()` will postpone indefinitely. Pass a third `true` parameter to also fire intermediate calls: 59 | 60 | ```js 61 | var fn = debounce(2000, function () { 62 | // Do things here. 63 | }, true); 64 | ``` 65 | 66 | ## License 67 | 68 | (The MIT License) 69 | 70 | Copyright (C) 2014 by Ruben Vermeersch 71 | 72 | Permission is hereby granted, free of charge, to any person obtaining a copy 73 | of this software and associated documentation files (the "Software"), to deal 74 | in the Software without restriction, including without limitation the rights 75 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 76 | copies of the Software, and to permit persons to whom the Software is 77 | furnished to do so, subject to the following conditions: 78 | 79 | The above copyright notice and this permission notice shall be included in 80 | all copies or substantial portions of the Software. 81 | 82 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 83 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 84 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 85 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 86 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 87 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 88 | THE SOFTWARE. 89 | -------------------------------------------------------------------------------- /test/debounce.js: -------------------------------------------------------------------------------- 1 | describe('Debounce', function () { 2 | var $timeout = null; 3 | var debounce = null; 4 | var calls = 0; 5 | var throttleCalls = 0; 6 | var fn = null; 7 | var throttleFn = null; 8 | 9 | beforeEach(function () { 10 | return module('rt.debounce'); 11 | }); 12 | 13 | beforeEach(inject(function (_debounce_, _$timeout_) { 14 | $timeout = _$timeout_; 15 | debounce = _debounce_; 16 | calls = 0; 17 | throttleCalls = 0; 18 | fn = debounce(100, function () { 19 | calls += 1; 20 | return calls; 21 | }); 22 | 23 | throttleFn = debounce(100, function () { 24 | throttleCalls += 1; 25 | return throttleCalls; 26 | }, { throttle: true }); 27 | })); 28 | 29 | afterEach(function () { 30 | $timeout.verifyNoPendingTasks(); 31 | }); 32 | 33 | it('Can debounce', function () { 34 | assert.equal(calls, 0); 35 | fn(); 36 | assert.equal(calls, 0); 37 | $timeout.flush(100); 38 | assert.equal(calls, 1); 39 | }); 40 | 41 | it('Can throttle', function () { 42 | assert.equal(throttleCalls, 0); 43 | throttleFn(); 44 | assert.equal(throttleCalls, 0); 45 | $timeout.flush(100); 46 | assert.equal(throttleCalls, 1); 47 | throttleFn(); 48 | $timeout.flush(50); 49 | throttleFn(); 50 | $timeout.flush(51); 51 | assert.equal(throttleCalls, 2); 52 | $timeout.flush(100); 53 | assert.equal(throttleCalls, 2); 54 | }); 55 | 56 | it('Will requeue calls', function () { 57 | fn(); 58 | $timeout.flush(50); 59 | assert.equal(calls, 0); 60 | fn(); 61 | assert.equal(calls, 0); 62 | $timeout.flush(50); 63 | assert.equal(calls, 0); 64 | $timeout.flush(50); 65 | assert.equal(calls, 1); 66 | }); 67 | 68 | it('Resets after execute', function () { 69 | fn(); 70 | $timeout.flush(100); 71 | fn(); 72 | $timeout.flush(100); 73 | assert.equal(calls, 2); 74 | }); 75 | 76 | it('Resets throttle after execute', function () { 77 | throttleFn(); 78 | $timeout.flush(100); 79 | throttleFn(); 80 | $timeout.flush(100); 81 | assert.equal(throttleCalls, 2); 82 | }); 83 | 84 | it('Flushes pending calls', function () { 85 | fn(); 86 | fn.flush(); 87 | assert.equal(calls, 1); 88 | }); 89 | 90 | it('Flushes pending throttle calls', function () { 91 | throttleFn(); 92 | throttleFn.flush(); 93 | assert.equal(throttleCalls, 1); 94 | }); 95 | 96 | it('Returns the result of a flushed call', function () { 97 | fn(); 98 | var result = fn.flush(); 99 | assert.equal(result, 1); 100 | }); 101 | 102 | it('Returns the result of a flushed throttled call', function () { 103 | throttleFn(); 104 | var result = throttleFn.flush(); 105 | assert.equal(result, 1); 106 | }); 107 | 108 | it('Does not execute again in flush unless needed', function () { 109 | fn(); 110 | $timeout.flush(100); 111 | assert.equal(calls, 1); 112 | var result = fn.flush(); 113 | assert.equal(result, 1); 114 | assert.equal(calls, 1); 115 | }); 116 | 117 | it('Does not execute again in throttle flush unless needed', function () { 118 | throttleFn(); 119 | $timeout.flush(100); 120 | assert.equal(throttleCalls, 1); 121 | var result = throttleFn.flush(); 122 | assert.equal(result, 1); 123 | assert.equal(throttleCalls, 1); 124 | }); 125 | 126 | it('Returns result of a flush even if never called', function () { 127 | var result = fn.flush(); 128 | assert.equal(result, 1); 129 | assert.equal(calls, 1); 130 | }); 131 | 132 | it('Returns result of a flush even if never called', function () { 133 | var result = throttleFn.flush(); 134 | assert.equal(result, 1); 135 | assert.equal(throttleCalls, 1); 136 | }); 137 | 138 | it('Does not execute the callback if cancel is called on the wrapper', function () { 139 | assert.equal(calls, 0); 140 | fn(); 141 | fn.cancel(); 142 | $timeout.flush(100); 143 | assert.equal(calls, 0); 144 | }); 145 | 146 | it('Does not execute the callback if cancel is called on the throttle wrapper', function () { 147 | assert.equal(throttleCalls, 0); 148 | throttleFn(); 149 | throttleFn.cancel(); 150 | $timeout.flush(100); 151 | assert.equal(throttleCalls, 0); 152 | }); 153 | 154 | it('Does not execute the callback if no calls were made when calling flushPending', function () { 155 | assert.equal(calls, 0); 156 | fn.flushPending(); 157 | assert.equal(calls, 0); 158 | }); 159 | 160 | it('Does not execute the callback if no calls were made when calling flushPending with throttle option', function () { 161 | assert.equal(throttleCalls, 0); 162 | throttleFn.flushPending(); 163 | assert.equal(throttleCalls, 0); 164 | }); 165 | 166 | it('Flushes pending calls when calling flushPending', function () { 167 | assert.equal(calls, 0); 168 | fn(); 169 | fn.flushPending(); 170 | assert.equal(calls, 1); 171 | }); 172 | 173 | it('Flushes pending calls when calling flushPending with throttle option', function () { 174 | assert.equal(throttleCalls, 0); 175 | throttleFn(); 176 | throttleFn.flushPending(); 177 | assert.equal(throttleCalls, 1); 178 | }); 179 | 180 | it('Returns the result of a flushPending call', function () { 181 | fn(); 182 | var result = fn.flushPending(); 183 | assert.equal(result, 1); 184 | }); 185 | 186 | it('Returns the result of a flushPending call with throttle option', function () { 187 | throttleFn(); 188 | var result = throttleFn.flushPending(); 189 | assert.equal(result, 1); 190 | }); 191 | }); 192 | --------------------------------------------------------------------------------