<!DOCTYPE html>
<html ng-app='ngPromiseButtonDemo' ng-controller='DemoController'>
<head>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.1/animate.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular.min.js"></script>
<script src='promise_button.js'></script>
<script>
angular.module('ngPromiseButtonDemo', ['ngPromiseButton'])
.controller('DemoController', function($scope, $q, $timeout) {
$scope.doRequest = function(successful) {
var defered = $q.defer();
// simulate request
$timeout(function() {successful ? defered.resolve() : defered.reject()}, 5000);
return {
$promise: defered.promise
}
}
$scope.doSuccessfulRequest = function() {
// simulate request or any async process
return $scope.doRequest(true).$promise;
}
$scope.doFailedRequest = function() {
// simulate request or any async process
return $scope.doRequest(false).$promise;
}
$scope.doChainedRequest = function() {
// example of chained request - not feature of promise button in particular
// but to show it just care about $q promise with all it is possible to do
// with $q promises
return $scope.doRequest(true).$promise.then(function() {
return $scope.doRequest(true).$promise
}).then(function() {
alert('DONE!');
})
}
$scope.doValidation = function(form) {
if(form.$valid){
// return a promise
return $scope.doSubmitRequest()
}else{
// don't return a promise - nothing will happen to the button
alert('Validation Error');
}
}
$scope.doSubmitRequest = function() {
return $scope.doSuccessfulRequest()
}
})
</script>
<style>
/* promise-button & animate.css */
.promise-button { transition: 0.3s linear all; }
.promise-button-success.ng-hide-remove { animation: fadeIn 0.3s; }
.promise-button-success.ng-hide-add { animation: fadeOut 0.3s; }
.promise-button-error.ng-hide-remove { animation: fadeIn 0.3s; }
.promise-button-error.ng-hide-add { animation: fadeOut 0.3s; }
</style>
</head>
<body>
<h1>ngPromiseButton DEMO</h1>
<p><a href='http://ngmodules.org/modules/ng-promise-button'>http://ngmodules.org/modules/ng-promise-button</a></p>
<p>by <a href='http://lukas.dolezalu.cz'>Lukáš Doležal</a></p>
<p>Fork by <a href='https://github.com/christurnbull'>Chris Turnbull</a></p>
<p> </p>
<p>
<h4>Added features</h4>
Dont change class, never revert:
<button type="button" class="btn btn-primary promise-button"
promise-button="doSuccessfulRequest()"
promise-success-class="none"
promise-error-class="none"
promise-revert="none"
>Save</button>
</p>
<p>
Use custom class and revert period:
<button type="button" class="btn btn-primary promise-button"
promise-button="doFailedRequest()"
promise-error-class="btn-warning"
promise-revert="2000"
>Save</button>
</p>
<p>
Turn the button into type="submit" (if the function returns a promise):
<form name="form">
<input name="text" type="text" ng-model="text" required placeholder="This text is Required"/>
<button type="button" class="btn btn-success promise-button"
promise-button="doValidation(form)"
promise-submit="true"
>Save</button>
<div>form.$submitted: {{form.$submitted}}</div>
</form>
</p>
<p> </p>
<p>
<h4>Master features</h4>
Successful request (processing 5secs):
<button class='btn btn-primary promise-button' promise-button='doSuccessfulRequest()'>Save</button>
</p>
<p>
Failed request (processing 5secs):
<button class='btn btn-primary promise-button' promise-button='doFailedRequest()'>Save</button>
</p>
<p>
Cancel period button:
<button class='btn btn-danger promise-button' promise-button='doSuccessfulRequest()' promise-cease-period='5'>Delete</button>
</p>
<p>
Chained request (processing 10secs):
<button class='btn btn-success promise-button' promise-button='doChainedRequest()'>Save and publish</button>
</p>
<p>
Own labels:
<label><input ng-model='labelSampleSuccess' type='checkbox'/> Successful</label>
<button class='btn btn-primary promise-button' promise-button='doRequest(labelSampleSuccess).$promise' promise-success='HELL YEAH!' promise-pending="It's here It's almost here!" promise-error='DOH'>Save</button>
</p>
</body>
</html>
'use strict';
/**
* @ngdoc Promise Button directive - call function that returns angular promise object, displaying progress and result of promise.
* @name ngPromiseButton.directive:promiseButton
* @description promise-button attribute expect function call. when button is pressed, function is called and button displays progress and result of the promise.
* # promiseButton
*
* Added functionality:
* handle ngResource promise and null/empty promises
* attribute to submit form - if a promise is supplied the button turns into type="submit"
* attributes for button success/error class - defaults to btn-success/btn-danger
* attribute to revert to original class after revert period
* classes for span elements & use fa-spin class
* added dependency injection annotations
*/
angular.module('ngPromiseButton', [])
.directive('promiseButton', ['$compile', '$timeout' , function ($compile, $timeout) {
return {
restrict: 'A',
template: [
'<span>',
'<span class="promise-button-idle" ng-show="state != \'ceaseInterval\'" ng-transclude></span> ',
'<span class="promise-button-countdown" ng-show="state == \'ceaseInterval\'">Cancel in {{ceaseIntervalSec}}s</span>',
'<span class="promise-button-progress" ng-show="state == \'progress\'"><span class="fa fa-refresh fa-spin"></span> {{labelPending}}</span>',
'<span class="promise-button-success" ng-show="state == \'success\'"><span class="fa fa-check"></span> {{labelSuccess}}</span>',
'<span class="promise-button-error" ng-show="state == \'error\'"><span class="fa fa-times"></span> {{labelError}}</span>',
'</span>'
].join(''),
transclude:true,
scope: {
promiseButton: '&',
promiseCeasePeriod: '@',
promisePending: '@',
promiseSuccess: '@',
promiseError: '@',
promiseRevert: '@', // revert attribute
promiseSuccessClass: '@', // success class attribute
promiseErrorClass: '@', // error class attribute
promiseSubmit: '=', // submit form
},
link: function postLink(scope, element, attrs) {
element.attr('ng-click', 'onClick()');
element.removeAttr('promise-button');
element.find('[ng-transclude]').removeAttr('ng-transclude');
// store whether to revert
var revert= scope.promiseRevert == 'none' ? false : true;
scope.state = 'idle';
scope.labelPending = scope.promisePending || '';
scope.labelSuccess = scope.promiseSuccess || '';
scope.labelError = scope.promiseError || 'Failed';
scope.revert = Number(scope.promiseRevert) || 4000; // revert attribute
scope.successClass = scope.promiseSuccessClass != null || scope.promiseSuccessClass == "none" ? scope.promiseSuccessClass : 'btn-success'; // success class attribute
scope.errorClass = scope.promiseErrorClass != null || scope.promiseErrorClass == "none" ? scope.promiseErrorClass : 'btn-danger'; // success class attribute
var ceaseTimer = null;
// only show cancel if there's cease-period attribute
var ceaseButton = scope.promiseCeasePeriod ? true : false;
var intervalTick = function() {
if (ceaseButton && scope.state != 'ceaseInterval') {
return;
}
scope.ceaseIntervalSec -= 1;
if (scope.ceaseIntervalSec <= 0) {
scope.start();
} else {
ceaseTimer = $timeout(intervalTick, 1000);
}
};
scope.onClick = function() {
if (scope.state == 'progress') {
return;
}
if (scope.state == 'ceaseInterval') {
scope.state = 'idle';
$timeout.cancel(ceaseTimer);
return;
}
if(ceaseButton){
scope.state = 'ceaseInterval';
}
scope.ceaseIntervalSec = Number(scope.promiseCeasePeriod) || 0;
scope.ceaseIntervalSec += 1;
intervalTick();
};
scope.start = function() {
if (scope.state == 'progress') {
return;
}
var promise = scope.promiseButton();
if(promise){
promise = promise.$promise ? promise.$promise : promise; // handle ngResource
if(scope.promiseSubmit){
var formEl= element[0].form;
element.attr('type', 'submit'); // change button to type "submit"
angular.element(formEl).triggerHandler('submit'); // trigger submit event
scope.promiseSubmit.$submitted= true ; // update submitted
}
scope.state = 'progress';
// check if success/error classes are already applied
var originalClass= {};
originalClass.success= element.hasClass(scope.successClass) ? true : false;
originalClass.error= element.hasClass(scope.errorClass) ? true : false;
promise
.then(function() {
scope.state = 'success';
// remove error class & add success class
element.removeClass(scope.errorClass);
element.addClass(scope.successClass);
element[0].blur();
// revert to orginal class
if(revert){
$timeout(function() {
scope.state = 'idle';
if(originalClass.error) element.addClass(scope.errorClass);
if(!originalClass.success) element.removeClass(scope.successClass);
}, scope.revert);
}
})
.catch(function() {
scope.state = 'error';
// remove success class & add error class
element.removeClass(scope.successClass);
element.addClass(scope.errorClass);
element[0].blur();
// revert to orginal class
if(revert){
$timeout(function() {
scope.state = 'idle';
if(originalClass.success) element.addClass(scope.successClass);
if(!originalClass.error) element.removeClass(scope.errorClass);
}, scope.revert);
}
});
}
};
$compile(element)(scope);
}
};
}]);