1. /**
    2. * uiProgressButton.js v1.0.0
    3. * http://www.codrops.com
    4. *
    5. * Licensed under the MIT license.
    6. * http://www.opensource.org/licenses/mit-license.php
    7. *
    8. * Copyright 2014, Codrops
    9. * http://www.codrops.com
    10. */
    11. ;( function( window ) {
    12. 'use strict';
    13. var transEndEventNames = {
    14. 'WebkitTransition': 'webkitTransitionEnd',
    15. 'MozTransition': 'transitionend',
    16. 'OTransition': 'oTransitionEnd',
    17. 'msTransition': 'MSTransitionEnd',
    18. 'transition': 'transitionend'
    19. },
    20. transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],
    21. support = { transitions : Modernizr.csstransitions };
    22. function extend( a, b ) {
    23. for( var key in b ) {
    24. if( b.hasOwnProperty( key ) ) {
    25. a[key] = b[key];
    26. }
    27. }
    28. return a;
    29. }
    30. function SVGEl( el ) {
    31. this.el = el;
    32. // the path elements
    33. this.paths = [].slice.call( this.el.querySelectorAll( 'path' ) );
    34. // we will save both paths and its lengths in arrays
    35. this.pathsArr = new Array();
    36. this.lengthsArr = new Array();
    37. this._init();
    38. }
    39. SVGEl.prototype._init = function() {
    40. var self = this;
    41. this.paths.forEach( function( path, i ) {
    42. self.pathsArr[i] = path;
    43. path.style.strokeDasharray = self.lengthsArr[i] = path.getTotalLength();
    44. } );
    45. // undraw stroke
    46. this.draw(0);
    47. }
    48. // val in [0,1] : 0 - no stroke is visible, 1 - stroke is visible
    49. SVGEl.prototype.draw = function( val ) {
    50. for( var i = 0, len = this.pathsArr.length; i < len; ++i ){
    51. this.pathsArr[ i ].style.strokeDashoffset = this.lengthsArr[ i ] * ( 1 - val );
    52. }
    53. }
    54. function UIProgressButton( el, options ) {
    55. this.el = el;
    56. this.options = extend( {}, this.options );
    57. extend( this.options, options );
    58. this._init();
    59. }
    60. UIProgressButton.prototype.options = {
    61. // time in ms that the status (success or error will be displayed) - should be at least higher than the transition-duration value defined for the stroke-dashoffset transition of both checkmark and cross strokes
    62. statusTime : 1500
    63. }
    64. UIProgressButton.prototype._init = function() {
    65. // the button
    66. this.button = this.el.querySelector( 'button' );
    67. // progress el
    68. this.progressEl = new SVGEl( this.el.querySelector( 'svg.progress-circle' ) );
    69. // the success/error elems
    70. this.successEl = new SVGEl( this.el.querySelector( 'svg.checkmark' ) );
    71. this.errorEl = new SVGEl( this.el.querySelector( 'svg.cross' ) );
    72. // init events
    73. this._initEvents();
    74. // enable button
    75. this._enable();
    76. }
    77. UIProgressButton.prototype._initEvents = function() {
    78. var self = this;
    79. this.button.addEventListener( 'click', function() { self._submit(); } );
    80. }
    81. UIProgressButton.prototype._submit = function() {
    82. // by adding the loading class the button will transition to a "circle"
    83. classie.addClass( this.el, 'loading' );
    84. var self = this,
    85. onEndBtnTransitionFn = function( ev ) {
    86. if( support.transitions ) {
    87. if( ev.propertyName !== 'width' ) return false;
    88. this.removeEventListener( transEndEventName, onEndBtnTransitionFn );
    89. }
    90. // disable the button - this should have been the first thing to do when clicking the button.
    91. // however if we do so Firefox does not seem to fire the transitionend event.
    92. this.setAttribute( 'disabled', '' );
    93. if( typeof self.options.callback === 'function' ) {
    94. self.options.callback( self );
    95. }
    96. else {
    97. // fill it (time will be the one defined in the CSS transition-duration property)
    98. self.setProgress(1);
    99. self.stop();
    100. }
    101. };
    102. if( support.transitions ) {
    103. this.button.addEventListener( transEndEventName, onEndBtnTransitionFn );
    104. }
    105. else {
    106. onEndBtnTransitionFn();
    107. }
    108. }
    109. // runs after the progress reaches 100%
    110. UIProgressButton.prototype.stop = function( status ) {
    111. var self = this,
    112. endLoading = function() {
    113. // first undraw progress stroke.
    114. self.progressEl.draw(0);
    115. if( typeof status === 'number' ) {
    116. var statusClass = status >= 0 ? 'success' : 'error',
    117. statusEl = status >=0 ? self.successEl : self.errorEl;
    118. // draw stroke of success (checkmark) or error (cross).
    119. statusEl.draw( 1 );
    120. // add respective class to the element
    121. classie.addClass( self.el, statusClass );
    122. // after options.statusTime remove status and undraw the respective stroke. Also enable the button.
    123. setTimeout( function() {
    124. classie.remove( self.el, statusClass );
    125. statusEl.draw(0);
    126. self._enable();
    127. }, self.options.statusTime );
    128. }
    129. else {
    130. self._enable();
    131. }
    132. // finally remove class loading.
    133. classie.removeClass( self.el, 'loading' );
    134. };
    135. // give it a time (ideally the same like the transition time) so that the last progress increment animation is still visible.
    136. setTimeout( endLoading, 300 );
    137. }
    138. UIProgressButton.prototype.setProgress = function( val ) {
    139. this.progressEl.draw( val );
    140. }
    141. // enable button
    142. UIProgressButton.prototype._enable = function() {
    143. this.button.removeAttribute( 'disabled' );
    144. }
    145. // add to global namespace
    146. window.UIProgressButton = UIProgressButton;
    147. })( window );