diff --git a/Gruntfile.js b/Gruntfile.js index b9f0e6b..8766c0e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -9,7 +9,7 @@ module.exports = function (grunt) { runnerPort: 9876, singleRun: true, reporters: ['progress'], - browsers: ['PhantomJS'] + browsers: ['Firefox'] } }, concat: { @@ -59,4 +59,4 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-karma'); grunt.registerTask('default', ['jshint', 'karma', 'concat', 'uglify']); -}; \ No newline at end of file +}; diff --git a/build/ngprogress.js b/build/ngprogress.js index 1527a4b..b557c6c 100644 --- a/build/ngprogress.js +++ b/build/ngprogress.js @@ -1,220 +1,268 @@ /* -ngprogress 1.1.2 - slim, site-wide progressbar for AngularJS +ngprogress 1.1.3 - slim, site-wide progressbar for AngularJS (C) 2013 - Victor Bjelkholm License: MIT Source: https://github.com/VictorBjelkholm/ngProgress -Date Compiled: 2015-07-27 +Date Compiled: 2016-08-16 */ angular.module('ngProgress.provider', ['ngProgress.directive']) - .service('ngProgress', function () { - 'use strict'; - return ['$document', '$window', '$compile', '$rootScope', '$timeout', function($document, $window, $compile, $rootScope, $timeout) { - this.autoStyle = true; - this.count = 0; - this.height = '2px'; - this.$scope = $rootScope.$new(); - this.color = 'firebrick'; - this.parent = $document.find('body')[0]; - this.count = 0; - - // Compile the directive - this.progressbarEl = $compile('')(this.$scope); - // Add the element to body - this.parent.appendChild(this.progressbarEl[0]); - // Set the initial height - this.$scope.count = this.count; - // If height or color isn't undefined, set the height, background-color and color. - if (this.height !== undefined) { - this.progressbarEl.eq(0).children().css('height', this.height); - } - if (this.color !== undefined) { - this.progressbarEl.eq(0).children().css('background-color', this.color); - this.progressbarEl.eq(0).children().css('color', this.color); - } - // The ID for the interval controlling start() - this.intervalCounterId = 0; - - // Starts the animation and adds between 0 - 5 percent to loading - // each 400 milliseconds. Should always be finished with progressbar.complete() - // to hide it - this.start = function () { - // TODO Use requestAnimationFrame instead of setInterval - // https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame - this.show(); - var self = this; - clearInterval(this.intervalCounterId); - this.intervalCounterId = setInterval(function () { - if (isNaN(self.count)) { - clearInterval(self.intervalCounterId); - self.count = 0; - self.hide(); - } else { - self.remaining = 100 - self.count; - self.count = self.count + (0.15 * Math.pow(1 - Math.sqrt(self.remaining), 2)); - self.updateCount(self.count); - } - }, 200); - }; - this.updateCount = function (new_count) { - this.$scope.count = new_count; - if(!this.$scope.$$phase) { - this.$scope.$apply(); - } - }; - // Sets the height of the progressbar. Use any valid CSS value - // Eg '10px', '1em' or '1%' - this.setHeight = function (new_height) { - if (new_height !== undefined) { - this.height = new_height; - this.$scope.height = this.height; - if(!this.$scope.$$phase) { - this.$scope.$apply(); - } - } - return this.height; - }; - // Sets the color of the progressbar and it's shadow. Use any valid HTML - // color - this.setColor = function(new_color) { - if (new_color !== undefined) { - this.color = new_color; - this.$scope.color = this.color; - if(!this.$scope.$$phase) { - this.$scope.$apply(); - } - } - return this.color; - }; - this.hide = function() { - this.progressbarEl.children().css('opacity', '0'); - var self = this; - self.animate(function () { - self.progressbarEl.children().css('width', '0%'); - self.animate(function () { - self.show(); - }, 500); - }, 500); - }; - this.show = function () { - var self = this; - self.animate(function () { - self.progressbarEl.children().css('opacity', '1'); - }, 100); - }; - // Cancel any prior animations before running new ones. - // Multiple simultaneous animations just look weird. - this.animate = function(fn, time) { - if(this.animation !== undefined) { $timeout.cancel(this.animation); } - this.animation = $timeout(fn, time); - }; - // Returns on how many percent the progressbar is at. Should'nt be needed - this.status = function () { - return this.count; - }; - // Stops the progressbar at it's current location - this.stop = function () { - clearInterval(this.intervalCounterId); - }; - // Set's the progressbar percentage. Use a number between 0 - 100. - // If 100 is provided, complete will be called. - this.set = function (new_count) { - this.show(); - this.updateCount(new_count); - this.count = new_count; - clearInterval(this.intervalCounterId); - return this.count; - }; - this.css = function (args) { - return this.progressbarEl.children().css(args); - }; - // Resets the progressbar to percetage 0 and therefore will be hided after - // it's rollbacked - this.reset = function () { - clearInterval(this.intervalCounterId); - this.count = 0; - this.updateCount(this.count); - return 0; - }; - // Jumps to 100% progress and fades away progressbar. - this.complete = function () { - this.count = 100; - this.updateCount(this.count); - var self = this; - clearInterval(this.intervalCounterId); - $timeout(function () { - self.hide(); - $timeout(function () { - self.count = 0; - self.updateCount(self.count); - }, 500); - }, 1000); - return this.count; - }; - // Set the parent of the directive, sometimes body is not sufficient - this.setParent = function(newParent) { - if(newParent === null || newParent === undefined) { - throw new Error('Provide a valid parent of type HTMLElement'); - } - - if(this.parent !== null && this.parent !== undefined) { - this.parent.removeChild(this.progressbarEl[0]); - } - - this.parent = newParent; - this.parent.appendChild(this.progressbarEl[0]); - }; - // Gets the current element the progressbar is attached to - this.getDomElement = function () { - return this.progressbarEl; - }; - this.setAbsolute = function() { - this.progressbarEl.css('position', 'absolute'); - }; - }]; - }) -.factory('ngProgressFactory', ['$injector', 'ngProgress', function($injector, ngProgress) { - var service = { - createInstance: function () { - return $injector.instantiate(ngProgress); + .service('ngProgress', function() { + 'use strict'; + return ['$document', '$window', '$compile', '$rootScope', '$timeout', function($document, $window, $compile, $rootScope, $timeout) { + this.autoStyle = true; + this.count = 0; + this.height = '2px'; + this.$scope = $rootScope.$new(); + this.color = 'firebrick'; + this.parent = $document.find('body')[0]; + this.count = 0; + this.updateCountInterval = 200; + this.updateCountRate = 0.15; + + // Compile the directive + this.progressbarEl = $compile('')(this.$scope); + + // Add the element to body + this.parent.appendChild(this.progressbarEl[0]); + + // Set the initial height + this.$scope.count = this.count; + + // If height or color isn't undefined, set the height, background-color and color. + if (this.height !== undefined) { + this.progressbarEl.eq(0).children().css('height', this.height); + } + + if (this.color !== undefined) { + this.progressbarEl.eq(0).children().css('background-color', this.color); + this.progressbarEl.eq(0).children().css('color', this.color); + } + + // The ID for the interval controlling start() + this.intervalCounterId = 0; + + this.runUpdateCountLoop = function(timestamp) { + if (!timestamp) { // first call not from requestAnimationFrame + $window.cancelAnimationFrame(this.intervalCounterId); + this.intervalCounterId = $window.requestAnimationFrame(this.runUpdateCountLoop.bind(this)); + return; + } + + if (isNaN(this.count)) { + $window.cancelAnimationFrame(this.intervalCounterId); + this.count = 0; + this.hide(); + } else { + if (!this.startUpdateTime) { + this.startUpdateTime = timestamp; + } + + if (timestamp - this.startUpdateTime >= this.updateCountInterval) { + this.startUpdateTime = timestamp; + this.remaining = 100 - this.count; + this.count += this.updateCountRate * Math.pow(1 - Math.sqrt(this.remaining), 2); + this.updateCount(this.count); + } + + this.intervalCounterId = $window.requestAnimationFrame(this.runUpdateCountLoop.bind(this)); + } + }; + + // Starts the animation and adds between 0 - 5 percent to loading + // each 400 milliseconds. Should always be finished with progressbar.complete() + // to hide it + this.start = function() { + this.show(); + this.runUpdateCountLoop(); + }; + + this.updateCount = function(new_count) { + this.$scope.count = new_count; + if (!this.$scope.$$phase) { + this.$scope.$apply(); + } + }; + + this.setUpdateRate = function(rate) { + var normalizedRate = rate ? Math.abs(parseFloat(rate)) : 0; + this.updateCountRate = normalizedRate || this.updateCountRate; + }; + + this.setUpdateInterval = function(interval) { + var normalizedInterval = interval ? Math.abs(parseInt(interval, 10)) : 0; + this.updateCountInterval = normalizedInterval >= 200 ? normalizedInterval : this.updateCountInterval; + }; + + // Sets the height of the progressbar. Use any valid CSS value + // Eg '10px', '1em' or '1%' + this.setHeight = function(new_height) { + if (new_height !== undefined) { + this.height = new_height; + this.$scope.height = this.height; + if (!this.$scope.$$phase) { + this.$scope.$apply(); + } + } + return this.height; + }; + + // Sets the color of the progressbar and it's shadow. Use any valid HTML + // color + this.setColor = function(new_color) { + if (new_color !== undefined) { + this.color = new_color; + this.$scope.color = this.color; + if (!this.$scope.$$phase) { + this.$scope.$apply(); + } } + return this.color; + }; + + this.hide = function() { + this.progressbarEl.children().css('opacity', '0'); + var self = this; + self.animate(function() { + self.progressbarEl.children().css('width', '0%'); + self.animate(function() { + self.show(); + }, 500); + }, 500); + }; + + this.show = function() { + var self = this; + self.animate(function() { + self.progressbarEl.children().css('opacity', '1'); + }, 100); + }; + + // Cancel any prior animations before running new ones. + // Multiple simultaneous animations just look weird. + this.animate = function(fn, time) { + if (this.animation !== undefined) { + $timeout.cancel(this.animation); + } + this.animation = $timeout(fn, time); + }; + + // Returns on how many percent the progressbar is at. Should'nt be needed + this.status = function() { + return this.count; + }; + + // Stops the progressbar at it's current location + this.stop = function() { + $window.cancelAnimationFrame(this.intervalCounterId); + }; + + // Set's the progressbar percentage. Use a number between 0 - 100. + // If 100 is provided, complete will be called. + this.set = function(new_count) { + this.show(); + this.updateCount(new_count); + this.count = new_count; + $window.cancelAnimationFrame(this.intervalCounterId); + return this.count; + }; + + this.css = function(args) { + return this.progressbarEl.children().css(args); + }; + + // Resets the progressbar to percetage 0 and therefore will be hided after + // it's rollbacked + this.reset = function() { + $window.cancelAnimationFrame(this.intervalCounterId); + this.count = 0; + this.updateCount(this.count); + return 0; + }; + + // Jumps to 100% progress and fades away progressbar. + this.complete = function() { + this.count = 100; + this.updateCount(this.count); + var self = this; + $window.cancelAnimationFrame(this.intervalCounterId); + $timeout(function() { + self.hide(); + $timeout(function() { + self.count = 0; + self.updateCount(self.count); + }, 500); + }, 1000); + return this.count; + }; + + // Set the parent of the directive, sometimes body is not sufficient + this.setParent = function(newParent) { + if (newParent === null || newParent === undefined) { + throw new Error('Provide a valid parent of type HTMLElement'); + } + + if (this.parent !== null && this.parent !== undefined) { + this.parent.removeChild(this.progressbarEl[0]); + } + + this.parent = newParent; + this.parent.appendChild(this.progressbarEl[0]); + }; + + // Gets the current element the progressbar is attached to + this.getDomElement = function() { + return this.progressbarEl; + }; + + this.setAbsolute = function() { + this.progressbarEl.css('position', 'absolute'); + }; + }]; + }) + .factory('ngProgressFactory', ['$injector', 'ngProgress', function($injector, ngProgress) { + var service = { + createInstance: function() { + return $injector.instantiate(ngProgress); + } }; return service; -}]); + }]); + angular.module('ngProgress.directive', []) - .directive('ngProgress', ["$window", "$rootScope", function ($window, $rootScope) { - var directiveObj = { - // Replace the directive - replace: true, - // Only use as a element - restrict: 'E', - link: function ($scope, $element, $attrs, $controller) { - // Watch the count on the $rootScope. As soon as count changes to something that - // isn't undefined or null, change the counter on $scope and also the width of - // the progressbar. The same goes for color and height on the $rootScope - $scope.$watch('count', function (newVal) { - if (newVal !== undefined || newVal !== null) { - $scope.counter = newVal; - $element.eq(0).children().css('width', newVal + '%'); - } - }); - $scope.$watch('color', function (newVal) { - if (newVal !== undefined || newVal !== null) { - $scope.color = newVal; - $element.eq(0).children().css('background-color', newVal); - $element.eq(0).children().css('color', newVal); - } - }); - $scope.$watch('height', function (newVal) { - if (newVal !== undefined || newVal !== null) { - $scope.height = newVal; - $element.eq(0).children().css('height', newVal); - } - }); - }, - // The actual html that will be used - template: '
' - }; - return directiveObj; - }]); - -angular.module('ngProgress', ['ngProgress.directive', 'ngProgress.provider']); \ No newline at end of file + .directive('ngProgress', ["$window", "$rootScope", function($window, $rootScope) { + // Replace the directive + var directiveObj = { + replace: true, + // Only use as a element + restrict: 'E', + link: function($scope, $element, $attrs, $controller) { + // Watch the count on the $rootScope. As soon as count changes to something that + // isn't undefined or null, change the counter on $scope and also the width of + // the progressbar. The same goes for color and height on the $rootScope + $scope.$watch('count', function(newVal) { + if (newVal !== undefined || newVal !== null) { + $scope.counter = newVal; + $element.eq(0).children().css('width', newVal + '%'); + } + }); + $scope.$watch('color', function(newVal) { + if (newVal !== undefined || newVal !== null) { + $scope.color = newVal; + $element.eq(0).children().css('background-color', newVal); + $element.eq(0).children().css('color', newVal); + } + }); + $scope.$watch('height', function(newVal) { + if (newVal !== undefined || newVal !== null) { + $scope.height = newVal; + $element.eq(0).children().css('height', newVal); + } + }); + }, + // The actual html that will be used + template: '
' + }; + return directiveObj; + }]); + +angular.module('ngProgress', ['ngProgress.directive', 'ngProgress.provider']); diff --git a/build/ngprogress.min.js b/build/ngprogress.min.js index 177e552..b3d5fec 100644 --- a/build/ngprogress.min.js +++ b/build/ngprogress.min.js @@ -1,8 +1,8 @@ /* -ngprogress 1.1.2 - slim, site-wide progressbar for AngularJS +ngprogress 1.1.3 - slim, site-wide progressbar for AngularJS (C) 2013 - Victor Bjelkholm License: MIT Source: https://github.com/VictorBjelkholm/ngProgress -Date Compiled: 2015-07-27 +Date Compiled: 2016-08-16 */ -angular.module("ngProgress.provider",["ngProgress.directive"]).service("ngProgress",function(){"use strict";return["$document","$window","$compile","$rootScope","$timeout",function(a,b,c,d,e){this.autoStyle=!0,this.count=0,this.height="2px",this.$scope=d.$new(),this.color="firebrick",this.parent=a.find("body")[0],this.count=0,this.progressbarEl=c("")(this.$scope),this.parent.appendChild(this.progressbarEl[0]),this.$scope.count=this.count,void 0!==this.height&&this.progressbarEl.eq(0).children().css("height",this.height),void 0!==this.color&&(this.progressbarEl.eq(0).children().css("background-color",this.color),this.progressbarEl.eq(0).children().css("color",this.color)),this.intervalCounterId=0,this.start=function(){this.show();var a=this;clearInterval(this.intervalCounterId),this.intervalCounterId=setInterval(function(){isNaN(a.count)?(clearInterval(a.intervalCounterId),a.count=0,a.hide()):(a.remaining=100-a.count,a.count=a.count+.15*Math.pow(1-Math.sqrt(a.remaining),2),a.updateCount(a.count))},200)},this.updateCount=function(a){this.$scope.count=a,this.$scope.$$phase||this.$scope.$apply()},this.setHeight=function(a){return void 0!==a&&(this.height=a,this.$scope.height=this.height,this.$scope.$$phase||this.$scope.$apply()),this.height},this.setColor=function(a){return void 0!==a&&(this.color=a,this.$scope.color=this.color,this.$scope.$$phase||this.$scope.$apply()),this.color},this.hide=function(){this.progressbarEl.children().css("opacity","0");var a=this;a.animate(function(){a.progressbarEl.children().css("width","0%"),a.animate(function(){a.show()},500)},500)},this.show=function(){var a=this;a.animate(function(){a.progressbarEl.children().css("opacity","1")},100)},this.animate=function(a,b){void 0!==this.animation&&e.cancel(this.animation),this.animation=e(a,b)},this.status=function(){return this.count},this.stop=function(){clearInterval(this.intervalCounterId)},this.set=function(a){return this.show(),this.updateCount(a),this.count=a,clearInterval(this.intervalCounterId),this.count},this.css=function(a){return this.progressbarEl.children().css(a)},this.reset=function(){return clearInterval(this.intervalCounterId),this.count=0,this.updateCount(this.count),0},this.complete=function(){this.count=100,this.updateCount(this.count);var a=this;return clearInterval(this.intervalCounterId),e(function(){a.hide(),e(function(){a.count=0,a.updateCount(a.count)},500)},1e3),this.count},this.setParent=function(a){if(null===a||void 0===a)throw new Error("Provide a valid parent of type HTMLElement");null!==this.parent&&void 0!==this.parent&&this.parent.removeChild(this.progressbarEl[0]),this.parent=a,this.parent.appendChild(this.progressbarEl[0])},this.getDomElement=function(){return this.progressbarEl},this.setAbsolute=function(){this.progressbarEl.css("position","absolute")}}]}).factory("ngProgressFactory",["$injector","ngProgress",function(a,b){var c={createInstance:function(){return a.instantiate(b)}};return c}]),angular.module("ngProgress.directive",[]).directive("ngProgress",["$window","$rootScope",function(a,b){var c={replace:!0,restrict:"E",link:function(a,b,c,d){a.$watch("count",function(c){(void 0!==c||null!==c)&&(a.counter=c,b.eq(0).children().css("width",c+"%"))}),a.$watch("color",function(c){(void 0!==c||null!==c)&&(a.color=c,b.eq(0).children().css("background-color",c),b.eq(0).children().css("color",c))}),a.$watch("height",function(c){(void 0!==c||null!==c)&&(a.height=c,b.eq(0).children().css("height",c))})},template:'
'};return c}]),angular.module("ngProgress",["ngProgress.directive","ngProgress.provider"]); \ No newline at end of file +angular.module("ngProgress.provider",["ngProgress.directive"]).service("ngProgress",function(){"use strict";return["$document","$window","$compile","$rootScope","$timeout",function(a,b,c,d,e){this.autoStyle=!0,this.count=0,this.height="2px",this.$scope=d.$new(),this.color="firebrick",this.parent=a.find("body")[0],this.count=0,this.updateCountInterval=200,this.updateCountRate=.15,this.progressbarEl=c("")(this.$scope),this.parent.appendChild(this.progressbarEl[0]),this.$scope.count=this.count,void 0!==this.height&&this.progressbarEl.eq(0).children().css("height",this.height),void 0!==this.color&&(this.progressbarEl.eq(0).children().css("background-color",this.color),this.progressbarEl.eq(0).children().css("color",this.color)),this.intervalCounterId=0,this.runUpdateCountLoop=function(a){return a?void(isNaN(this.count)?(b.cancelAnimationFrame(this.intervalCounterId),this.count=0,this.hide()):(this.startUpdateTime||(this.startUpdateTime=a),a-this.startUpdateTime>=this.updateCountInterval&&(this.startUpdateTime=a,this.remaining=100-this.count,this.count+=this.updateCountRate*Math.pow(1-Math.sqrt(this.remaining),2),this.updateCount(this.count)),this.intervalCounterId=b.requestAnimationFrame(this.runUpdateCountLoop.bind(this)))):(b.cancelAnimationFrame(this.intervalCounterId),void(this.intervalCounterId=b.requestAnimationFrame(this.runUpdateCountLoop.bind(this))))},this.start=function(){this.show(),this.runUpdateCountLoop()},this.updateCount=function(a){this.$scope.count=a,this.$scope.$$phase||this.$scope.$apply()},this.setUpdateRate=function(a){var b=a?Math.abs(parseFloat(a)):0;this.updateCountRate=b||this.updateCountRate},this.setUpdateInterval=function(a){var b=a?Math.abs(parseInt(a,10)):0;this.updateCountInterval=b>=200?b:this.updateCountInterval},this.setHeight=function(a){return void 0!==a&&(this.height=a,this.$scope.height=this.height,this.$scope.$$phase||this.$scope.$apply()),this.height},this.setColor=function(a){return void 0!==a&&(this.color=a,this.$scope.color=this.color,this.$scope.$$phase||this.$scope.$apply()),this.color},this.hide=function(){this.progressbarEl.children().css("opacity","0");var a=this;a.animate(function(){a.progressbarEl.children().css("width","0%"),a.animate(function(){a.show()},500)},500)},this.show=function(){var a=this;a.animate(function(){a.progressbarEl.children().css("opacity","1")},100)},this.animate=function(a,b){void 0!==this.animation&&e.cancel(this.animation),this.animation=e(a,b)},this.status=function(){return this.count},this.stop=function(){b.cancelAnimationFrame(this.intervalCounterId)},this.set=function(a){return this.show(),this.updateCount(a),this.count=a,b.cancelAnimationFrame(this.intervalCounterId),this.count},this.css=function(a){return this.progressbarEl.children().css(a)},this.reset=function(){return b.cancelAnimationFrame(this.intervalCounterId),this.count=0,this.updateCount(this.count),0},this.complete=function(){this.count=100,this.updateCount(this.count);var a=this;return b.cancelAnimationFrame(this.intervalCounterId),e(function(){a.hide(),e(function(){a.count=0,a.updateCount(a.count)},500)},1e3),this.count},this.setParent=function(a){if(null===a||void 0===a)throw new Error("Provide a valid parent of type HTMLElement");null!==this.parent&&void 0!==this.parent&&this.parent.removeChild(this.progressbarEl[0]),this.parent=a,this.parent.appendChild(this.progressbarEl[0])},this.getDomElement=function(){return this.progressbarEl},this.setAbsolute=function(){this.progressbarEl.css("position","absolute")}}]}).factory("ngProgressFactory",["$injector","ngProgress",function(a,b){var c={createInstance:function(){return a.instantiate(b)}};return c}]),angular.module("ngProgress.directive",[]).directive("ngProgress",["$window","$rootScope",function(a,b){var c={replace:!0,restrict:"E",link:function(a,b,c,d){a.$watch("count",function(c){(void 0!==c||null!==c)&&(a.counter=c,b.eq(0).children().css("width",c+"%"))}),a.$watch("color",function(c){(void 0!==c||null!==c)&&(a.color=c,b.eq(0).children().css("background-color",c),b.eq(0).children().css("color",c))}),a.$watch("height",function(c){(void 0!==c||null!==c)&&(a.height=c,b.eq(0).children().css("height",c))})},template:'
'};return c}]),angular.module("ngProgress",["ngProgress.directive","ngProgress.provider"]); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js index d947620..eaca4d2 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -57,7 +57,7 @@ module.exports = function(config) { // - Safari (only Mac) // - PhantomJS // - IE (only Windows) - browsers: ['PhantomJS'], + browsers: ['Firefox'], // If browser does not capture in given timeout [ms], kill it diff --git a/package.json b/package.json index c5dba72..d8a8236 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ngprogress", - "version": "1.1.3", + "version": "1.1.4", "description": "slim, site-wide progressbar for AngularJS", "main": "build/ngprogress.js", "repository": { @@ -29,10 +29,13 @@ "grunt-contrib-uglify": "~0.2.2", "grunt-karma": "~0.7.0", "karma": "~0.12", - "karma-jasmine": "~0.1", - "karma-phantomjs-launcher": "^0.1.4" + "karma-firefox-launcher": "^1.0.0", + "karma-jasmine": "~0.1" }, "scripts": { "test": "./node_modules/karma/bin/karma start --single-run" + }, + "dependencies": { + "grunt": "^1.0.1" } } diff --git a/src/directive.js b/src/directive.js index e649d99..2c64bcd 100644 --- a/src/directive.js +++ b/src/directive.js @@ -1,38 +1,38 @@ // Here we have the directive that we append to the body. It handles all the things related to the // styling and DOM angular.module('ngProgress.directive', []) - .directive('ngProgress', ["$window", "$rootScope", function ($window, $rootScope) { - var directiveObj = { - // Replace the directive - replace: true, - // Only use as a element - restrict: 'E', - link: function ($scope, $element, $attrs, $controller) { - // Watch the count on the $rootScope. As soon as count changes to something that - // isn't undefined or null, change the counter on $scope and also the width of - // the progressbar. The same goes for color and height on the $rootScope - $scope.$watch('count', function (newVal) { - if (newVal !== undefined || newVal !== null) { - $scope.counter = newVal; - $element.eq(0).children().css('width', newVal + '%'); - } - }); - $scope.$watch('color', function (newVal) { - if (newVal !== undefined || newVal !== null) { - $scope.color = newVal; - $element.eq(0).children().css('background-color', newVal); - $element.eq(0).children().css('color', newVal); - } - }); - $scope.$watch('height', function (newVal) { - if (newVal !== undefined || newVal !== null) { - $scope.height = newVal; - $element.eq(0).children().css('height', newVal); - } - }); - }, - // The actual html that will be used - template: '
' - }; - return directiveObj; - }]); + .directive('ngProgress', ["$window", "$rootScope", function($window, $rootScope) { + var directiveObj = { + // Replace the directive + replace: true, + // Only use as a element + restrict: 'E', + link: function($scope, $element, $attrs, $controller) { + // Watch the count on the $rootScope. As soon as count changes to something that + // isn't undefined or null, change the counter on $scope and also the width of + // the progressbar. The same goes for color and height on the $rootScope + $scope.$watch('count', function(newVal) { + if (newVal !== undefined || newVal !== null) { + $scope.counter = newVal; + $element.eq(0).children().css('width', newVal + '%'); + } + }); + $scope.$watch('color', function(newVal) { + if (newVal !== undefined || newVal !== null) { + $scope.color = newVal; + $element.eq(0).children().css('background-color', newVal); + $element.eq(0).children().css('color', newVal); + } + }); + $scope.$watch('height', function(newVal) { + if (newVal !== undefined || newVal !== null) { + $scope.height = newVal; + $element.eq(0).children().css('height', newVal); + } + }); + }, + // The actual html that will be used + template: '
' + }; + return directiveObj; + }]); diff --git a/src/provider.js b/src/provider.js index 726f3b3..d4e547e 100644 --- a/src/provider.js +++ b/src/provider.js @@ -1,176 +1,223 @@ // Here is the business logic that we will use for the progress angular.module('ngProgress.provider', ['ngProgress.directive']) - .service('ngProgress', function () { - 'use strict'; - return ['$document', '$window', '$compile', '$rootScope', '$timeout', function($document, $window, $compile, $rootScope, $timeout) { - this.autoStyle = true; - this.count = 0; - this.height = '2px'; - this.$scope = $rootScope.$new(); - this.color = 'firebrick'; - this.parent = $document.find('body')[0]; - this.count = 0; - - // Compile the directive - this.progressbarEl = $compile('')(this.$scope); - // Add the element to body - this.parent.appendChild(this.progressbarEl[0]); - // Set the initial height - this.$scope.count = this.count; - // If height or color isn't undefined, set the height, background-color and color. - if (this.height !== undefined) { - this.progressbarEl.eq(0).children().css('height', this.height); - } - if (this.color !== undefined) { - this.progressbarEl.eq(0).children().css('background-color', this.color); - this.progressbarEl.eq(0).children().css('color', this.color); - } - // The ID for the interval controlling start() - this.intervalCounterId = 0; - - // Starts the animation and adds between 0 - 5 percent to loading - // each 400 milliseconds. Should always be finished with progressbar.complete() - // to hide it - this.start = function () { - // TODO Use requestAnimationFrame instead of setInterval - // https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame - this.show(); - var self = this; - clearInterval(this.intervalCounterId); - this.intervalCounterId = setInterval(function () { - if (isNaN(self.count)) { - clearInterval(self.intervalCounterId); - self.count = 0; - self.hide(); - } else { - self.remaining = 100 - self.count; - self.count = self.count + (0.15 * Math.pow(1 - Math.sqrt(self.remaining), 2)); - self.updateCount(self.count); - } - }, 200); - }; - this.updateCount = function (new_count) { - this.$scope.count = new_count; - if(!this.$scope.$$phase) { - this.$scope.$apply(); - } - }; - // Sets the height of the progressbar. Use any valid CSS value - // Eg '10px', '1em' or '1%' - this.setHeight = function (new_height) { - if (new_height !== undefined) { - this.height = new_height; - this.$scope.height = this.height; - if(!this.$scope.$$phase) { - this.$scope.$apply(); - } - } - return this.height; - }; - // Sets the color of the progressbar and it's shadow. Use any valid HTML - // color - this.setColor = function(new_color) { - if (new_color !== undefined) { - this.color = new_color; - this.$scope.color = this.color; - if(!this.$scope.$$phase) { - this.$scope.$apply(); - } - } - return this.color; - }; - this.hide = function() { - this.progressbarEl.children().css('opacity', '0'); - var self = this; - self.animate(function () { - self.progressbarEl.children().css('width', '0%'); - self.animate(function () { - self.show(); - }, 500); - }, 500); - }; - this.show = function () { - var self = this; - self.animate(function () { - self.progressbarEl.children().css('opacity', '1'); - }, 100); - }; - // Cancel any prior animations before running new ones. - // Multiple simultaneous animations just look weird. - this.animate = function(fn, time) { - if(this.animation !== undefined) { $timeout.cancel(this.animation); } - this.animation = $timeout(fn, time); - }; - // Returns on how many percent the progressbar is at. Should'nt be needed - this.status = function () { - return this.count; - }; - // Stops the progressbar at it's current location - this.stop = function () { - clearInterval(this.intervalCounterId); - }; - // Set's the progressbar percentage. Use a number between 0 - 100. - // If 100 is provided, complete will be called. - this.set = function (new_count) { - this.show(); - this.updateCount(new_count); - this.count = new_count; - clearInterval(this.intervalCounterId); - return this.count; - }; - this.css = function (args) { - return this.progressbarEl.children().css(args); - }; - // Resets the progressbar to percetage 0 and therefore will be hided after - // it's rollbacked - this.reset = function () { - clearInterval(this.intervalCounterId); - this.count = 0; - this.updateCount(this.count); - return 0; - }; - // Jumps to 100% progress and fades away progressbar. - this.complete = function () { - this.count = 100; - this.updateCount(this.count); - var self = this; - clearInterval(this.intervalCounterId); - $timeout(function () { - self.hide(); - $timeout(function () { - self.count = 0; - self.updateCount(self.count); - }, 500); - }, 1000); - return this.count; - }; - // Set the parent of the directive, sometimes body is not sufficient - this.setParent = function(newParent) { - if(newParent === null || newParent === undefined) { - throw new Error('Provide a valid parent of type HTMLElement'); - } - - if(this.parent !== null && this.parent !== undefined) { - this.parent.removeChild(this.progressbarEl[0]); - } - - this.parent = newParent; - this.parent.appendChild(this.progressbarEl[0]); - }; - // Gets the current element the progressbar is attached to - this.getDomElement = function () { - return this.progressbarEl; - }; - this.setAbsolute = function() { - this.progressbarEl.css('position', 'absolute'); - }; - }]; - }) -.factory('ngProgressFactory', ['$injector', 'ngProgress', function($injector, ngProgress) { - var service = { - createInstance: function () { - return $injector.instantiate(ngProgress); + .service('ngProgress', function() { + 'use strict'; + return ['$document', '$window', '$compile', '$rootScope', '$timeout', function($document, $window, $compile, $rootScope, $timeout) { + this.autoStyle = true; + this.count = 0; + this.height = '2px'; + this.$scope = $rootScope.$new(); + this.color = 'firebrick'; + this.parent = $document.find('body')[0]; + this.count = 0; + this.updateCountInterval = 200; + this.updateCountRate = 0.15; + + // Compile the directive + this.progressbarEl = $compile('')(this.$scope); + + // Add the element to body + this.parent.appendChild(this.progressbarEl[0]); + + // Set the initial height + this.$scope.count = this.count; + + // If height or color isn't undefined, set the height, background-color and color. + if (this.height !== undefined) { + this.progressbarEl.eq(0).children().css('height', this.height); + } + + if (this.color !== undefined) { + this.progressbarEl.eq(0).children().css('background-color', this.color); + this.progressbarEl.eq(0).children().css('color', this.color); + } + + // The ID for the interval controlling start() + this.intervalCounterId = 0; + + this.runUpdateCountLoop = function(timestamp) { + if (!timestamp) { // first call not from requestAnimationFrame + $window.cancelAnimationFrame(this.intervalCounterId); + this.intervalCounterId = $window.requestAnimationFrame(this.runUpdateCountLoop.bind(this)); + return; + } + + if (isNaN(this.count)) { + $window.cancelAnimationFrame(this.intervalCounterId); + this.count = 0; + this.hide(); + } else { + if (!this.startUpdateTime) { + this.startUpdateTime = timestamp; + } + + if (timestamp - this.startUpdateTime >= this.updateCountInterval) { + this.startUpdateTime = timestamp; + this.remaining = 100 - this.count; + this.count += this.updateCountRate * Math.pow(1 - Math.sqrt(this.remaining), 2); + this.updateCount(this.count); + } + + this.intervalCounterId = $window.requestAnimationFrame(this.runUpdateCountLoop.bind(this)); + } + }; + + // Starts the animation and adds between 0 - 5 percent to loading + // each 400 milliseconds. Should always be finished with progressbar.complete() + // to hide it + this.start = function() { + this.show(); + this.runUpdateCountLoop(); + }; + + this.updateCount = function(new_count) { + this.$scope.count = new_count; + if (!this.$scope.$$phase) { + this.$scope.$apply(); } + }; + + this.setUpdateRate = function(rate) { + var normalizedRate = rate ? Math.abs(parseFloat(rate)) : 0; + this.updateCountRate = normalizedRate || this.updateCountRate; + }; + + this.setUpdateInterval = function(interval) { + var normalizedInterval = interval ? Math.abs(parseInt(interval, 10)) : 0; + this.updateCountInterval = normalizedInterval >= 200 ? normalizedInterval : this.updateCountInterval; + }; + + // Sets the height of the progressbar. Use any valid CSS value + // Eg '10px', '1em' or '1%' + this.setHeight = function(new_height) { + if (new_height !== undefined) { + this.height = new_height; + this.$scope.height = this.height; + if (!this.$scope.$$phase) { + this.$scope.$apply(); + } + } + return this.height; + }; + + // Sets the color of the progressbar and it's shadow. Use any valid HTML + // color + this.setColor = function(new_color) { + if (new_color !== undefined) { + this.color = new_color; + this.$scope.color = this.color; + if (!this.$scope.$$phase) { + this.$scope.$apply(); + } + } + return this.color; + }; + + this.hide = function() { + this.progressbarEl.children().css('opacity', '0'); + var self = this; + self.animate(function() { + self.progressbarEl.children().css('width', '0%'); + self.animate(function() { + self.show(); + }, 500); + }, 500); + }; + + this.show = function() { + var self = this; + self.animate(function() { + self.progressbarEl.children().css('opacity', '1'); + }, 100); + }; + + // Cancel any prior animations before running new ones. + // Multiple simultaneous animations just look weird. + this.animate = function(fn, time) { + if (this.animation !== undefined) { + $timeout.cancel(this.animation); + } + this.animation = $timeout(fn, time); + }; + + // Returns on how many percent the progressbar is at. Should'nt be needed + this.status = function() { + return this.count; + }; + + // Stops the progressbar at it's current location + this.stop = function() { + $window.cancelAnimationFrame(this.intervalCounterId); + }; + + // Set's the progressbar percentage. Use a number between 0 - 100. + // If 100 is provided, complete will be called. + this.set = function(new_count) { + this.show(); + this.updateCount(new_count); + this.count = new_count; + $window.cancelAnimationFrame(this.intervalCounterId); + return this.count; + }; + + this.css = function(args) { + return this.progressbarEl.children().css(args); + }; + + // Resets the progressbar to percetage 0 and therefore will be hided after + // it's rollbacked + this.reset = function() { + $window.cancelAnimationFrame(this.intervalCounterId); + this.count = 0; + this.updateCount(this.count); + return 0; + }; + + // Jumps to 100% progress and fades away progressbar. + this.complete = function() { + this.count = 100; + this.updateCount(this.count); + var self = this; + $window.cancelAnimationFrame(this.intervalCounterId); + $timeout(function() { + self.hide(); + $timeout(function() { + self.count = 0; + self.updateCount(self.count); + }, 500); + }, 1000); + return this.count; + }; + + // Set the parent of the directive, sometimes body is not sufficient + this.setParent = function(newParent) { + if (newParent === null || newParent === undefined) { + throw new Error('Provide a valid parent of type HTMLElement'); + } + + if (this.parent !== null && this.parent !== undefined) { + this.parent.removeChild(this.progressbarEl[0]); + } + + this.parent = newParent; + this.parent.appendChild(this.progressbarEl[0]); + }; + + // Gets the current element the progressbar is attached to + this.getDomElement = function() { + return this.progressbarEl; + }; + + this.setAbsolute = function() { + this.progressbarEl.css('position', 'absolute'); + }; + }]; + }) + .factory('ngProgressFactory', ['$injector', 'ngProgress', function($injector, ngProgress) { + var service = { + createInstance: function() { + return $injector.instantiate(ngProgress); + } }; return service; -}]); \ No newline at end of file + }]); diff --git a/tests/ngProgressProviderSpec.js b/tests/ngProgressProviderSpec.js index cc8ebb4..9ac8985 100644 --- a/tests/ngProgressProviderSpec.js +++ b/tests/ngProgressProviderSpec.js @@ -1,161 +1,213 @@ /*globals describe:true,it:true,inject:true,expect:true,beforeEach:true,runs:true, -waitsFor:true */ -describe('How the provider should work', function () { - beforeEach(function () { - module('ngProgress'); + waitsFor:true */ +describe('How the provider should work', function() { + beforeEach(function() { + module('ngProgress'); + }); + + beforeEach(inject(function(ngProgressFactory, $window) { + this.progressbar = ngProgressFactory.createInstance(); + this.$window = $window; + })); + + it('starts at zero when just being injected', function() { + expect(this.progressbar.status()).toBe(0); + }); + + it('can change the status to 30 if you call set()', function() { + this.progressbar.set(30); + expect(this.progressbar.status()).toBe(30); + }); + + it('will stop progress when call set()', function() { + this.progressbar.start(); + this.progressbar.set(1); + expect(this.progressbar.status()).toBe(1); + }); + var value, flag; + it('increaments over time after calling start()', function() { + // var value, flag; + this.progressbar.start(); + + runs(function() { + flag = false; + value = this.progressbar.status(); + + setTimeout(function() { + flag = true; + }, 1000); + }); + waitsFor(function() { + value = this.progressbar.status(); + return flag; + }, "The Value should be incremented", 1000); + + runs(function() { + expect(value).toBeGreaterThan(20); + expect(value).toBeLessThan(50); + }); + }); + + it('have 100 returned from status() after complete()', function() { + this.progressbar.start(); + this.progressbar.complete(); + expect(this.progressbar.status()).toBe(100); + }); + + it('resets to zero when calling reset() after start() or set()', function() { + this.progressbar.set(30); + this.progressbar.reset(); + expect(this.progressbar.status()).toBe(0); + }); + it('will return 100 after calling complete', function() { + this.progressbar.set(30); + this.progressbar.complete(); + expect(this.progressbar.status()).toBe(100); + }); + it('return current height when calling height() without parameters', function() { + expect(this.progressbar.height).toBe('2px'); + }); + it('set the height when calling height() with parameter', function() { + this.progressbar.setHeight('5px'); + expect(this.progressbar.height).toBe('5px'); + }); + it('return current color when calling color() without parameters', function() { + expect(this.progressbar.color).toBe('firebrick'); + }); + it('set the color when calling color() with parameter', function() { + this.progressbar.setColor('green'); + expect(this.progressbar.color).toBe('green'); + }); + it('stops at it\'s current progress when calling stop()', function() { + // var value, flag; + this.progressbar.start(); + + runs(function() { + flag = false; + value = this.progressbar.status(); + + setTimeout(function() { + flag = true; + }, 200); + }); + waitsFor(function() { + value = this.progressbar.status(); + return flag; + }, "The Value should be incremented", 1000); + + runs(function() { + this.progressbar.stop(); + expect(value).toBeGreaterThan(10); + expect(value).toBeLessThan(20); + }); + }); + + it('frequently calling reset, start and complete should work', function() { + + runs(function() { + this.progressbar.reset(); + }); + waits(10); + runs(function() { + this.progressbar.start(); + }); + waits(10); + runs(function() { + this.progressbar.complete(); + }); + waits(10); + runs(function() { + expect([1, this.progressbar.status()]).toEqual([1, 100]); + }); + + runs(function() { + this.progressbar.reset(); + }); + waits(10); + runs(function() { + this.progressbar.start(); + }); + waits(10); + runs(function() { + this.progressbar.complete(); + }); + waits(10); + runs(function() { + expect([2, this.progressbar.status()]).toEqual([2, 100]); + }); + + runs(function() { + this.progressbar.reset(); + }); + waits(10); + runs(function() { + this.progressbar.start(); + }); + waits(10); + runs(function() { + this.progressbar.complete(); + }); + waits(10); + runs(function() { + expect([3, this.progressbar.status()]).toEqual([3, 100]); + }); + + waits(200); + runs(function() { + expect([4, this.progressbar.status()]).toEqual([4, 100]); + }); + + waits(200); + runs(function() { + expect([5, this.progressbar.status()]).toEqual([5, 100]); }); - beforeEach(inject(function (ngProgressFactory, $window) { - this.progressbar = ngProgressFactory.createInstance(); - this.$window = $window; - })); - - it('starts at zero when just being injected', function () { - expect(this.progressbar.status()).toBe(0); - }); - - it('can change the status to 30 if you call set()', function () { - this.progressbar.set(30); - expect(this.progressbar.status()).toBe(30); - }); - - it('will stop progress when call set()', function () { - this.progressbar.start(); - this.progressbar.set(1); - expect(this.progressbar.status()).toBe(1); + waits(200); + runs(function() { + expect([6, this.progressbar.status()]).toEqual([6, 100]); }); - var value, flag; - it('increaments over time after calling start()', function () { - // var value, flag; - this.progressbar.start(); - - runs(function () { - flag = false; - value = this.progressbar.status(); + }); - setTimeout(function () { - flag = true; - }, 1000); - }); - waitsFor(function () { - value = this.progressbar.status(); - return flag; - }, "The Value should be incremented", 1000); + it('allow you to change the parent of the progressbar', function() { + var domElement = this.progressbar.getDomElement()[0]; + expect(domElement.parentNode).toEqual(document.body); - runs(function () { - expect(value).toBeGreaterThan(20); - expect(value).toBeLessThan(50); - }); - }); - - it('have 100 returned from status() after complete()', function () { - this.progressbar.start(); - this.progressbar.complete(); - expect(this.progressbar.status()).toBe(100); - }); + var div = document.createElement('div'); + document.body.appendChild(div); + this.progressbar.setParent(div); + expect(domElement.parentNode).toEqual(div); + }); - it('resets to zero when calling reset() after start() or set()', function () { - this.progressbar.set(30); - this.progressbar.reset(); - expect(this.progressbar.status()).toBe(0); - }); - it('will return 100 after calling complete', function () { - this.progressbar.set(30); - this.progressbar.complete(); - expect(this.progressbar.status()).toBe(100); - }); - it('return current height when calling height() without parameters', function () { - expect(this.progressbar.height).toBe('2px'); - }); - it('set the height when calling height() with parameter', function () { - this.progressbar.setHeight('5px'); - expect(this.progressbar.height).toBe('5px'); - }); - it('return current color when calling color() without parameters', function () { - expect(this.progressbar.color).toBe('firebrick'); - }); - it('set the color when calling color() with parameter', function () { - this.progressbar.setColor('green'); - expect(this.progressbar.color).toBe('green'); - }); - it('stops at it\'s current progress when calling sotp()', function () { - // var value, flag; - this.progressbar.start(); + it('throws exception when invalid parent is set', function() { + var that = this; + expect(function() { + that.progressbar.setParent(null); + }).toThrow(new Error('Provide a valid parent of type HTMLElement')); + }); - runs(function () { - flag = false; - value = this.progressbar.status(); + it('set update count interval', function() { + var defaultInterval = this.progressbar.updateCountInterval; + // default interval is minimum possible value + this.progressbar.setUpdateInterval(defaultInterval - 10); + expect(this.progressbar.updateCountInterval).toBe(defaultInterval); - setTimeout(function () { - flag = true; - }, 200); - }); - waitsFor(function () { - value = this.progressbar.status(); - return flag; - }, "The Value should be incremented", 1000); + this.progressbar.setUpdateInterval(defaultInterval + 10); + expect(this.progressbar.updateCountInterval).toBe(defaultInterval + 10); + }); - runs(function () { - this.progressbar.stop(); - expect(value).toBeGreaterThan(10); - expect(value).toBeLessThan(20); - }); - }); + it('set update count rate', function() { + var defaultUpdateCountRate = this.progressbar.updateCountRate; - it('frequently calling reset, start and complete should work', function () { + // rate can not be zero + this.progressbar.setUpdateRate(0); + expect(this.progressbar.updateCountRate).toBe(defaultUpdateCountRate); - runs(function () { this.progressbar.reset(); }); - waits(10); - runs(function () { this.progressbar.start(); }); - waits(10); - runs(function () { this.progressbar.complete(); }); - waits(10); - runs(function () { expect([1, this.progressbar.status()]).toEqual([1, 100]); }); - - runs(function () { this.progressbar.reset(); }); - waits(10); - runs(function () { this.progressbar.start(); }); - waits(10); - runs(function () { this.progressbar.complete(); }); - waits(10); - runs(function () { expect([2, this.progressbar.status()]).toEqual([2, 100]); }); - - - runs(function () { this.progressbar.reset(); }); - waits(10); - runs(function () { this.progressbar.start(); }); - waits(10); - runs(function () { this.progressbar.complete(); }); - waits(10); - runs(function () { expect([3, this.progressbar.status()]).toEqual([3, 100]); }); - - - waits(200); - runs(function () { expect([4, this.progressbar.status()]).toEqual([4, 100]); }); - - waits(200); - runs(function () { expect([5, this.progressbar.status()]).toEqual([5, 100]); }); - - waits(200); - runs(function () { expect([6, this.progressbar.status()]).toEqual([6, 100]); }); - }); - - it('allow you to change the parent of the progressbar', function () { - var domElement = this.progressbar.getDomElement()[0]; - expect(domElement.parentNode).toEqual(document.body); - - var div = document.createElement('div'); - document.body.appendChild(div); - this.progressbar.setParent(div); - expect(domElement.parentNode).toEqual(div); - }); - - it('throws exception when invalid parent is set', function () { - var that = this; - expect(function () { - that.progressbar.setParent(null); - }).toThrow(new Error('Provide a valid parent of type HTMLElement')); - }); + // negative value is converted to positive + this.progressbar.setUpdateRate(-15); + expect(this.progressbar.updateCountRate).toBe(15); + this.progressbar.setUpdateRate(defaultUpdateCountRate + 10); + expect(this.progressbar.updateCountRate).toBe(defaultUpdateCountRate + 10); + }); });