<!DOCTYPE html>
<html ng-app="app">

  <head>
    <link data-require="font-awesome@*" data-semver="4.5.0" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.css" />
    <link data-require="bootstrap-css@3.3.6" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
    <link data-require="animate.css@*" data-semver="3.2.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/animate.css/3.2.0/animate.min.css" />
    <link data-require="ngToast@*" data-semver="1.5.0" rel="stylesheet" href="//rawgit.com/tameraydin/ngToast/1.5.0/dist/ngToast.css" />
    <link data-require="ngToast@*" data-semver="1.5.0" rel="stylesheet" href="//rawgit.com/tameraydin/ngToast/1.5.0/dist/ngToast-animations.css" />
    <script data-require="jquery@*" data-semver="2.2.0" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
    <script data-require="angular.js@1.4.9" data-semver="1.4.9" src="https://code.angularjs.org/1.4.9/angular.js"></script>
    <script data-require="ui-router@*" data-semver="0.2.18" src="//cdn.rawgit.com/angular-ui/ui-router/0.2.18/release/angular-ui-router.js"></script>
    <script data-require="underscore.js@*" data-semver="1.8.3" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
    <script data-require="ui-bootstrap@*" data-semver="1.3.2" src="https://cdn.rawgit.com/angular-ui/bootstrap/gh-pages/ui-bootstrap-tpls-1.3.2.js"></script>
    <script data-require="angular-sanitize@*" data-semver="1.4.3" src="https://code.angularjs.org/1.4.3/angular-sanitize.js"></script>
    <script data-require="angular-animate@*" data-semver="1.4.9" src="https://code.angularjs.org/1.4.9/angular-animate.js"></script>
    <script data-require="ngToast@*" data-semver="1.5.0" src="//rawgit.com/tameraydin/ngToast/1.5.0/dist/ngToast.js"></script>
    <script src="angular-clipboard.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="app.js"></script>
    <script src="MainController.js"></script>
  </head>

  <body ng-controller="mainController as vm">
    <toast></toast>
    <div ui-view=""></div>
    <div class="container-fluid">
      <form>
        <div class="row">
          <div class="col-md-12">
            <h4>Kinobody Calories / Macros</h4>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <div class="form-group has-warning">
              <div class="input-group input-select">
                <span class="input-group-addon input-label">Kinobody Program</span>
                <select class="form-control" ng-options="(option.name) for option in vm.programOptions" ng-model="vm.selectedProgram" title="{{vm.selectedProgram.description}}" ng-change="vm.onProgramChange()"></select>
              </div>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <div class="form-group has-warning">
              <div class="input-group">
                <span class="input-group-addon input-label">Weight in {{vm.inputUnitsWeight}}</span>
                <input type="text" class="form-control" ng-model="vm.inputWeight" ng-model-options="{debounce: 500}" ng-change="vm.onWeightChange()" />
                <span class="input-group-btn">
                  <button type="button" class="btn btn-toggle" ng-click="vm.toggleInputUnitsWeight()" tabindex="-1" title="Click to toggle kg/lbs">
                    {{vm.inputUnitsWeight}}                                                                                <i class="fa fa-toggle-on" ng-class="{'fa-flip-horizontal': vm.inputUnitsWeight !== 'kg'}"></i>
                  </button>
                </span>
              </div>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <fieldset disabled="">
              <div class="form-group">
                <div class="input-group">
                  <span class="input-group-addon input-label">KB maintenance (lbs x 15)</span>
                  <input type="text" ng-model="vm.kinobodyEstimateMaintenanceCalories_display" class="form-control text-right" placeholder="Required: weight" readonly="" />
                  <span class="input-group-addon">kcal</span>
                </div>
              </div>
            </fieldset>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <fieldset disabled="">
              <div class="form-group" ng-class="{'has-success' : vm.activeMaintenanceFormula === 'KB'}">
                <div class="input-group">
                  <span class="input-group-addon input-label">KB daily calories (lbs x {{vm.selectedProgram.lbsFactor}}{{vm.programOffset}})</span>
                  <input type="text" ng-model="vm.kinobodyEstimateDailyCalories_display" class="form-control text-right" placeholder="Required: weight" readonly="" />
                  <span class="input-group-addon">kcal</span>
                </div>
              </div>
            </fieldset>
          </div>
        </div>
        <hr />
        <div class="row">
          <div class="col-md-12">
            <h4>Your macros</h4>
          </div>
        </div>
        <fieldset disabled="">
          <div class="row">
            <div class="col-md-6 col-sm-9 col-xs-12">
              <div class="row">
                <div class="col-xs-8">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <span class="input-group-addon input-macros">Protein</span>
                      <input type="text" ng-model="vm.macrosProtein" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">g</span>
                    </div>
                  </div>
                </div>
                <div class="col-xs-4">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <input type="text" ng-model="vm.percentageProtein" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">%</span>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-md-6 col-sm-9 col-xs-12">
              <div class="row">
                <div class="col-xs-8">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <span class="input-group-addon input-macros">Fats</span>
                      <input type="text" ng-model="vm.macrosFats" id="macrosFat" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">g</span>
                    </div>
                  </div>
                </div>
                <div class="col-xs-4">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <input type="text" ng-model="vm.percentageFats" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">%</span>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-md-6 col-sm-9 col-xs-12">
              <div class="row">
                <div class="col-xs-8">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <span class="input-group-addon input-macros">Carbs</span>
                      <input type="text" ng-model="vm.macrosCarbs" id="macrosCarb" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">g</span>
                    </div>
                  </div>
                </div>
                <div class="col-xs-4">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <input type="text" ng-model="vm.percentageCarbs" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">%</span>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </fieldset>
        <hr />
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <div class="form-group" ng-class="{'has-success' : vm.activeMaintenanceFormula === 'KB'}">
              <div class="input-group">
                <span class="input-group-addon">Link: </span>
                <input type="text" ng-model="vm.shareLink" class="form-control" />
                <span class="input-group-btn">
                  <button clipboard="" supported="vm.clipboardSupported" text="vm.shareLink" on-copied="vm.onClipboardSuccess()" on-error="onClipboardFail(err)" type="button" class="btn btn-toggle" tabindex="-1" title="Click to copy to clipboard">
                    <i class="fa fa-copy"></i>
                  </button>
                </span>
              </div>
            </div>
          </div>
        </div>
      </form>
    </div>
  </body>

</html>
/* Styles go here */

body {
  padding: 15px;
}

.input-label {
  width: 150px;
  text-align: left;
}

.input-macros {
  width: 100px;
  text-align: left;
}

.input-select {
  width: 100%;
}

.btn-toggle {
  color: #A66D3B;
  background-color: #FCF8E3;
  border-color: #8A6D3B;
}
(function() {

  var app = angular.module('app', [
    'ui.router', 
    'ui.bootstrap', 
    'angular-clipboard',
    'ngAnimate',
    'ngSanitize',
    'ngToast'
    ]);

  app.run(['$state', function($state) {
    console.log("app run");
  }]);

  app.config(function() {
    console.log("app config");
  });
  
  app.config(['ngToastProvider', function(ngToastProvider) {
    ngToastProvider.configure({
      animation: 'slide' // or 'fade'
    });
  }]);
  
  app.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', uiRouterConfigurator]);

  function uiRouterConfigurator($stateProvider, $urlRouterProvider, $locationProvider) {
  
    $urlRouterProvider.otherwise('/');
    
    // States
    $stateProvider
      .state('home', {
          url: '/{programId}/{weight}_{weightUnits}',
          //templateUrl: 'test.html',
      })
      // .state('status', {
      //     url: '/status/:statusName',
      //     templateUrl: 'hallo.html',
      // })
    ;
  }

})();
(function() {
  'use strict';
  
  angular.module('app')
    .controller('mainController', MainController);
    
  MainController.$inject = ['$scope', '$log', '$state', '$stateParams', '$location', '$timeout', 'ngToast']; 
    
  function MainController($scope, $log, $state, $stateParams, $location, $timeout, ngToast) {
    
    var _kgToPoundRatio = 2.2046226218;
    var _cmToInRatio = 0.39370;
    var _kinobodyMaintenanceFactor = 15;
    var _selectedProgram = '';
    var _selectedActivity = '';
    var _selectedDeficit = '';
    var _selectedMacroSplit = '';
    var _inputWeight = '';
    var _inputHeight = '';
    var initialized = false;
    var _initTimeout = 3000;
    
    var vm = this;
    
    vm.title = 'ThinkEatLift Calories/Macros!';
    vm.clipboardSupported = false;
    vm.weightKg = '';
    vm.selectedProgramId = 'kinobody_afl';
    vm.inputUnitsWeight = 'kg';
    
    vm.programOptions = [
        { 
          id: 'kinobody_afl',
          name: 'Kinobody AFL',
          description: 'Aggressive Fat Loss',
          lbsFactor: 11,
          fatsDefaultPercentage: 0.30,
          caloriesOffset: 0,
        },
        { 
          id: 'kinobody_wsp',
          name: 'Kinobody WSP',
          description: 'Warrior Shredding Program',
          lbsFactor: 12,
          fatsDefaultPercentage: 0.30,
          caloriesOffset: 0,
        },
        { 
          id: 'kinobody_ggp_lb_rest',
          name: 'Kinobody GGP, Lean Bulk, Rest Days',
          description: 'Greek God Program - Lean Bulk - Rest Days',
          lbsFactor: 15,
          fatsDefaultPercentage: 0.25,
          caloriesOffset: 100,
        },
        { 
          id: 'kinobody_ggp_lb_lift',
          name: 'Kinobody GGP, Lean Bulk, Lifting Days',
          description: 'Greek God Program - Lean Bulk - Lifting Days',
          lbsFactor: 15,
          fatsDefaultPercentage: 0.25,
          caloriesOffset: 500,
        },
        { 
          id: 'kinobody_ggp_rc_rest',
          name: 'Kinobody GGP, Recomp, Rest Days',
          description: 'Greek God Program - Recomp - Rest Days',
          lbsFactor: 15,
          fatsDefaultPercentage: 0.25,
          caloriesOffset: -300,
        },
        { 
          id: 'kinobody_ggp_rc_lift',
          name: 'Kinobody GGP, Recomp, Lifting Days',
          description: 'Greek God Program - Recomp - Lifting Days',
          lbsFactor: 15,
          fatsDefaultPercentage: 0.25,
          caloriesOffset: 400,
        },
        { 
          id: 'kinobody_gtp',
          name: 'Kinobody GTP',
          description: 'Goddess Toning Program',
          lbsFactor: 12,
          fatsDefaultPercentage: 0.30,
          caloriesOffset: 0,
        },
        { 
          id: 'kinobody_gtp_afl10',
          name: 'Kinobody GTP / AFL 10',
          description: 'Goddess Toning Program',
          lbsFactor: 10,
          fatsDefaultPercentage: 0.30,
          caloriesOffset: 0,
        },
        { 
          id: 'kinobody_gtp_afl11',
          name: 'Kinobody GTP / AFL 11',
          description: 'Goddess Toning Program',
          lbsFactor: 11,
          fatsDefaultPercentage: 0.30,
          caloriesOffset: 0,
        },
      ];

    function init() {
      // process state params here
      processStateParams($stateParams);
      
      updateRoute();
      
      $timeout(function() {
        initialized = true;
      }, _initTimeout);
    }
    
    // http://run.plnkr.co/9LEGjB97qU2mTQcg/#/kinobody_afl/74_kg
    function processStateParams(stateParams) {
      // $log.log('processStateParams $location', $location);
      // $log.log('processStateParams stateParams', stateParams);
      // $log.log('processStateParams $state.params', $state.params);
      if(stateParams) {

        // restore program by id:
        if (stateParams.programId) {
          vm.selectedProgramId = stateParams.programId;
        }
        
        // weight / weight units
        if (stateParams.weight && stateParams.weightUnits) {
          var weight = stateParams.weight;
          var units = stateParams.weightUnits;
          
          vm.inputUnitsWeight = (units === 'lbs') ? 'lbs' : 'kg';
          
          if (vm.inputUnitsWeight === 'kg') {
            vm.weightKg = weight;
          } else {
            vm.weightKg = weight / _kgToPoundRatio;
          }
          
          vm.inputWeight = (vm.inputUnitsWeight === 'kg') ? vm.weightKg : (vm.weightKg * _kgToPoundRatio);
        }
      }
      
      // initialize controls
      _selectedProgram = _.find(vm.programOptions, function(item) { return item.id.toUpperCase() === vm.selectedProgramId.toUpperCase(); });
      if(!_selectedProgram) {
        // $log.log('invalid program name');
        _selectedProgram = vm.programOptions[0];
        vm.selectedProgramId = _selectedProgram.programId;
        $timeout(function() {
          updateRoute();
        }, 10);
      }
      
      // INIT: vm.weightKg is always in kg!  
      vm.inputWeight = (vm.inputUnitsWeight === 'kg') ? vm.weightKg : (vm.weightKg * _kgToPoundRatio);
      vm.inputHeight = (vm.inputUnitsHeight === 'cm') ? vm.heightCm : (vm.heightCm * _cmToInRatio);
      
      updateRoute();
    }
    
    Object.defineProperty(vm, "shareLink", {
      get: function() {
        return $location.absUrl();
      }
    });
    
    Object.defineProperty(vm, "programOffset", {
      get: function() {
        if (_selectedProgram) {
          if(_selectedProgram.caloriesOffset && _selectedProgram.caloriesOffset !== 0) {
            if(_selectedProgram.caloriesOffset > 0) {
              return ' + ' + _selectedProgram.caloriesOffset;
            } else {
              return ' - ' + Math.abs(_selectedProgram.caloriesOffset);
            }
          }
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "selectedProgram", {
      get: function() {
        return _selectedProgram;
      },
      set: function(value) {
        _selectedProgram = value;
        vm.selectedProgramId = _selectedProgram.id;
      }
    });
    
    Object.defineProperty(vm, "inputWeight", {
      get: function() {
        return _inputWeight;
      },
      set: function(value) {
        _inputWeight = value;
        vm.weightKg = convertInputWeightToKg(_inputWeight);
      }
    });
    
    Object.defineProperty(vm, "weightLb", {
      get: function() {
        if(vm.weightKg) {
          return vm.weightKg * _kgToPoundRatio;
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateMaintenanceCalories", {
      get: function() {
        if(vm.weightLb) {
          return _kinobodyMaintenanceFactor * vm.weightLb;
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateMaintenanceCalories_display", {
      get: function() {
        if(vm.weightLb) {
          return formatCalNumber(vm.kinobodyEstimateMaintenanceCalories);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateDailyCalories", {
      get: function() {
        if(vm.weightLb) {
          return (vm.selectedProgram.lbsFactor * vm.weightLb) + vm.selectedProgram.caloriesOffset;
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateDailyCalories_display", {
      get: function() {
        if(vm.weightLb) {
          return formatCalNumber(vm.kinobodyEstimateDailyCalories);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "activeMaintenanceFormula", {
      get: function() {
        // => only use KB for now!
        if (vm.kinobodyEstimateMaintenanceCalories) {
          return 'KB';   
        }
        return '';
      }
    });
    
    
    Object.defineProperty(vm, "maintenanceCalories", {
      get: function() {
        if (vm.kinobodyEstimateMaintenanceCalories) {
          return formatCalNumber(vm.kinobodyEstimateMaintenanceCalories);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "deficitCalories", {
      get: function() {
        if(vm.kinobodyEstimateDailyCalories) {
          return formatCalNumber(vm.kinobodyEstimateDailyCalories);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosProtein", {
      get: function() {
        // 2g of protein pr kg / 0.9g per lb
        if(vm.deficitCalories) {
          var proteinGrams = roundToNearestX(vm.weightKg * 2, 5);
          return formatCalNumber(proteinGrams);
        }
        return '';
      }
    });
    
    function roundToNearestX(val, x) {
      return Math.ceil(val/x)*x;
    }
    
    Object.defineProperty(vm, "macrosFats", {
      get: function() {
        // 25%/30% of calories from fat (program dependent)
        if(vm.deficitCalories) {
          var fatsFactor = vm.selectedProgram.fatsDefaultPercentage;
          var fatGrams = vm.deficitCalories * fatsFactor / 9;
          return formatCalNumber(fatGrams);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosCarbs", {
      get: function() {
        // the rest from carbs
        if(vm.deficitCalories) {
          var carbGrams = 0;
          var calsProtein = vm.macrosProtein * 4;
          var calsFats = vm.macrosFats * 9;
          var calsPF = calsProtein + calsFats;
          var calsCarbs = vm.deficitCalories - calsPF;
          carbGrams = calsCarbs / 4;
          return formatCalNumber(carbGrams);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageProtein", {
      get: function() {
        if(vm.macrosProtein) {
          var calsFats = vm.macrosProtein * 4;
          var p = calsFats / vm.deficitCalories * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageFats", {
      get: function() {
        if(vm.macrosFats) {
          var calsFats = vm.macrosFats * 9;
          var p = calsFats / vm.deficitCalories * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageCarbs", {
      get: function() {
        if(vm.macrosCarbs) {
          var calsCarbs = vm.macrosCarbs * 4;
          var p = calsCarbs / vm.deficitCalories * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "crossCheckCalories", {
      get: function() {
        if(vm.macrosProtein && vm.macrosFats && vm.macrosCarbs) {
          var calsProtein = vm.macrosProtein * 4;
          var calsFats = vm.macrosFats * 9;
          var calsCarbs = vm.macrosCarbs * 4;
          return formatCalNumber(calsProtein + calsFats + calsCarbs);
        }
        return '';
      }
    });
    
    vm.formatCalNumber = formatCalNumber;
    vm.toggleInputUnitsWeight = toggleInputUnitsWeight;
    vm.onWeightChange = onWeightChange;
    vm.onProgramChange = onProgramChange;
    
    vm.onClipboardSuccess = onClipboardSuccess;
    vm.onClipboardFail = onClipboardFail;
    
    function onClipboardSuccess() {
      ngToast.create("Address copied to clipboard");
      // $log.log('onClipboardSuccess');
    }
    
    function onClipboardFail(err) {
      $log.log('onClipboardFail', err);
    }
    
    function onWeightChange() {
      updateRoute();
    }
    
    function onProgramChange() {
      updateRoute();
    }
    
    function updateRoute() {
      if(!initialized || vm.inputWeight === '') 
        return;
      
      // $log.log('updateRoute', vm.storage, $location, $location.absUrl());
      var stateOptions = {
        programId: vm.selectedProgram.id,
        weight: vm.inputWeight,
        weightUnits: vm.inputUnitsWeight,
      };
      $state.transitionTo('home', stateOptions, { notify: false });
    }
    
    function formatCalNumber(i) {
      if(i === '') return '';
      return i.toFixed(0);
    }
    
    function toggleInputUnitsWeight() {
      // $log.log('toggleInputUnitsWeight');
      switch(vm.inputUnitsWeight){
        case 'kg':
          // LBS
          vm.inputUnitsWeight = 'lbs';
          vm.inputWeight = vm.inputWeight * _kgToPoundRatio;
          break;
        case 'lbs':
          // KG
          vm.inputUnitsWeight = 'kg';
          vm.inputWeight = vm.inputWeight / _kgToPoundRatio;
          break;
      }
      updateRoute();
    }
    
    function convertInputWeightToKg(inputWeight) {
      if(vm.inputUnitsWeight === 'kg') {
        return inputWeight;
      } else {
        return inputWeight / _kgToPoundRatio;
      }
    }
    
    // state change event handler
    $scope.$on("$stateChangeStart",
      function (event, toState, toParams, fromState, fromParams) {
        // $log.log('$stateChangeStart', $location);
        processStateParams(toParams);
      }
    );
    
    init();
  }
  
})();
Kinobody Calories / Macros 

  - Kinobody easy formulas for maintenance/deficit calculations
  - Creates url that can be forum-posted:
      http://run.plnkr.co/9LEGjB97qU2mTQcg/#/kinobody_ggp_lb_rest/78_kg
      http://run.plnkr.co/9LEGjB97qU2mTQcg/#/kinobody_afl/165_lbs
      http://run.plnkr.co/9LEGjB97qU2mTQcg/#/kinobody_wsp/80_kg
  
Todo:
  - 

  
Useful links for this plunk:

https://github.com/omichelsen/angular-clipboard
http://tamerayd.in/ngToast/

https://www.niclassahlin.com/2014/05/31/kickstarting-angular-ui-router/

http://www.metric-conversions.org/length/centimeters-to-inches.htm
http://www.metric-conversions.org/weight/kilograms-to-pounds.htm
https://github.com/gsklee/ngStorage
https://scotch.io/tutorials/the-many-ways-to-use-ngclass

// fix enter key strange behaviour:
http://stackoverflow.com/a/11591419/54159 : Just add the type="button" attribute to the button element, some browsers interpret the type as submit by default.
http://stackoverflow.com/questions/10400149/avoid-modal-dismiss-on-enter-keypress
(function (root, factory) {
    /* istanbul ignore next */
    if (typeof define === 'function' && define.amd) {
        define(['angular'], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory(require('angular'));
    } else {
        root.angularClipboard = factory(root.angular);
  }
}(this, function (angular) {

return angular.module('angular-clipboard', [])
    .factory('clipboard', ['$document', function ($document) {
        function createNode(text) {
            var node = $document[0].createElement('textarea');
            node.style.position = 'absolute';
            node.style.left = '-10000px';
            node.textContent = text;
            return node;
        }

        function copyNode(node) {
            try {
                // Set inline style to override css styles
                $document[0].body.style.webkitUserSelect = 'initial';

                var selection = $document[0].getSelection();
                selection.removeAllRanges();
                node.select();

                if(!$document[0].execCommand('copy')) {
                    throw('failure copy');
                }
                selection.removeAllRanges();
            } finally {
                // Reset inline style
                $document[0].body.style.webkitUserSelect = '';
            }
        }

        function copyText(text) {
            var node = createNode(text);
            $document[0].body.appendChild(node);
            copyNode(node);
            $document[0].body.removeChild(node);
        }

        return {
            copyText: copyText,
            supported: 'queryCommandSupported' in document && document.queryCommandSupported('copy')
        };
    }])
    .directive('clipboard', ['clipboard', function (clipboard) {
        return {
            restrict: 'A',
            scope: {
                onCopied: '&',
                onError: '&',
                text: '=',
                supported: '='
            },
            link: function (scope, element) {
                scope.supported = clipboard.supported;

                element.on('click', function (event) {
                    try {
                        clipboard.copyText(scope.text);
                        if (angular.isFunction(scope.onCopied)) {
                            scope.$evalAsync(scope.onCopied());
                        }
                    } catch (err) {
                        if (angular.isFunction(scope.onError)) {
                            scope.$evalAsync(scope.onError({err: err}));
                        }
                    }
                });
            }
        };
    }]);

}));