commonroutesApp/www/lib/angular-material/modules/js/progressCircular/progressCircular.js
www/lib/angular-material/modules/js/progressCircular/progressCircular.jsRaw
1/*!
2 * Angular Material Design
3 * https://github.com/angular/material
4 * @license MIT
5 * v1.1.1
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
16angular.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
63MdProgressCircularDirective.$inject = ["$window", "$mdProgressCircular", "$mdTheming", "$mdUtil", "$interval", "$log"];
64angular
65 .module('material.components.progressCircular')
66 .directive('mdProgressCircular', MdProgressCircularDirective);
67
68/* ngInject */
69function 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 DEGREE_IN_RADIANS = $window.Math.PI / 180;
84 var MODE_DETERMINATE = 'determinate';
85 var MODE_INDETERMINATE = 'indeterminate';
86 var DISABLED_CLASS = '_md-progress-circular-disabled';
87 var INDETERMINATE_CLASS = 'md-mode-indeterminate';
88
89 return {
90 restrict: 'E',
91 scope: {
92 value: '@',
93 mdDiameter: '@',
94 mdMode: '@'
95 },
96 template:
97 '<svg xmlns="http://www.w3.org/2000/svg">' +
98 '<path fill="none"/>' +
99 '</svg>',
100 compile: function(element, attrs) {
101 element.attr({
102 'aria-valuemin': 0,
103 'aria-valuemax': 100,
104 'role': 'progressbar'
105 });
106
107 if (angular.isUndefined(attrs.mdMode)) {
108 var hasValue = angular.isDefined(attrs.value);
109 var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE;
110 var info = "Auto-adding the missing md-mode='{0}' to the ProgressCircular element";
111
112 // $log.debug( $mdUtil.supplant(info, [mode]) );
113 attrs.$set('mdMode', mode);
114 } else {
115 attrs.$set('mdMode', attrs.mdMode.trim());
116 }
117
118 return MdProgressCircularLink;
119 }
120 };
121
122 function MdProgressCircularLink(scope, element, attrs) {
123 var node = element[0];
124 var svg = angular.element(node.querySelector('svg'));
125 var path = angular.element(node.querySelector('path'));
126 var startIndeterminate = $mdProgressCircular.startIndeterminate;
127 var endIndeterminate = $mdProgressCircular.endIndeterminate;
128 var rotationIndeterminate = 0;
129 var lastAnimationId = 0;
130 var lastDrawFrame;
131 var interval;
132
133 $mdTheming(element);
134 element.toggleClass(DISABLED_CLASS, attrs.hasOwnProperty('disabled'));
135
136 // If the mode is indeterminate, it doesn't need to
137 // wait for the next digest. It can start right away.
138 if(scope.mdMode === MODE_INDETERMINATE){
139 startIndeterminateAnimation();
140 }
141
142 scope.$on('$destroy', function(){
143 cleanupIndeterminateAnimation();
144
145 if (lastDrawFrame) {
146 cAF(lastDrawFrame);
147 }
148 });
149
150 scope.$watchGroup(['value', 'mdMode', function() {
151 var isDisabled = node.disabled;
152
153 // Sometimes the browser doesn't return a boolean, in
154 // which case we should check whether the attribute is
155 // present.
156 if (isDisabled === true || isDisabled === false){
157 return isDisabled;
158 }
159 return angular.isDefined(element.attr('disabled'));
160
161 }], function(newValues, oldValues) {
162 var mode = newValues[1];
163 var isDisabled = newValues[2];
164 var wasDisabled = oldValues[2];
165
166 if (isDisabled !== wasDisabled) {
167 element.toggleClass(DISABLED_CLASS, !!isDisabled);
168 }
169
170 if (isDisabled) {
171 cleanupIndeterminateAnimation();
172 } else {
173 if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) {
174 mode = MODE_INDETERMINATE;
175 attrs.$set('mdMode', mode);
176 }
177
178 if (mode === MODE_INDETERMINATE) {
179 startIndeterminateAnimation();
180 } else {
181 var newValue = clamp(newValues[0]);
182
183 cleanupIndeterminateAnimation();
184
185 element.attr('aria-valuenow', newValue);
186 renderCircle(clamp(oldValues[0]), newValue);
187 }
188 }
189
190 });
191
192 // This is in a separate watch in order to avoid layout, unless
193 // the value has actually changed.
194 scope.$watch('mdDiameter', function(newValue) {
195 var diameter = getSize(newValue);
196 var strokeWidth = getStroke(diameter);
197 var transformOrigin = (diameter / 2) + 'px';
198 var dimensions = {
199 width: diameter + 'px',
200 height: diameter + 'px'
201 };
202
203 // The viewBox has to be applied via setAttribute, because it is
204 // case-sensitive. If jQuery is included in the page, `.attr` lowercases
205 // all attribute names.
206 svg[0].setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);
207
208 // Usually viewBox sets the dimensions for the SVG, however that doesn't
209 // seem to be the case on IE10.
210 // Important! The transform origin has to be set from here and it has to
211 // be in the format of "Ypx Ypx Ypx", otherwise the rotation wobbles in
212 // IE and Edge, because they don't account for the stroke width when
213 // rotating. Also "center" doesn't help in this case, it has to be a
214 // precise value.
215 svg
216 .css(dimensions)
217 .css('transform-origin', transformOrigin + ' ' + transformOrigin + ' ' + transformOrigin);
218
219 element.css(dimensions);
220 path.css('stroke-width', strokeWidth + 'px');
221 });
222
223 function renderCircle(animateFrom, animateTo, easing, duration, rotation) {
224 var id = ++lastAnimationId;
225 var startTime = $mdUtil.now();
226 var changeInValue = animateTo - animateFrom;
227 var diameter = getSize(scope.mdDiameter);
228 var pathDiameter = diameter - getStroke(diameter);
229 var ease = easing || $mdProgressCircular.easeFn;
230 var animationDuration = duration || $mdProgressCircular.duration;
231
232 // No need to animate it if the values are the same
233 if (animateTo === animateFrom) {
234 path.attr('d', getSvgArc(animateTo, diameter, pathDiameter, rotation));
235 } else {
236 lastDrawFrame = rAF(function animation() {
237 var currentTime = $window.Math.max(0, $window.Math.min($mdUtil.now() - startTime, animationDuration));
238
239 path.attr('d', getSvgArc(
240 ease(currentTime, animateFrom, changeInValue, animationDuration),
241 diameter,
242 pathDiameter,
243 rotation
244 ));
245
246 // Do not allow overlapping animations
247 if (id === lastAnimationId && currentTime < animationDuration) {
248 lastDrawFrame = rAF(animation);
249 }
250 });
251 }
252 }
253
254 function animateIndeterminate() {
255 renderCircle(
256 startIndeterminate,
257 endIndeterminate,
258 $mdProgressCircular.easeFnIndeterminate,
259 $mdProgressCircular.durationIndeterminate,
260 rotationIndeterminate
261 );
262
263 // The % 100 technically isn't necessary, but it keeps the rotation
264 // under 100, instead of becoming a crazy large number.
265 rotationIndeterminate = (rotationIndeterminate + endIndeterminate) % 100;
266
267 var temp = startIndeterminate;
268 startIndeterminate = -endIndeterminate;
269 endIndeterminate = -temp;
270 }
271
272 function startIndeterminateAnimation() {
273 if (!interval) {
274 // Note that this interval isn't supposed to trigger a digest.
275 interval = $interval(
276 animateIndeterminate,
277 $mdProgressCircular.durationIndeterminate + 50,
278 0,
279 false
280 );
281
282 animateIndeterminate();
283
284 element
285 .addClass(INDETERMINATE_CLASS)
286 .removeAttr('aria-valuenow');
287 }
288 }
289
290 function cleanupIndeterminateAnimation() {
291 if (interval) {
292 $interval.cancel(interval);
293 interval = null;
294 element.removeClass(INDETERMINATE_CLASS);
295 }
296 }
297 }
298
299 /**
300 * Generates an arc following the SVG arc syntax.
301 * Syntax spec: https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
302 *
303 * @param {number} current Current value between 0 and 100.
304 * @param {number} diameter Diameter of the container.
305 * @param {number} pathDiameter Diameter of the path element.
306 * @param {number=0} rotation The point at which the semicircle should start rendering.
307 * Used for doing the indeterminate animation.
308 *
309 * @returns {string} String representation of an SVG arc.
310 */
311 function getSvgArc(current, diameter, pathDiameter, rotation) {
312 // The angle can't be exactly 360, because the arc becomes hidden.
313 var maximumAngle = 359.99 / 100;
314 var startPoint = rotation || 0;
315 var radius = diameter / 2;
316 var pathRadius = pathDiameter / 2;
317
318 var startAngle = startPoint * maximumAngle;
319 var endAngle = current * maximumAngle;
320 var start = polarToCartesian(radius, pathRadius, startAngle);
321 var end = polarToCartesian(radius, pathRadius, endAngle + startAngle);
322 var arcSweep = endAngle < 0 ? 0 : 1;
323 var largeArcFlag;
324
325 if (endAngle < 0) {
326 largeArcFlag = endAngle >= -180 ? 0 : 1;
327 } else {
328 largeArcFlag = endAngle <= 180 ? 0 : 1;
329 }
330
331 return 'M' + start + 'A' + pathRadius + ',' + pathRadius +
332 ' 0 ' + largeArcFlag + ',' + arcSweep + ' ' + end;
333 }
334
335 /**
336 * Converts Polar coordinates to Cartesian.
337 *
338 * @param {number} radius Radius of the container.
339 * @param {number} pathRadius Radius of the path element
340 * @param {number} angleInDegress Angle at which to place the point.
341 *
342 * @returns {string} Cartesian coordinates in the format of `x,y`.
343 */
344 function polarToCartesian(radius, pathRadius, angleInDegrees) {
345 var angleInRadians = (angleInDegrees - 90) * DEGREE_IN_RADIANS;
346
347 return (radius + (pathRadius * $window.Math.cos(angleInRadians))) +
348 ',' + (radius + (pathRadius * $window.Math.sin(angleInRadians)));
349 }
350
351 /**
352 * Limits a value between 0 and 100.
353 */
354 function clamp(value) {
355 return $window.Math.max(0, $window.Math.min(value || 0, 100));
356 }
357
358 /**
359 * Determines the size of a progress circle, based on the provided
360 * value in the following formats: `X`, `Ypx`, `Z%`.
361 */
362 function getSize(value) {
363 var defaultValue = $mdProgressCircular.progressSize;
364
365 if (value) {
366 var parsed = parseFloat(value);
367
368 if (value.lastIndexOf('%') === value.length - 1) {
369 parsed = (parsed / 100) * defaultValue;
370 }
371
372 return parsed;
373 }
374
375 return defaultValue;
376 }
377
378 /**
379 * Determines the circle's stroke width, based on
380 * the provided diameter.
381 */
382 function getStroke(diameter) {
383 return $mdProgressCircular.strokeWidth / 100 * diameter;
384 }
385}
386
387/**
388 * @ngdoc service
389 * @name $mdProgressCircular
390 * @module material.components.progressCircular
391 *
392 * @description
393 * Allows the user to specify the default options for the `progressCircular` directive.
394 *
395 * @property {number} progressSize Diameter of the progress circle in pixels.
396 * @property {number} strokeWidth Width of the circle's stroke as a percentage of the circle's size.
397 * @property {number} duration Length of the circle animation in milliseconds.
398 * @property {function} easeFn Default easing animation function.
399 * @property {object} easingPresets Collection of pre-defined easing functions.
400 *
401 * @property {number} durationIndeterminate Duration of the indeterminate animation.
402 * @property {number} startIndeterminate Indeterminate animation start point.
403 * @property {number} endIndeterminate Indeterminate animation end point.
404 * @property {function} easeFnIndeterminate Easing function to be used when animating
405 * between the indeterminate values.
406 *
407 * @property {(function(object): object)} configure Used to modify the default options.
408 *
409 * @usage
410 * <hljs lang="js">
411 * myAppModule.config(function($mdProgressCircularProvider) {
412 *
413 * // Example of changing the default progress options.
414 * $mdProgressCircularProvider.configure({
415 * progressSize: 100,
416 * strokeWidth: 20,
417 * duration: 800
418 * });
419 * });
420 * </hljs>
421 *
422 */
423
424angular
425 .module('material.components.progressCircular')
426 .provider("$mdProgressCircular", MdProgressCircularProvider);
427
428function MdProgressCircularProvider() {
429 var progressConfig = {
430 progressSize: 50,
431 strokeWidth: 10,
432 duration: 100,
433 easeFn: linearEase,
434
435 durationIndeterminate: 500,
436 startIndeterminate: 3,
437 endIndeterminate: 80,
438 easeFnIndeterminate: materialEase,
439
440 easingPresets: {
441 linearEase: linearEase,
442 materialEase: materialEase
443 }
444 };
445
446 return {
447 configure: function(options) {
448 progressConfig = angular.extend(progressConfig, options || {});
449 return progressConfig;
450 },
451 $get: function() { return progressConfig; }
452 };
453
454 function linearEase(t, b, c, d) {
455 return c * t / d + b;
456 }
457
458 function materialEase(t, b, c, d) {
459 // via http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
460 // with settings of [0, 0, 1, 1]
461 var ts = (t /= d) * t;
462 var tc = ts * t;
463 return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc);
464 }
465}
466
467})(window, window.angular);