<!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}}
<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}}
<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