blob: 30307a3dd1eda859efe23ed23bd35a0b942b2e3f [file] [log] [blame]
/*!
* Angular Material Design
* https://github.com/angular/material
* @license MIT
* v1.0.1
*/
goog.provide('ng.material.components.progressCircular');
goog.require('ng.material.core');
/**
* @ngdoc module
* @name material.components.progressCircular
* @description Circular Progress module!
*/
angular.module('material.components.progressCircular', [
'material.core'
])
.directive('mdProgressCircular', MdProgressCircularDirective);
/**
* @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 it’s
* 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 `.ng-hide`
* will be auto-applied as a style to the component.
*
* 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 diamter of the circular progress. The value
* may be a percentage (eg '25%') or a pixel-size value (eg '48'). If this attribute is
* not present then a default value of '48px' is assumed.
*
* @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>
*/
function MdProgressCircularDirective($mdTheming, $mdUtil, $log) {
var DEFAULT_PROGRESS_SIZE = 100;
var DEFAULT_SCALING = 0.5;
var MODE_DETERMINATE = "determinate",
MODE_INDETERMINATE = "indeterminate";
return {
restrict: 'E',
scope : true,
template:
// The progress 'circle' is composed of two half-circles: the left side and the right
// side. Each side has CSS applied to 'fill-in' the half-circle to the appropriate progress.
'<div class="md-scale-wrapper">' +
'<div class="md-spinner-wrapper">' +
'<div class="md-inner">' +
'<div class="md-gap"></div>' +
'<div class="md-left">' +
'<div class="md-half-circle"></div>' +
'</div>' +
'<div class="md-right">' +
'<div class="md-half-circle"></div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>',
compile: compile
};
function compile(tElement) {
// The javascript in this file is mainly responsible for setting the correct aria attributes.
// The animation of the progress spinner is done entirely with just CSS.
tElement.attr('aria-valuemin', 0);
tElement.attr('aria-valuemax', 100);
tElement.attr('role', 'progressbar');
return postLink;
}
function postLink(scope, element, attr) {
$mdTheming(element);
var circle = element;
var spinnerWrapper = angular.element(element.children()[0]);
var lastMode, toVendorCSS = $mdUtil.dom.animator.toCss;
element.attr('md-mode', mode());
updateScale();
validateMode();
watchAttributes();
/**
* Watch the value and md-mode attributes
*/
function watchAttributes() {
attr.$observe('value', function(value) {
var percentValue = clamp(value);
element.attr('aria-valuenow', percentValue);
if (mode() == MODE_DETERMINATE) {
animateIndicator(percentValue);
}
});
attr.$observe('mdMode',function(mode){
switch( mode ) {
case MODE_DETERMINATE:
case MODE_INDETERMINATE:
spinnerWrapper.removeClass('ng-hide');
if (lastMode) spinnerWrapper.removeClass(lastMode);
spinnerWrapper.addClass( lastMode = "md-mode-" + mode );
break;
default:
if (lastMode) spinnerWrapper.removeClass( lastMode );
spinnerWrapper.addClass('ng-hide');
lastMode = undefined;
break;
}
});
}
/**
* Update size/scaling of the progress indicator
* Watch the "value" and "md-mode" attributes
*/
function updateScale() {
// set the outer container to the size the user specified
circle.css({
width: (100 * getDiameterRatio()) + 'px',
height: (100 * getDiameterRatio()) + 'px'
});
// the internal element is still 100px, so we have to scale it down to match the size
circle.children().eq(0).css(toVendorCSS({
transform : $mdUtil.supplant('translate(-50%, -50%) scale( {0} )',[getDiameterRatio()])
}));
}
/**
* Auto-defaults the mode to either `determinate` or `indeterminate` mode; if not specified
*/
function validateMode() {
if ( angular.isUndefined(attr.mdMode) ) {
var hasValue = angular.isDefined(attr.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]) );
element.attr("md-mode",mode);
attr['mdMode'] = mode;
}
}
var leftC, rightC, gap;
/**
* Manually animate the Determinate indicator based on the specified
* percentage value (0-100).
*
* Note: this animation was previously done using SCSS.
* - generated 54K of styles
* - use attribute selectors which had poor performances in IE
*/
function animateIndicator(value) {
if ( !mode() ) return;
leftC = leftC || angular.element(element[0].querySelector('.md-left > .md-half-circle'));
rightC = rightC || angular.element(element[0].querySelector('.md-right > .md-half-circle'));
gap = gap || angular.element(element[0].querySelector('.md-gap'));
var gapStyles = removeEmptyValues({
borderBottomColor: (value <= 50) ? "transparent !important" : "",
transition: (value <= 50) ? "" : "borderBottomColor 0.1s linear"
}),
leftStyles = removeEmptyValues({
transition: (value <= 50) ? "transform 0.1s linear" : "",
transform: $mdUtil.supplant("rotate({0}deg)", [value <= 50 ? 135 : (((value - 50) / 50 * 180) + 135)])
}),
rightStyles = removeEmptyValues({
transition: (value >= 50) ? "transform 0.1s linear" : "",
transform: $mdUtil.supplant("rotate({0}deg)", [value >= 50 ? 45 : (value / 50 * 180 - 135)])
});
leftC.css(toVendorCSS(leftStyles));
rightC.css(toVendorCSS(rightStyles));
gap.css(toVendorCSS(gapStyles));
}
/**
* We will scale the progress circle based on the default diameter.
*
* Determine the diameter percentage (defaults to 100%)
* May be express as float, percentage, or integer
*/
function getDiameterRatio() {
if ( !attr.mdDiameter ) return DEFAULT_SCALING;
var match = /([0-9]*)%/.exec(attr.mdDiameter);
var value = Math.max(0, (match && match[1]/100) || parseFloat(attr.mdDiameter));
// should return ratio; DEFAULT_PROGRESS_SIZE === 100px is default size
return (value > 1) ? value / DEFAULT_PROGRESS_SIZE : value;
}
/**
* Is the md-mode a valid option?
*/
function mode() {
var value = (attr.mdMode || "").trim();
if ( value ) {
switch(value) {
case MODE_DETERMINATE :
case MODE_INDETERMINATE :
break;
default:
value = undefined;
break;
}
}
return value;
}
}
/**
* Clamps the value to be between 0 and 100.
* @param {number} value The value to clamp.
* @returns {number}
*/
function clamp(value) {
return Math.max(0, Math.min(value || 0, 100));
}
function removeEmptyValues(target) {
for (var key in target) {
if (target.hasOwnProperty(key)) {
if ( target[key] == "" ) delete target[key];
}
}
return target;
}
}
MdProgressCircularDirective.$inject = ["$mdTheming", "$mdUtil", "$log"];
ng.material.components.progressCircular = angular.module("material.components.progressCircular");