Draft of React test
[clamp.git] / src / main / resources / META-INF / resources / designer / lib / loading-bar.js
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', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, 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             cfpLoadingBar.start();
104             reqsTotal++;
105             cfpLoadingBar.set(reqsCompleted / reqsTotal);
106           }
107           return config;
108         },
109
110         'response': function(response) {
111           if (!response.config.ignoreLoadingBar && !isCached(response.config)) {
112             reqsCompleted++;
113             $rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url, result: response});
114             if (reqsCompleted >= reqsTotal) {
115               setComplete();
116             } else {
117               cfpLoadingBar.set(reqsCompleted / reqsTotal);
118             }
119           }
120           return response;
121         },
122
123         'responseError': function(rejection) {
124           if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) {
125             reqsCompleted++;
126             $rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url, result: rejection});
127             if (reqsCompleted >= reqsTotal) {
128               setComplete();
129             } else {
130               cfpLoadingBar.set(reqsCompleted / reqsTotal);
131             }
132           }
133           return $q.reject(rejection);
134         }
135       };
136     }];
137
138     $httpProvider.interceptors.push(interceptor);
139   }]);
140
141
142 /**
143  * Loading Bar
144  *
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
149  * use a service.
150  */
151 angular.module('cfp.loadingBar', [])
152   .provider('cfpLoadingBar', function() {
153
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>';
161
162     this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) {
163       var $animate;
164       var $parentSelector = this.parentSelector,
165         loadingBarContainer = angular.element(this.loadingBarTemplate),
166         loadingBar = loadingBarContainer.find('div').eq(0),
167         spinner = angular.element(this.spinnerTemplate);
168
169       var incTimeout,
170         completeTimeout,
171         started = false,
172         status = 0;
173
174       var includeSpinner = this.includeSpinner;
175       var includeBar = this.includeBar;
176       var startSize = this.startSize;
177
178       /**
179        * Inserts the loading bar element into the dom, and sets it to 2%
180        */
181       function _start() {
182         if (!$animate) {
183           $animate = $injector.get('$animate');
184         }
185
186         var $parent = $document.find($parentSelector).eq(0);
187         $timeout.cancel(completeTimeout);
188
189         // do not continually broadcast the started event:
190         if (started) {
191           return;
192         }
193
194         $rootScope.$broadcast('cfpLoadingBar:started');
195         started = true;
196
197         if (includeBar) {
198           $animate.enter(loadingBarContainer, $parent);
199         }
200
201         if (includeSpinner) {
202           $animate.enter(spinner, $parent);
203         }
204
205         _set(startSize);
206       }
207
208       /**
209        * Set the loading bar's width to a certain percent.
210        *
211        * @param n any value between 0 and 1
212        */
213       function _set(n) {
214         if (!started) {
215           return;
216         }
217         var pct = (n * 100) + '%';
218         loadingBar.css('width', pct);
219         status = n;
220
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() {
226           _inc();
227         }, 250);
228       }
229
230       /**
231        * Increments the loading bar by a random amount
232        * but slows down as it progresses
233        */
234       function _inc() {
235         if (_status() >= 1) {
236           return;
237         }
238
239         var rnd = 0;
240
241         // TODO: do this mathmatically instead of through conditions
242
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 %
255           rnd = 0.005;
256         } else {
257           // after 99%, don't increment:
258           rnd = 0;
259         }
260
261         var pct = _status() + rnd;
262         _set(pct);
263       }
264
265       function _status() {
266         return status;
267       }
268
269       function _completeAnimation() {
270         status = 0;
271         started = false;
272       }
273
274       function _complete() {
275         if (!$animate) {
276           $animate = $injector.get('$animate');
277         }
278
279         $rootScope.$broadcast('cfpLoadingBar:completed');
280         _set(1);
281
282         $timeout.cancel(completeTimeout);
283
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);
289           }
290           $animate.leave(spinner);
291         }, 500);
292       }
293
294       return {
295         start            : _start,
296         set              : _set,
297         status           : _status,
298         inc              : _inc,
299         complete         : _complete,
300         includeSpinner   : this.includeSpinner,
301         latencyThreshold : this.latencyThreshold,
302         parentSelector   : this.parentSelector,
303         startSize        : this.startSize
304       };
305
306
307     }];     //
308   });       // wtf javascript. srsly
309 })();       //