Files
commonroutesApp/www/lib/angular-material/modules/closure/progressCircular/progressCircular.js
2017-01-27 17:49:04 +01:00

466 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*!
* Angular Material Design
* https://github.com/angular/material
* @license MIT
* v1.1.1
*/
goog.provide('ngmaterial.components.progressCircular');
goog.require('ngmaterial.core');
/**
* @ngdoc module
* @name material.components.progressCircular
* @description Module for a circular progressbar
*/
angular.module('material.components.progressCircular', ['material.core']);
/**
* @ngdoc directive
* @name mdProgressCircular
* @module material.components.progressCircular
* @restrict E
*
* @description
* The circular progress directive is used to make loading content in your app as delightful and
* painless as possible by minimizing the amount of visual change a user sees before they can view
* and interact with content.
*
* For operations where the percentage of the operation completed can be determined, use a
* determinate indicator. They give users a quick sense of how long an operation will take.
*
* For operations where the user is asked to wait a moment while something finishes up, and its
* not necessary to expose what's happening behind the scenes and how long it will take, use an
* indeterminate indicator.
*
* @param {string} md-mode Select from one of two modes: **'determinate'** and **'indeterminate'**.
*
* Note: if the `md-mode` value is set as undefined or specified as not 1 of the two (2) valid modes, then **'indeterminate'**
* will be auto-applied as the mode.
*
* Note: if not configured, the `md-mode="indeterminate"` will be auto injected as an attribute.
* If `value=""` is also specified, however, then `md-mode="determinate"` would be auto-injected instead.
* @param {number=} value In determinate mode, this number represents the percentage of the
* circular progress. Default: 0
* @param {number=} md-diameter This specifies the diameter of the circular progress. The value
* should be a pixel-size value (eg '100'). If this attribute is
* not present then a default value of '50px' is assumed.
*
* @param {boolean=} ng-disabled Determines whether to disable the progress element.
*
* @usage
* <hljs lang="html">
* <md-progress-circular md-mode="determinate" value="..."></md-progress-circular>
*
* <md-progress-circular md-mode="determinate" ng-value="..."></md-progress-circular>
*
* <md-progress-circular md-mode="determinate" value="..." md-diameter="100"></md-progress-circular>
*
* <md-progress-circular md-mode="indeterminate"></md-progress-circular>
* </hljs>
*/
MdProgressCircularDirective.$inject = ["$window", "$mdProgressCircular", "$mdTheming", "$mdUtil", "$interval", "$log"];
angular
.module('material.components.progressCircular')
.directive('mdProgressCircular', MdProgressCircularDirective);
/* ngInject */
function MdProgressCircularDirective($window, $mdProgressCircular, $mdTheming,
$mdUtil, $interval, $log) {
// Note that this shouldn't use use $$rAF, because it can cause an infinite loop
// in any tests that call $animate.flush.
var rAF = $window.requestAnimationFrame ||
$window.webkitRequestAnimationFrame ||
angular.noop;
var cAF = $window.cancelAnimationFrame ||
$window.webkitCancelAnimationFrame ||
$window.webkitCancelRequestAnimationFrame ||
angular.noop;
var DEGREE_IN_RADIANS = $window.Math.PI / 180;
var MODE_DETERMINATE = 'determinate';
var MODE_INDETERMINATE = 'indeterminate';
var DISABLED_CLASS = '_md-progress-circular-disabled';
var INDETERMINATE_CLASS = 'md-mode-indeterminate';
return {
restrict: 'E',
scope: {
value: '@',
mdDiameter: '@',
mdMode: '@'
},
template:
'<svg xmlns="http://www.w3.org/2000/svg">' +
'<path fill="none"/>' +
'</svg>',
compile: function(element, attrs) {
element.attr({
'aria-valuemin': 0,
'aria-valuemax': 100,
'role': 'progressbar'
});
if (angular.isUndefined(attrs.mdMode)) {
var hasValue = angular.isDefined(attrs.value);
var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE;
var info = "Auto-adding the missing md-mode='{0}' to the ProgressCircular element";
// $log.debug( $mdUtil.supplant(info, [mode]) );
attrs.$set('mdMode', mode);
} else {
attrs.$set('mdMode', attrs.mdMode.trim());
}
return MdProgressCircularLink;
}
};
function MdProgressCircularLink(scope, element, attrs) {
var node = element[0];
var svg = angular.element(node.querySelector('svg'));
var path = angular.element(node.querySelector('path'));
var startIndeterminate = $mdProgressCircular.startIndeterminate;
var endIndeterminate = $mdProgressCircular.endIndeterminate;
var rotationIndeterminate = 0;
var lastAnimationId = 0;
var lastDrawFrame;
var interval;
$mdTheming(element);
element.toggleClass(DISABLED_CLASS, attrs.hasOwnProperty('disabled'));
// If the mode is indeterminate, it doesn't need to
// wait for the next digest. It can start right away.
if(scope.mdMode === MODE_INDETERMINATE){
startIndeterminateAnimation();
}
scope.$on('$destroy', function(){
cleanupIndeterminateAnimation();
if (lastDrawFrame) {
cAF(lastDrawFrame);
}
});
scope.$watchGroup(['value', 'mdMode', function() {
var isDisabled = node.disabled;
// Sometimes the browser doesn't return a boolean, in
// which case we should check whether the attribute is
// present.
if (isDisabled === true || isDisabled === false){
return isDisabled;
}
return angular.isDefined(element.attr('disabled'));
}], function(newValues, oldValues) {
var mode = newValues[1];
var isDisabled = newValues[2];
var wasDisabled = oldValues[2];
if (isDisabled !== wasDisabled) {
element.toggleClass(DISABLED_CLASS, !!isDisabled);
}
if (isDisabled) {
cleanupIndeterminateAnimation();
} else {
if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) {
mode = MODE_INDETERMINATE;
attrs.$set('mdMode', mode);
}
if (mode === MODE_INDETERMINATE) {
startIndeterminateAnimation();
} else {
var newValue = clamp(newValues[0]);
cleanupIndeterminateAnimation();
element.attr('aria-valuenow', newValue);
renderCircle(clamp(oldValues[0]), newValue);
}
}
});
// This is in a separate watch in order to avoid layout, unless
// the value has actually changed.
scope.$watch('mdDiameter', function(newValue) {
var diameter = getSize(newValue);
var strokeWidth = getStroke(diameter);
var transformOrigin = (diameter / 2) + 'px';
var dimensions = {
width: diameter + 'px',
height: diameter + 'px'
};
// The viewBox has to be applied via setAttribute, because it is
// case-sensitive. If jQuery is included in the page, `.attr` lowercases
// all attribute names.
svg[0].setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);
// Usually viewBox sets the dimensions for the SVG, however that doesn't
// seem to be the case on IE10.
// Important! The transform origin has to be set from here and it has to
// be in the format of "Ypx Ypx Ypx", otherwise the rotation wobbles in
// IE and Edge, because they don't account for the stroke width when
// rotating. Also "center" doesn't help in this case, it has to be a
// precise value.
svg
.css(dimensions)
.css('transform-origin', transformOrigin + ' ' + transformOrigin + ' ' + transformOrigin);
element.css(dimensions);
path.css('stroke-width', strokeWidth + 'px');
});
function renderCircle(animateFrom, animateTo, easing, duration, rotation) {
var id = ++lastAnimationId;
var startTime = $mdUtil.now();
var changeInValue = animateTo - animateFrom;
var diameter = getSize(scope.mdDiameter);
var pathDiameter = diameter - getStroke(diameter);
var ease = easing || $mdProgressCircular.easeFn;
var animationDuration = duration || $mdProgressCircular.duration;
// No need to animate it if the values are the same
if (animateTo === animateFrom) {
path.attr('d', getSvgArc(animateTo, diameter, pathDiameter, rotation));
} else {
lastDrawFrame = rAF(function animation() {
var currentTime = $window.Math.max(0, $window.Math.min($mdUtil.now() - startTime, animationDuration));
path.attr('d', getSvgArc(
ease(currentTime, animateFrom, changeInValue, animationDuration),
diameter,
pathDiameter,
rotation
));
// Do not allow overlapping animations
if (id === lastAnimationId && currentTime < animationDuration) {
lastDrawFrame = rAF(animation);
}
});
}
}
function animateIndeterminate() {
renderCircle(
startIndeterminate,
endIndeterminate,
$mdProgressCircular.easeFnIndeterminate,
$mdProgressCircular.durationIndeterminate,
rotationIndeterminate
);
// The % 100 technically isn't necessary, but it keeps the rotation
// under 100, instead of becoming a crazy large number.
rotationIndeterminate = (rotationIndeterminate + endIndeterminate) % 100;
var temp = startIndeterminate;
startIndeterminate = -endIndeterminate;
endIndeterminate = -temp;
}
function startIndeterminateAnimation() {
if (!interval) {
// Note that this interval isn't supposed to trigger a digest.
interval = $interval(
animateIndeterminate,
$mdProgressCircular.durationIndeterminate + 50,
0,
false
);
animateIndeterminate();
element
.addClass(INDETERMINATE_CLASS)
.removeAttr('aria-valuenow');
}
}
function cleanupIndeterminateAnimation() {
if (interval) {
$interval.cancel(interval);
interval = null;
element.removeClass(INDETERMINATE_CLASS);
}
}
}
/**
* Generates an arc following the SVG arc syntax.
* Syntax spec: https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
*
* @param {number} current Current value between 0 and 100.
* @param {number} diameter Diameter of the container.
* @param {number} pathDiameter Diameter of the path element.
* @param {number=0} rotation The point at which the semicircle should start rendering.
* Used for doing the indeterminate animation.
*
* @returns {string} String representation of an SVG arc.
*/
function getSvgArc(current, diameter, pathDiameter, rotation) {
// The angle can't be exactly 360, because the arc becomes hidden.
var maximumAngle = 359.99 / 100;
var startPoint = rotation || 0;
var radius = diameter / 2;
var pathRadius = pathDiameter / 2;
var startAngle = startPoint * maximumAngle;
var endAngle = current * maximumAngle;
var start = polarToCartesian(radius, pathRadius, startAngle);
var end = polarToCartesian(radius, pathRadius, endAngle + startAngle);
var arcSweep = endAngle < 0 ? 0 : 1;
var largeArcFlag;
if (endAngle < 0) {
largeArcFlag = endAngle >= -180 ? 0 : 1;
} else {
largeArcFlag = endAngle <= 180 ? 0 : 1;
}
return 'M' + start + 'A' + pathRadius + ',' + pathRadius +
' 0 ' + largeArcFlag + ',' + arcSweep + ' ' + end;
}
/**
* Converts Polar coordinates to Cartesian.
*
* @param {number} radius Radius of the container.
* @param {number} pathRadius Radius of the path element
* @param {number} angleInDegress Angle at which to place the point.
*
* @returns {string} Cartesian coordinates in the format of `x,y`.
*/
function polarToCartesian(radius, pathRadius, angleInDegrees) {
var angleInRadians = (angleInDegrees - 90) * DEGREE_IN_RADIANS;
return (radius + (pathRadius * $window.Math.cos(angleInRadians))) +
',' + (radius + (pathRadius * $window.Math.sin(angleInRadians)));
}
/**
* Limits a value between 0 and 100.
*/
function clamp(value) {
return $window.Math.max(0, $window.Math.min(value || 0, 100));
}
/**
* Determines the size of a progress circle, based on the provided
* value in the following formats: `X`, `Ypx`, `Z%`.
*/
function getSize(value) {
var defaultValue = $mdProgressCircular.progressSize;
if (value) {
var parsed = parseFloat(value);
if (value.lastIndexOf('%') === value.length - 1) {
parsed = (parsed / 100) * defaultValue;
}
return parsed;
}
return defaultValue;
}
/**
* Determines the circle's stroke width, based on
* the provided diameter.
*/
function getStroke(diameter) {
return $mdProgressCircular.strokeWidth / 100 * diameter;
}
}
/**
* @ngdoc service
* @name $mdProgressCircular
* @module material.components.progressCircular
*
* @description
* Allows the user to specify the default options for the `progressCircular` directive.
*
* @property {number} progressSize Diameter of the progress circle in pixels.
* @property {number} strokeWidth Width of the circle's stroke as a percentage of the circle's size.
* @property {number} duration Length of the circle animation in milliseconds.
* @property {function} easeFn Default easing animation function.
* @property {object} easingPresets Collection of pre-defined easing functions.
*
* @property {number} durationIndeterminate Duration of the indeterminate animation.
* @property {number} startIndeterminate Indeterminate animation start point.
* @property {number} endIndeterminate Indeterminate animation end point.
* @property {function} easeFnIndeterminate Easing function to be used when animating
* between the indeterminate values.
*
* @property {(function(object): object)} configure Used to modify the default options.
*
* @usage
* <hljs lang="js">
* myAppModule.config(function($mdProgressCircularProvider) {
*
* // Example of changing the default progress options.
* $mdProgressCircularProvider.configure({
* progressSize: 100,
* strokeWidth: 20,
* duration: 800
* });
* });
* </hljs>
*
*/
angular
.module('material.components.progressCircular')
.provider("$mdProgressCircular", MdProgressCircularProvider);
function MdProgressCircularProvider() {
var progressConfig = {
progressSize: 50,
strokeWidth: 10,
duration: 100,
easeFn: linearEase,
durationIndeterminate: 500,
startIndeterminate: 3,
endIndeterminate: 80,
easeFnIndeterminate: materialEase,
easingPresets: {
linearEase: linearEase,
materialEase: materialEase
}
};
return {
configure: function(options) {
progressConfig = angular.extend(progressConfig, options || {});
return progressConfig;
},
$get: function() { return progressConfig; }
};
function linearEase(t, b, c, d) {
return c * t / d + b;
}
function materialEase(t, b, c, d) {
// via http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
// with settings of [0, 0, 1, 1]
var ts = (t /= d) * t;
var tc = ts * t;
return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc);
}
}
ngmaterial.components.progressCircular = angular.module("material.components.progressCircular");