4 * intercepts XHR requests and creates a loading bar.
5 * Based on the excellent nprogress work by rstacruz (more info in readme)
16 // Alias the loading bar for various backwards compatibilities since the project has matured:
17 angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']);
18 angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']);
22 * loadingBarInterceptor service
24 * Registers itself as an Angular interceptor and listens for XHR requests.
26 angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar'])
27 .config(['$httpProvider', function ($httpProvider) {
29 var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, cfpLoadingBar) {
32 * The total number of requests made
37 * The number of requests completed (either successfully or not)
39 var reqsCompleted = 0;
42 * The amount of time spent fetching before showing the loading bar
44 var latencyThreshold = cfpLoadingBar.latencyThreshold;
47 * $timeout handle for latencyThreshold
53 * calls cfpLoadingBar.complete() which removes the
54 * loading bar from the DOM.
56 function setComplete() {
57 $timeout.cancel(startTimeout);
58 cfpLoadingBar.complete();
64 * Determine if the response has already been cached
65 * @param {Object} config the config option from the request
66 * @return {Boolean} retrns true if cached, otherwise false
68 function isCached(config) {
70 var defaultCache = $cacheFactory.get('$http');
71 var defaults = $httpProvider.defaults;
73 // Choose the proper cache source. Borrowed from angular: $http service
74 if ((config.cache || defaults.cache) && config.cache !== false &&
75 (config.method === 'GET' || config.method === 'JSONP')) {
76 cache = angular.isObject(config.cache) ? config.cache
77 : angular.isObject(defaults.cache) ? defaults.cache
81 var cached = cache !== undefined ?
82 cache.get(config.url) !== undefined : false;
84 if (config.cached !== undefined && cached !== config.cached) {
87 config.cached = cached;
93 'request': function(config) {
94 // Check to make sure this request hasn't already been cached and that
95 // the requester didn't explicitly ask us to ignore this request:
96 if (!config.ignoreLoadingBar && !isCached(config)) {
97 $rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url});
98 /*if (reqsTotal === 0) {
99 startTimeout = $timeout(function() {
100 cfpLoadingBar.start();
101 }, latencyThreshold);
103 cfpLoadingBar.start();
105 cfpLoadingBar.set(reqsCompleted / reqsTotal);
110 'response': function(response) {
111 if (!response.config.ignoreLoadingBar && !isCached(response.config)) {
113 $rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url, result: response});
114 if (reqsCompleted >= reqsTotal) {
117 cfpLoadingBar.set(reqsCompleted / reqsTotal);
123 'responseError': function(rejection) {
124 if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) {
126 $rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url, result: rejection});
127 if (reqsCompleted >= reqsTotal) {
130 cfpLoadingBar.set(reqsCompleted / reqsTotal);
133 return $q.reject(rejection);
138 $httpProvider.interceptors.push(interceptor);
145 * This service handles adding and removing the actual element in the DOM.
146 * Generally, best practices for DOM manipulation is to take place in a
147 * directive, but because the element itself is injected in the DOM only upon
148 * XHR requests, and it's likely needed on every view, the best option is to
151 angular.module('cfp.loadingBar', [])
152 .provider('cfpLoadingBar', function() {
154 this.includeSpinner = true;
155 this.includeBar = true;
156 this.latencyThreshold = 100;
157 this.startSize = 0.02;
158 this.parentSelector = 'body';
159 this.spinnerTemplate = '<div id="loading-bar-spinner"><div class="spinner-icon"></div></div>';
160 this.loadingBarTemplate = '<div id="loading-bar"><div class="bar"><div class="peg"></div></div></div>';
162 this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) {
164 var $parentSelector = this.parentSelector,
165 loadingBarContainer = angular.element(this.loadingBarTemplate),
166 loadingBar = loadingBarContainer.find('div').eq(0),
167 spinner = angular.element(this.spinnerTemplate);
174 var includeSpinner = this.includeSpinner;
175 var includeBar = this.includeBar;
176 var startSize = this.startSize;
179 * Inserts the loading bar element into the dom, and sets it to 2%
183 $animate = $injector.get('$animate');
186 var $parent = $document.find($parentSelector).eq(0);
187 $timeout.cancel(completeTimeout);
189 // do not continually broadcast the started event:
194 $rootScope.$broadcast('cfpLoadingBar:started');
198 $animate.enter(loadingBarContainer, $parent);
201 if (includeSpinner) {
202 $animate.enter(spinner, $parent);
209 * Set the loading bar's width to a certain percent.
211 * @param n any value between 0 and 1
217 var pct = (n * 100) + '%';
218 loadingBar.css('width', pct);
221 // increment loadingbar to give the illusion that there is always
222 // progress but make sure to cancel the previous timeouts so we don't
223 // have multiple incs running at the same time.
224 $timeout.cancel(incTimeout);
225 incTimeout = $timeout(function() {
231 * Increments the loading bar by a random amount
232 * but slows down as it progresses
235 if (_status() >= 1) {
241 // TODO: do this mathmatically instead of through conditions
243 var stat = _status();
244 if (stat >= 0 && stat < 0.25) {
245 // Start out between 3 - 6% increments
246 rnd = (Math.random() * (5 - 3 + 1) + 3) / 100;
247 } else if (stat >= 0.25 && stat < 0.65) {
248 // increment between 0 - 3%
249 rnd = (Math.random() * 3) / 100;
250 } else if (stat >= 0.65 && stat < 0.9) {
251 // increment between 0 - 2%
252 rnd = (Math.random() * 2) / 100;
253 } else if (stat >= 0.9 && stat < 0.99) {
254 // finally, increment it .5 %
257 // after 99%, don't increment:
261 var pct = _status() + rnd;
269 function _completeAnimation() {
274 function _complete() {
276 $animate = $injector.get('$animate');
279 $rootScope.$broadcast('cfpLoadingBar:completed');
282 $timeout.cancel(completeTimeout);
284 // Attempt to aggregate any start/complete calls within 500ms:
285 completeTimeout = $timeout(function() {
286 var promise = $animate.leave(loadingBarContainer, _completeAnimation);
287 if (promise && promise.then) {
288 promise.then(_completeAnimation);
290 $animate.leave(spinner);
299 complete : _complete,
300 includeSpinner : this.includeSpinner,
301 latencyThreshold : this.latencyThreshold,
302 parentSelector : this.parentSelector,
303 startSize : this.startSize
308 }); // wtf javascript. srsly