socketioMEANseed/www/bower_components/angular-material/modules/closure/progressCircular/progressCircular.js
www/bower_components/angular-material/modules/closure/progressCircular/progressCircular.jsRaw
1/*!
2 * Angular Material Design
3 * https://github.com/angular/material
4 * @license MIT
5 * v1.1.3
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 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
416angular
417 .module('material.components.progressCircular')
418 .provider("$mdProgressCircular", MdProgressCircularProvider);
419
420function 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
459ngmaterial.components.progressCircular = angular.module("material.components.progressCircular");