Created
May 20, 2015 11:07
-
-
Save hugsbrugs/79c329541937d019838b to your computer and use it in GitHub Desktop.
angular loading bar with bootstrap backdrop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* angular-loading-bar v0.7.1 | |
* https://chieffancypants.github.io/angular-loading-bar | |
* Copyright (c) 2015 Wes Cruver | |
* License: MIT | |
*/ | |
/* | |
* angular-loading-bar | |
* | |
* intercepts XHR requests and creates a loading bar. | |
* Based on the excellent nprogress work by rstacruz (more info in readme) | |
* | |
* (c) 2013 Wes Cruver | |
* License: MIT | |
*/ | |
(function() { | |
'use strict'; | |
// Alias the loading bar for various backwards compatibilities since the project has matured: | |
angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']); | |
angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']); | |
/** | |
* loadingBarInterceptor service | |
* | |
* Registers itself as an Angular interceptor and listens for XHR requests. | |
*/ | |
angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar']) | |
.config(['$httpProvider', function ($httpProvider) { | |
var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', '$log', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, $log, cfpLoadingBar) { | |
/** | |
* The total number of requests made | |
*/ | |
var reqsTotal = 0; | |
/** | |
* The number of requests completed (either successfully or not) | |
*/ | |
var reqsCompleted = 0; | |
/** | |
* The amount of time spent fetching before showing the loading bar | |
*/ | |
var latencyThreshold = cfpLoadingBar.latencyThreshold; | |
/** | |
* $timeout handle for latencyThreshold | |
*/ | |
var startTimeout; | |
/** | |
* calls cfpLoadingBar.complete() which removes the | |
* loading bar from the DOM. | |
*/ | |
function setComplete() { | |
$timeout.cancel(startTimeout); | |
cfpLoadingBar.complete(); | |
reqsCompleted = 0; | |
reqsTotal = 0; | |
} | |
/** | |
* Determine if the response has already been cached | |
* @param {Object} config the config option from the request | |
* @return {Boolean} retrns true if cached, otherwise false | |
*/ | |
function isCached(config) { | |
var cache; | |
var defaultCache = $cacheFactory.get('$http'); | |
var defaults = $httpProvider.defaults; | |
// Choose the proper cache source. Borrowed from angular: $http service | |
if ((config.cache || defaults.cache) && config.cache !== false && | |
(config.method === 'GET' || config.method === 'JSONP')) { | |
cache = angular.isObject(config.cache) ? config.cache | |
: angular.isObject(defaults.cache) ? defaults.cache | |
: defaultCache; | |
} | |
var cached = cache !== undefined ? | |
cache.get(config.url) !== undefined : false; | |
if (config.cached !== undefined && cached !== config.cached) { | |
return config.cached; | |
} | |
config.cached = cached; | |
return cached; | |
} | |
return { | |
'request': function(config) { | |
// Check to make sure this request hasn't already been cached and that | |
// the requester didn't explicitly ask us to ignore this request: | |
if (!config.ignoreLoadingBar && !isCached(config)) { | |
$rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url}); | |
if (reqsTotal === 0) { | |
startTimeout = $timeout(function() { | |
cfpLoadingBar.start(); | |
}, latencyThreshold); | |
} | |
reqsTotal++; | |
cfpLoadingBar.set(reqsCompleted / reqsTotal); | |
} | |
return config; | |
}, | |
'response': function(response) { | |
if (!response || !response.config) { | |
$log.error('Broken interceptor detected: Config object not supplied in response:\n https://github.com/chieffancypants/angular-loading-bar/pull/50'); | |
return response; | |
} | |
if (!response.config.ignoreLoadingBar && !isCached(response.config)) { | |
reqsCompleted++; | |
$rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url, result: response}); | |
if (reqsCompleted >= reqsTotal) { | |
setComplete(); | |
} else { | |
cfpLoadingBar.set(reqsCompleted / reqsTotal); | |
} | |
} | |
return response; | |
}, | |
'responseError': function(rejection) { | |
if (!rejection || !rejection.config) { | |
$log.error('Broken interceptor detected: Config object not supplied in rejection:\n https://github.com/chieffancypants/angular-loading-bar/pull/50'); | |
return $q.reject(rejection); | |
} | |
if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) { | |
reqsCompleted++; | |
$rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url, result: rejection}); | |
if (reqsCompleted >= reqsTotal) { | |
setComplete(); | |
} else { | |
cfpLoadingBar.set(reqsCompleted / reqsTotal); | |
} | |
} | |
return $q.reject(rejection); | |
} | |
}; | |
}]; | |
$httpProvider.interceptors.push(interceptor); | |
}]); | |
/** | |
* Loading Bar | |
* | |
* This service handles adding and removing the actual element in the DOM. | |
* Generally, best practices for DOM manipulation is to take place in a | |
* directive, but because the element itself is injected in the DOM only upon | |
* XHR requests, and it's likely needed on every view, the best option is to | |
* use a service. | |
*/ | |
angular.module('cfp.loadingBar', []) | |
.provider('cfpLoadingBar', function() { | |
this.includeSpinner = true; | |
this.includeBar = true; | |
this.includeBackdrop = true; | |
this.latencyThreshold = 100; | |
this.startSize = 0.02; | |
this.parentSelector = 'body'; | |
this.spinnerTemplate = '<div id="loading-bar-spinner"><div class="spinner-icon"></div></div>'; | |
this.loadingBarTemplate = '<div id="loading-bar"><div class="bar"><div class="peg"></div></div></div>'; | |
this.backdropTemplate = '<div class="modal-backdrop fade in" ng-class="{in: animate}" style="z-index: 1040;"></div>'; | |
this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) { | |
var $animate; | |
var $parentSelector = this.parentSelector, | |
loadingBarContainer = angular.element(this.loadingBarTemplate), | |
backdropContainer = angular.element(this.backdropTemplate), | |
loadingBar = loadingBarContainer.find('div').eq(0), | |
spinner = angular.element(this.spinnerTemplate); | |
var incTimeout, | |
completeTimeout, | |
started = false, | |
status = 0; | |
var includeSpinner = this.includeSpinner; | |
var includeBar = this.includeBar; | |
var includeBackdrop = this.includeBackdrop; | |
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'); | |
} | |
var $parent = $document.find($parentSelector).eq(0); | |
$timeout.cancel(completeTimeout); | |
// do not continually broadcast the started event: | |
if (started) { | |
return; | |
} | |
$rootScope.$broadcast('cfpLoadingBar:started'); | |
started = true; | |
if (includeBar) { | |
$animate.enter(loadingBarContainer, $parent, angular.element($parent[0].lastChild)); | |
} | |
if (includeBackdrop) { | |
$animate.enter(backdropContainer, $parent, angular.element($parent[0].lastChild)); | |
} | |
if (includeSpinner) { | |
$animate.enter(spinner, $parent, angular.element($parent[0].lastChild)); | |
} | |
_set(startSize); | |
} | |
/** | |
* Set the loading bar's width to a certain percent. | |
* | |
* @param n any value between 0 and 1 | |
*/ | |
function _set(n) { | |
if (!started) { | |
return; | |
} | |
var pct = (n * 100) + '%'; | |
loadingBar.css('width', pct); | |
status = n; | |
// 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. | |
$timeout.cancel(incTimeout); | |
incTimeout = $timeout(function() { | |
_inc(); | |
}, 250); | |
} | |
/** | |
* Increments the loading bar by a random amount | |
* but slows down as it progresses | |
*/ | |
function _inc() { | |
if (_status() >= 1) { | |
return; | |
} | |
var rnd = 0; | |
// TODO: do this mathmatically instead of through conditions | |
var stat = _status(); | |
if (stat >= 0 && stat < 0.25) { | |
// Start out between 3 - 6% increments | |
rnd = (Math.random() * (5 - 3 + 1) + 3) / 100; | |
} else if (stat >= 0.25 && stat < 0.65) { | |
// increment between 0 - 3% | |
rnd = (Math.random() * 3) / 100; | |
} else if (stat >= 0.65 && stat < 0.9) { | |
// increment between 0 - 2% | |
rnd = (Math.random() * 2) / 100; | |
} else if (stat >= 0.9 && stat < 0.99) { | |
// finally, increment it .5 % | |
rnd = 0.005; | |
} else { | |
// after 99%, don't increment: | |
rnd = 0; | |
} | |
var pct = _status() + rnd; | |
_set(pct); | |
} | |
function _status() { | |
return status; | |
} | |
function _completeAnimation() { | |
status = 0; | |
started = false; | |
} | |
function _complete() { | |
if (!$animate) { | |
$animate = $injector.get('$animate'); | |
} | |
$rootScope.$broadcast('cfpLoadingBar:completed'); | |
_set(1); | |
$timeout.cancel(completeTimeout); | |
// 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); | |
$animate.leave(backdropContainer); | |
}, 500); | |
} | |
return { | |
start : _start, | |
set : _set, | |
status : _status, | |
inc : _inc, | |
complete : _complete, | |
includeSpinner : this.includeSpinner, | |
includeBackdrop : this.includeBackdrop, | |
latencyThreshold : this.latencyThreshold, | |
parentSelector : this.parentSelector, | |
startSize : this.startSize | |
}; | |
}]; // | |
}); // wtf javascript. srsly | |
})(); // |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment