progressCircular.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /*!
  2. * Angular Material Design
  3. * https://github.com/angular/material
  4. * @license MIT
  5. * v0.11.4
  6. */
  7. (function( window, angular, undefined ){
  8. "use strict";
  9. /**
  10. * @ngdoc module
  11. * @name material.components.progressCircular
  12. * @description Circular Progress module!
  13. */
  14. angular.module('material.components.progressCircular', [
  15. 'material.core'
  16. ])
  17. .directive('mdProgressCircular', MdProgressCircularDirective);
  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 `.ng-hide`
  39. * will be auto-applied as a style to the component.
  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 diamter of the circular progress. The value
  46. * may be a percentage (eg '25%') or a pixel-size value (eg '48'). If this attribute is
  47. * not present then a default value of '48px' is assumed.
  48. *
  49. * @usage
  50. * <hljs lang="html">
  51. * <md-progress-circular md-mode="determinate" value="..."></md-progress-circular>
  52. *
  53. * <md-progress-circular md-mode="determinate" ng-value="..."></md-progress-circular>
  54. *
  55. * <md-progress-circular md-mode="determinate" value="..." md-diameter="100"></md-progress-circular>
  56. *
  57. * <md-progress-circular md-mode="indeterminate"></md-progress-circular>
  58. * </hljs>
  59. */
  60. function MdProgressCircularDirective($mdTheming, $mdUtil, $log) {
  61. var DEFAULT_PROGRESS_SIZE = 100;
  62. var DEFAULT_SCALING = 0.5;
  63. var MODE_DETERMINATE = "determinate",
  64. MODE_INDETERMINATE = "indeterminate";
  65. return {
  66. restrict: 'E',
  67. scope : true,
  68. template:
  69. // The progress 'circle' is composed of two half-circles: the left side and the right
  70. // side. Each side has CSS applied to 'fill-in' the half-circle to the appropriate progress.
  71. '<div class="md-spinner-wrapper">' +
  72. '<div class="md-inner">' +
  73. '<div class="md-gap"></div>' +
  74. '<div class="md-left">' +
  75. '<div class="md-half-circle"></div>' +
  76. '</div>' +
  77. '<div class="md-right">' +
  78. '<div class="md-half-circle"></div>' +
  79. '</div>' +
  80. '</div>' +
  81. '</div>',
  82. compile: compile
  83. };
  84. function compile(tElement) {
  85. // The javascript in this file is mainly responsible for setting the correct aria attributes.
  86. // The animation of the progress spinner is done entirely with just CSS.
  87. tElement.attr('aria-valuemin', 0);
  88. tElement.attr('aria-valuemax', 100);
  89. tElement.attr('role', 'progressbar');
  90. return postLink;
  91. }
  92. function postLink(scope, element, attr) {
  93. $mdTheming(element);
  94. var circle = element;
  95. var spinnerWrapper = angular.element(element.children()[0]);
  96. var lastMode, toVendorCSS = $mdUtil.dom.animator.toCss;
  97. element.attr('md-mode', mode());
  98. updateScale();
  99. validateMode();
  100. watchAttributes();
  101. /**
  102. * Watch the value and md-mode attributes
  103. */
  104. function watchAttributes() {
  105. attr.$observe('value', function(value) {
  106. var percentValue = clamp(value);
  107. element.attr('aria-valuenow', percentValue);
  108. if (mode() == MODE_DETERMINATE) {
  109. animateIndicator(percentValue);
  110. }
  111. });
  112. attr.$observe('mdMode',function(mode){
  113. switch( mode ) {
  114. case MODE_DETERMINATE:
  115. case MODE_INDETERMINATE:
  116. spinnerWrapper.removeClass('ng-hide');
  117. spinnerWrapper.removeClass( lastMode );
  118. spinnerWrapper.addClass( lastMode = "md-mode-" + mode );
  119. break;
  120. default:
  121. spinnerWrapper.removeClass( lastMode );
  122. spinnerWrapper.addClass('ng-hide');
  123. lastMode = undefined;
  124. break;
  125. }
  126. });
  127. }
  128. /**
  129. * Update size/scaling of the progress indicator
  130. * Watch the "value" and "md-mode" attributes
  131. */
  132. function updateScale() {
  133. circle.css(toVendorCSS({
  134. transform : $mdUtil.supplant('scale( {0} )',[getDiameterRatio()])
  135. }));
  136. }
  137. /**
  138. * Auto-defaults the mode to either `determinate` or `indeterminate` mode; if not specified
  139. */
  140. function validateMode() {
  141. if ( angular.isUndefined(attr.mdMode) ) {
  142. var hasValue = angular.isDefined(attr.value);
  143. var mode = hasValue ? MODE_DETERMINATE : MODE_INDETERMINATE;
  144. var info = "Auto-adding the missing md-mode='{0}' to the ProgressCircular element";
  145. $log.debug( $mdUtil.supplant(info, [mode]) );
  146. element.attr("md-mode",mode);
  147. attr['mdMode'] = mode;
  148. }
  149. }
  150. var leftC, rightC, gap;
  151. /**
  152. * Manually animate the Determinate indicator based on the specified
  153. * percentage value (0-100).
  154. *
  155. * Note: this animation was previously done using SCSS.
  156. * - generated 54K of styles
  157. * - use attribute selectors which had poor performances in IE
  158. */
  159. function animateIndicator(value) {
  160. if ( !mode() ) return;
  161. leftC = leftC || angular.element(element[0].querySelector('.md-left > .md-half-circle'));
  162. rightC = rightC || angular.element(element[0].querySelector('.md-right > .md-half-circle'));
  163. gap = gap || angular.element(element[0].querySelector('.md-gap'));
  164. var gapStyles = removeEmptyValues({
  165. borderBottomColor: (value <= 50) ? "transparent !important" : "",
  166. transition: (value <= 50) ? "" : "borderBottomColor 0.1s linear"
  167. }),
  168. leftStyles = removeEmptyValues({
  169. transition: (value <= 50) ? "transform 0.1s linear" : "",
  170. transform: $mdUtil.supplant("rotate({0}deg)", [value <= 50 ? 135 : (((value - 50) / 50 * 180) + 135)])
  171. }),
  172. rightStyles = removeEmptyValues({
  173. transition: (value >= 50) ? "transform 0.1s linear" : "",
  174. transform: $mdUtil.supplant("rotate({0}deg)", [value >= 50 ? 45 : (value / 50 * 180 - 135)])
  175. });
  176. leftC.css(toVendorCSS(leftStyles));
  177. rightC.css(toVendorCSS(rightStyles));
  178. gap.css(toVendorCSS(gapStyles));
  179. }
  180. /**
  181. * We will scale the progress circle based on the default diameter.
  182. *
  183. * Determine the diameter percentage (defaults to 100%)
  184. * May be express as float, percentage, or integer
  185. */
  186. function getDiameterRatio() {
  187. if ( !attr.mdDiameter ) return DEFAULT_SCALING;
  188. var match = /([0-9]*)%/.exec(attr.mdDiameter);
  189. var value = Math.max(0, (match && match[1]/100) || parseFloat(attr.mdDiameter));
  190. // should return ratio; DEFAULT_PROGRESS_SIZE === 100px is default size
  191. return (value > 1) ? value / DEFAULT_PROGRESS_SIZE : value;
  192. }
  193. /**
  194. * Is the md-mode a valid option?
  195. */
  196. function mode() {
  197. var value = (attr.mdMode || "").trim();
  198. if ( value ) {
  199. switch(value) {
  200. case MODE_DETERMINATE :
  201. case MODE_INDETERMINATE :
  202. break;
  203. default:
  204. value = undefined;
  205. break;
  206. }
  207. }
  208. return value;
  209. }
  210. }
  211. /**
  212. * Clamps the value to be between 0 and 100.
  213. * @param {number} value The value to clamp.
  214. * @returns {number}
  215. */
  216. function clamp(value) {
  217. return Math.max(0, Math.min(value || 0, 100));
  218. }
  219. function removeEmptyValues(target) {
  220. for (var key in target) {
  221. if (target.hasOwnProperty(key)) {
  222. if ( target[key] == "" ) delete target[key];
  223. }
  224. }
  225. return target;
  226. }
  227. }
  228. MdProgressCircularDirective.$inject = ["$mdTheming", "$mdUtil", "$log"];
  229. })(window, window.angular);