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