diff --git a/Gruntfile.js b/Gruntfile.js index abcfc5e..69d7e4f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -40,6 +40,7 @@ module.exports = function (grunt) { grunt.registerTask('workflow:dev', [ 'connect:dev', 'build', + 'test:dev', 'open:dev', 'watch:dev' ]); diff --git a/README.md b/README.md index 271fb79..14f3104 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Add the `ng.httpLoader` module as a dependency in your application: angular.module('demo', ['ng.httpLoader']) ``` -Whitelist the domains that you want the loader to show for: +Whitelist the external domains that you want the loader to show for: ```javascript .config([ @@ -54,6 +54,19 @@ Whitelist the domains that you want the loader to show for: ]) ``` +You can whitelist requests to the local server: + +```javascript +.config([ + 'httpMethodInterceptorProvider', + function (httpMethodInterceptorProvider) { + // ... + httpMethodInterceptorProvider.whitelistLocalRequests(); + // ... + } +]) +``` + Add an HTML element with the `ng-http-loader` directive. This will be displayed while requests are pending: diff --git a/app/package/js/angular-http-loader.js b/app/package/js/angular-http-loader.js index 1d739d3..3a50033 100644 --- a/app/package/js/angular-http-loader.js +++ b/app/package/js/angular-http-loader.js @@ -134,7 +134,8 @@ angular .module('ng.httpLoader.httpMethodInterceptor', []) .provider('httpMethodInterceptor', function () { - var domains = []; + var domains = [], + whitelistLocalRequests = false; /** * Add domains to the white list @@ -146,6 +147,13 @@ angular domains.push(domain); }; + /** + * White list requests to the local domain + */ + this.whitelistLocalRequests = function () { + whitelistLocalRequests = true; + }; + this.$get = [ '$q', '$rootScope', @@ -160,6 +168,12 @@ angular * @returns {boolean} */ var isUrlOnWhitelist = function (url) { + if (url.substring(0, 2) !== '//' && + url.indexOf('://') === -1 && + whitelistLocalRequests) { + return true; + } + for (var i = domains.length; i--;) { if (url.indexOf(domains[i]) !== -1) { return true; diff --git a/app/package/js/angular-http-loader.min.js b/app/package/js/angular-http-loader.min.js index d9d760a..8806dae 100644 --- a/app/package/js/angular-http-loader.min.js +++ b/app/package/js/angular-http-loader.min.js @@ -1 +1 @@ -angular.module("ng.httpLoader",["ng.httpLoader.httpMethodInterceptor"]).directive("ngHttpLoader",["$rootScope","$parse","$timeout",function(a,b,c){return{scope:{methods:"@",template:"@",title:"@",ttl:"@"},template:'
',link:function(d){var e=b(d.methods)()||d.methods;e=angular.isUndefined(e)?[]:e,e=angular.isArray(e)?e:[e],angular.forEach(e,function(a,b){e[b]=a.toUpperCase()});var f=b(d.ttl)()||d.ttl;f=angular.isUndefined(f)?0:f,f=1e3*Number(f),f=angular.isNumber(f)?f:0,Array.prototype.indexOf||(e.indexOf=function(a){for(var b=this.length;b--;)if(this[b]===a)return b;return-1}),d.showLoader=!1;var g,h=d.showLoader,i=function(a,b){return-1!==e.indexOf(b.toUpperCase())?h="loaderShow"===a.name:0===e.length&&(h="loaderShow"===a.name),0>=f||!g&&!h?void(d.showLoader=h):void(g||(d.showLoader=h,g=c(function(){h||(d.showLoader=h),g=void 0},f)))};a.$on("loaderShow",i),a.$on("loaderHide",i)}}}]),angular.module("ng.httpLoader.httpMethodInterceptor",[]).provider("httpMethodInterceptor",function(){var a=[];this.whitelistDomain=function(b){a.push(b)},this.$get=["$q","$rootScope",function(b,c){var d=0,e=function(b){for(var c=a.length;c--;)if(-1!==b.indexOf(a[c]))return!0;return!1},f=function(a){e(a.url)&&0===--d&&c.$emit("loaderHide",a.method)};return{request:function(a){return e(a.url)&&(d++,c.$emit("loaderShow",a.method)),a||b.when(a)},response:function(a){return f(a.config),a||b.when(a)},responseError:function(a){return f(a.config),b.reject(a)}}}]}).config(["$httpProvider",function(a){a.interceptors.push("httpMethodInterceptor")}]); \ No newline at end of file +angular.module("ng.httpLoader",["ng.httpLoader.httpMethodInterceptor"]).directive("ngHttpLoader",["$rootScope","$parse","$timeout",function(a,b,c){return{scope:{methods:"@",template:"@",title:"@",ttl:"@"},template:'
',link:function(d){var e=b(d.methods)()||d.methods;e=angular.isUndefined(e)?[]:e,e=angular.isArray(e)?e:[e],angular.forEach(e,function(a,b){e[b]=a.toUpperCase()});var f=b(d.ttl)()||d.ttl;f=angular.isUndefined(f)?0:f,f=1e3*Number(f),f=angular.isNumber(f)?f:0,Array.prototype.indexOf||(e.indexOf=function(a){for(var b=this.length;b--;)if(this[b]===a)return b;return-1}),d.showLoader=!1;var g,h=d.showLoader,i=function(a,b){return-1!==e.indexOf(b.toUpperCase())?h="loaderShow"===a.name:0===e.length&&(h="loaderShow"===a.name),0>=f||!g&&!h?void(d.showLoader=h):void(g||(d.showLoader=h,g=c(function(){h||(d.showLoader=h),g=void 0},f)))};a.$on("loaderShow",i),a.$on("loaderHide",i)}}}]),angular.module("ng.httpLoader.httpMethodInterceptor",[]).provider("httpMethodInterceptor",function(){var a=[],b=!1;this.whitelistDomain=function(b){a.push(b)},this.whitelistLocalRequests=function(){b=!0},this.$get=["$q","$rootScope",function(c,d){var e=0,f=function(c){if("//"!==c.substring(0,2)&&-1===c.indexOf("://")&&b)return!0;for(var d=a.length;d--;)if(-1!==c.indexOf(a[d]))return!0;return!1},g=function(a){f(a.url)&&0===--e&&d.$emit("loaderHide",a.method)};return{request:function(a){return f(a.url)&&(e++,d.$emit("loaderShow",a.method)),a||c.when(a)},response:function(a){return g(a.config),a||c.when(a)},responseError:function(a){return g(a.config),c.reject(a)}}}]}).config(["$httpProvider",function(a){a.interceptors.push("httpMethodInterceptor")}]); \ No newline at end of file diff --git a/app/src/demo.html b/app/src/demo.html index dcaa79e..1e8beab 100644 --- a/app/src/demo.html +++ b/app/src/demo.html @@ -18,6 +18,7 @@ function (httpMethodInterceptorProvider) { httpMethodInterceptorProvider.whitelistDomain('validate.jsontest.com'); httpMethodInterceptorProvider.whitelistDomain('github.com'); + httpMethodInterceptorProvider.whitelistLocalRequests(); } ]) @@ -25,28 +26,22 @@ '$scope', '$http', function (scope, $http) { - scope.makeRequest = function (index) { - switch (index) { - case 1: - $http.get('http://validate.jsontest.com/?json=%7B%22key%22:%22value%22%7D'); - break; - case 2: - $http.get('https://api.github.com/users/fabpot/repos'); - break; - case 3: - $http.get('http://www.flickr.com/services/feeds/photos_public.gne?tags=soccer&format=json'); - break; - } + scope.buttons = [ + {'name': 'jsontest (Whitelisted)', 'link': 'http://validate.jsontest.com/?json=%7B%22key%22:%22value%22%7D'}, + {'name': 'Github (Whitelisted)', 'link': '//api.github.com/users/fabpot/repos'}, + {'name': 'Local request (Whitelisted)', 'link': '/demo.html'}, + {'name': 'Flickr (Not whitelisted - loader won\'t show)', 'link': 'http://www.flickr.com/services/feeds/photos_public.gne?tags=soccer&format=json'}, + ]; + + scope.makeRequest = function (link) { + $http.get(link); } } ]); - - - - + diff --git a/app/src/js/httpMethodInterceptor.js b/app/src/js/httpMethodInterceptor.js index e196b13..be22f36 100644 --- a/app/src/js/httpMethodInterceptor.js +++ b/app/src/js/httpMethodInterceptor.js @@ -7,7 +7,8 @@ angular .module('ng.httpLoader.httpMethodInterceptor', []) .provider('httpMethodInterceptor', function () { - var domains = []; + var domains = [], + whitelistLocalRequests = false; /** * Add domains to the white list @@ -19,6 +20,13 @@ angular domains.push(domain); }; + /** + * White list requests to the local domain + */ + this.whitelistLocalRequests = function () { + whitelistLocalRequests = true; + }; + this.$get = [ '$q', '$rootScope', @@ -33,6 +41,12 @@ angular * @returns {boolean} */ var isUrlOnWhitelist = function (url) { + if (url.substring(0, 2) !== '//' && + url.indexOf('://') === -1 && + whitelistLocalRequests) { + return true; + } + for (var i = domains.length; i--;) { if (url.indexOf(domains[i]) !== -1) { return true; diff --git a/app/src/js/httpMethodInterceptor.spec.js b/app/src/js/httpMethodInterceptor.spec.js index 015e7e6..44dc9ac 100644 --- a/app/src/js/httpMethodInterceptor.spec.js +++ b/app/src/js/httpMethodInterceptor.spec.js @@ -1,4 +1,4 @@ -/* global describe, beforeEach, module, it, inject, jasmine, expect, console */ +/* global describe, beforeEach, module, it, inject, jasmine, expect */ describe('httpMethodInterceptor', function () { var httpMethodInterceptor, httpMethodInterceptorProvider, mockQ, @@ -9,130 +9,201 @@ describe('httpMethodInterceptor', function () { mockQ = jasmine.createSpyObj('$q', ['when', 'reject']); mockQ.reject.andReturn(mockRejectPromise); mockRootScope = jasmine.createSpyObj('$rootScope', ['$emit']); + }); - module('ng.httpLoader.httpMethodInterceptor', - function ($provide, _httpMethodInterceptorProvider_) { - httpMethodInterceptorProvider = _httpMethodInterceptorProvider_; - httpMethodInterceptorProvider.whitelistDomain('foo.bar'); - $provide.value('$q', mockQ); - $provide.value('$rootScope', mockRootScope); - } - ); - - inject(function (_httpMethodInterceptor_) { - httpMethodInterceptor = _httpMethodInterceptor_; + describe('request method with local requests disabled', function () { + beforeEach(function () { + module('ng.httpLoader.httpMethodInterceptor', + function ($provide, _httpMethodInterceptorProvider_) { + httpMethodInterceptorProvider = _httpMethodInterceptorProvider_; + httpMethodInterceptorProvider.whitelistDomain('foo.bar'); + $provide.value('$rootScope', mockRootScope); + } + ); + + inject(function (_httpMethodInterceptor_) { + httpMethodInterceptor = _httpMethodInterceptor_; + }); }); - }); - it("should not broadcast the show loader event if the domain is not " + - "on the white list", function () { - //Arrange. - var config = { url: 'foo.wrong' }; + it("should not broadcast the show loader event if the domain is not " + + "on the white list", function () { + //Arrange. + var config = { url: 'http://foo.wrong' }; - //Act. - var response = httpMethodInterceptor.request(config); + //Act. + var response = httpMethodInterceptor.request(config); - //Assert. - expect(mockRootScope.$emit).not.toHaveBeenCalled(); - expect(response).toBe(config); - }); + //Assert. + expect(mockRootScope.$emit).not.toHaveBeenCalled(); + expect(response).toBe(config); + }); - it("should broadcast the show loader event on the request method " + - "and return the config", function () { - //Arrange. - var config = { method: 'GET', url: 'foo.bar' }; + it("should broadcast the show loader event for external request on " + + "whitelist", function () { + //Arrange. + var config = { method: 'GET', url: 'http://foo.bar/my/api' }; - //Act. - var response = httpMethodInterceptor.request(config); + //Act. + httpMethodInterceptor.request(config); - //Assert. - expect(mockRootScope.$emit) - .toHaveBeenCalledWith('loaderShow', config.method); - expect(response).toBe(config); - }); + //Assert. + expect(mockRootScope.$emit) + .toHaveBeenCalledWith('loaderShow', config.method); + }); - it("should not broadcast the hide event if the show event " + - "was not already fired", function () { - //Arrange. - var response = { config: { method: 'GET', url: 'foo.bar' } }; + it("should not broadcast the show loader event for local request", + function () { + //Arrange. + var config = { method: 'GET', url: '/my/api' }; - //Act. - httpMethodInterceptor.response(response); + //Act. + httpMethodInterceptor.request(config); - //Assert. - expect(mockRootScope.$emit) - .not.toHaveBeenCalledWith('loaderHide', response.config.method); - }); + //Assert. + expect(mockRootScope.$emit).not.toHaveBeenCalled(); + }); - it("should broadcast the hide event on the response method " + - "and return the response", function () { - //Arrange. - var config = { url: 'foo.bar' }, - response = { config: { method: 'GET', url: 'foo.bar' } }; + it("should return the config", function () { + //Arrange. + var config = { method: 'GET', url: 'http://foo.bar/my/api' }; - //Act. - httpMethodInterceptor.request(config); - var res = httpMethodInterceptor.response(response); + //Act. + var response = httpMethodInterceptor.request(config); - //Assert. - expect(mockRootScope.$emit) - .toHaveBeenCalledWith('loaderHide', response.config.method); - expect(res).toBe(response); + //Assert. + expect(response).toBe(config); + }); }); - it("should not broadcast the hide event on the response error " + - "if the show event was not already fired", function () { - //Arrange. - var response = { config: { method: 'GET', url: 'foo.bar' } }; - - //Act. - var res = httpMethodInterceptor.responseError(response); - - //Assert. - expect(mockRootScope.$emit) - .not.toHaveBeenCalledWith('loaderHide', response.config.method); - expect(res).toBe(mockRejectPromise); - }); + describe('request method with local requests enabled', function () { + beforeEach(function () { + module('ng.httpLoader.httpMethodInterceptor', + function ($provide, _httpMethodInterceptorProvider_) { + httpMethodInterceptorProvider = _httpMethodInterceptorProvider_; + httpMethodInterceptorProvider.whitelistLocalRequests(); + $provide.value('$rootScope', mockRootScope); + } + ); + + inject(function (_httpMethodInterceptor_) { + httpMethodInterceptor = _httpMethodInterceptor_; + }); + }); - it("should broadcast the hide loader event on the response error method " + - "and return the reject promise", function () { - //Arrange. - var config = { url: 'foo.bar' }, - response = { config: { method: 'GET', url: 'foo.bar' } }; + it("should broadcast the show loader event for local request", + function () { + //Arrange. + var config = { method: 'GET', url: '/my/api' }; - //Act. - httpMethodInterceptor.request(config); - var res = httpMethodInterceptor.responseError(response); + //Act. + httpMethodInterceptor.request(config); - //Assert. - expect(mockRootScope.$emit) - .toHaveBeenCalledWith('loaderHide', response.config.method); - expect(res).toBe(mockRejectPromise); + //Assert. + expect(mockRootScope.$emit) + .toHaveBeenCalledWith('loaderShow', config.method); + }); }); - it("should only emit the hide event once all requests have closed", - function () { - // Arrange. - var res, - config = { url: 'foo.bar' }, - response = { config: { method: 'GET', url: 'foo.bar' } }; - - //Act. - httpMethodInterceptor.request(config); - httpMethodInterceptor.request(config); - res = httpMethodInterceptor.response(response); - - //Assert. - expect(mockRootScope.$emit) - .not.toHaveBeenCalledWith('loaderHide', response.config.method); - expect(res).toBe(response); - - //Act. - res = httpMethodInterceptor.response(response); - - //Assert. - expect(mockRootScope.$emit) - .toHaveBeenCalledWith('loaderHide', response.config.method); - expect(res).toBe(response); + describe('response method', function () { + beforeEach(function () { + module('ng.httpLoader.httpMethodInterceptor', + function ($provide, _httpMethodInterceptorProvider_) { + httpMethodInterceptorProvider = _httpMethodInterceptorProvider_; + httpMethodInterceptorProvider.whitelistDomain('foo.bar/my/api'); + $provide.value('$q', mockQ); + $provide.value('$rootScope', mockRootScope); + } + ); + + inject(function (_httpMethodInterceptor_) { + httpMethodInterceptor = _httpMethodInterceptor_; + }); }); + + it("should not broadcast the hide event if the show event " + + "was not already fired", function () { + //Arrange. + var response = { config: { method: 'GET', url: 'foo.bar/my/api' } }; + + //Act. + httpMethodInterceptor.response(response); + + //Assert. + expect(mockRootScope.$emit) + .not.toHaveBeenCalledWith('loaderHide', response.config.method); + }); + + it("should broadcast the hide event on the response method " + + "and return the response", function () { + //Arrange. + var config = { url: 'foo.bar/my/api' }, + response = { config: { method: 'GET', url: 'foo.bar/my/api' } }; + + //Act. + httpMethodInterceptor.request(config); + var res = httpMethodInterceptor.response(response); + + //Assert. + expect(mockRootScope.$emit) + .toHaveBeenCalledWith('loaderHide', response.config.method); + expect(res).toBe(response); + }); + + it("should not broadcast the hide event on the response error " + + "if the show event was not already fired", function () { + //Arrange. + var response = { config: { method: 'GET', url: 'foo.bar/my/api' } }; + + //Act. + var res = httpMethodInterceptor.responseError(response); + + //Assert. + expect(mockRootScope.$emit) + .not.toHaveBeenCalledWith('loaderHide', response.config.method); + expect(res).toBe(mockRejectPromise); + }); + + it("should broadcast the hide loader event on the response error method " + + "and return the reject promise", function () { + //Arrange. + var config = { url: 'foo.bar/my/api' }, + response = { config: { method: 'GET', url: 'foo.bar/my/api' } }; + + //Act. + httpMethodInterceptor.request(config); + var res = httpMethodInterceptor.responseError(response); + + //Assert. + expect(mockRootScope.$emit) + .toHaveBeenCalledWith('loaderHide', response.config.method); + expect(res).toBe(mockRejectPromise); + }); + + it("should only emit the hide event once all requests have closed", + function () { + // Arrange. + var res, + config = { url: 'foo.bar/my/api' }, + response = { config: { method: 'GET', url: 'foo.bar/my/api' } }; + + //Act. + httpMethodInterceptor.request(config); + httpMethodInterceptor.request(config); + res = httpMethodInterceptor.response(response); + + //Assert. + expect(mockRootScope.$emit) + .not.toHaveBeenCalledWith('loaderHide', response.config.method); + expect(res).toBe(response); + + //Act. + res = httpMethodInterceptor.response(response); + + //Assert. + expect(mockRootScope.$emit) + .toHaveBeenCalledWith('loaderHide', response.config.method); + expect(res).toBe(response); + }); + }); }); diff --git a/bower.json b/bower.json index 12e9364..ceb9c61 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,5 @@ { "name": "angular-http-loader", - "version": "0.1.4", "ignore": [ "app/src", ".bowerrc",