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