├── .editorconfig ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── README.md ├── bower.json ├── dist ├── angular-http-decelerator.js └── angular-http-decelerator.min.js ├── karma-unit.conf.js ├── package.json ├── src └── httpDecelerator │ ├── httpDecelerator.js │ ├── httpDecelerator.prefix │ ├── httpDecelerator.suffix │ └── services │ └── httpDecelerator.js └── test ├── app ├── index.html └── sampleApp │ ├── businessCard.directive.js │ ├── businessCard.html │ ├── introText.attribute.js │ ├── main.controller.js │ ├── phoneNumber.filter.js │ └── sampleApp.css └── unit └── httpDecelerator └── httpDeceleratorSpec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ._* 2 | .~lock.* 3 | .buildpath 4 | .DS_Store 5 | .idea 6 | .project 7 | .settings 8 | 9 | # Ignore node stuff 10 | node_modules/ 11 | npm-debug.log 12 | libpeerconnection.log 13 | 14 | # OS-specific 15 | .DS_Store 16 | 17 | # Bower components 18 | bower_components -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "es5": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "white": true 22 | } 23 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | library: grunt.file.readJSON('bower.json'), 6 | concat: { 7 | options: { 8 | separator: '' 9 | }, 10 | library: { 11 | src: [ 12 | 'src/httpDecelerator/httpDecelerator.prefix', 13 | 'src/httpDecelerator/httpDecelerator.js', 14 | 'src/httpDecelerator/services/**/*.js', 15 | 'src/httpDecelerator/httpDecelerator.suffix' 16 | ], 17 | dest: 'dist/angular-http-decelerator.js' 18 | } 19 | }, 20 | uglify: { 21 | options: { 22 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' 23 | }, 24 | library: { 25 | files: { 26 | 'dist/angular-http-decelerator.min.js': ['<%= concat.library.dest %>'] 27 | } 28 | } 29 | }, 30 | jshint: { 31 | beforeConcat: { 32 | src: ['gruntfile.js', 'httpDecelerator/**/*.js'] 33 | }, 34 | afterConcat: { 35 | src: [ 36 | '<%= concat.library.dest %>' 37 | ] 38 | }, 39 | options: { 40 | // options here to override JSHint defaults 41 | globals: { 42 | jQuery: true, 43 | console: true, 44 | module: true, 45 | document: true, 46 | angular: true 47 | }, 48 | globalstrict: false 49 | } 50 | }, 51 | watch: { 52 | options: { 53 | livereload: true 54 | }, 55 | files: [ 56 | 'Gruntfile.js', 57 | 'src/**/*' 58 | ], 59 | tasks: ['default'] 60 | } 61 | }); 62 | 63 | grunt.loadNpmTasks('grunt-contrib-uglify'); 64 | grunt.loadNpmTasks('grunt-contrib-jshint'); 65 | grunt.loadNpmTasks('grunt-contrib-concat'); 66 | grunt.loadNpmTasks('grunt-contrib-watch'); 67 | 68 | grunt.registerTask('default', ['jshint:beforeConcat', 'concat', 'jshint:afterConcat', 'uglify']); 69 | grunt.registerTask('livereload', ['default', 'watch']); 70 | 71 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTP decelerator for AngularJS 2 | 3 | A lightweight HTTP interceptor for AngularJS that allows you to slow down HTTP responses provided by `$http`. 4 | 5 | Features: 6 | 7 | - Plug and play: requires no changes to your code! 8 | - Supports milliseconds e.g. slow down HTTP responses by 2000ms 9 | - Supports percentages e.g. slow down HTTP responses by 300% 10 | - Logs deceleration to the console for debugging purposes 11 | 12 | ## Why use it? 13 | 14 | During development of your AngularJS application it is often hard to see how your application deals with slow HTTP responses. 15 | 16 | Example: a loader shown when a form is posting data is not working correctly but you can't see it because the response gets in too quickly and the loader is hidden too fast. 17 | 18 | HTTP decelerator conveniently allows you to simulate a slow connection without changing your own code so you can control the speed of the responses. 19 | 20 | ## Installation 21 | 22 | First install the package using Bower: 23 | 24 | ```sh 25 | $ bower install angular-http-decelerator 26 | ``` 27 | 28 | then include the file in your scripts: 29 | 30 | ```html 31 | 32 | ``` 33 | 34 | and finally add the module to your AngularJS application: 35 | 36 | ```javascript 37 | angular.module('yourApp', ['httpDecelerator']) 38 | ``` 39 | 40 | ## Decelerate requests 41 | 42 | To decelerate requests, add `httpDecelerator` to `$httpProvider.interceptors` like this: 43 | 44 | ```javascript 45 | angular.module('yourApp') 46 | .config([ '$httpProvider', function ($httpProvider) { 47 | $httpProvider.interceptors.push(['httpDecelerator', function(httpDecelerator){ 48 | return httpDecelerator(); 49 | }]); 50 | }]); 51 | ``` 52 | 53 | By default, HTTP decelerator will decelerate all HTTP responses by 1000ms, but you can easily change this behavior. 54 | 55 | Use milliseconds: 56 | 57 | ```javascript 58 | $httpProvider.interceptors.push(['httpDecelerator', function(httpDecelerator){ 59 | 60 | // Decelerate responses by 500ms 61 | // A response that originally takes 80ms, will now take 580ms 62 | // A response that originally takes 150ms, will now take 650ms 63 | return httpDecelerator(500); 64 | 65 | // You can also use a string with 'ms' for easier reading 66 | return httpDecelerator('500ms'); 67 | }]); 68 | ``` 69 | 70 | Use percentages: 71 | 72 | ```javascript 73 | $httpProvider.interceptors.push(['httpDecelerator', function(httpDecelerator){ 74 | 75 | // Decelerate responses by 300% 76 | // A response that originally takes 80ms, will now take 320ms (80ms + (300% * 80ms)) 77 | // A response that originally takes 150ms, will now take 600ms (150ms + (300% * 150ms)) 78 | return httpDecelerator('300%'); 79 | }]); 80 | ``` 81 | 82 | Optionally, you can include a filter to only decelerate certain routes: 83 | 84 | ```javascript 85 | $httpProvider.interceptors.push(['httpDecelerator', function(httpDecelerator){ 86 | return httpDecelerator(1000, 'api/users'); // This will only decelerate routes which match the string 'api/users' 87 | }]); 88 | ``` 89 | 90 | This parameter is matched using [`String.prototype.search`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search), so you can use a string, number or regular expression. 91 | 92 | ## History 93 | 94 | ### v2.1.0 95 | 96 | - Added url filtering 97 | 98 | ### v2.0.0 99 | 100 | - Refactor distribution names for better compatibility with official angular packages 101 | 102 | ### v1.1.0 103 | 104 | - Add support for percentages 105 | - Add support for strings with 'ms' for easier reading 106 | 107 | ### v1.0.0 108 | 109 | - Used in production so bump to v1.0.0 110 | 111 | ### v0.3.0 112 | 113 | - Refactor as provider 114 | - Add more detailed logging 115 | 116 | ### v0.2.0 117 | 118 | - Add deceleration logging 119 | 120 | ### v0.1.0 121 | 122 | - Initial working version 123 | 124 | ## License 125 | MIT -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-http-decelerator", 3 | "version": "2.0.0", 4 | "dependencies": { 5 | "angular": "~1.2.x" 6 | }, 7 | "devDependencies": { 8 | "angular-mocks": "~1.2.x", 9 | "angular-scenario": "~1.2.x" 10 | }, 11 | "ignore": [ 12 | "**/.*", 13 | "node_modules", 14 | "bower_components", 15 | "test", 16 | "tests", 17 | "src", 18 | "Gruntfile.js", 19 | "karma-unit.conf.js", 20 | "package.json" 21 | ] 22 | } 23 | 24 | -------------------------------------------------------------------------------- /dist/angular-http-decelerator.js: -------------------------------------------------------------------------------- 1 | (function(window, document, angular) { 2 | 3 | // Create all modules and define dependencies to make sure they exist 4 | // and are loaded in the correct order to satisfy dependency injection 5 | // before all nested files are concatenated by Grunt 6 | 7 | // Config 8 | angular.module('httpDecelerator.config', []) 9 | .value('httpDeceleratorConfig', { 10 | debug: true 11 | }); 12 | 13 | // Modules 14 | angular.module('httpDecelerator.services', []); 15 | angular.module('httpDecelerator', 16 | [ 17 | 'httpDecelerator.config', 18 | 'httpDecelerator.services' 19 | ]); 20 | angular.module('httpDecelerator.services') 21 | .provider('httpDecelerator', [function () { 22 | 23 | // Default options 24 | var options = { 25 | deceleration: 1000, 26 | route: '' // A filter. Only routes which match this string will be decelerated 27 | }; 28 | self = this; 29 | 30 | // Return true if the url is one of the ones we're supposed to decelerate 31 | skipDeceleration = function (config) { 32 | if(!config || !config.url) { return true; } 33 | if(config.url.search(options.route) === -1) { 34 | console.log('skipDeceleration: ' + config.url + ' doesn\'t contain ' + options.route); 35 | return true; 36 | } 37 | return false; 38 | }; 39 | 40 | /** 41 | * Provider convenience method to get or set default deceleration 42 | * 43 | * @param deceleration 44 | * @returns {*} 45 | */ 46 | this.deceleration = function(deceleration, route){ 47 | if(angular.isDefined(deceleration)){ 48 | options.deceleration = deceleration; 49 | return this; 50 | } 51 | return options.deceleration; 52 | }; 53 | 54 | /** 55 | * Provider convenience method to get or set default route filter 56 | * 57 | * @param route 58 | * @returns {*} 59 | */ 60 | this.route = function(route){ 61 | if(angular.isDefined(route)){ 62 | options.route = route; 63 | return this; 64 | } 65 | return options.route; 66 | }; 67 | 68 | this.decelerate = function(ms){ 69 | var deceleration = this.deceleration().toString(), 70 | percentage = 0; 71 | if(deceleration.indexOf('%') !== -1){ 72 | percentage = parseInt(deceleration.split('%')[0], 10); 73 | return Math.round(ms * percentage / 100); 74 | } 75 | if(deceleration.indexOf('ms') !== -1){ 76 | return parseInt(deceleration.split('ms')[0], 10); 77 | } 78 | return parseInt(deceleration, 10); 79 | }; 80 | 81 | /** 82 | * Factory method 83 | * 84 | * @param $timeout 85 | * @param $rootScope 86 | * @returns {HttpDecelerator} 87 | */ 88 | this.$get = function ($q, $log, $timeout) { 89 | 90 | function HttpDeceleratorInterceptor() { 91 | 92 | return { 93 | 'request': function(config) { 94 | if(skipDeceleration(config)) { return config; } 95 | $log.log('HttpDecelerator request'); 96 | config.start = new Date(); 97 | return config; 98 | }, 99 | 'response': function(response) { 100 | if(skipDeceleration(response.config)) { return response; } 101 | console.dir(response); 102 | response.config.end = new Date(); 103 | response.config.time = response.config.end - response.config.start; 104 | $log.log('HttpDecelerator: decelerate response from ' + response.config.url + ' by ' + self.decelerate(response.config.time) + 'ms'); 105 | return $timeout(function(){ 106 | return response; 107 | }, self.decelerate(response.config.time)); 108 | }, 109 | 'responseError': function(rejection) { 110 | if(skipDeceleration(rejection.config)) { return rejection; } 111 | $log.log('HttpDecelerator: decelerate response error by ' + self.deceleration() + 'ms'); 112 | console.dir(rejection); 113 | return $timeout(function(){ 114 | return $q.reject(rejection); 115 | }, self.decelerate(rejection.config.time)); 116 | } 117 | }; 118 | 119 | } 120 | 121 | return function(deceleration, route){ 122 | self.deceleration(deceleration); 123 | self.route(route); 124 | return new HttpDeceleratorInterceptor(); 125 | }; 126 | 127 | }; 128 | 129 | this.$get.$inject = ['$q', '$log', '$timeout']; 130 | 131 | 132 | }]);})(window, document, angular); -------------------------------------------------------------------------------- /dist/angular-http-decelerator.min.js: -------------------------------------------------------------------------------- 1 | /*! angular-http-decelerator 26-10-2014 */ 2 | (function(e,t,r){r.module("httpDecelerator.config",[]).value("httpDeceleratorConfig",{debug:!0}),r.module("httpDecelerator.services",[]),r.module("httpDecelerator",["httpDecelerator.config","httpDecelerator.services"]),r.module("httpDecelerator.services").provider("httpDecelerator",[function(){var e={deceleration:1e3,route:""};self=this,skipDeceleration=function(t){return t&&t.url?-1===t.url.search(e.route)?(console.log("skipDeceleration: "+t.url+"doesn't contain "+e.route),!0):!1:!0},this.deceleration=function(t){return r.isDefined(t)?(e.deceleration=t,this):e.deceleration},this.route=function(t){return r.isDefined(t)?(e.route=t,this):e.route},this.decelerate=function(e){var t=""+this.deceleration(),r=0;return-1!==t.indexOf("%")?(r=parseInt(t.split("%")[0],10),Math.round(e*r/100)):-1!==t.indexOf("ms")?parseInt(t.split("ms")[0],10):parseInt(t,10)},this.$get=function(e,t,r){function n(){return{request:function(e){return skipDeceleration(e)?e:(t.log("HttpDecelerator request"),e.start=new Date,e)},response:function(e){return skipDeceleration(e.config)?e:(console.dir(e),e.config.end=new Date,e.config.time=e.config.end-e.config.start,t.log("HttpDecelerator: decelerate response from "+e.config.url+" by "+self.decelerate(e.config.time)+"ms"),r(function(){return e},self.decelerate(e.config.time)))},responseError:function(n){return skipDeceleration(n.config)?n:(t.log("HttpDecelerator: decelerate response error by "+self.deceleration()+"ms"),console.dir(n),r(function(){return e.reject(n)},self.decelerate(n.config.time)))}}}return function(e,t){return self.deceleration(e),self.route(t),new n}},this.$get.$inject=["$q","$log","$timeout"]}])})(window,document,angular); -------------------------------------------------------------------------------- /karma-unit.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | 4 | 5 | // base path, that will be used to resolve files and exclude 6 | basePath: '', 7 | 8 | frameworks: ['mocha', 'chai-jquery', 'jquery-1.8.3', 'sinon-chai'], 9 | 10 | plugins: [ 11 | 'karma-mocha', 12 | 'karma-chai', 13 | 'karma-sinon-chai', 14 | 'karma-chrome-launcher', 15 | 'karma-phantomjs-launcher', 16 | 'karma-jquery', 17 | 'karma-chai-jquery' 18 | ], 19 | 20 | // list of files / patterns to load in the browser 21 | files: [ 22 | // Skip jQuery because it is loaded by karma-jquery 23 | // 'bower_components/jquery/dist/jquery.js', 24 | 'bower_components/angular/angular.js', 25 | 'bower_components/angular-mocks/angular-mocks.js', 26 | 'dist/angular-http-decelerator.js', 27 | 'test/unit/httpDecelerator/**/*Spec.js' 28 | ], 29 | 30 | // list of files to exclude 31 | exclude: [], 32 | 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress', 'junit' 36 | reporters: ['progress'], 37 | 38 | 39 | // web server port 40 | port: 9876, 41 | 42 | 43 | // cli runner port 44 | runnerPort: 9100, 45 | 46 | 47 | // enable / disable colors in the output (reporters and logs) 48 | colors: true, 49 | 50 | 51 | // level of logging 52 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 53 | logLevel: config.LOG_INFO, 54 | 55 | 56 | // enable / disable watching file and executing tests whenever any file changes 57 | autoWatch: true, 58 | 59 | 60 | // Start these browsers, currently available: 61 | // - Chrome 62 | // - ChromeCanary 63 | // - Firefox 64 | // - Opera 65 | // - Safari (only Mac) 66 | // - PhantomJS 67 | // - IE (only Windows) 68 | // browsers: ['Chrome'], 69 | browsers: ['PhantomJS'], 70 | 71 | 72 | // If browser does not capture in given timeout [ms], kill it 73 | captureTimeout: 60000, 74 | 75 | 76 | // Continuous Integration mode 77 | // if true, it capture browsers, run tests and exit 78 | singleRun: false 79 | 80 | }); 81 | }; 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-http-decelerator", 3 | "version": "2.0.0", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "grunt": "~0.4.1", 7 | "grunt-contrib-jshint": "~0.1.1", 8 | "grunt-contrib-concat": "~0.1.2", 9 | "grunt-contrib-uglify": "~0.1.1", 10 | "grunt-contrib-watch": "~0.4.3", 11 | "grunt-contrib-less": "~0.6.2", 12 | "karma-phantomjs-launcher": "~0.1.2", 13 | "mocha": "~1.17.1", 14 | "phantomjs": "~1.9.1-0", 15 | "karma": "~0.10.9", 16 | "karma-script-launcher": "~0.1.0", 17 | "karma-chrome-launcher": "~0.1.2", 18 | "karma-firefox-launcher": "~0.1.3", 19 | "karma-mocha": "~0.1.1", 20 | "chai": "~1.9.0", 21 | "sinon": "~1.9.0", 22 | "sinon-chai": "~2.5.0", 23 | "karma-chai": "~0.1.0", 24 | "karma-sinon-chai": "~0.1.4", 25 | "karma-jquery": "^0.1.0", 26 | "chai-jquery": "^1.2.1", 27 | "karma-chai-jquery": "^1.0.0", 28 | "grunt-conventional-changelog": "^1.1.0" 29 | }, 30 | "engines": { 31 | "node": ">=0.8.0" 32 | } 33 | } -------------------------------------------------------------------------------- /src/httpDecelerator/httpDecelerator.js: -------------------------------------------------------------------------------- 1 | // Create all modules and define dependencies to make sure they exist 2 | // and are loaded in the correct order to satisfy dependency injection 3 | // before all nested files are concatenated by Grunt 4 | 5 | // Config 6 | angular.module('httpDecelerator.config', []) 7 | .value('httpDeceleratorConfig', { 8 | debug: true 9 | }); 10 | 11 | // Modules 12 | angular.module('httpDecelerator.services', []); 13 | angular.module('httpDecelerator', 14 | [ 15 | 'httpDecelerator.config', 16 | 'httpDecelerator.services' 17 | ]); 18 | -------------------------------------------------------------------------------- /src/httpDecelerator/httpDecelerator.prefix: -------------------------------------------------------------------------------- 1 | (function(window, document, angular) { 2 | 3 | -------------------------------------------------------------------------------- /src/httpDecelerator/httpDecelerator.suffix: -------------------------------------------------------------------------------- 1 | })(window, document, angular); -------------------------------------------------------------------------------- /src/httpDecelerator/services/httpDecelerator.js: -------------------------------------------------------------------------------- 1 | angular.module('httpDecelerator.services') 2 | .provider('httpDecelerator', [function () { 3 | 4 | // Default options 5 | var options = { 6 | deceleration: 1000, 7 | route: '' // A filter. Only routes which match this string will be decelerated 8 | }; 9 | var self = this; 10 | 11 | // Return true if the url is one of the ones we're supposed to decelerate 12 | skipDeceleration = function (config) { 13 | if(!config || !config.url) { return true; } 14 | if(config.url.search(options.route) === -1) { 15 | console.log('skipDeceleration: ' + config.url + 'doesn\'t contain ' + options.route); 16 | return true; 17 | } 18 | return false; 19 | }; 20 | 21 | /** 22 | * Provider convenience method to get or set default deceleration 23 | * 24 | * @param deceleration 25 | * @returns {*} 26 | */ 27 | this.deceleration = function(deceleration, route){ 28 | if(angular.isDefined(deceleration)){ 29 | options.deceleration = deceleration; 30 | return this; 31 | } 32 | return options.deceleration; 33 | }; 34 | 35 | /** 36 | * Provider convenience method to get or set default route filter 37 | * 38 | * @param route 39 | * @returns {*} 40 | */ 41 | this.route = function(route){ 42 | if(angular.isDefined(route)){ 43 | options.route = route; 44 | return this; 45 | } 46 | return options.route; 47 | }; 48 | 49 | this.decelerate = function(ms){ 50 | var deceleration = this.deceleration().toString(), 51 | percentage = 0; 52 | if(deceleration.indexOf('%') !== -1){ 53 | percentage = parseInt(deceleration.split('%')[0], 10); 54 | return Math.round(ms * percentage / 100); 55 | } 56 | if(deceleration.indexOf('ms') !== -1){ 57 | return parseInt(deceleration.split('ms')[0], 10); 58 | } 59 | return parseInt(deceleration, 10); 60 | }; 61 | 62 | /** 63 | * Factory method 64 | * 65 | * @param $timeout 66 | * @param $rootScope 67 | * @returns {HttpDecelerator} 68 | */ 69 | this.$get = function ($q, $log, $timeout) { 70 | 71 | function HttpDeceleratorInterceptor() { 72 | 73 | return { 74 | 'request': function(config) { 75 | if(skipDeceleration(config)) { return config; } 76 | $log.log('HttpDecelerator request'); 77 | config.start = new Date(); 78 | return config; 79 | }, 80 | 'response': function(response) { 81 | if(skipDeceleration(response.config)) { return response; } 82 | console.dir(response); 83 | response.config.end = new Date(); 84 | response.config.time = response.config.end - response.config.start; 85 | $log.log('HttpDecelerator: decelerate response from ' + response.config.url + ' by ' + self.decelerate(response.config.time) + 'ms'); 86 | return $timeout(function(){ 87 | return response; 88 | }, self.decelerate(response.config.time)); 89 | }, 90 | 'responseError': function(rejection) { 91 | if(skipDeceleration(rejection.config)) { return rejection; } 92 | $log.log('HttpDecelerator: decelerate response error by ' + self.deceleration() + 'ms'); 93 | console.dir(rejection); 94 | return $timeout(function(){ 95 | return $q.reject(rejection); 96 | }, self.decelerate(rejection.config.time)); 97 | } 98 | }; 99 | 100 | } 101 | 102 | return function(deceleration, route){ 103 | self.deceleration(deceleration); 104 | self.route(route); 105 | return new HttpDeceleratorInterceptor(); 106 | }; 107 | 108 | }; 109 | 110 | this.$get.$inject = ['$q', '$log', '$timeout']; 111 | 112 | 113 | }]); -------------------------------------------------------------------------------- /test/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample Application with The WatchWatcher 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |

Sample Rolodex App

16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 |
39 | 40 | 41 |
42 | Add Fake Card 43 | 44 |
45 | 46 |
47 | 48 |
49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /test/app/sampleApp/businessCard.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('sampleApp').directive('businessCard', [ '$http', function($http){ 2 | return { 3 | restrict: 'E', 4 | scope: { person: '=' }, 5 | templateUrl: 'sampleApp/businessCard.html', 6 | link: function (scope) { 7 | 8 | // Hit a random API, just to try it. 9 | // http://docs.ckan.org/en/latest/api/index.html 10 | $http.get('http://demo.ckan.org/api/3/action/package_list'); 11 | $http.get('http://demo.ckan.org/api/3/action/package_search?q=spending'); 12 | 13 | } 14 | }; 15 | }]); -------------------------------------------------------------------------------- /test/app/sampleApp/businessCard.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | https://github.com/flores/moarcats 6 |
7 |

{{person.firstName}} {{person.lastName}}

8 |

{{person.email}}

9 |

{{person.phone | phoneNumber}}

10 |

{{person.address1}}

11 |

{{person.postalCode}}

12 |
13 | -------------------------------------------------------------------------------- /test/app/sampleApp/introText.attribute.js: -------------------------------------------------------------------------------- 1 | angular.module('sampleApp').directive('introText', function(){ 2 | return { 3 | restrict: 'A', 4 | link: function (scope, el, attrs) { 5 | 6 | var margin = 10; 7 | 8 | setTimeout(showTip, 2000); 9 | 10 | function showTip() { 11 | var tip = angular.element("

" + attrs.introText + "

"); 12 | // tip.css( 'top', el.offset().top + el.height() ); 13 | // tip.css( 'right', el.offset().top + el.height() ); 14 | tip.css( 'top', '-100px'); 15 | 16 | 17 | angular.element(document.body).append(tip); 18 | angular.element(window).on('click', function(){ 19 | tip.remove(); 20 | }); 21 | 22 | setTimeout(function(){ 23 | if (el[0].style.top) { 24 | var newTop = parseFloat(el[0].style.top) + el[0].offsetHeight + margin; 25 | tip.css( 'top', newTop + 'px' ); 26 | } else { 27 | var newBot = parseFloat(el[0].style.bottom) + el[0].offsetHeight; 28 | tip.css( 'bottom', newBot + 'px' ); 29 | } 30 | 31 | if (el[0].style.right) { 32 | var newRight = parseFloat(el[0].style.right); 33 | tip.css( 'right', newRight + 'px' ); 34 | } else { 35 | var newLeft = parseFloat(el[0].style.left); 36 | tip.css( 'left', newLeft + 'px'); 37 | } 38 | }, 1) 39 | 40 | tip.on('click', function(){ 41 | this.remove(); 42 | }); 43 | } 44 | 45 | 46 | } 47 | }; 48 | }); -------------------------------------------------------------------------------- /test/app/sampleApp/main.controller.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('sampleApp').controller('MainController', function($scope, $interval){ 3 | 4 | 5 | // Sample Application Stuff, not part of the Watch Watcher 6 | $scope.rolodex = [ 7 | { 8 | firstName: 'Gordon', 9 | lastName: 'Sumner', 10 | email: 'sting@thePolice.com', 11 | phone: 1238675309, 12 | address1: '123 Sesame St', 13 | postalCode: '12345' 14 | }, 15 | { 16 | firstName: 'Frank', 17 | lastName: 'Zappa', 18 | email: 'franktank@spot.com', 19 | phone: 9887323423, 20 | address1: '123 Sesame St', 21 | postalCode: '25422' 22 | } 23 | ]; 24 | 25 | $scope.user = { 26 | firstName: 'Your', 27 | lastName: 'Name' 28 | }; 29 | 30 | $scope.saveCurrentUser = function () { 31 | $scope.rolodex.push( angular.copy($scope.user) ); 32 | $scope.user = {}; // start fresh 33 | $scope.userForm.$setPristine(); 34 | document.getElementsByTagName('input')[0].focus(); // start in a nice spot 35 | }; 36 | 37 | $scope.addFake = function(){ 38 | $scope.user = { 39 | firstName: 'Bob', 40 | lastName: 'Jones', 41 | email: 'bob@jones.com', 42 | phone: 1238675309, 43 | address1: '123 Sesame St', 44 | postalCode: Math.floor(Math.random() * 10000) 45 | } 46 | $scope.saveCurrentUser(); 47 | } 48 | 49 | }); -------------------------------------------------------------------------------- /test/app/sampleApp/phoneNumber.filter.js: -------------------------------------------------------------------------------- 1 | // http://stackoverflow.com/a/16699507/111243 2 | angular.module('sampleApp').filter('phoneNumber', function () { 3 | return function (tel) { 4 | if (!tel) { return ''; } 5 | 6 | var value = tel.toString().trim().replace(/^\+/, ''); 7 | 8 | if (value.match(/[^0-9]/)) { 9 | return tel; 10 | } 11 | 12 | var country, city, number; 13 | 14 | switch (value.length) { 15 | case 10: // +1PPP####### -> C (PPP) ###-#### 16 | country = 1; 17 | city = value.slice(0, 3); 18 | number = value.slice(3); 19 | break; 20 | 21 | case 11: // +CPPP####### -> CCC (PP) ###-#### 22 | country = value[0]; 23 | city = value.slice(1, 4); 24 | number = value.slice(4); 25 | break; 26 | 27 | case 12: // +CCCPP####### -> CCC (PP) ###-#### 28 | country = value.slice(0, 3); 29 | city = value.slice(3, 5); 30 | number = value.slice(5); 31 | break; 32 | 33 | default: 34 | return tel; 35 | } 36 | 37 | if (country == 1) { 38 | country = ""; 39 | } 40 | 41 | number = number.slice(0, 3) + '-' + number.slice(3); 42 | 43 | return (country + " (" + city + ") " + number).trim(); 44 | }; 45 | }); -------------------------------------------------------------------------------- /test/app/sampleApp/sampleApp.css: -------------------------------------------------------------------------------- 1 | html, body, p, h1, h2 { 2 | margin: 0; 3 | line-height: 1.5; 4 | } 5 | .content { 6 | padding: 25px 50px; 7 | max-width: 800px; 8 | margin: 0 auto; 9 | } 10 | img { 11 | max-width: 100%; 12 | } 13 | body, input { 14 | font-family: "helvetica neue", "Helvetica", sans-serif; 15 | font-size: 20px; 16 | } 17 | label { 18 | display: block; 19 | margin: 5px 0 -5px 0; 20 | color: #bbb; 21 | } 22 | form { 23 | width: 200px; 24 | float: left; 25 | margin: 0 25px 25px 0; 26 | } 27 | input { 28 | width: 100%; 29 | } 30 | .link { 31 | font-size: 14px; 32 | color: #369; 33 | } 34 | .businessCard { 35 | display: inline-block; 36 | position: relative; 37 | min-width: 175px; 38 | min-height: 100px; 39 | border: 1px solid #ddd; 40 | box-shadow: 1px 2px 2px rgba(0,0,0,.2); 41 | box-sizing: border-box; 42 | padding: 5px; 43 | padding-left: 60px; 44 | font-size: 11px; 45 | color: #666; 46 | margin-top: 10px; 47 | } 48 | .businessCard .name { 49 | color: black; 50 | font-weight: bold; 51 | } 52 | .businessCard .photo { 53 | position: absolute; 54 | top: 5px; left: 5px; 55 | width: 50px; 56 | height: 50px; 57 | } 58 | .businessCard p { 59 | margin-bottom: 5px; 60 | } 61 | 62 | input[type="submit"] { 63 | margin-top: 10px; 64 | } 65 | 66 | /* Validation Display */ 67 | input.ng-dirty.ng-invalid { 68 | background-color: rgba(255,50,0,.1); 69 | box-shadow: inset 0 0 0 2px rgba(255,50,0,.25); 70 | } 71 | input.ng-dirty.ng-valid { 72 | background-color: rgba(100,200,50,.1); 73 | box-shadow: inset 0 0 0 2px rgba(100,200,50,.5); 74 | } 75 | .muted { 76 | color: #999; 77 | } 78 | h1 .muted { 79 | font-size: 50%; 80 | } 81 | h1 { 82 | line-height: 1; 83 | margin-bottom: 20px; 84 | } 85 | nav { 86 | background-color:black; 87 | min-height: 15px; 88 | background: #444; 89 | color: white; 90 | padding: 10px; 91 | line-height: 15px; 92 | font-size: 13px; 93 | } -------------------------------------------------------------------------------- /test/unit/httpDecelerator/httpDeceleratorSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('httpDecelerator', function() { 4 | 5 | var dependencies = []; 6 | 7 | function hasModule(dependency) { 8 | return dependencies.indexOf(dependency) >= 0; 9 | } 10 | 11 | // Get module as variable to extract dependencies 12 | beforeEach(function() { 13 | dependencies = angular.module('httpDecelerator').requires; 14 | }); 15 | 16 | // Load module 17 | beforeEach(module('httpDecelerator')); 18 | 19 | it('should load config module', function() { 20 | expect(hasModule('httpDecelerator.config')).to.be.ok; 21 | }); 22 | 23 | it('should load services module', function() { 24 | expect(hasModule('httpDecelerator.services')).to.be.ok; 25 | }); 26 | 27 | }); 28 | --------------------------------------------------------------------------------