7b5a246723a0578aaf3485326cc119a4c459bbc6
[vnfsdk/refrepo.git] /
1 /*!
2  * Angular Material Design
3  * https://github.com/angular/material
4  * @license MIT
5  * v1.1.3
6  */
7 (function( window, angular, undefined ){
8 "use strict";
9
10 /**
11  * @ngdoc module
12  * @name material.components.progressCircular
13  * @description Module for a circular progressbar
14  */
15
16 angular.module('material.components.progressCircular', ['material.core']);
17
18 /**
19  * @ngdoc directive
20  * @name mdProgressCircular
21  * @module material.components.progressCircular
22  * @restrict E
23  *
24  * @description
25  * The circular progress directive is used to make loading content in your app as delightful and
26  * painless as possible by minimizing the amount of visual change a user sees before they can view
27  * and interact with content.
28  *
29  * For operations where the percentage of the operation completed can be determined, use a
30  * determinate indicator. They give users a quick sense of how long an operation will take.
31  *
32  * For operations where the user is asked to wait a moment while something finishes up, and it’s
33  * not necessary to expose what's happening behind the scenes and how long it will take, use an
34  * indeterminate indicator.
35  *
36  * @param {string} md-mode Select from one of two modes: **'determinate'** and **'indeterminate'**.
37  *
38  * Note: if the `md-mode` value is set as undefined or specified as not 1 of the two (2) valid modes, then **'indeterminate'**
39  * will be auto-applied as the mode.
40  *
41  * Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute.
42  * If `value=""` is also specified, however, then `md-mode="determinate"` would be auto-injected instead.
43  * @param {number=} value In determinate mode, this number represents the percentage of the
44  *     circular progress. Default: 0
45  * @param {number=} md-diameter This specifies the diameter of the circular progress. The value
46  * should be a pixel-size value (eg '100'). If this attribute is
47  * not present then a default value of '50px' is assumed.
48  *
49  * @param {boolean=} ng-disabled Determines whether to disable the progress element.
50  *
51  * @usage
52  * <hljs lang="html">
53  * <md-progress-circular md-mode="determinate" value="..."></md-progress-circular>
54  *
55  * <md-progress-circular md-mode="determinate" ng-value="..."></md-progress-circular>
56  *
57  * <md-progress-circular md-mode="determinate" value="..." md-diameter="100"></md-progress-circular>
58  *
59  * <md-progress-circular md-mode="indeterminate"></md-progress-circular>
60  * </hljs>
61  */
62
63 MdProgressCircularDirective['$inject'] = ["$window", "$mdProgressCircular", "$mdTheming", "$mdUtil", "$interval", "$log"];
64 angular
65   .module('material.components.progressCircular')
66   .directive('mdProgressCircular', MdProgressCircularDirective);
67
68 /* ngInject */
69 function MdProgressCircularDirective($window, $mdProgressCircular, $mdTheming,
70                                      $mdUtil, $interval, $log) {
71
72   // Note that this shouldn't use use $$rAF, because it can cause an infinite loop
73   // in any tests that call $animate.flush.
74   var rAF = $window.requestAnimationFrame ||
75             $window.webkitRequestAnimationFrame ||
76             angular.noop;
77
78   var cAF = $window.cancelAnimationFrame ||
79             $window.webkitCancelAnimationFrame ||
80             $window.webkitCancelRequestAnimationFrame ||
81             angular.noop;
82
83   var MODE_DETERMINATE = 'determinate';
84   var MODE_INDETERMINATE = 'indeterminate';
85   var DISABLED_CLASS = '_md-progress-circular-disabled';
86   var INDETERMINATE_CLASS = 'md-mode-indeterminate';
87
88   return {
89     restrict: 'E',
90     scope: {
91       value: '@',
92       mdDiameter: '@',
93       mdMode: '@'
94     },
95     template:
96       '<svg xmlns="http://www.w3.org/2000/svg">' +
97         '<path fill="none"/>' +
98       '</svg>',
99     compile: function(element, attrs) {
100       element.attr({
101         'aria-valuemin': 0,
102         'aria-valuemax': 100,
103         'role': 'progressbar'
104       });
105
106       if (angular.isUndefined(attrs.mdMode)) {
107         var mode = attrs.hasOwnProperty('value') ? MODE_DETERMINATE : MODE_INDETERMINATE;
108         attrs.$set('mdMode', mode);
109       } else {
110         attrs.$set('mdMode', attrs.mdMode.trim());
111       }
112
113       return MdProgressCircularLink;
114     }
115   };
116
117   function MdProgressCircularLink(scope, element, attrs) {
118     var node = element[0];
119     var svg = angular.element(node.querySelector('svg'));
120     var path = angular.element(node.querySelector('path'));
121     var startIndeterminate = $mdProgressCircular.startIndeterminate;
122     var endIndeterminate = $mdProgressCircular.endIndeterminate;
123     var iterationCount = 0;
124     var lastAnimationId = 0;
125     var lastDrawFrame;
126     var interval;
127
128     $mdTheming(element);
129     element.toggleClass(DISABLED_CLASS, attrs.hasOwnProperty('disabled'));
130
131     // If the mode is indeterminate, it doesn't need to
132     // wait for the next digest. It can start right away.
133     if(scope.mdMode === MODE_INDETERMINATE){
134       startIndeterminateAnimation();
135     }
136
137     scope.$on('$destroy', function(){
138       cleanupIndeterminateAnimation();
139
140       if (lastDrawFrame) {
141         cAF(lastDrawFrame);
142       }
143     });
144
145     scope.$watchGroup(['value', 'mdMode', function() {
146       var isDisabled = node.disabled;
147
148       // Sometimes the browser doesn't return a boolean, in
149       // which case we should check whether the attribute is
150       // present.
151       if (isDisabled === true || isDisabled === false){
152         return isDisabled;
153       }
154
155       return angular.isDefined(element.attr('disabled'));
156     }], function(newValues, oldValues) {
157       var mode = newValues[1];
158       var isDisabled = newValues[2];
159       var wasDisabled = oldValues[2];
160
161       if (isDisabled !== wasDisabled) {
162         element.toggleClass(DISABLED_CLASS, !!isDisabled);
163       }
164
165       if (isDisabled) {
166         cleanupIndeterminateAnimation();
167       } else {
168         if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) {
169           mode = MODE_INDETERMINATE;
170           attrs.$set('mdMode', mode);
171         }
172
173         if (mode === MODE_INDETERMINATE) {
174           startIndeterminateAnimation();
175         } else {
176           var newValue = clamp(newValues[0]);
177
178           cleanupIndeterminateAnimation();
179
180           element.attr('aria-valuenow', newValue);
181           renderCircle(clamp(oldValues[0]), newValue);
182         }
183       }
184
185     });
186
187     // This is in a separate watch in order to avoid layout, unless
188     // the value has actually changed.
189     scope.$watch('mdDiameter', function(newValue) {
190       var diameter = getSize(newValue);
191       var strokeWidth = getStroke(diameter);
192       var value = clamp(scope.value);
193       var transformOrigin = (diameter / 2) + 'px';
194       var dimensions = {
195         width: diameter + 'px',
196         height: diameter + 'px'
197       };
198
199       // The viewBox has to be applied via setAttribute, because it is
200       // case-sensitive. If jQuery is included in the page, `.attr` lowercases
201       // all attribute names.
202       svg[0].setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);
203
204       // Usually viewBox sets the dimensions for the SVG, however that doesn't
205       // seem to be the case on IE10.
206       // Important! The transform origin has to be set from here and it has to
207       // be in the format of "Ypx Ypx Ypx", otherwise the rotation wobbles in
208       // IE and Edge, because they don't account for the stroke width when
209       // rotating. Also "center" doesn't help in this case, it has to be a
210       // precise value.
211       svg
212         .css(dimensions)
213         .css('transform-origin', transformOrigin + ' ' + transformOrigin + ' ' + transformOrigin);
214
215       element.css(dimensions);
216
217       path.attr('stroke-width', strokeWidth);
218       path.attr('stroke-linecap', 'square');
219       if (scope.mdMode == MODE_INDETERMINATE) {
220         path.attr('d', getSvgArc(diameter, strokeWidth, true));
221         path.attr('stroke-dasharray', (diameter - strokeWidth) * $window.Math.PI * 0.75);
222         path.attr('stroke-dashoffset', getDashLength(diameter, strokeWidth, 1, 75));
223       } else {
224         path.attr('d', getSvgArc(diameter, strokeWidth, false));
225         path.attr('stroke-dasharray', (diameter - strokeWidth) * $window.Math.PI);
226         path.attr('stroke-dashoffset', getDashLength(diameter, strokeWidth, 0, 100));
227         renderCircle(value, value);
228       }
229
230     });
231
232     function renderCircle(animateFrom, animateTo, easing, duration, iterationCount, maxValue) {
233       var id = ++lastAnimationId;
234       var startTime = $mdUtil.now();
235       var changeInValue = animateTo - animateFrom;
236       var diameter = getSize(scope.mdDiameter);
237       var strokeWidth = getStroke(diameter);
238       var ease = easing || $mdProgressCircular.easeFn;
239       var animationDuration = duration || $mdProgressCircular.duration;
240       var rotation = -90 * (iterationCount || 0);
241       var dashLimit = maxValue || 100;
242
243       // No need to animate it if the values are the same
244       if (animateTo === animateFrom) {
245         renderFrame(animateTo);
246       } else {
247         lastDrawFrame = rAF(function animation() {
248           var currentTime = $window.Math.max(0, $window.Math.min($mdUtil.now() - startTime, animationDuration));
249
250           renderFrame(ease(currentTime, animateFrom, changeInValue, animationDuration));
251
252           // Do not allow overlapping animations
253           if (id === lastAnimationId && currentTime < animationDuration) {
254             lastDrawFrame = rAF(animation);
255           }
256         });
257       }
258
259       function renderFrame(value) {
260         path.attr('stroke-dashoffset', getDashLength(diameter, strokeWidth, value, dashLimit));
261         path.attr('transform','rotate(' + (rotation) + ' ' + diameter/2 + ' ' + diameter/2 + ')');
262       }
263     }
264
265     function animateIndeterminate() {
266       renderCircle(
267         startIndeterminate,
268         endIndeterminate,
269         $mdProgressCircular.easeFnIndeterminate,
270         $mdProgressCircular.durationIndeterminate,
271         iterationCount,
272         75
273       );
274
275       // The %4 technically isn't necessary, but it keeps the rotation
276       // under 360, instead of becoming a crazy large number.
277       iterationCount = ++iterationCount % 4;
278
279     }
280
281     function startIndeterminateAnimation() {
282       if (!interval) {
283         // Note that this interval isn't supposed to trigger a digest.
284         interval = $interval(
285           animateIndeterminate,
286           $mdProgressCircular.durationIndeterminate,
287           0,
288           false
289         );
290
291         animateIndeterminate();
292
293         element
294           .addClass(INDETERMINATE_CLASS)
295           .removeAttr('aria-valuenow');
296       }
297     }
298
299     function cleanupIndeterminateAnimation() {
300       if (interval) {
301         $interval.cancel(interval);
302         interval = null;
303         element.removeClass(INDETERMINATE_CLASS);
304       }
305     }
306   }
307
308   /**
309    * Returns SVG path data for progress circle
310    * Syntax spec: https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
311    *
312    * @param {number} diameter Diameter of the container.
313    * @param {number} strokeWidth Stroke width to be used when drawing circle
314    * @param {boolean} indeterminate Use if progress circle will be used for indeterminate
315    *
316    * @returns {string} String representation of an SVG arc.
317    */
318   function getSvgArc(diameter, strokeWidth, indeterminate) {
319     var radius = diameter / 2;
320     var offset = strokeWidth / 2;
321     var start = radius + ',' + offset; // ie: (25, 2.5) or 12 o'clock
322     var end = offset + ',' + radius;   // ie: (2.5, 25) or  9 o'clock
323     var arcRadius = radius - offset;
324     return 'M' + start
325          + 'A' + arcRadius + ',' + arcRadius + ' 0 1 1 ' + end // 75% circle
326          + (indeterminate ? '' : 'A' + arcRadius + ',' + arcRadius + ' 0 0 1 ' + start); // loop to start
327   }
328
329   /**
330    * Return stroke length for progress circle
331    *
332    * @param {number} diameter Diameter of the container.
333    * @param {number} strokeWidth Stroke width to be used when drawing circle
334    * @param {number} value Percentage of circle (between 0 and 100)
335    * @param {number} limit Max percentage for circle
336    *
337    * @returns {number} Stroke length for progres circle
338    */
339   function getDashLength(diameter, strokeWidth, value, limit) {
340     return (diameter - strokeWidth) * $window.Math.PI * ( (3 * (limit || 100) / 100) - (value/100) );
341   }
342
343   /**
344    * Limits a value between 0 and 100.
345    */
346   function clamp(value) {
347     return $window.Math.max(0, $window.Math.min(value || 0, 100));
348   }
349
350   /**
351    * Determines the size of a progress circle, based on the provided
352    * value in the following formats: `X`, `Ypx`, `Z%`.
353    */
354   function getSize(value) {
355     var defaultValue = $mdProgressCircular.progressSize;
356
357     if (value) {
358       var parsed = parseFloat(value);
359
360       if (value.lastIndexOf('%') === value.length - 1) {
361         parsed = (parsed / 100) * defaultValue;
362       }
363
364       return parsed;
365     }
366
367     return defaultValue;
368   }
369
370   /**
371    * Determines the circle's stroke width, based on
372    * the provided diameter.
373    */
374   function getStroke(diameter) {
375     return $mdProgressCircular.strokeWidth / 100 * diameter;
376   }
377
378 }
379
380 /**
381  * @ngdoc service
382  * @name $mdProgressCircular
383  * @module material.components.progressCircular
384  *
385  * @description
386  * Allows the user to specify the default options for the `progressCircular` directive.
387  *
388  * @property {number} progressSize Diameter of the progress circle in pixels.
389  * @property {number} strokeWidth Width of the circle's stroke as a percentage of the circle's size.
390  * @property {number} duration Length of the circle animation in milliseconds.
391  * @property {function} easeFn Default easing animation function.
392  * @property {object} easingPresets Collection of pre-defined easing functions.
393  *
394  * @property {number} durationIndeterminate Duration of the indeterminate animation.
395  * @property {number} startIndeterminate Indeterminate animation start point.
396  * @property {number} endIndeterminate Indeterminate animation end point.
397  * @property {function} easeFnIndeterminate Easing function to be used when animating
398  * between the indeterminate values.
399  *
400  * @property {(function(object): object)} configure Used to modify the default options.
401  *
402  * @usage
403  * <hljs lang="js">
404  *   myAppModule.config(function($mdProgressCircularProvider) {
405  *
406  *     // Example of changing the default progress options.
407  *     $mdProgressCircularProvider.configure({
408  *       progressSize: 100,
409  *       strokeWidth: 20,
410  *       duration: 800
411  *     });
412  * });
413  * </hljs>
414  *
415  */
416
417 angular
418   .module('material.components.progressCircular')
419   .provider("$mdProgressCircular", MdProgressCircularProvider);
420
421 function MdProgressCircularProvider() {
422   var progressConfig = {
423     progressSize: 50,
424     strokeWidth: 10,
425     duration: 100,
426     easeFn: linearEase,
427
428     durationIndeterminate: 1333,
429     startIndeterminate: 1,
430     endIndeterminate: 149,
431     easeFnIndeterminate: materialEase,
432
433     easingPresets: {
434       linearEase: linearEase,
435       materialEase: materialEase
436     }
437   };
438
439   return {
440     configure: function(options) {
441       progressConfig = angular.extend(progressConfig, options || {});
442       return progressConfig;
443     },
444     $get: function() { return progressConfig; }
445   };
446
447   function linearEase(t, b, c, d) {
448     return c * t / d + b;
449   }
450
451   function materialEase(t, b, c, d) {
452     // via http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
453     // with settings of [0, 0, 1, 1]
454     var ts = (t /= d) * t;
455     var tc = ts * t;
456     return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc);
457   }
458 }
459
460 })(window, window.angular);