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

  <head>
    <link data-require="bootstrap@*" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
    <link data-require="font-awesome@*" data-semver="4.3.0" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" />
    <script data-require="jquery@*" data-semver="2.1.4" src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
    <script data-require="bootstrap@*" data-semver="3.3.5" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
    <script data-require="angular.js@1.4.3" data-semver="1.4.3" src="https://code.angularjs.org/1.4.3/angular.js"></script>
    <script data-require="ui-router@*" data-semver="0.2.15" src="//rawgit.com/angular-ui/ui-router/0.2.15/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.1.1" src="https://cdn.rawgit.com/angular-ui/bootstrap/gh-pages/ui-bootstrap-1.1.1.js"></script>
    <script src="ngStorage.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">
    <div class="container-fluid">
      <form>
        <div class="row">
          <div class="col-md-12">
            <h4>Estimating Maintenance Calories - the easy way</h4>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6">
            <div class="form-group has-warning">
              <div class="input-group input-select">
                <span class="input-group-addon input-label">Activity</span>
                <select class="form-control" 
                  ng-options="option.name for option in vm.activityOptions" 
                  ng-model="vm.selectedActivity" 
                  title="{{vm.selectedActivity.description}}"></select>
              </div>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6">
            <div class="form-group has-warning">
              <div class="input-group">
                <span class="input-group-addon input-label">Weight in kg</span>
                <input type="text" class="form-control" ng-model="vm.inputWeight" ng-model-options="{debounce: 500}" placeholder="" />
                <!--<span class="input-group-addon">kg</span>-->
                <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.storage.inputUnitsWeight}}
                    &nbsp;<i class="fa fa-toggle-on" ng-class="{'fa-flip-horizontal': vm.storage.inputUnitsWeight !== 'kg'}"></i>
                  </button>
                </span>
              </div>
            </div>
          </div>
          <div class="col-md-6">
            <fieldset disabled="">
              <div class="form-group" ng-class="{'has-success' : vm.activeMaintenanceFormula === 'KB'}">
                <div class="input-group">
                  <span class="input-group-addon input-label">Kinobody formula (factor: {{vm.selectedActivity.kinoFactor}})</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>
        <hr />
        
        <!-- <div class="row">
          <div class="col-md-12">
            <h4>Estimating Maintenance Calories - Mifflin-St Jeor Formula</h4>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6">
            <div class="form-group">
              <div class="input-group">
                <span class="input-group-addon input-label">Height in cm</span>
                <input type="text" class="form-control" ng-model="vm.inputHeight" ng-model-options="{debounce: 500}" placeholder="" />
                <span class="input-group-addon">cm</span>
                <span class="input-group-btn">
                  <button type="button" class="btn btn-toggle" ng-click="vm.toggleInputUnitsHeight()" tabindex="-1" title="Click to toggle cm/inches">
                    {{vm.storage.inputUnitsHeight}}
                    &nbsp;<i class="fa fa-toggle-on" ng-class="{'fa-flip-horizontal': vm.storage.inputUnitsHeight !== 'cm'}"></i>
                  </button>
                </span>
              </div>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6">
            <div class="form-group">
              <div class="input-group">
                <span class="input-group-addon input-label">Age in years</span>
                <input type="text" class="form-control" ng-model="vm.storage.ageYears" ng-model-options="{debounce: 500}" placeholder="" />
                <span class="input-group-addon">years</span>
              </div>
            </div>
            <div class="form-group">
              <div class="btn-group btn-group-justified">
                <label class="btn btn-default" ng-model="vm.storage.gender" uib-btn-radio="'male'">
                  <i class="fa fa-fw fa-male"></i> male
                </label>
                <label class="btn btn-default" ng-model="vm.storage.gender" uib-btn-radio="'female'">
                  <i class="fa fa-fw fa-female"></i> female
                </label>
              </div>
            </div>
          </div>
          <div class="col-md-6">
            <fieldset disabled="">
              <div class="form-group">
                <div class="input-group">
                  <span class="input-group-addon input-label">Mifflin-St Jeor BMR</span>
                  <input type="text" ng-model="vm.bmr_display" class="form-control text-right" placeholder="Required: weight, gender, height, age" readonly="" />
                  <span class="input-group-addon">kcal</span>
                </div>
              </div>
              <div class="form-group" ng-class="{'has-success' : vm.activeMaintenanceFormula === 'TEE'}">
                <div class="input-group">
                  <span class="input-group-addon input-label">Total Energy Expenditure TEE</span>
                  <input type="text" ng-model="vm.tee_display" id="tee" class="form-control text-right" placeholder="Required: BMR, activity" readonly/>
                  <span class="input-group-addon">kcal</span>
                </div>
              </div>
            </fieldset>
          </div>
        </div>
        <hr /> -->
        
        <div class="row">
          <div class="col-md-12">
            <h4>Set your goal</h4>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6">
            <div class="form-group has-warning">
              <div class="input-group input-select">
                <span class="input-group-addon input-label">Your goal</span>
                <select class="form-control" 
                  ng-options="option.name for option in vm.deficitOptions" 
                  ng-model="vm.selectedDeficit"></select>
              </div>
            </div>
          </div>
          <div class="col-md-6">
            <fieldset disabled="">
              <div class="form-group has-success">
                <div class="input-group">
                  <span class="input-group-addon input-label">Maintenance</span>
                  <input type="text" ng-model="vm.maintenanceCalories" id="maintenanceCalories" class="form-control text-right" placeholder="" readonly="" />
                  <span class="input-group-addon">kcal</span>
                </div>
              </div>
              <div class="form-group has-success">
                <div class="input-group">
                  <span class="input-group-addon input-label">Daily calories ({{vm.selectedDeficit.goal}})</span>
                  <input type="text" ng-model="vm.deficitCalories" id="deficitCalories" class="form-control text-right" placeholder="" 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>
        <div class="row">
          <div class="col-md-6">
            <div class="form-group">
              <div class="input-group input-select">
                <span class="input-group-addon input-label">Macros split</span>
                <select class="form-control" 
                  ng-options="option.name for option in vm.macroSplitOptions" 
                  ng-model="vm.selectedMacroSplit"></select>
              </div>
            </div>
          </div>
          <div class="col-md-6">
            <fieldset disabled="">
              <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 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 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>
            </fieldset>
          </div>
        </div>
        <hr />
        
        <div class="row">
          <div class="col-md-12">
            <div class="form-group">
              <button type="button" class="btn btn-default" ng-click="vm.resetStorage()" tabindex="-1">
                <i class="fa fa-fw fa-remove"></i>
                Clear my data
              </button>
            </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', 
    'ngStorage',
    ]);

  app.run(function() {
    console.log("app run");
  });

  //app.config(function($stateProvider, $urlRouterProvider) {
  app.config(function() {
    console.log("app config");
    
    //$urlRouterProvider.otherwise('/home');
  });

})();
(function() {
  'use strict';
  
  angular.module('app')
    .controller('mainController', MainController);
    
  MainController.$inject = ['$localStorage', '$log']; 
    
  function MainController($localStorage, $log) {
    
    var _kgToPoundRatio = 2.2046226218;
    var _cmToInRatio = 0.39370;
    var _selectedActivity = '';
    var _selectedDeficit = '';
    var _selectedMacroSplit = '';
    var _inputWeight = '';
    
    var vm = this;
    
    vm.title = 'ThinkEatLift Calories/Macros!';

    vm.activityOptions = [
      {value: '1.2', name: 'Sedentary', kinoFactor: 13, description: 'only weight traing at the gym and sedentary the rest of the time'},
      {value: '1.35', name: 'Lightly active (fits most people)', kinoFactor: 15, description: 'mostly sedentary, one hour of activity every day + weight training 3-4 times a week'},
      {value: '1.55', name: 'Active', kinoFactor: 15, description: 'walking or cycling every day + weight training 3-4 times a week'},
      {value: '1.75', name: 'Very active', kinoFactor: 16, description: 'manual labour + weight training and sports 4-5 times a week'}
    ];
    
    vm.deficitOptions = [
      {value: '0.9', name: 'Light Cut (-10%)', proteinKgFactor: 2, goal: 'cut'},
      {value: '0.8', name: 'Normal Cut (-20%)', proteinKgFactor: 2, goal: 'cut'},
      {value: '0.75', name: 'Maximum Cut (-25%)', proteinKgFactor: 2, goal: 'cut'},
      {value: '1.0', name: 'Maintenance', proteinKgFactor: 2, goal: 'maintain'},
      {value: '1.1', name: 'Lean Bulk (+10%)', proteinKgFactor: 2.2, goal: 'lean bulk'},
    ];
    
    vm.macroSplitOptions = [
      {
        id: 'kb_ggp_f20', 
        name: 'Kinobody GGP / Radu / 20% fats', 
        fatFactor: 0.2,
      },
      {
        id: 'kb_ggp_f25', 
        name: 'Kinobody GGP / Radu / 25% fats (default)', 
        fatFactor: 0.25,
        getProteinGrams: function(weight, gender, availableCalories){
          return weight * vm.selectedDeficit.proteinKgFactor;
        },
        getFatGrams: function(weight, gender, availableCalories){
          return 0.25 * availableCalories / 9;
        },
      },
      {
        id: 'kb_ggp_f30', 
        name: 'Kinobody GGP / Radu / 30% fats', 
        fatFactor: 0.30
        
      },
      {
        id: 'kb_afl', 
        name: 'Kinobody AFL (p: 35%, f: 30%, c: 35%)', 
        fatFactor: 0.30,
        getProteinGrams: function(weight, gender, availableCalories) {
          return 0.35 * availableCalories / 4;
        },
        getFatGrams: function(weight, gender, availableCalories) {
          return 0.30 * availableCalories / 9;
        },
        // getCarbGrams: function(weight, gender, availableCalories) {
        //   return 0.35 * availableCalories / 4;
        // },
      },
      {
        id: 'kb_gtp', 
        name: 'Kinobody GTP (p: 35%, f: 25%, c: 40%)', 
        fatFactor: 0.30,
        getProteinGrams: function(weight, gender, availableCalories) {
          return 0.35 * availableCalories / 4;
        },
        getFatGrams: function(weight, gender, availableCalories) {
          return 0.25 * availableCalories / 9;
        },
        // getCarbGrams: function(weight, gender, availableCalories) {
        //   return 0.40 * availableCalories / 4;
        // },
      },
      // {
      //   id: 'test_f10', 
      //   name: '[Test] Low fat (10%)', 
      //   fatFactor: 0.10
        
      // },
    ];
    
    function init() {
      vm.storage = $localStorage.$default({
        weightKg: '',
        selectedActivityIndex: 1,
        selectedDeficitIndex: 1,
        selectedMacroSplitIndex: 1,
        inputUnitsWeight: 'kg',
      });
      
      _selectedActivity = vm.activityOptions[vm.storage.selectedActivityIndex];
      _selectedDeficit = vm.deficitOptions[vm.storage.selectedDeficitIndex];
      _selectedMacroSplit = vm.macroSplitOptions[vm.storage.selectedMacroSplitIndex];
      
      // INIT: vm.storage.weightKg is always in kg!  
      vm.inputWeight = (vm.storage.inputUnitsWeight === 'kg') ? vm.storage.weightKg : (vm.storage.weightKg * _kgToPoundRatio);
    }
    
    Object.defineProperty(vm, "selectedActivity", {
      get: function() {
        return _selectedActivity;
      },
      set: function(value) {
        _selectedActivity = value;
        var index = _.indexOf(vm.activityOptions, value);
        vm.storage.selectedActivityIndex = index;
      }
    });
    
    Object.defineProperty(vm, "selectedDeficit", {
      get: function() {
        return _selectedDeficit;
      },
      set: function(value) {
        _selectedDeficit = value;
        var index = _.indexOf(vm.deficitOptions, value);
        vm.storage.selectedDeficitIndex = index;
      }
    });
    
    Object.defineProperty(vm, "selectedMacroSplit", {
      get: function() {
        return _selectedMacroSplit;
      },
      set: function(value) {
        _selectedMacroSplit = value;
        var index = _.indexOf(vm.macroSplitOptions, value);
        vm.storage.selectedMacroSplitIndex = index;
      }
    });
    
    Object.defineProperty(vm, "inputWeight", {
      get: function() {
        return _inputWeight;
      },
      set: function(value) {
        _inputWeight = value;
        vm.storage.weightKg = convertInputWeightToKg(_inputWeight);
      }
    });
    
    Object.defineProperty(vm, "weightLb", {
      get: function() {
        if(vm.storage.weightKg) {
          return vm.storage.weightKg * _kgToPoundRatio;
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateMaintenanceCalories", {
      get: function() {
        if(vm.weightLb) {
          return vm.selectedActivity.kinoFactor * vm.weightLb;
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateMaintenanceCalories_display", {
      get: function() {
        if(vm.weightLb) {
          return formatCalNumber(vm.kinobodyEstimateMaintenanceCalories);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "activeMaintenanceFormula", {
      get: function() {
        if(vm.kinobodyEstimateMaintenanceCalories) {
            return 'KB'; // only KB estimate available -> use KB (good estimate)
        }
        return '';
      }
    });
    
    
    Object.defineProperty(vm, "maintenanceCalories", {
      get: function() {
        if(vm.kinobodyEstimateMaintenanceCalories) {
          return formatCalNumber(vm.kinobodyEstimateMaintenanceCalories);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "deficitCalories", {
      get: function() {
        if(vm.maintenanceCalories) {
          return formatCalNumber(vm.maintenanceCalories * vm.selectedDeficit.value);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosProtein", {
      get: function() {
        // 2g of protein pr kg / 0.9g per lb
        if(vm.deficitCalories) {
          var proteinGrams = 0;
          if(vm.selectedMacroSplit.getProteinGrams) {
            proteinGrams = vm.selectedMacroSplit.getProteinGrams(vm.storage.weightKg, vm.storage.gender, vm.deficitCalories);
          } else {
            proteinGrams = vm.storage.weightKg * vm.selectedDeficit.proteinKgFactor;
          }
          return formatCalNumber(proteinGrams);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosFats", {
      get: function() {
        // 25% of calories from fat
        if(vm.deficitCalories) {
          var fatGrams= 0;
          if(vm.selectedMacroSplit.getFatGrams) {
            fatGrams = vm.selectedMacroSplit.getFatGrams(vm.storage.weightKg, vm.storage.gender, vm.deficitCalories);
          } else { 
            var fatsFactor = vm.selectedMacroSplit.fatFactor;
            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;
          if(vm.selectedMacroSplit.getCarbGrams) {
            carbGrams = vm.selectedMacroSplit.getCarbGrams(vm.storage.weightKg, vm.storage.gender, vm.deficitCalories);
          } else { 
            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.onActivityChange = onActivityChange;
    //vm.onDeficitChange = onDeficitChange;
    vm.resetStorage = resetStorage;
    vm.toggleInputUnitsWeight = toggleInputUnitsWeight;
    
    function formatCalNumber(i) {
      if(i === '') return '';
      return i.toFixed(0);
    }
    
    /*
    function onActivityChange() {
      var index = _.indexOf(vm.activityOptions, vm.selectedActivity);
      vm.storage.selectedActivityIndex = index;
    }
    
    function onDeficitChange() {
      var index = _.indexOf(vm.deficitOptions, vm.selectedDeficit);
      vm.storage.selectedDeficitIndex = index;
    }
    */
    
    function resetStorage() {
      $localStorage.$reset();
      init();
    }
    
    function toggleInputUnitsWeight() {
      $log.log('toggleInputUnitsWeight');
      switch(vm.storage.inputUnitsWeight){
        case 'kg':
          // LBS
          vm.storage.inputUnitsWeight = 'lbs';
          vm.inputWeight = vm.inputWeight * _kgToPoundRatio;
          break;
        case 'lbs':
          // KG
          vm.storage.inputUnitsWeight = 'kg';
          vm.inputWeight = vm.inputWeight / _kgToPoundRatio;
          break;
      }
    }
    
    function convertInputWeightToKg(inputWeight) {
      if(vm.storage.inputUnitsWeight === 'kg') {
        return inputWeight;
      } else {
        return inputWeight / _kgToPoundRatio;
      }
    }
    
    init();
  }
  
})();
ThinkEatLift Calculations:
  
  - Maintenance the easy way (Kinobody formula)
  - Cut/Maintenance/Lean bulk selection
  - Macro split
  
Todo:
  - Move macro split selection to top
  - Combine macro split with activity (?)
  - Show lean bulk workout/rest days kcals (options?) / visualize using amCharts?
  
(function (root, factory) {
  'use strict';

  if (typeof define === 'function' && define.amd) {
    define(['angular'], factory);
  } else if (root.hasOwnProperty('angular')) {
    // Browser globals (root is window), we don't register it.
    factory(root.angular);
  } else if (typeof exports === 'object') {
    module.exports = factory(require('angular'));
  }
}(this , function (angular) {
    'use strict';

    // In cases where Angular does not get passed or angular is a truthy value
    // but misses .module we can fall back to using window.
    angular = (angular && angular.module ) ? angular : window.angular;


    function isStorageSupported($window, storageType) {

      // Some installations of IE, for an unknown reason, throw "SCRIPT5: Error: Access is denied"
      // when accessing window.localStorage. This happens before you try to do anything with it. Catch
      // that error and allow execution to continue.

      // fix 'SecurityError: DOM Exception 18' exception in Desktop Safari, Mobile Safari
      // when "Block cookies": "Always block" is turned on
      var supported;
      try {
        supported = $window[storageType];
      }
      catch(err) {
        supported = false;
      }

      // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage
      // is available, but trying to call .setItem throws an exception below:
      // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota."
      if(supported && storageType === 'localStorage') {
        var key = '__' + Math.round(Math.random() * 1e7);

        try {
          localStorage.setItem(key, key);
          localStorage.removeItem(key);
        }
        catch(err) {
          supported = false;
        }
      }

      return supported;
    }

    /**
     * @ngdoc overview
     * @name ngStorage
     */

    return angular.module('ngStorage', [])

    /**
     * @ngdoc object
     * @name ngStorage.$localStorage
     * @requires $rootScope
     * @requires $window
     */

    .provider('$localStorage', _storageProvider('localStorage'))

    /**
     * @ngdoc object
     * @name ngStorage.$sessionStorage
     * @requires $rootScope
     * @requires $window
     */

    .provider('$sessionStorage', _storageProvider('sessionStorage'));

    function _storageProvider(storageType) {
        var providerWebStorage = isStorageSupported(window, storageType);

        return function () {
          var storageKeyPrefix = 'ngStorage-';

          this.setKeyPrefix = function (prefix) {
            if (typeof prefix !== 'string') {
              throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setKeyPrefix() expects a String.');
            }
            storageKeyPrefix = prefix;
          };

          var serializer = angular.toJson;
          var deserializer = angular.fromJson;

          this.setSerializer = function (s) {
            if (typeof s !== 'function') {
              throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setSerializer expects a function.');
            }

            serializer = s;
          };

          this.setDeserializer = function (d) {
            if (typeof d !== 'function') {
              throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setDeserializer expects a function.');
            }

            deserializer = d;
          };

          this.supported = function() {
            return !!providerWebStorage;
          };

          // Note: This is not very elegant at all.
          this.get = function (key) {
            return providerWebStorage && deserializer(providerWebStorage.getItem(storageKeyPrefix + key));
          };

          // Note: This is not very elegant at all.
          this.set = function (key, value) {
            return providerWebStorage && providerWebStorage.setItem(storageKeyPrefix + key, serializer(value));
          };

          this.$get = [
              '$rootScope',
              '$window',
              '$log',
              '$timeout',
              '$document',

              function(
                  $rootScope,
                  $window,
                  $log,
                  $timeout,
                  $document
              ){

                // The magic number 10 is used which only works for some keyPrefixes...
                // See https://github.com/gsklee/ngStorage/issues/137
                var prefixLength = storageKeyPrefix.length;

                // #9: Assign a placeholder object if Web Storage is unavailable to prevent breaking the entire AngularJS app
                // Note: recheck mainly for testing (so we can use $window[storageType] rather than window[storageType])
                var isSupported = isStorageSupported($window, storageType),
                    webStorage = isSupported || ($log.warn('This browser does not support Web Storage!'), {setItem: angular.noop, getItem: angular.noop, removeItem: angular.noop}),
                    $storage = {
                        $default: function(items) {
                            for (var k in items) {
                                angular.isDefined($storage[k]) || ($storage[k] = angular.copy(items[k]) );
                            }

                            $storage.$sync();
                            return $storage;
                        },
                        $reset: function(items) {
                            for (var k in $storage) {
                                '$' === k[0] || (delete $storage[k] && webStorage.removeItem(storageKeyPrefix + k));
                            }

                            return $storage.$default(items);
                        },
                        $sync: function () {
                            for (var i = 0, l = webStorage.length, k; i < l; i++) {
                                // #8, #10: `webStorage.key(i)` may be an empty string (or throw an exception in IE9 if `webStorage` is empty)
                                (k = webStorage.key(i)) && storageKeyPrefix === k.slice(0, prefixLength) && ($storage[k.slice(prefixLength)] = deserializer(webStorage.getItem(k)));
                            }
                        },
                        $apply: function() {
                            var temp$storage;

                            _debounce = null;

                            if (!angular.equals($storage, _last$storage)) {
                                temp$storage = angular.copy(_last$storage);
                                angular.forEach($storage, function(v, k) {
                                    if (angular.isDefined(v) && '$' !== k[0]) {
                                        webStorage.setItem(storageKeyPrefix + k, serializer(v));
                                        delete temp$storage[k];
                                    }
                                });

                                for (var k in temp$storage) {
                                    webStorage.removeItem(storageKeyPrefix + k);
                                }

                                _last$storage = angular.copy($storage);
                            }
                        },
                        $supported: function() {
                            return !!isSupported;
                        }
                    },
                    _last$storage,
                    _debounce;

                $storage.$sync();

                _last$storage = angular.copy($storage);

                $rootScope.$watch(function() {
                    _debounce || (_debounce = $timeout($storage.$apply, 100, false));
                });

                // #6: Use `$window.addEventListener` instead of `angular.element` to avoid the jQuery-specific `event.originalEvent`
                $window.addEventListener && $window.addEventListener('storage', function(event) {
                    if (!event.key) {
                      return;
                    }

                    // Reference doc.
                    var doc = $document[0];

                    if ( (!doc.hasFocus || !doc.hasFocus()) && storageKeyPrefix === event.key.slice(0, prefixLength) ) {
                        event.newValue ? $storage[event.key.slice(prefixLength)] = deserializer(event.newValue) : delete $storage[event.key.slice(prefixLength)];

                        _last$storage = angular.copy($storage);

                        $rootScope.$apply();
                    }
                });

                $window.addEventListener && $window.addEventListener('beforeunload', function() {
                    $storage.$apply();
                });

                return $storage;
              }
          ];
      };
    }

}));
Useful links for this plunk:

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