<!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>&nbsp;</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>&nbsp;</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>&nbsp;',
        '<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);
      }
    };
  }]);