[12428] | 1 | /*
|
---|
| 2 | * angular-loading-bar
|
---|
| 3 | *
|
---|
| 4 | * intercepts XHR requests and creates a loading bar.
|
---|
| 5 | * Based on the excellent nprogress work by rstacruz (more info in readme)
|
---|
| 6 | *
|
---|
| 7 | * (c) 2013 Wes Cruver
|
---|
| 8 | * License: MIT
|
---|
| 9 | */
|
---|
| 10 |
|
---|
| 11 |
|
---|
| 12 | (function () {
|
---|
| 13 |
|
---|
| 14 | 'use strict';
|
---|
| 15 |
|
---|
| 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']);
|
---|
| 19 |
|
---|
| 20 |
|
---|
| 21 | /**
|
---|
| 22 | * loadingBarInterceptor service
|
---|
| 23 | *
|
---|
| 24 | * Registers itself as an Angular interceptor and listens for XHR requests.
|
---|
| 25 | */
|
---|
| 26 | angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar'])
|
---|
| 27 | .config(['$httpProvider', function ($httpProvider) {
|
---|
| 28 |
|
---|
| 29 | var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', '$log', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, $log, cfpLoadingBar) {
|
---|
| 30 |
|
---|
| 31 | /**
|
---|
| 32 | * The total number of requests made
|
---|
| 33 | */
|
---|
| 34 | var reqsTotal = 0;
|
---|
| 35 |
|
---|
| 36 | /**
|
---|
| 37 | * The number of requests completed (either successfully or not)
|
---|
| 38 | */
|
---|
| 39 | var reqsCompleted = 0;
|
---|
| 40 |
|
---|
| 41 | /**
|
---|
| 42 | * The amount of time spent fetching before showing the loading bar
|
---|
| 43 | */
|
---|
| 44 | var latencyThreshold = cfpLoadingBar.latencyThreshold;
|
---|
| 45 |
|
---|
| 46 | /**
|
---|
| 47 | * $timeout handle for latencyThreshold
|
---|
| 48 | */
|
---|
| 49 | var startTimeout;
|
---|
| 50 |
|
---|
| 51 |
|
---|
| 52 | /**
|
---|
| 53 | * calls cfpLoadingBar.complete() which removes the
|
---|
| 54 | * loading bar from the DOM.
|
---|
| 55 | */
|
---|
| 56 | function setComplete() {
|
---|
| 57 | $timeout.cancel(startTimeout);
|
---|
| 58 | cfpLoadingBar.complete();
|
---|
| 59 | reqsCompleted = 0;
|
---|
| 60 | reqsTotal = 0;
|
---|
| 61 | }
|
---|
| 62 |
|
---|
| 63 | /**
|
---|
| 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
|
---|
| 67 | */
|
---|
| 68 | function isCached(config) {
|
---|
| 69 | var cache;
|
---|
| 70 | var defaultCache = $cacheFactory.get('$http');
|
---|
| 71 | var defaults = $httpProvider.defaults;
|
---|
| 72 |
|
---|
| 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
|
---|
| 78 | : defaultCache;
|
---|
| 79 | }
|
---|
| 80 |
|
---|
| 81 | var cached = cache !== undefined ?
|
---|
| 82 | cache.get(config.url) !== undefined : false;
|
---|
| 83 |
|
---|
| 84 | if (config.cached !== undefined && cached !== config.cached) {
|
---|
| 85 | return config.cached;
|
---|
| 86 | }
|
---|
| 87 | config.cached = cached;
|
---|
| 88 | return cached;
|
---|
| 89 | }
|
---|
| 90 |
|
---|
| 91 |
|
---|
| 92 | return {
|
---|
| 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);
|
---|
| 102 | }
|
---|
| 103 | reqsTotal++;
|
---|
| 104 | cfpLoadingBar.set(reqsCompleted / reqsTotal);
|
---|
| 105 | }
|
---|
| 106 | return config;
|
---|
| 107 | },
|
---|
| 108 |
|
---|
| 109 | 'response': function (response) {
|
---|
| 110 | if (!response || !response.config) {
|
---|
| 111 | $log.error('Broken interceptor detected: Config object not supplied in response:\n https://github.com/chieffancypants/angular-loading-bar/pull/50');
|
---|
| 112 | return response;
|
---|
| 113 | }
|
---|
| 114 |
|
---|
| 115 | if (!response.config.ignoreLoadingBar && !isCached(response.config)) {
|
---|
| 116 | reqsCompleted++;
|
---|
| 117 | $rootScope.$broadcast('cfpLoadingBar:loaded', { url: response.config.url, result: response });
|
---|
| 118 | if (reqsCompleted >= reqsTotal) {
|
---|
| 119 | setComplete();
|
---|
| 120 | } else {
|
---|
| 121 | cfpLoadingBar.set(reqsCompleted / reqsTotal);
|
---|
| 122 | }
|
---|
| 123 | }
|
---|
| 124 | return response;
|
---|
| 125 | },
|
---|
| 126 |
|
---|
| 127 | 'responseError': function (rejection) {
|
---|
| 128 | if (!rejection || !rejection.config) {
|
---|
| 129 | $log.error('Broken interceptor detected: Config object not supplied in rejection:\n https://github.com/chieffancypants/angular-loading-bar/pull/50');
|
---|
| 130 | return $q.reject(rejection);
|
---|
| 131 | }
|
---|
| 132 |
|
---|
| 133 | if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) {
|
---|
| 134 | reqsCompleted++;
|
---|
| 135 | $rootScope.$broadcast('cfpLoadingBar:loaded', { url: rejection.config.url, result: rejection });
|
---|
| 136 | if (reqsCompleted >= reqsTotal) {
|
---|
| 137 | setComplete();
|
---|
| 138 | } else {
|
---|
| 139 | cfpLoadingBar.set(reqsCompleted / reqsTotal);
|
---|
| 140 | }
|
---|
| 141 | }
|
---|
| 142 | return $q.reject(rejection);
|
---|
| 143 | }
|
---|
| 144 | };
|
---|
| 145 | }];
|
---|
| 146 |
|
---|
| 147 | $httpProvider.interceptors.push(interceptor);
|
---|
| 148 | }]);
|
---|
| 149 |
|
---|
| 150 |
|
---|
| 151 | /**
|
---|
| 152 | * Loading Bar
|
---|
| 153 | *
|
---|
| 154 | * This service handles adding and removing the actual element in the DOM.
|
---|
| 155 | * Generally, best practices for DOM manipulation is to take place in a
|
---|
| 156 | * directive, but because the element itself is injected in the DOM only upon
|
---|
| 157 | * XHR requests, and it's likely needed on every view, the best option is to
|
---|
| 158 | * use a service.
|
---|
| 159 | */
|
---|
| 160 | angular.module('cfp.loadingBar', [])
|
---|
| 161 | .provider('cfpLoadingBar', function () {
|
---|
| 162 |
|
---|
| 163 | this.includeSpinner = false;
|
---|
| 164 | this.includeBar = true;
|
---|
| 165 | this.latencyThreshold = 150;
|
---|
| 166 | this.startSize = 0.02;
|
---|
| 167 | this.parentSelector = '#view';
|
---|
| 168 | this.spinnerTemplate = '<div id="loading-bar-spinner"><div class="spinner-icon"></div></div>';
|
---|
| 169 | this.loadingBarTemplate = '<div id="loading-bar"><div class="bar"><div class="peg"></div></div></div>';
|
---|
| 170 |
|
---|
| 171 | this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) {
|
---|
| 172 | var $animate;
|
---|
| 173 | var $parentSelector = this.parentSelector,
|
---|
| 174 | loadingBarContainer = angular.element(this.loadingBarTemplate),
|
---|
| 175 | loadingBar = loadingBarContainer.find('div').eq(0),
|
---|
| 176 | spinner = angular.element(this.spinnerTemplate);
|
---|
| 177 |
|
---|
| 178 | var incTimeout,
|
---|
| 179 | completeTimeout,
|
---|
| 180 | started = false,
|
---|
| 181 | status = 0;
|
---|
| 182 |
|
---|
| 183 | var includeSpinner = this.includeSpinner;
|
---|
| 184 | var includeBar = this.includeBar;
|
---|
| 185 | var startSize = this.startSize;
|
---|
| 186 |
|
---|
| 187 | /**
|
---|
| 188 | * Inserts the loading bar element into the dom, and sets it to 2%
|
---|
| 189 | */
|
---|
| 190 | function _start() {
|
---|
| 191 | if (!$animate) {
|
---|
| 192 | $animate = $injector.get('$animate');
|
---|
| 193 | }
|
---|
| 194 |
|
---|
| 195 | var $parent = $document.find($parentSelector).eq(0);
|
---|
| 196 | $timeout.cancel(completeTimeout);
|
---|
| 197 |
|
---|
| 198 | // do not continually broadcast the started event:
|
---|
| 199 | if (started) {
|
---|
| 200 | return;
|
---|
| 201 | }
|
---|
| 202 |
|
---|
| 203 | $rootScope.$broadcast('cfpLoadingBar:started');
|
---|
| 204 | started = true;
|
---|
| 205 |
|
---|
| 206 | if (includeBar) {
|
---|
| 207 | $animate.enter(loadingBarContainer, $parent);
|
---|
| 208 | }
|
---|
| 209 |
|
---|
| 210 | if (includeSpinner) {
|
---|
| 211 | $animate.enter(spinner, $parent);
|
---|
| 212 | }
|
---|
| 213 |
|
---|
| 214 | _set(startSize);
|
---|
| 215 | }
|
---|
| 216 |
|
---|
| 217 | /**
|
---|
| 218 | * Set the loading bar's width to a certain percent.
|
---|
| 219 | *
|
---|
| 220 | * @param n any value between 0 and 1
|
---|
| 221 | */
|
---|
| 222 | function _set(n) {
|
---|
| 223 | if (!started) {
|
---|
| 224 | return;
|
---|
| 225 | }
|
---|
| 226 | var pct = (n * 100) + '%';
|
---|
| 227 | loadingBar.css('width', pct);
|
---|
| 228 | status = n;
|
---|
| 229 |
|
---|
| 230 | // increment loadingbar to give the illusion that there is always
|
---|
| 231 | // progress but make sure to cancel the previous timeouts so we don't
|
---|
| 232 | // have multiple incs running at the same time.
|
---|
| 233 | $timeout.cancel(incTimeout);
|
---|
| 234 | incTimeout = $timeout(function () {
|
---|
| 235 | _inc();
|
---|
| 236 | }, 250);
|
---|
| 237 | }
|
---|
| 238 |
|
---|
| 239 | /**
|
---|
| 240 | * Increments the loading bar by a random amount
|
---|
| 241 | * but slows down as it progresses
|
---|
| 242 | */
|
---|
| 243 | function _inc() {
|
---|
| 244 | if (_status() >= 1) {
|
---|
| 245 | return;
|
---|
| 246 | }
|
---|
| 247 |
|
---|
| 248 | var rnd = 0;
|
---|
| 249 |
|
---|
| 250 | // TODO: do this mathmatically instead of through conditions
|
---|
| 251 |
|
---|
| 252 | var stat = _status();
|
---|
| 253 | if (stat >= 0 && stat < 0.25) {
|
---|
| 254 | // Start out between 3 - 6% increments
|
---|
| 255 | rnd = (Math.random() * (5 - 3 + 1) + 3) / 100;
|
---|
| 256 | } else if (stat >= 0.25 && stat < 0.65) {
|
---|
| 257 | // increment between 0 - 3%
|
---|
| 258 | rnd = (Math.random() * 3) / 100;
|
---|
| 259 | } else if (stat >= 0.65 && stat < 0.9) {
|
---|
| 260 | // increment between 0 - 2%
|
---|
| 261 | rnd = (Math.random() * 2) / 100;
|
---|
| 262 | } else if (stat >= 0.9 && stat < 0.99) {
|
---|
| 263 | // finally, increment it .5 %
|
---|
| 264 | rnd = 0.005;
|
---|
| 265 | } else {
|
---|
| 266 | // after 99%, don't increment:
|
---|
| 267 | rnd = 0;
|
---|
| 268 | }
|
---|
| 269 |
|
---|
| 270 | var pct = _status() + rnd;
|
---|
| 271 | _set(pct);
|
---|
| 272 | }
|
---|
| 273 |
|
---|
| 274 | function _status() {
|
---|
| 275 | return status;
|
---|
| 276 | }
|
---|
| 277 |
|
---|
| 278 | function _completeAnimation() {
|
---|
| 279 | status = 0;
|
---|
| 280 | started = false;
|
---|
| 281 | }
|
---|
| 282 |
|
---|
| 283 | function _complete() {
|
---|
| 284 | if (!$animate) {
|
---|
| 285 | $animate = $injector.get('$animate');
|
---|
| 286 | }
|
---|
| 287 |
|
---|
| 288 | $rootScope.$broadcast('cfpLoadingBar:completed');
|
---|
| 289 | _set(1);
|
---|
| 290 |
|
---|
| 291 | $timeout.cancel(completeTimeout);
|
---|
| 292 |
|
---|
| 293 | // Attempt to aggregate any start/complete calls within 500ms:
|
---|
| 294 | completeTimeout = $timeout(function () {
|
---|
| 295 | var promise = $animate.leave(loadingBarContainer, _completeAnimation);
|
---|
| 296 | if (promise && promise.then) {
|
---|
| 297 | promise.then(_completeAnimation);
|
---|
| 298 | }
|
---|
| 299 | $animate.leave(spinner);
|
---|
| 300 | }, 500);
|
---|
| 301 | }
|
---|
| 302 |
|
---|
| 303 | return {
|
---|
| 304 | start: _start,
|
---|
| 305 | set: _set,
|
---|
| 306 | status: _status,
|
---|
| 307 | inc: _inc,
|
---|
| 308 | complete: _complete,
|
---|
| 309 | includeSpinner: this.includeSpinner,
|
---|
| 310 | latencyThreshold: this.latencyThreshold,
|
---|
| 311 | parentSelector: this.parentSelector,
|
---|
| 312 | startSize: this.startSize
|
---|
| 313 | };
|
---|
| 314 |
|
---|
| 315 |
|
---|
| 316 | }]; //
|
---|
| 317 | }); // wtf javascript. srsly
|
---|
| 318 | })(); //
|
---|