Instantly share code, notes, and snippets.
Last active
January 20, 2017 11:11
-
Star
4
(4)
You must be signed in to star a gist -
Fork
1
(1)
You must be signed in to fork a gist
-
Save kevinrenskers/d66dbebe43c8533efaf2 to your computer and use it in GitHub Desktop.
Progress button for AngularJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* General styles for all types of buttons */ | |
.progress-button { | |
position: relative; | |
display: inline-block; | |
} | |
.progress-button .progress-button-content { | |
position: relative; | |
display: block; | |
} | |
// Success / error icon | |
.progress-button .progress-button-content::before, | |
.progress-button .progress-button-content::after { | |
position: absolute; | |
right: 20px; | |
font-family: "Glyphicons Halflings"; | |
opacity: 0; | |
transition: opacity 0.3s 0.3s; | |
} | |
.progress-button .progress-button-content::before { | |
color: darken(@brand, 20%); | |
content: "\e013"; /* Checkmark for success */ | |
} | |
.progress-button .progress-button-content::after { | |
color: red; | |
content: "\e014"; /* Cross for error */ | |
} | |
.progress-button.state-success .progress-button-content::before, | |
.progress-button.state-error .progress-button-content::after { | |
opacity: 1; | |
} | |
.progress-button .progress-button-progress { | |
background: @brand; | |
} | |
.progress-button .progress-button-progress-inner { | |
position: absolute; | |
left: 0; | |
background: darken(@brand, 20%); | |
} | |
.progress-button.horizontal .progress-button-progress-inner { | |
top: 0; | |
width: 0; | |
height: 100%; | |
transition: width 0.3s, opacity 0.3s; | |
} | |
/* Fill horizontal */ | |
/* ====================== */ | |
.progress-button.fill.horizontal { | |
overflow: hidden; | |
} | |
.progress-button.fill.horizontal .progress-button-content { | |
z-index: 10; | |
transition: transform 0.3s; | |
} | |
.progress-button.fill.horizontal .progress-button-content::before, | |
.progress-button.fill.horizontal .progress-button-content::after { | |
top: 100%; | |
right: auto; | |
left: 50%; | |
transition: opacity 0.3s; | |
transform: translateX(-50%); | |
} | |
.progress-button.fill.horizontal.state-success .progress-button-content, | |
.progress-button.fill.horizontal.state-error .progress-button-content { | |
transform: translateY(-100%); | |
} | |
/* Shrink horizontal */ | |
/* ====================== */ | |
.progress-button.shrink { | |
overflow: hidden; | |
transition: transform 0.2s; | |
} | |
.progress-button.shrink.horizontal .progress-button-content { | |
transition: opacity 0.3s, transform 0.3s; | |
} | |
.progress-button.shrink.horizontal .progress-button-content::before, | |
.progress-button.shrink.horizontal .progress-button-content::after { | |
top: 100%; | |
right: auto; | |
left: 50%; | |
transition: opacity 0.3s; | |
transform: translateX(-50%); | |
} | |
.progress-button.shrink.horizontal.state-loading { | |
transform: scaleY(0.3); | |
} | |
.progress-button.shrink.horizontal.state-loading .progress-button-content { | |
opacity: 0; | |
} | |
.progress-button.shrink.horizontal.state-success .progress-button-content, | |
.progress-button.shrink.horizontal.state-error .progress-button-content { | |
transform: translateY(-100%); | |
} | |
/* Rotate side down 3d */ | |
/* ====================== */ | |
.progress-button.rotate-side-down .progress { | |
position: absolute; | |
top: 100%; | |
left: 0; | |
width: 100%; | |
height: 20px; | |
transform: rotateX(-90deg); | |
transform-origin: 50% 0%; | |
backface-visibility: hidden; | |
} | |
.progress-button.rotate-side-down.state-loading .progress-wrap { | |
transform: rotateX(90deg) translateZ(10px); | |
} | |
/* top-line */ | |
/* ====================== */ | |
.progress-button.top-line .progress-inner { | |
height: 3px; | |
} | |
.progress-button.top-line .progress-button-content::before, | |
.progress-button.top-line .progress-button-content::after { | |
right: auto; | |
left: 100%; | |
margin-left: 25px; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<button class="btn btn-primary progress-button fill horizontal"> | |
<span class="progress-button-content" ng-transclude></span> | |
<span class="progress-button-progress"> | |
<span class="progress-button-progress-inner" style="width:{{ width }}%"></span> | |
</span> | |
</button> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
angular.module('components.directives').directive('progressButton', function progressButton($parse, $timeout) { | |
'use strict'; | |
return { | |
restrict: 'E', | |
replace: true, | |
transclude: true, | |
scope: { | |
inprogress: '=' | |
}, | |
templateUrl: 'components/directives/progressButton.html', | |
link: function(scope, element, attrs) { | |
var _status = 0; | |
var _incTimeout; | |
var _completeTimeout; | |
var _fn; | |
scope.width = 0; | |
if (!attrs.type) { | |
element.type = 'button'; | |
} | |
function _complete() { | |
attrs.$set('disabled', false); | |
element.removeClass('state-loading'); | |
_set(1); | |
scope.width = 0; | |
_completeTimeout = $timeout(function() { | |
element.removeClass('state-success'); | |
element.removeClass('state-error'); | |
}, 1500); | |
} | |
function _success(data) { | |
_complete(); | |
element.addClass('state-success'); | |
return data; | |
} | |
function _fail(error) { | |
_complete(); | |
element.addClass('state-error'); | |
return error; | |
} | |
function _set(n) { | |
scope.width = (n * 100); | |
_status = n; | |
$timeout.cancel(_incTimeout); | |
_incTimeout = $timeout(function() { | |
_inc(); | |
}, 250); | |
} | |
function _inc() { | |
if (_status >= 1) { | |
return; | |
} | |
var rnd = 0; | |
if (_status >= 0 && _status < 0.25) { | |
// Start out between 10 - 15% increments | |
rnd = (Math.random() * 10 + 5) / 100; | |
} else if (_status >= 0.25 && _status < 0.65) { | |
// increment between 0 - 10% | |
rnd = (Math.random() * 10) / 100; | |
} else if (_status >= 0.65 && _status < 0.9) { | |
// increment between 0 - 5% | |
rnd = (Math.random() * 5) / 100; | |
} else if (_status >= 0.9 && _status < 0.99) { | |
// finally, increment it .5 % | |
rnd = 0.005; | |
} else { | |
// after 99%, don't increment: | |
rnd = 0; | |
} | |
var pct = _status + rnd; | |
_set(pct); | |
} | |
function _disable() { | |
$timeout.cancel(_completeTimeout); | |
attrs.$set('disabled', true); | |
element.addClass('state-loading'); | |
_set(0.02); | |
} | |
if (attrs.pbClick) { | |
element.on('click', function(event) { | |
_fn = $parse(attrs.pbClick); | |
var promise = _fn(scope.$parent, { $event: event }); | |
if (promise && promise.then) { | |
_disable(); | |
promise.then(_success, _fail); | |
} | |
}); | |
} else if (element[0].form && element[0].form.attributes['pb-submit']) { | |
element[0].form.onsubmit = function(event) { | |
_fn = $parse(element[0].form.attributes['pb-submit'].nodeValue); | |
var promise = _fn(scope.$parent, { $event: event }); | |
if (promise && promise.then) { | |
_disable(); | |
promise.then(_success, _fail); | |
} | |
} | |
} | |
scope.$watch('inprogress', function(newValue, oldValue) { | |
if (newValue === true) { | |
_disable(); | |
} | |
if (newValue === 'error' && oldValue === true) { | |
_fail(); | |
} | |
if (newValue === false && oldValue === true) { | |
_success(); | |
} | |
}); | |
} | |
}; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The logic to randomly increment the progress bar comes from angular-loading-bar, the css for the button comes from http://tympanus.net/Development/ProgressButtonStyles/.
Usage
As a simple button. The function needs to return a promise.
In a form with a onsubmit listener. The function needs to return a promise.
In a form with a inprogress scope variable.