diff --git a/bower.json b/bower.json index f1d1f7a..9dab976 100644 --- a/bower.json +++ b/bower.json @@ -20,10 +20,7 @@ "angular-1.4": "angular#1.4", "angular-mocks": "~1.2.9", "angular-mocks-1.3": "angular-mocks#1.3", - "angular-mocks-1.4": "angular-mocks#1.4", - "angular-animate": "~1.2.9", - "angular-animate-1.3": "angular-animate#1.3", - "angular-animate-1.4": "angular-animate#1.4" + "angular-mocks-1.4": "angular-mocks#1.4" }, "resolutions": { "angular": "~1.2.23" diff --git a/src/loading-bar.css b/src/loading-bar.css index 3ee0618..69af12c 100644 --- a/src/loading-bar.css +++ b/src/loading-bar.css @@ -4,31 +4,29 @@ #loading-bar-spinner { pointer-events: none; -webkit-pointer-events: none; - -webkit-transition: 350ms linear all; - -moz-transition: 350ms linear all; - -o-transition: 350ms linear all; - transition: 350ms linear all; -} -#loading-bar.ng-enter, -#loading-bar.ng-leave.ng-leave-active, -#loading-bar-spinner.ng-enter, -#loading-bar-spinner.ng-leave.ng-leave-active { - opacity: 0; + -webkit-animation: fade linear 350ms; + -moz-animation: fade linear 350ms; + -ms-animation: fade linear 350ms; + -o-animation: fade linear 350ms; + animation: fade linear 350ms; + + -webkit-transition: opacity 350ms linear; + -moz-transition: opacity 350ms linear; + -o-transition: opacity 350ms linear; + transition: opacity 350ms linear; } -#loading-bar.ng-enter.ng-enter-active, -#loading-bar.ng-leave, -#loading-bar-spinner.ng-enter.ng-enter-active, -#loading-bar-spinner.ng-leave { - opacity: 1; +#loading-bar.out, +#loading-bar-spinner.out { + opacity: 0; } #loading-bar .bar { - -webkit-transition: width 350ms; - -moz-transition: width 350ms; - -o-transition: width 350ms; - transition: width 350ms; + -webkit-transition: width 350ms linear; + -moz-transition: width 350ms linear; + -o-transition: width 350ms linear; + transition: width 350ms linear; background: #29d; position: fixed; @@ -102,3 +100,24 @@ 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } + +@-webkit-keyframes fade { + 0% { opacity: 0; } + 100% { opacity: 1; } +} +@-moz-keyframes fade { + 0% { opacity: 0; } + 100% { opacity: 1; } +} +@-o-keyframes fade { + 0% { opacity: 0; } + 100% { opacity: 1; } +} +@-ms-keyframes fade { + 0% { opacity: 0; } + 100% { opacity: 1; } +} +@keyframes fade { + 0% { opacity: 0; } + 100% { opacity: 1; } +} diff --git a/src/loading-bar.js b/src/loading-bar.js index 44e092a..bf5b780 100644 --- a/src/loading-bar.js +++ b/src/loading-bar.js @@ -168,75 +168,49 @@ angular.module('cfp.loadingBar', []) this.parentSelector = 'body'; this.spinnerTemplate = '
'; this.loadingBarTemplate = '
'; + this.fadeOutDuration = 350; - this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) { - var $animate; - var $parentSelector = this.parentSelector, - loadingBarContainer = angular.element(this.loadingBarTemplate), - loadingBar = loadingBarContainer.find('div').eq(0), - spinner = angular.element(this.spinnerTemplate); - + function LoadingBar($document, $timeout, options) { + var _this = this; + var loadingBarContainer = angular.element(options.loadingBarTemplate); + var loadingBar = loadingBarContainer.find('div').eq(0); + var spinner = angular.element(options.spinnerTemplate); var incTimeout, - completeTimeout, - started = false, status = 0; - var autoIncrement = this.autoIncrement; - var includeSpinner = this.includeSpinner; - var includeBar = this.includeBar; - var startSize = this.startSize; - - /** - * Inserts the loading bar element into the dom, and sets it to 2% - */ - function _start() { - if (!$animate) { - $animate = $injector.get('$animate'); - } - - $timeout.cancel(completeTimeout); - - // do not continually broadcast the started event: - if (started) { - return; - } - + var init = function() { var document = $document[0]; var parent = document.querySelector ? - document.querySelector($parentSelector) - : $document.find($parentSelector)[0] - ; + document.querySelector(options.parentSelector) + : $document.find(options.parentSelector)[0] + ; - if (! parent) { + if(!parent) { parent = document.getElementsByTagName('body')[0]; } var $parent = angular.element(parent); - var $after = parent.lastChild && angular.element(parent.lastChild); - $rootScope.$broadcast('cfpLoadingBar:started'); - started = true; - - if (includeBar) { - $animate.enter(loadingBarContainer, $parent, $after); + if(options.includeBar) { + $parent.append(loadingBarContainer); } - if (includeSpinner) { - $animate.enter(spinner, $parent, loadingBarContainer); + if(options.includeSpinner) { + $parent.append(spinner); } + }; - _set(startSize); - } + init(); - /** - * Set the loading bar's width to a certain percent. - * - * @param n any value between 0 and 1 - */ - function _set(n) { - if (!started) { - return; - } + this.activate = function() { + _this.set(options.startSize); + }; + + this.status = function() { + return status; + }; + + this.set = function(n) { var pct = (n * 100) + '%'; loadingBar.css('width', pct); status = n; @@ -244,20 +218,17 @@ angular.module('cfp.loadingBar', []) // increment loadingbar to give the illusion that there is always // progress but make sure to cancel the previous timeouts so we don't // have multiple incs running at the same time. - if (autoIncrement) { + if (options.autoIncrement) { $timeout.cancel(incTimeout); + incTimeout = $timeout(function() { - _inc(); + _this.inc(); }, 250); } - } + }; - /** - * Increments the loading bar by a random amount - * but slows down as it progresses - */ - function _inc() { - if (_status() >= 1) { + this.inc = function() { + if (_this.status() >= 1) { return; } @@ -265,7 +236,7 @@ angular.module('cfp.loadingBar', []) // TODO: do this mathmatically instead of through conditions - var stat = _status(); + var stat = _this.status(); if (stat >= 0 && stat < 0.25) { // Start out between 3 - 6% increments rnd = (Math.random() * (5 - 3 + 1) + 3) / 100; @@ -283,22 +254,79 @@ angular.module('cfp.loadingBar', []) rnd = 0; } - var pct = _status() + rnd; - _set(pct); + var pct = _this.status() + rnd; + _this.set(pct); + }; + + this.cleanup = function() { + spinner.addClass('out'); + loadingBarContainer.addClass('out'); + + $timeout(function() { + spinner.remove(); + loadingBarContainer.remove(); + }, options.fadeOutDuration); + }; + } + + this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) { + var _this = this; + + var options = { + parentSelector: _this.parentSelector, + loadingBarTemplate: _this.loadingBarTemplate, + spinnerTemplate: _this.spinnerTemplate, + autoIncrement: _this.autoIncrement, + includeSpinner: _this.includeSpinner, + includeBar: _this.includeBar, + startSize: _this.startSize, + fadeOutDuration: _this.fadeOutDuration + }; + + var completeTimeout, + currentBar = null; + + /** + * Inserts the loading bar element into the dom, and sets it to 2% + */ + function _start() { + $timeout.cancel(completeTimeout); + + // do not continually broadcast the started event: + if (currentBar) { + return; + } + + currentBar = new LoadingBar($document, $timeout, options); + currentBar.activate(); + + $rootScope.$broadcast('cfpLoadingBar:started'); } - function _status() { - return status; + /** + * Set the loading bar's width to a certain percent. + * + * @param n any value between 0 and 1 + */ + function _set(n) { + if (currentBar) currentBar.set(n); } - function _completeAnimation() { - status = 0; - started = false; + /** + * Increments the loading bar by a random amount + * but slows down as it progresses + */ + function _inc() { + if (currentBar) currentBar.inc(); + } + + function _status() { + return currentBar ? currentBar.status() : 0; } function _complete() { - if (!$animate) { - $animate = $injector.get('$animate'); + if (!currentBar) { + return; } _set(1); @@ -306,11 +334,8 @@ angular.module('cfp.loadingBar', []) // Attempt to aggregate any start/complete calls within 500ms: completeTimeout = $timeout(function() { - var promise = $animate.leave(loadingBarContainer, _completeAnimation); - if (promise && promise.then) { - promise.then(_completeAnimation); - } - $animate.leave(spinner); + currentBar.cleanup(); + currentBar = null; $rootScope.$broadcast('cfpLoadingBar:completed'); }, 500); } diff --git a/test/loading-bar-interceptor-config.coffee b/test/loading-bar-interceptor-config.coffee index f29ab1e..3380f7c 100644 --- a/test/loading-bar-interceptor-config.coffee +++ b/test/loading-bar-interceptor-config.coffee @@ -10,6 +10,7 @@ describe 'loadingBarInterceptor Service - config options', -> expect(spinner).toBeNull cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should show the spinner if configured', -> module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) -> @@ -21,6 +22,7 @@ describe 'loadingBarInterceptor Service - config options', -> expect(spinner).not.toBeNull cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should hide the loadingBar if configured', -> module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) -> @@ -32,6 +34,7 @@ describe 'loadingBarInterceptor Service - config options', -> expect(spinner).toBeNull cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should show the loadingBar if configured', -> module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) -> @@ -43,6 +46,7 @@ describe 'loadingBarInterceptor Service - config options', -> expect(spinner).not.toBeNull cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should not auto increment loadingBar if configured', (done) -> module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) -> @@ -66,6 +70,7 @@ describe 'loadingBarInterceptor Service - config options', -> expect(cfpLoadingBar.status()).toBe .5; cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should auto increment loadingBar if configured', -> module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) -> @@ -79,6 +84,7 @@ describe 'loadingBarInterceptor Service - config options', -> expect(cfpLoadingBar.status()).toBeGreaterThan .5 cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should append the loadingbar as the first child of the parent container if empty', -> emptyEl = angular.element '
' @@ -96,6 +102,7 @@ describe 'loadingBarInterceptor Service - config options', -> expect(children[1].id).toBe 'loading-bar-spinner' cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should append the loading bar to the body if parentSelector is empty', -> module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) -> @@ -112,3 +119,4 @@ describe 'loadingBarInterceptor Service - config options', -> expect(spinner.length).toBe 1 cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() diff --git a/test/loading-bar-interceptor.coffee b/test/loading-bar-interceptor.coffee index 241967f..1848881 100644 --- a/test/loading-bar-interceptor.coffee +++ b/test/loading-bar-interceptor.coffee @@ -7,31 +7,23 @@ isLoadingBarInjected = (doc) -> break return injected -flush = null - describe 'loadingBarInterceptor Service', -> - $http = $httpBackend = $document = $timeout = result = loadingBar = $animate = null + $http = $httpBackend = $document = $timeout = result = loadingBar = null response = {message:'OK'} endpoint = '/service' beforeEach -> - module 'ngAnimateMock', 'chieffancypants.loadingBar', (cfpLoadingBarProvider) -> + module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) -> loadingBar = cfpLoadingBarProvider return result = null - inject (_$http_, _$httpBackend_, _$document_, _$timeout_, _$animate_) -> + inject (_$http_, _$httpBackend_, _$document_, _$timeout_) -> $http = _$http_ $httpBackend = _$httpBackend_ $document = _$document_ $timeout = _$timeout_ - $animate = _$animate_ - - # Angular 1.4 removed triggerCalbacks(), so try them both: - flush = () -> - $animate.flush && $animate.flush() - $animate.triggerCallbacks && $animate.triggerCallbacks() beforeEach -> this.addMatchers @@ -46,6 +38,7 @@ describe 'loadingBarInterceptor Service', -> afterEach -> $httpBackend.verifyNoOutstandingRequest() $timeout.verifyNoPendingTasks() + expect(isLoadingBarInjected($document)).toBe false, "Loading bar not cleaned up!" it 'should not increment if the response is cached in a cacheFactory', inject (cfpLoadingBar, $cacheFactory) -> @@ -61,8 +54,7 @@ describe 'loadingBarInterceptor Service', -> expect(cfpLoadingBar.status()).toBe 1 cfpLoadingBar.complete() # set as complete $timeout.flush() - flush() - + $timeout.flush() $http.get(endpoint, cache: cache).then (data) -> result = data @@ -84,7 +76,7 @@ describe 'loadingBarInterceptor Service', -> expect(cfpLoadingBar.status()).toBe 1 cfpLoadingBar.complete() # set as complete $timeout.flush() - flush() + $timeout.flush() $http.get(endpoint).then (data) -> @@ -106,7 +98,7 @@ describe 'loadingBarInterceptor Service', -> expect(cfpLoadingBar.status()).toBe 1 cfpLoadingBar.complete() # set as complete $timeout.flush() - flush() + $timeout.flush() $http.get(endpoint, cache: true).then (data) -> @@ -130,7 +122,7 @@ describe 'loadingBarInterceptor Service', -> expect(cfpLoadingBar.status()).toBe 1 cfpLoadingBar.complete() # set as complete $timeout.flush() - flush() + $timeout.flush() $http.get(endpoint).then (data) -> @@ -151,7 +143,7 @@ describe 'loadingBarInterceptor Service', -> $httpBackend.flush(1) expect(cfpLoadingBar.status()).toBe 1 $timeout.flush() - flush() + $timeout.flush() $httpBackend.expectPOST(endpoint).respond response @@ -164,6 +156,7 @@ describe 'loadingBarInterceptor Service', -> $httpBackend.flush() expect(cfpLoadingBar.status()).toBe 1 $timeout.flush() + $timeout.flush() it 'should increment the loading bar when not all requests have been recieved', inject (cfpLoadingBar) -> @@ -183,6 +176,7 @@ describe 'loadingBarInterceptor Service', -> $httpBackend.flush() expect(cfpLoadingBar.status()).toBe 1 $timeout.flush() # loading bar is animated, so flush timeout + $timeout.flush() it 'should count http errors as responses so the loading bar can complete', inject (cfpLoadingBar) -> # $httpBackend.expectGET(endpoint).respond response @@ -198,7 +192,7 @@ describe 'loadingBarInterceptor Service', -> expect(cfpLoadingBar.status()).toBe 0.5 $httpBackend.flush() expect(cfpLoadingBar.status()).toBe 1 - + $timeout.flush() $timeout.flush() it 'should insert the loadingbar into the DOM when a request is sent', inject (cfpLoadingBar) -> @@ -214,6 +208,7 @@ describe 'loadingBarInterceptor Service', -> $httpBackend.flush() $timeout.flush() + $timeout.flush() it 'should insert the loadingbar as the last children of the parent container', inject (cfpLoadingBar) -> $httpBackend.expectGET(endpoint).respond response @@ -231,6 +226,7 @@ describe 'loadingBarInterceptor Service', -> $httpBackend.flush() $timeout.flush() + $timeout.flush() it 'should remove the loading bar when all requests have been received', inject (cfpLoadingBar) -> $httpBackend.expectGET(endpoint).respond response @@ -245,6 +241,7 @@ describe 'loadingBarInterceptor Service', -> $httpBackend.flush() $timeout.flush() + $timeout.flush() expect(isLoadingBarInjected($document.find(cfpLoadingBar.parentSelector))).toBe false @@ -261,6 +258,7 @@ describe 'loadingBarInterceptor Service', -> cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should increment things randomly', inject (cfpLoadingBar) -> cfpLoadingBar.start() @@ -345,6 +343,7 @@ describe 'loadingBarInterceptor Service', -> cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should not set the status if the loading bar has not yet been started', inject (cfpLoadingBar) -> cfpLoadingBar.set(0.5) @@ -358,6 +357,7 @@ describe 'loadingBarInterceptor Service', -> cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should broadcast started and completed events', inject (cfpLoadingBar, $rootScope) -> startedEventCalled = false @@ -377,6 +377,7 @@ describe 'loadingBarInterceptor Service', -> cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() expect(completedEventCalled).toBe true it 'should debounce the calls to start()', inject (cfpLoadingBar, $rootScope) -> @@ -390,13 +391,14 @@ describe 'loadingBarInterceptor Service', -> expect(startedEventCalled).toBe 1 # Should still be one, as complete was never called: cfpLoadingBar.complete() $timeout.flush() - flush() + $timeout.flush() cfpLoadingBar.start() expect(startedEventCalled).toBe 2 cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() it 'should ignore requests when ignoreLoadingBar is true', inject (cfpLoadingBar) -> $httpBackend.expectGET(endpoint).respond response @@ -424,6 +426,7 @@ describe 'loadingBarInterceptor Service', -> expect(cfpLoadingBar.status()).toBe 1 $timeout.flush() # loading bar is animated, so flush timeout + $timeout.flush() it 'should ignore errors when ignoreLoadingBar is true (#70)', inject (cfpLoadingBar) -> $httpBackend.expectGET(endpoint).respond 400 @@ -441,6 +444,7 @@ describe 'loadingBarInterceptor Service', -> expect(cfpLoadingBar.status()).toBe 1 $timeout.flush() # loading bar is animated, so flush timeout + $timeout.flush() @@ -474,6 +478,7 @@ describe 'LoadingBar only', -> # test the complete call, which should remove it from the DOM cfpLoadingBar.complete() $timeout.flush() + $timeout.flush() expect(isLoadingBarInjected($document.find(cfpLoadingBar.parentSelector))).toBe false it 'should start after multiple calls to complete()', -> @@ -490,7 +495,7 @@ describe 'LoadingBar only', -> cfpLoadingBar.complete() $timeout.flush() - flush() + $timeout.flush() expect(isLoadingBarInjected($document.find(cfpLoadingBar.parentSelector))).toBe false