Update build.js to actually use iframe.html as source of truth; data-theem and data-scale not working
This commit is contained in:
parent
19416c7592
commit
f0666c247b
382
build.js
382
build.js
@ -100,340 +100,43 @@ function transformHTMLForWidget(html) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create production-ready widget JavaScript
|
* Create production-ready widget JavaScript that uses the ACTUAL extracted JS from iframe.html
|
||||||
*/
|
*/
|
||||||
function createWidgetJS(css, html, js) {
|
function createWidgetJS(css, html, js) {
|
||||||
// Use original CSS and HTML without transformation for consistency
|
// Transform the extracted JavaScript from iframe.html to work as a widget
|
||||||
const widgetCSS = css;
|
|
||||||
const widgetHTML = html;
|
|
||||||
|
|
||||||
const widgetCode = `/**
|
// Replace the iframe's DOMContentLoaded listener with widget initialization
|
||||||
* Dog Calorie Calculator Widget
|
let transformedJS = js
|
||||||
* Embeddable JavaScript widget for websites
|
// Replace the iframe class name with widget class name
|
||||||
*
|
.replace(/class DogCalorieCalculator/g, 'class DogCalorieCalculatorWidget')
|
||||||
* Usage:
|
// Replace document.getElementById with scoped selectors within the widget
|
||||||
* <script src="sundog-dog-food-calculator.js"></script>
|
.replace(/document\.getElementById\('([^']+)'\)/g, 'this.container.querySelector(\'#$1\')')
|
||||||
* <div id="dog-calorie-calculator"></div>
|
// Replace direct document queries in the class with container-scoped queries
|
||||||
*
|
.replace(/document\.querySelector\(/g, 'this.container.querySelector(')
|
||||||
* Or with options:
|
.replace(/document\.querySelectorAll\(/g, 'this.container.querySelectorAll(')
|
||||||
* <div id="dog-calorie-calculator" data-theme="dark" data-scale="1.2"></div>
|
// Remove the DOMContentLoaded listener and class instantiation - we'll handle this in the widget wrapper
|
||||||
*
|
.replace(/document\.addEventListener\('DOMContentLoaded'.*?\n.*?new DogCalorieCalculator.*?\n.*?\}\);/s, '')
|
||||||
* By Canine Nutrition and Wellness
|
// Add widget initialization methods
|
||||||
* https://caninenutritionandwellness.com
|
.replace(/constructor\(\) \{/, `constructor(container, options = {}) {
|
||||||
*/
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Inject widget styles with proper namespacing
|
|
||||||
const CSS_STYLES = \`${widgetCSS}\`;
|
|
||||||
|
|
||||||
function injectStyles() {
|
|
||||||
if (document.getElementById('dog-calculator-styles')) return;
|
|
||||||
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.id = 'dog-calculator-styles';
|
|
||||||
style.textContent = CSS_STYLES;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main widget class
|
|
||||||
class DogCalorieCalculatorWidget {
|
|
||||||
constructor(container, options = {}) {
|
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.options = {
|
this.options = {
|
||||||
theme: options.theme || this.getThemeFromURL() || 'system',
|
theme: options.theme || this.getThemeFromURL() || 'system',
|
||||||
scale: options.scale || this.getScaleFromURL() || 1.0,
|
scale: options.scale || this.getScaleFromURL() || 1.0,
|
||||||
...options
|
...options
|
||||||
};
|
};`)
|
||||||
this.init();
|
// Replace the init() method to inject HTML and apply widget settings
|
||||||
}
|
.replace(/init\(\) \{/, `init() {
|
||||||
|
// Inject the calculator HTML into the container
|
||||||
init() {
|
this.container.innerHTML = \`${html}\`;
|
||||||
// Insert the transformed calculator HTML
|
|
||||||
this.container.innerHTML = \`${widgetHTML}\`;
|
|
||||||
|
|
||||||
// Apply widget-specific settings
|
// Apply widget-specific settings
|
||||||
this.applyTheme();
|
this.applyTheme();
|
||||||
this.applyScale();
|
this.applyScale();
|
||||||
|
|
||||||
// Initialize the calculator with the same functionality as iframe
|
// Continue with original init logic`);
|
||||||
this.initCalculator();
|
|
||||||
}
|
// Add widget-specific methods before the class closing brace
|
||||||
|
transformedJS = transformedJS.replace(/(\s+)(\}\s*$)/, `$1
|
||||||
initCalculator() {
|
|
||||||
const container = this.container;
|
|
||||||
|
|
||||||
// Helper functions to scope DOM queries to this widget
|
|
||||||
const $ = (selector) => container.querySelector(selector);
|
|
||||||
const $$ = (selector) => container.querySelectorAll(selector);
|
|
||||||
|
|
||||||
// Create calculator instance with exact same logic as iframe
|
|
||||||
const calc = {
|
|
||||||
currentMER: 0,
|
|
||||||
isImperial: false,
|
|
||||||
theme: this.options.theme,
|
|
||||||
scale: this.options.scale,
|
|
||||||
|
|
||||||
// Exact same calculation methods from iframe
|
|
||||||
calculateRER: (weightKg) => 70 * Math.pow(weightKg, 0.75),
|
|
||||||
calculateMER: (rer, factor) => rer * factor,
|
|
||||||
|
|
||||||
formatNumber: (num, decimals = 0) => {
|
|
||||||
if (decimals === 0) return Math.round(num).toString();
|
|
||||||
return num.toFixed(decimals).replace(/\\.?0+$/, '');
|
|
||||||
},
|
|
||||||
|
|
||||||
validateInput: (value, min = 0, isInteger = false) => {
|
|
||||||
const num = parseFloat(value);
|
|
||||||
if (isNaN(num) || num < min) return false;
|
|
||||||
if (isInteger && !Number.isInteger(num)) return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
convertUnits: (grams, unit) => {
|
|
||||||
switch (unit) {
|
|
||||||
case 'kg': return grams / 1000;
|
|
||||||
case 'oz': return grams / 28.3495;
|
|
||||||
case 'lb': return grams / 453.592;
|
|
||||||
default: return grams;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getWeightInKg: () => {
|
|
||||||
const weightInput = $('#weight');
|
|
||||||
if (!weightInput || !weightInput.value) return null;
|
|
||||||
const weight = parseFloat(weightInput.value);
|
|
||||||
if (isNaN(weight)) return null;
|
|
||||||
return calc.isImperial ? weight / 2.20462 : weight;
|
|
||||||
},
|
|
||||||
|
|
||||||
getFoodEnergyPer100g: () => {
|
|
||||||
const foodEnergyInput = $('#foodEnergy');
|
|
||||||
const energyUnitSelect = $('#energyUnit');
|
|
||||||
if (!foodEnergyInput || !foodEnergyInput.value || !energyUnitSelect) return null;
|
|
||||||
|
|
||||||
const energy = parseFloat(foodEnergyInput.value);
|
|
||||||
if (isNaN(energy)) return null;
|
|
||||||
|
|
||||||
const unit = energyUnitSelect.value;
|
|
||||||
switch (unit) {
|
|
||||||
case 'kcal100g': return energy;
|
|
||||||
case 'kcalkg': return energy / 10;
|
|
||||||
case 'kcalcup': return energy / 1.2;
|
|
||||||
case 'kcalcan': return energy / 4.5;
|
|
||||||
default: return energy;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showError: (elementId, show = true) => {
|
|
||||||
const errorElement = $('#' + elementId);
|
|
||||||
if (errorElement) {
|
|
||||||
if (show) {
|
|
||||||
errorElement.classList.remove('dog-calculator-hidden');
|
|
||||||
} else {
|
|
||||||
errorElement.classList.add('dog-calculator-hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateCalorieCalculations: () => {
|
|
||||||
const dogTypeSelect = $('#dogType');
|
|
||||||
const calorieResults = $('#calorieResults');
|
|
||||||
const rerValue = $('#rerValue');
|
|
||||||
const merValue = $('#merValue');
|
|
||||||
|
|
||||||
if (!dogTypeSelect || !calorieResults || !rerValue || !merValue) return;
|
|
||||||
|
|
||||||
const weightKg = calc.getWeightInKg();
|
|
||||||
const dogTypeFactor = dogTypeSelect.value;
|
|
||||||
|
|
||||||
calc.showError('weightError', false);
|
|
||||||
|
|
||||||
if (!weightKg || weightKg < 0.1) {
|
|
||||||
const weightInput = $('#weight');
|
|
||||||
if (weightInput && weightInput.value) calc.showError('weightError', true);
|
|
||||||
calorieResults.style.display = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dogTypeFactor) {
|
|
||||||
calorieResults.style.display = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const factor = parseFloat(dogTypeFactor);
|
|
||||||
const rer = calc.calculateRER(weightKg);
|
|
||||||
const mer = calc.calculateMER(rer, factor);
|
|
||||||
|
|
||||||
calc.currentMER = mer;
|
|
||||||
|
|
||||||
rerValue.textContent = calc.formatNumber(rer, 0) + ' cal/day';
|
|
||||||
merValue.textContent = calc.formatNumber(mer, 0) + ' cal/day';
|
|
||||||
calorieResults.style.display = 'block';
|
|
||||||
|
|
||||||
calc.updateFoodCalculations();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFoodCalculations: () => {
|
|
||||||
if (calc.currentMER === 0) return;
|
|
||||||
|
|
||||||
const daysInput = $('#days');
|
|
||||||
const unitSelect = $('#unit');
|
|
||||||
const dailyFoodResults = $('#dailyFoodResults');
|
|
||||||
const dailyFoodValue = $('#dailyFoodValue');
|
|
||||||
const totalFoodDisplay = $('#totalFoodDisplay');
|
|
||||||
|
|
||||||
if (!daysInput || !unitSelect || !dailyFoodResults || !dailyFoodValue || !totalFoodDisplay) return;
|
|
||||||
|
|
||||||
const energyPer100g = calc.getFoodEnergyPer100g();
|
|
||||||
const days = daysInput.value;
|
|
||||||
const unit = unitSelect.value;
|
|
||||||
|
|
||||||
calc.showError('foodEnergyError', false);
|
|
||||||
calc.showError('daysError', false);
|
|
||||||
|
|
||||||
if (!energyPer100g || energyPer100g < 0.1) {
|
|
||||||
const foodEnergyInput = $('#foodEnergy');
|
|
||||||
if (foodEnergyInput && foodEnergyInput.value) calc.showError('foodEnergyError', true);
|
|
||||||
dailyFoodResults.style.display = 'none';
|
|
||||||
totalFoodDisplay.value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!days || !calc.validateInput(days, 1, true)) {
|
|
||||||
if (days) calc.showError('daysError', true);
|
|
||||||
totalFoodDisplay.value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const numDays = parseInt(days);
|
|
||||||
const dailyFoodGrams = (calc.currentMER / energyPer100g) * 100;
|
|
||||||
const totalFoodGrams = dailyFoodGrams * numDays;
|
|
||||||
|
|
||||||
dailyFoodValue.textContent = calc.formatNumber(dailyFoodGrams, 1) + ' g/day';
|
|
||||||
dailyFoodResults.style.display = 'block';
|
|
||||||
|
|
||||||
const convertedAmount = calc.convertUnits(totalFoodGrams, unit);
|
|
||||||
const unitLabel = unit === 'g' ? 'g' : unit === 'kg' ? 'kg' : unit === 'oz' ? 'oz' : 'lb';
|
|
||||||
const decimals = unit === 'g' ? 0 : unit === 'kg' ? 2 : 1;
|
|
||||||
|
|
||||||
totalFoodDisplay.value = calc.formatNumber(convertedAmount, decimals) + ' ' + unitLabel;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Unit switching methods (missing from previous version!)
|
|
||||||
toggleUnits: () => {
|
|
||||||
const toggle = $('#unitToggle');
|
|
||||||
calc.isImperial = toggle.checked;
|
|
||||||
|
|
||||||
calc.updateUnitLabels();
|
|
||||||
calc.convertExistingValues();
|
|
||||||
calc.updateCalorieCalculations();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateUnitLabels: () => {
|
|
||||||
const metricLabel = $('#metricLabel');
|
|
||||||
const imperialLabel = $('#imperialLabel');
|
|
||||||
const weightLabel = $('#weightLabel');
|
|
||||||
const weightInput = $('#weight');
|
|
||||||
const unitSelect = $('#unit');
|
|
||||||
const energyUnitSelect = $('#energyUnit');
|
|
||||||
|
|
||||||
if (metricLabel && imperialLabel) {
|
|
||||||
metricLabel.classList.toggle('active', !calc.isImperial);
|
|
||||||
imperialLabel.classList.toggle('active', calc.isImperial);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (calc.isImperial) {
|
|
||||||
if (weightLabel) weightLabel.textContent = "Dog's Weight (lbs):";
|
|
||||||
if (weightInput) {
|
|
||||||
weightInput.placeholder = "Enter weight in lbs";
|
|
||||||
weightInput.min = "0.2";
|
|
||||||
weightInput.step = "0.1";
|
|
||||||
}
|
|
||||||
if (unitSelect) {
|
|
||||||
unitSelect.innerHTML = '<option value="oz">ounces (oz)</option>' +
|
|
||||||
'<option value="lb">pounds (lb)</option>' +
|
|
||||||
'<option value="g">grams (g)</option>' +
|
|
||||||
'<option value="kg">kilograms (kg)</option>';
|
|
||||||
}
|
|
||||||
// Set energy unit to kcal/cup for imperial
|
|
||||||
if (energyUnitSelect && energyUnitSelect.value === 'kcal100g') {
|
|
||||||
energyUnitSelect.value = 'kcalcup';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (weightLabel) weightLabel.textContent = "Dog's Weight (kg):";
|
|
||||||
if (weightInput) {
|
|
||||||
weightInput.placeholder = "Enter weight in kg";
|
|
||||||
weightInput.min = "0.1";
|
|
||||||
weightInput.step = "0.1";
|
|
||||||
}
|
|
||||||
if (unitSelect) {
|
|
||||||
unitSelect.innerHTML = '<option value="g">grams (g)</option>' +
|
|
||||||
'<option value="kg">kilograms (kg)</option>' +
|
|
||||||
'<option value="oz">ounces (oz)</option>' +
|
|
||||||
'<option value="lb">pounds (lb)</option>';
|
|
||||||
}
|
|
||||||
// Set energy unit to kcal/100g for metric
|
|
||||||
if (energyUnitSelect && energyUnitSelect.value === 'kcalcup') {
|
|
||||||
energyUnitSelect.value = 'kcal100g';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
convertExistingValues: () => {
|
|
||||||
const weightInput = $('#weight');
|
|
||||||
|
|
||||||
if (weightInput && weightInput.value) {
|
|
||||||
const currentWeight = parseFloat(weightInput.value);
|
|
||||||
if (!isNaN(currentWeight)) {
|
|
||||||
if (calc.isImperial) {
|
|
||||||
weightInput.value = calc.formatNumber(currentWeight * 2.20462, 1);
|
|
||||||
} else {
|
|
||||||
weightInput.value = calc.formatNumber(currentWeight / 2.20462, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bindEvents: () => {
|
|
||||||
const weightInput = $('#weight');
|
|
||||||
const dogTypeSelect = $('#dogType');
|
|
||||||
const foodEnergyInput = $('#foodEnergy');
|
|
||||||
const daysInput = $('#days');
|
|
||||||
const unitSelect = $('#unit');
|
|
||||||
const energyUnitSelect = $('#energyUnit');
|
|
||||||
const unitToggle = $('#unitToggle');
|
|
||||||
|
|
||||||
if (weightInput) {
|
|
||||||
weightInput.addEventListener('input', () => calc.updateCalorieCalculations());
|
|
||||||
}
|
|
||||||
if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => calc.updateCalorieCalculations());
|
|
||||||
if (foodEnergyInput) {
|
|
||||||
foodEnergyInput.addEventListener('input', () => calc.updateFoodCalculations());
|
|
||||||
}
|
|
||||||
if (energyUnitSelect) energyUnitSelect.addEventListener('change', () => calc.updateFoodCalculations());
|
|
||||||
if (daysInput) {
|
|
||||||
daysInput.addEventListener('input', () => calc.updateFoodCalculations());
|
|
||||||
}
|
|
||||||
if (unitSelect) unitSelect.addEventListener('change', () => calc.updateFoodCalculations());
|
|
||||||
if (unitToggle) unitToggle.addEventListener('change', () => calc.toggleUnits());
|
|
||||||
},
|
|
||||||
|
|
||||||
init: () => {
|
|
||||||
calc.bindEvents();
|
|
||||||
calc.updateUnitLabels(); // Initialize unit labels
|
|
||||||
const calcContainer = $('#dogCalculator');
|
|
||||||
if (calcContainer) {
|
|
||||||
calcContainer.classList.add('loaded');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
calc.init();
|
|
||||||
return calc;
|
|
||||||
}
|
|
||||||
|
|
||||||
getThemeFromURL() {
|
getThemeFromURL() {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const theme = urlParams.get('theme');
|
const theme = urlParams.get('theme');
|
||||||
@ -458,9 +161,44 @@ function createWidgetJS(css, html, js) {
|
|||||||
this.container.style.transform = \`scale(\${scale})\`;
|
this.container.style.transform = \`scale(\${scale})\`;
|
||||||
this.container.style.transformOrigin = 'top left';
|
this.container.style.transformOrigin = 'top left';
|
||||||
}
|
}
|
||||||
}
|
}$2`);
|
||||||
|
|
||||||
|
const widgetCode = `/**
|
||||||
|
* Dog Calorie Calculator Widget
|
||||||
|
* Embeddable JavaScript widget for websites
|
||||||
|
*
|
||||||
|
* THIS CODE IS AUTO-GENERATED FROM iframe.html - DO NOT EDIT MANUALLY
|
||||||
|
* Edit iframe.html and run 'node build.js' to update this file
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* <script src="sundog-dog-food-calculator.js"></script>
|
||||||
|
* <div id="dog-calorie-calculator"></div>
|
||||||
|
*
|
||||||
|
* Or with options:
|
||||||
|
* <div id="dog-calorie-calculator" data-theme="dark" data-scale="1.2"></div>
|
||||||
|
*
|
||||||
|
* By Canine Nutrition and Wellness
|
||||||
|
* https://caninenutritionandwellness.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Inject widget styles
|
||||||
|
const CSS_STYLES = \`${css}\`;
|
||||||
|
|
||||||
|
function injectStyles() {
|
||||||
|
if (document.getElementById('dog-calculator-styles')) return;
|
||||||
|
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = 'dog-calculator-styles';
|
||||||
|
style.textContent = CSS_STYLES;
|
||||||
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ACTUAL JavaScript from iframe.html (transformed for widget use)
|
||||||
|
${transformedJS}
|
||||||
|
|
||||||
// Auto-initialize widgets on page load
|
// Auto-initialize widgets on page load
|
||||||
function initializeWidget() {
|
function initializeWidget() {
|
||||||
injectStyles();
|
injectStyles();
|
||||||
|
|||||||
@ -2,6 +2,9 @@
|
|||||||
* Dog Calorie Calculator Widget
|
* Dog Calorie Calculator Widget
|
||||||
* Embeddable JavaScript widget for websites
|
* Embeddable JavaScript widget for websites
|
||||||
*
|
*
|
||||||
|
* THIS CODE IS AUTO-GENERATED FROM iframe.html - DO NOT EDIT MANUALLY
|
||||||
|
* Edit iframe.html and run 'node build.js' to update this file
|
||||||
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* <script src="sundog-dog-food-calculator.js"></script>
|
* <script src="sundog-dog-food-calculator.js"></script>
|
||||||
* <div id="dog-calorie-calculator"></div>
|
* <div id="dog-calorie-calculator"></div>
|
||||||
@ -16,7 +19,7 @@
|
|||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Inject widget styles with proper namespacing
|
// Inject widget styles
|
||||||
const CSS_STYLES = `/* Sundog Dog Food Calorie Calculator Styles */
|
const CSS_STYLES = `/* Sundog Dog Food Calorie Calculator Styles */
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -928,20 +931,29 @@
|
|||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main widget class
|
// ACTUAL JavaScript from iframe.html (transformed for widget use)
|
||||||
class DogCalorieCalculatorWidget {
|
/**
|
||||||
constructor(container, options = {}) {
|
* Dog Calorie Calculator - iframe version
|
||||||
|
* by Canine Nutrition and Wellness
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DogCalorieCalculatorWidget {
|
||||||
|
constructor(container, options = {}) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.options = {
|
this.options = {
|
||||||
theme: options.theme || this.getThemeFromURL() || 'system',
|
theme: options.theme || this.getThemeFromURL() || 'system',
|
||||||
scale: options.scale || this.getScaleFromURL() || 1.0,
|
scale: options.scale || this.getScaleFromURL() || 1.0,
|
||||||
...options
|
...options
|
||||||
};
|
};
|
||||||
this.init();
|
this.currentMER = 0;
|
||||||
}
|
this.isImperial = false;
|
||||||
|
this.theme = this.getThemeFromURL() || 'system';
|
||||||
init() {
|
this.scale = this.getScaleFromURL() || 1.0;
|
||||||
// Insert the transformed calculator HTML
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Inject the calculator HTML into the container
|
||||||
this.container.innerHTML = `<div class="dog-calculator-container" id="dogCalculator">
|
this.container.innerHTML = `<div class="dog-calculator-container" id="dogCalculator">
|
||||||
<div class="dog-calculator-section">
|
<div class="dog-calculator-section">
|
||||||
<div class="dog-calculator-section-header">
|
<div class="dog-calculator-section-header">
|
||||||
@ -1125,309 +1137,587 @@
|
|||||||
this.applyTheme();
|
this.applyTheme();
|
||||||
this.applyScale();
|
this.applyScale();
|
||||||
|
|
||||||
// Initialize the calculator with the same functionality as iframe
|
// Continue with original init logic
|
||||||
this.initCalculator();
|
this.applyTheme();
|
||||||
}
|
this.applyScale();
|
||||||
|
this.bindEvents();
|
||||||
initCalculator() {
|
this.updateUnitLabels();
|
||||||
const container = this.container;
|
this.setupIframeResize();
|
||||||
|
|
||||||
// Helper functions to scope DOM queries to this widget
|
|
||||||
const $ = (selector) => container.querySelector(selector);
|
|
||||||
const $$ = (selector) => container.querySelectorAll(selector);
|
|
||||||
|
|
||||||
// Create calculator instance with exact same logic as iframe
|
|
||||||
const calc = {
|
|
||||||
currentMER: 0,
|
|
||||||
isImperial: false,
|
|
||||||
theme: this.options.theme,
|
|
||||||
scale: this.options.scale,
|
|
||||||
|
|
||||||
// Exact same calculation methods from iframe
|
// Show the calculator with fade-in
|
||||||
calculateRER: (weightKg) => 70 * Math.pow(weightKg, 0.75),
|
const container = this.container.querySelector('#dogCalculator');
|
||||||
calculateMER: (rer, factor) => rer * factor,
|
container.classList.add('loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
getThemeFromURL() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const theme = urlParams.get('theme');
|
||||||
|
return ['light', 'dark', 'system'].includes(theme) ? theme : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getScaleFromURL() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const scale = parseFloat(urlParams.get('scale'));
|
||||||
|
return (!isNaN(scale) && scale >= 0.5 && scale <= 2.0) ? scale : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTheme() {
|
||||||
|
const container = this.container.querySelector('#dogCalculator');
|
||||||
|
container.classList.remove('theme-light', 'theme-dark', 'theme-system');
|
||||||
|
container.classList.add('theme-' + this.theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyScale() {
|
||||||
|
const container = this.container.querySelector('#dogCalculator');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
// Clamp scale between 0.5 and 2.0 for usability
|
||||||
|
const clampedScale = Math.max(0.5, Math.min(2.0, this.scale));
|
||||||
|
|
||||||
formatNumber: (num, decimals = 0) => {
|
if (clampedScale !== 1.0) {
|
||||||
if (decimals === 0) return Math.round(num).toString();
|
container.style.transform = `scale(${clampedScale})`;
|
||||||
return num.toFixed(decimals).replace(/\.?0+$/, '');
|
container.style.transformOrigin = 'top center';
|
||||||
},
|
|
||||||
|
// Adjust container to account for scaling
|
||||||
|
setTimeout(() => {
|
||||||
|
const actualHeight = container.offsetHeight * clampedScale;
|
||||||
|
container.style.marginBottom = `${(clampedScale - 1) * container.offsetHeight}px`;
|
||||||
|
this.sendHeightToParent();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
const weightInput = this.container.querySelector('#weight');
|
||||||
|
const dogTypeSelect = this.container.querySelector('#dogType');
|
||||||
|
const foodEnergyInput = this.container.querySelector('#foodEnergy');
|
||||||
|
const daysInput = this.container.querySelector('#days');
|
||||||
|
const unitSelect = this.container.querySelector('#unit');
|
||||||
|
const unitToggle = this.container.querySelector('#unitToggle');
|
||||||
|
|
||||||
|
if (weightInput) {
|
||||||
|
weightInput.addEventListener('input', () => this.updateCalorieCalculations());
|
||||||
|
weightInput.addEventListener('blur', () => this.validateWeight());
|
||||||
|
}
|
||||||
|
|
||||||
validateInput: (value, min = 0, isInteger = false) => {
|
if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations());
|
||||||
const num = parseFloat(value);
|
|
||||||
if (isNaN(num) || num < min) return false;
|
|
||||||
if (isInteger && !Number.isInteger(num)) return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
convertUnits: (grams, unit) => {
|
if (foodEnergyInput) {
|
||||||
switch (unit) {
|
foodEnergyInput.addEventListener('input', () => this.updateFoodCalculations());
|
||||||
case 'kg': return grams / 1000;
|
foodEnergyInput.addEventListener('blur', () => this.validateFoodEnergy());
|
||||||
case 'oz': return grams / 28.3495;
|
}
|
||||||
case 'lb': return grams / 453.592;
|
|
||||||
default: return grams;
|
const energyUnitSelect = this.container.querySelector('#energyUnit');
|
||||||
}
|
if (energyUnitSelect) energyUnitSelect.addEventListener('change', () => this.updateFoodCalculations());
|
||||||
},
|
|
||||||
|
|
||||||
getWeightInKg: () => {
|
if (daysInput) {
|
||||||
const weightInput = $('#weight');
|
daysInput.addEventListener('input', () => this.updateFoodCalculations());
|
||||||
if (!weightInput || !weightInput.value) return null;
|
daysInput.addEventListener('blur', () => this.validateDays());
|
||||||
const weight = parseFloat(weightInput.value);
|
}
|
||||||
if (isNaN(weight)) return null;
|
|
||||||
return calc.isImperial ? weight / 2.20462 : weight;
|
|
||||||
},
|
|
||||||
|
|
||||||
getFoodEnergyPer100g: () => {
|
if (unitSelect) unitSelect.addEventListener('change', () => this.updateFoodCalculations());
|
||||||
const foodEnergyInput = $('#foodEnergy');
|
|
||||||
const energyUnitSelect = $('#energyUnit');
|
|
||||||
if (!foodEnergyInput || !foodEnergyInput.value || !energyUnitSelect) return null;
|
|
||||||
|
|
||||||
const energy = parseFloat(foodEnergyInput.value);
|
|
||||||
if (isNaN(energy)) return null;
|
|
||||||
|
|
||||||
const unit = energyUnitSelect.value;
|
|
||||||
switch (unit) {
|
|
||||||
case 'kcal100g': return energy;
|
|
||||||
case 'kcalkg': return energy / 10;
|
|
||||||
case 'kcalcup': return energy / 1.2;
|
|
||||||
case 'kcalcan': return energy / 4.5;
|
|
||||||
default: return energy;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showError: (elementId, show = true) => {
|
if (unitToggle) unitToggle.addEventListener('change', () => this.toggleUnits());
|
||||||
const errorElement = $('#' + elementId);
|
|
||||||
if (errorElement) {
|
// Modal event listeners
|
||||||
if (show) {
|
const shareBtn = this.container.querySelector('#shareBtn');
|
||||||
errorElement.classList.remove('dog-calculator-hidden');
|
const embedBtn = this.container.querySelector('#embedBtn');
|
||||||
} else {
|
const shareModalClose = this.container.querySelector('#shareModalClose');
|
||||||
errorElement.classList.add('dog-calculator-hidden');
|
const embedModalClose = this.container.querySelector('#embedModalClose');
|
||||||
}
|
|
||||||
}
|
if (shareBtn) shareBtn.addEventListener('click', () => this.showShareModal());
|
||||||
},
|
if (embedBtn) embedBtn.addEventListener('click', () => this.showEmbedModal());
|
||||||
|
if (shareModalClose) shareModalClose.addEventListener('click', () => this.hideShareModal());
|
||||||
|
if (embedModalClose) embedModalClose.addEventListener('click', () => this.hideEmbedModal());
|
||||||
|
|
||||||
|
// Share buttons
|
||||||
|
const shareFacebook = this.container.querySelector('#shareFacebook');
|
||||||
|
const shareTwitter = this.container.querySelector('#shareTwitter');
|
||||||
|
const shareLinkedIn = this.container.querySelector('#shareLinkedIn');
|
||||||
|
const shareEmail = this.container.querySelector('#shareEmail');
|
||||||
|
const shareCopy = this.container.querySelector('#shareCopy');
|
||||||
|
|
||||||
|
if (shareFacebook) shareFacebook.addEventListener('click', () => this.shareToFacebook());
|
||||||
|
if (shareTwitter) shareTwitter.addEventListener('click', () => this.shareToTwitter());
|
||||||
|
if (shareLinkedIn) shareLinkedIn.addEventListener('click', () => this.shareToLinkedIn());
|
||||||
|
if (shareEmail) shareEmail.addEventListener('click', () => this.shareViaEmail());
|
||||||
|
if (shareCopy) shareCopy.addEventListener('click', () => this.copyShareLink());
|
||||||
|
|
||||||
|
// Copy buttons
|
||||||
|
const copyWidget = this.container.querySelector('#copyWidget');
|
||||||
|
const copyIframe = this.container.querySelector('#copyIframe');
|
||||||
|
|
||||||
|
if (copyWidget) copyWidget.addEventListener('click', () => this.copyEmbedCode('widget'));
|
||||||
|
if (copyIframe) copyIframe.addEventListener('click', () => this.copyEmbedCode('iframe'));
|
||||||
|
|
||||||
|
// Close modals on outside click
|
||||||
|
const shareModal = this.container.querySelector('#shareModal');
|
||||||
|
const embedModal = this.container.querySelector('#embedModal');
|
||||||
|
|
||||||
|
if (shareModal) {
|
||||||
|
shareModal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === shareModal) this.hideShareModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embedModal) {
|
||||||
|
embedModal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === embedModal) this.hideEmbedModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleUnits() {
|
||||||
|
const toggle = this.container.querySelector('#unitToggle');
|
||||||
|
this.isImperial = toggle.checked;
|
||||||
|
|
||||||
updateCalorieCalculations: () => {
|
this.updateUnitLabels();
|
||||||
const dogTypeSelect = $('#dogType');
|
this.convertExistingValues();
|
||||||
const calorieResults = $('#calorieResults');
|
this.updateCalorieCalculations();
|
||||||
const rerValue = $('#rerValue');
|
}
|
||||||
const merValue = $('#merValue');
|
|
||||||
|
|
||||||
if (!dogTypeSelect || !calorieResults || !rerValue || !merValue) return;
|
updateUnitLabels() {
|
||||||
|
const metricLabel = this.container.querySelector('#metricLabel');
|
||||||
|
const imperialLabel = this.container.querySelector('#imperialLabel');
|
||||||
|
const weightLabel = this.container.querySelector('#weightLabel');
|
||||||
|
const weightInput = this.container.querySelector('#weight');
|
||||||
|
const unitSelect = this.container.querySelector('#unit');
|
||||||
|
const energyUnitSelect = this.container.querySelector('#energyUnit');
|
||||||
|
|
||||||
const weightKg = calc.getWeightInKg();
|
if (metricLabel && imperialLabel) {
|
||||||
const dogTypeFactor = dogTypeSelect.value;
|
metricLabel.classList.toggle('active', !this.isImperial);
|
||||||
|
imperialLabel.classList.toggle('active', this.isImperial);
|
||||||
calc.showError('weightError', false);
|
}
|
||||||
|
|
||||||
if (!weightKg || weightKg < 0.1) {
|
|
||||||
const weightInput = $('#weight');
|
|
||||||
if (weightInput && weightInput.value) calc.showError('weightError', true);
|
|
||||||
calorieResults.style.display = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dogTypeFactor) {
|
|
||||||
calorieResults.style.display = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const factor = parseFloat(dogTypeFactor);
|
|
||||||
const rer = calc.calculateRER(weightKg);
|
|
||||||
const mer = calc.calculateMER(rer, factor);
|
|
||||||
|
|
||||||
calc.currentMER = mer;
|
|
||||||
|
|
||||||
rerValue.textContent = calc.formatNumber(rer, 0) + ' cal/day';
|
|
||||||
merValue.textContent = calc.formatNumber(mer, 0) + ' cal/day';
|
|
||||||
calorieResults.style.display = 'block';
|
|
||||||
|
|
||||||
calc.updateFoodCalculations();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFoodCalculations: () => {
|
|
||||||
if (calc.currentMER === 0) return;
|
|
||||||
|
|
||||||
const daysInput = $('#days');
|
|
||||||
const unitSelect = $('#unit');
|
|
||||||
const dailyFoodResults = $('#dailyFoodResults');
|
|
||||||
const dailyFoodValue = $('#dailyFoodValue');
|
|
||||||
const totalFoodDisplay = $('#totalFoodDisplay');
|
|
||||||
|
|
||||||
if (!daysInput || !unitSelect || !dailyFoodResults || !dailyFoodValue || !totalFoodDisplay) return;
|
|
||||||
|
|
||||||
const energyPer100g = calc.getFoodEnergyPer100g();
|
|
||||||
const days = daysInput.value;
|
|
||||||
const unit = unitSelect.value;
|
|
||||||
|
|
||||||
calc.showError('foodEnergyError', false);
|
|
||||||
calc.showError('daysError', false);
|
|
||||||
|
|
||||||
if (!energyPer100g || energyPer100g < 0.1) {
|
|
||||||
const foodEnergyInput = $('#foodEnergy');
|
|
||||||
if (foodEnergyInput && foodEnergyInput.value) calc.showError('foodEnergyError', true);
|
|
||||||
dailyFoodResults.style.display = 'none';
|
|
||||||
totalFoodDisplay.value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!days || !calc.validateInput(days, 1, true)) {
|
|
||||||
if (days) calc.showError('daysError', true);
|
|
||||||
totalFoodDisplay.value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const numDays = parseInt(days);
|
|
||||||
const dailyFoodGrams = (calc.currentMER / energyPer100g) * 100;
|
|
||||||
const totalFoodGrams = dailyFoodGrams * numDays;
|
|
||||||
|
|
||||||
dailyFoodValue.textContent = calc.formatNumber(dailyFoodGrams, 1) + ' g/day';
|
|
||||||
dailyFoodResults.style.display = 'block';
|
|
||||||
|
|
||||||
const convertedAmount = calc.convertUnits(totalFoodGrams, unit);
|
|
||||||
const unitLabel = unit === 'g' ? 'g' : unit === 'kg' ? 'kg' : unit === 'oz' ? 'oz' : 'lb';
|
|
||||||
const decimals = unit === 'g' ? 0 : unit === 'kg' ? 2 : 1;
|
|
||||||
|
|
||||||
totalFoodDisplay.value = calc.formatNumber(convertedAmount, decimals) + ' ' + unitLabel;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Unit switching methods (missing from previous version!)
|
|
||||||
toggleUnits: () => {
|
|
||||||
const toggle = $('#unitToggle');
|
|
||||||
calc.isImperial = toggle.checked;
|
|
||||||
|
|
||||||
calc.updateUnitLabels();
|
|
||||||
calc.convertExistingValues();
|
|
||||||
calc.updateCalorieCalculations();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateUnitLabels: () => {
|
|
||||||
const metricLabel = $('#metricLabel');
|
|
||||||
const imperialLabel = $('#imperialLabel');
|
|
||||||
const weightLabel = $('#weightLabel');
|
|
||||||
const weightInput = $('#weight');
|
|
||||||
const unitSelect = $('#unit');
|
|
||||||
const energyUnitSelect = $('#energyUnit');
|
|
||||||
|
|
||||||
if (metricLabel && imperialLabel) {
|
|
||||||
metricLabel.classList.toggle('active', !calc.isImperial);
|
|
||||||
imperialLabel.classList.toggle('active', calc.isImperial);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (calc.isImperial) {
|
|
||||||
if (weightLabel) weightLabel.textContent = "Dog's Weight (lbs):";
|
|
||||||
if (weightInput) {
|
|
||||||
weightInput.placeholder = "Enter weight in lbs";
|
|
||||||
weightInput.min = "0.2";
|
|
||||||
weightInput.step = "0.1";
|
|
||||||
}
|
|
||||||
if (unitSelect) {
|
|
||||||
unitSelect.innerHTML = '<option value="oz">ounces (oz)</option>' +
|
|
||||||
'<option value="lb">pounds (lb)</option>' +
|
|
||||||
'<option value="g">grams (g)</option>' +
|
|
||||||
'<option value="kg">kilograms (kg)</option>';
|
|
||||||
}
|
|
||||||
// Set energy unit to kcal/cup for imperial
|
|
||||||
if (energyUnitSelect && energyUnitSelect.value === 'kcal100g') {
|
|
||||||
energyUnitSelect.value = 'kcalcup';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (weightLabel) weightLabel.textContent = "Dog's Weight (kg):";
|
|
||||||
if (weightInput) {
|
|
||||||
weightInput.placeholder = "Enter weight in kg";
|
|
||||||
weightInput.min = "0.1";
|
|
||||||
weightInput.step = "0.1";
|
|
||||||
}
|
|
||||||
if (unitSelect) {
|
|
||||||
unitSelect.innerHTML = '<option value="g">grams (g)</option>' +
|
|
||||||
'<option value="kg">kilograms (kg)</option>' +
|
|
||||||
'<option value="oz">ounces (oz)</option>' +
|
|
||||||
'<option value="lb">pounds (lb)</option>';
|
|
||||||
}
|
|
||||||
// Set energy unit to kcal/100g for metric
|
|
||||||
if (energyUnitSelect && energyUnitSelect.value === 'kcalcup') {
|
|
||||||
energyUnitSelect.value = 'kcal100g';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
convertExistingValues: () => {
|
|
||||||
const weightInput = $('#weight');
|
|
||||||
|
|
||||||
if (weightInput && weightInput.value) {
|
|
||||||
const currentWeight = parseFloat(weightInput.value);
|
|
||||||
if (!isNaN(currentWeight)) {
|
|
||||||
if (calc.isImperial) {
|
|
||||||
weightInput.value = calc.formatNumber(currentWeight * 2.20462, 1);
|
|
||||||
} else {
|
|
||||||
weightInput.value = calc.formatNumber(currentWeight / 2.20462, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bindEvents: () => {
|
|
||||||
const weightInput = $('#weight');
|
|
||||||
const dogTypeSelect = $('#dogType');
|
|
||||||
const foodEnergyInput = $('#foodEnergy');
|
|
||||||
const daysInput = $('#days');
|
|
||||||
const unitSelect = $('#unit');
|
|
||||||
const energyUnitSelect = $('#energyUnit');
|
|
||||||
const unitToggle = $('#unitToggle');
|
|
||||||
|
|
||||||
|
if (this.isImperial) {
|
||||||
|
if (weightLabel) weightLabel.textContent = "Dog's Weight (lbs):";
|
||||||
if (weightInput) {
|
if (weightInput) {
|
||||||
weightInput.addEventListener('input', () => calc.updateCalorieCalculations());
|
weightInput.placeholder = "Enter weight in lbs";
|
||||||
|
weightInput.min = "0.2";
|
||||||
|
weightInput.step = "0.1";
|
||||||
}
|
}
|
||||||
if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => calc.updateCalorieCalculations());
|
if (unitSelect) {
|
||||||
if (foodEnergyInput) {
|
unitSelect.innerHTML = '<option value="oz">ounces (oz)</option>' +
|
||||||
foodEnergyInput.addEventListener('input', () => calc.updateFoodCalculations());
|
'<option value="lb">pounds (lb)</option>' +
|
||||||
|
'<option value="g">grams (g)</option>' +
|
||||||
|
'<option value="kg">kilograms (kg)</option>';
|
||||||
}
|
}
|
||||||
if (energyUnitSelect) energyUnitSelect.addEventListener('change', () => calc.updateFoodCalculations());
|
// Set energy unit to kcal/cup for imperial
|
||||||
if (daysInput) {
|
if (energyUnitSelect && energyUnitSelect.value === 'kcal100g') {
|
||||||
daysInput.addEventListener('input', () => calc.updateFoodCalculations());
|
energyUnitSelect.value = 'kcalcup';
|
||||||
}
|
}
|
||||||
if (unitSelect) unitSelect.addEventListener('change', () => calc.updateFoodCalculations());
|
} else {
|
||||||
if (unitToggle) unitToggle.addEventListener('change', () => calc.toggleUnits());
|
if (weightLabel) weightLabel.textContent = "Dog's Weight (kg):";
|
||||||
},
|
if (weightInput) {
|
||||||
|
weightInput.placeholder = "Enter weight in kg";
|
||||||
init: () => {
|
weightInput.min = "0.1";
|
||||||
calc.bindEvents();
|
weightInput.step = "0.1";
|
||||||
calc.updateUnitLabels(); // Initialize unit labels
|
}
|
||||||
const calcContainer = $('#dogCalculator');
|
if (unitSelect) {
|
||||||
if (calcContainer) {
|
unitSelect.innerHTML = '<option value="g">grams (g)</option>' +
|
||||||
calcContainer.classList.add('loaded');
|
'<option value="kg">kilograms (kg)</option>' +
|
||||||
|
'<option value="oz">ounces (oz)</option>' +
|
||||||
|
'<option value="lb">pounds (lb)</option>';
|
||||||
|
}
|
||||||
|
// Set energy unit to kcal/100g for metric
|
||||||
|
if (energyUnitSelect && energyUnitSelect.value === 'kcalcup') {
|
||||||
|
energyUnitSelect.value = 'kcal100g';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
calc.init();
|
convertExistingValues() {
|
||||||
return calc;
|
const weightInput = this.container.querySelector('#weight');
|
||||||
|
|
||||||
|
if (weightInput && weightInput.value) {
|
||||||
|
const currentWeight = parseFloat(weightInput.value);
|
||||||
|
if (!isNaN(currentWeight)) {
|
||||||
|
if (this.isImperial) {
|
||||||
|
weightInput.value = this.formatNumber(currentWeight * 2.20462, 1);
|
||||||
|
} else {
|
||||||
|
weightInput.value = this.formatNumber(currentWeight / 2.20462, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getWeightInKg() {
|
||||||
|
const weightInput = this.container.querySelector('#weight');
|
||||||
|
if (!weightInput || !weightInput.value) return null;
|
||||||
|
|
||||||
|
const weight = parseFloat(weightInput.value);
|
||||||
|
if (isNaN(weight)) return null;
|
||||||
|
|
||||||
|
return this.isImperial ? weight / 2.20462 : weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFoodEnergyPer100g() {
|
||||||
|
const foodEnergyInput = this.container.querySelector('#foodEnergy');
|
||||||
|
const energyUnitSelect = this.container.querySelector('#energyUnit');
|
||||||
|
if (!foodEnergyInput || !foodEnergyInput.value || !energyUnitSelect) return null;
|
||||||
|
|
||||||
|
const energy = parseFloat(foodEnergyInput.value);
|
||||||
|
if (isNaN(energy)) return null;
|
||||||
|
|
||||||
|
const unit = energyUnitSelect.value;
|
||||||
|
|
||||||
|
// Convert all units to kcal/100g for internal calculations
|
||||||
|
switch (unit) {
|
||||||
|
case 'kcal100g':
|
||||||
|
return energy;
|
||||||
|
case 'kcalkg':
|
||||||
|
return energy / 10; // 1 kg = 10 × 100g
|
||||||
|
case 'kcalcup':
|
||||||
|
return energy / 1.2; // Assume 1 cup ≈ 120g for dry dog food
|
||||||
|
case 'kcalcan':
|
||||||
|
return energy / 4.5; // Assume 1 can ≈ 450g for wet dog food
|
||||||
|
default:
|
||||||
|
return energy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateRER(weightKg) {
|
||||||
|
return 70 * Math.pow(weightKg, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateMER(rer, factor) {
|
||||||
|
return rer * factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateInput(value, min = 0, isInteger = false) {
|
||||||
|
const num = parseFloat(value);
|
||||||
|
if (isNaN(num) || num < min) return false;
|
||||||
|
if (isInteger && !Number.isInteger(num)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(elementId, show = true) {
|
||||||
|
const errorElement = document.getElementById(elementId);
|
||||||
|
if (errorElement) {
|
||||||
|
if (show) {
|
||||||
|
errorElement.classList.remove('dog-calculator-hidden');
|
||||||
|
} else {
|
||||||
|
errorElement.classList.add('dog-calculator-hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convertUnits(grams, unit) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'kg':
|
||||||
|
return grams / 1000;
|
||||||
|
case 'oz':
|
||||||
|
return grams / 28.3495;
|
||||||
|
case 'lb':
|
||||||
|
return grams / 453.592;
|
||||||
|
default:
|
||||||
|
return grams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatNumber(num, decimals = 0) {
|
||||||
|
if (decimals === 0) {
|
||||||
|
return Math.round(num).toString();
|
||||||
|
}
|
||||||
|
return num.toFixed(decimals).replace(/\.?0+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
validateWeight() {
|
||||||
|
const weightKg = this.getWeightInKg();
|
||||||
|
if (weightKg !== null && weightKg < 0.1) {
|
||||||
|
this.showError('weightError', true);
|
||||||
|
} else {
|
||||||
|
this.showError('weightError', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateFoodEnergy() {
|
||||||
|
const energyInput = this.container.querySelector('#foodEnergy');
|
||||||
|
const energyUnitSelect = this.container.querySelector('#energyUnit');
|
||||||
|
|
||||||
|
if (!energyInput || !energyInput.value) {
|
||||||
|
this.showError('foodEnergyError', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const energy = parseFloat(energyInput.value);
|
||||||
|
const unit = energyUnitSelect?.value || 'kcal100g';
|
||||||
|
|
||||||
|
let minValue = 1;
|
||||||
|
switch (unit) {
|
||||||
|
case 'kcal100g':
|
||||||
|
minValue = 1;
|
||||||
|
break;
|
||||||
|
case 'kcalkg':
|
||||||
|
minValue = 10;
|
||||||
|
break;
|
||||||
|
case 'kcalcup':
|
||||||
|
minValue = 50;
|
||||||
|
break;
|
||||||
|
case 'kcalcan':
|
||||||
|
minValue = 100;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.validateInput(energy, minValue)) {
|
||||||
|
this.showError('foodEnergyError', true);
|
||||||
|
} else {
|
||||||
|
this.showError('foodEnergyError', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateDays() {
|
||||||
|
const days = this.container.querySelector('#days')?.value;
|
||||||
|
if (days && !this.validateInput(days, 1, true)) {
|
||||||
|
this.showError('daysError', true);
|
||||||
|
} else {
|
||||||
|
this.showError('daysError', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCalorieCalculations() {
|
||||||
|
const dogTypeSelect = this.container.querySelector('#dogType');
|
||||||
|
const calorieResults = this.container.querySelector('#calorieResults');
|
||||||
|
const rerValue = this.container.querySelector('#rerValue');
|
||||||
|
const merValue = this.container.querySelector('#merValue');
|
||||||
|
|
||||||
|
if (!dogTypeSelect || !calorieResults || !rerValue || !merValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const weightKg = this.getWeightInKg();
|
||||||
|
const dogTypeFactor = dogTypeSelect.value;
|
||||||
|
|
||||||
|
this.showError('weightError', false);
|
||||||
|
|
||||||
|
if (!weightKg || weightKg < 0.1) {
|
||||||
|
const weightInput = this.container.querySelector('#weight');
|
||||||
|
if (weightInput && weightInput.value) this.showError('weightError', true);
|
||||||
|
calorieResults.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dogTypeFactor) {
|
||||||
|
calorieResults.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const factor = parseFloat(dogTypeFactor);
|
||||||
|
|
||||||
|
const rer = this.calculateRER(weightKg);
|
||||||
|
const mer = this.calculateMER(rer, factor);
|
||||||
|
|
||||||
|
this.currentMER = mer;
|
||||||
|
|
||||||
|
rerValue.textContent = this.formatNumber(rer, 0) + ' cal/day';
|
||||||
|
merValue.textContent = this.formatNumber(mer, 0) + ' cal/day';
|
||||||
|
calorieResults.style.display = 'block';
|
||||||
|
|
||||||
|
this.updateFoodCalculations();
|
||||||
|
this.sendHeightToParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFoodCalculations() {
|
||||||
|
if (this.currentMER === 0) return;
|
||||||
|
|
||||||
|
const daysInput = this.container.querySelector('#days');
|
||||||
|
const unitSelect = this.container.querySelector('#unit');
|
||||||
|
const dailyFoodResults = this.container.querySelector('#dailyFoodResults');
|
||||||
|
const dailyFoodValue = this.container.querySelector('#dailyFoodValue');
|
||||||
|
const totalFoodDisplay = this.container.querySelector('#totalFoodDisplay');
|
||||||
|
|
||||||
|
if (!daysInput || !unitSelect || !dailyFoodResults || !dailyFoodValue || !totalFoodDisplay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const energyPer100g = this.getFoodEnergyPer100g();
|
||||||
|
const days = daysInput.value;
|
||||||
|
const unit = unitSelect.value;
|
||||||
|
|
||||||
|
this.showError('foodEnergyError', false);
|
||||||
|
this.showError('daysError', false);
|
||||||
|
|
||||||
|
if (!energyPer100g || energyPer100g < 0.1) {
|
||||||
|
const foodEnergyInput = this.container.querySelector('#foodEnergy');
|
||||||
|
if (foodEnergyInput && foodEnergyInput.value) this.showError('foodEnergyError', true);
|
||||||
|
dailyFoodResults.style.display = 'none';
|
||||||
|
totalFoodDisplay.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!days || !this.validateInput(days, 1, true)) {
|
||||||
|
if (days) this.showError('daysError', true);
|
||||||
|
totalFoodDisplay.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numDays = parseInt(days);
|
||||||
|
|
||||||
|
const dailyFoodGrams = (this.currentMER / energyPer100g) * 100;
|
||||||
|
const totalFoodGrams = dailyFoodGrams * numDays;
|
||||||
|
|
||||||
|
dailyFoodValue.textContent = this.formatNumber(dailyFoodGrams, 1) + ' g/day';
|
||||||
|
dailyFoodResults.style.display = 'block';
|
||||||
|
|
||||||
|
const convertedAmount = this.convertUnits(totalFoodGrams, unit);
|
||||||
|
const unitLabel = unit === 'g' ? 'g' : unit === 'kg' ? 'kg' : unit === 'oz' ? 'oz' : 'lb';
|
||||||
|
const decimals = unit === 'g' ? 0 : unit === 'kg' ? 2 : 1;
|
||||||
|
|
||||||
|
totalFoodDisplay.value = this.formatNumber(convertedAmount, decimals) + ' ' + unitLabel;
|
||||||
|
|
||||||
|
this.sendHeightToParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupIframeResize() {
|
||||||
|
// Send height to parent window for iframe auto-resize
|
||||||
|
this.sendHeightToParent();
|
||||||
|
|
||||||
|
// Monitor for content changes that might affect height
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
setTimeout(() => this.sendHeightToParent(), 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send height on window resize
|
||||||
|
window.addEventListener('resize', () => this.sendHeightToParent());
|
||||||
|
}
|
||||||
|
|
||||||
|
sendHeightToParent() {
|
||||||
|
const height = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
||||||
|
if (window.parent && window.parent !== window) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'dogCalculatorResize',
|
||||||
|
height: height
|
||||||
|
}, '*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal functionality
|
||||||
|
showShareModal() {
|
||||||
|
const modal = this.container.querySelector('#shareModal');
|
||||||
|
const shareUrl = this.container.querySelector('#shareUrl');
|
||||||
|
if (modal && shareUrl) {
|
||||||
|
shareUrl.value = window.location.href;
|
||||||
|
modal.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideShareModal() {
|
||||||
|
const modal = this.container.querySelector('#shareModal');
|
||||||
|
if (modal) modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
showEmbedModal() {
|
||||||
|
const modal = this.container.querySelector('#embedModal');
|
||||||
|
const widgetCode = this.container.querySelector('#widgetCode');
|
||||||
|
const iframeCode = this.container.querySelector('#iframeCode');
|
||||||
|
|
||||||
|
if (modal && widgetCode && iframeCode) {
|
||||||
|
// Build embed URL
|
||||||
|
const baseUrl = window.location.protocol + '//embed.' + window.location.hostname;
|
||||||
|
|
||||||
|
// Create widget code using createElement to avoid quote issues
|
||||||
|
const scriptTag = document.createElement('script');
|
||||||
|
scriptTag.src = baseUrl + '/dog-calorie-calculator/dog-food-calculator-widget.js';
|
||||||
|
const divTag = document.createElement('div');
|
||||||
|
divTag.id = 'dog-calorie-calculator';
|
||||||
|
|
||||||
|
const widgetHtml = scriptTag.outerHTML + '\n' + divTag.outerHTML;
|
||||||
|
widgetCode.textContent = widgetHtml;
|
||||||
|
|
||||||
|
// Create iframe code using createElement
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.src = baseUrl + '/dog-calorie-calculator/iframe.html';
|
||||||
|
iframe.width = '100%';
|
||||||
|
iframe.height = '600';
|
||||||
|
iframe.frameBorder = '0';
|
||||||
|
iframe.title = 'Dog Calorie Calculator';
|
||||||
|
|
||||||
|
iframeCode.textContent = iframe.outerHTML;
|
||||||
|
modal.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideEmbedModal() {
|
||||||
|
const modal = this.container.querySelector('#embedModal');
|
||||||
|
if (modal) modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
shareToFacebook() {
|
||||||
|
const url = encodeURIComponent(window.location.href);
|
||||||
|
window.open('https://www.facebook.com/sharer/sharer.php?u=' + url, '_blank', 'width=600,height=400');
|
||||||
|
}
|
||||||
|
|
||||||
|
shareToTwitter() {
|
||||||
|
const url = encodeURIComponent(window.location.href);
|
||||||
|
const text = encodeURIComponent('Check out this useful dog calorie calculator!');
|
||||||
|
window.open('https://twitter.com/intent/tweet?url=' + url + '&text=' + text, '_blank', 'width=600,height=400');
|
||||||
|
}
|
||||||
|
|
||||||
|
shareToLinkedIn() {
|
||||||
|
const url = encodeURIComponent(window.location.href);
|
||||||
|
window.open('https://www.linkedin.com/sharing/share-offsite/?url=' + url, '_blank', 'width=600,height=400');
|
||||||
|
}
|
||||||
|
|
||||||
|
shareViaEmail() {
|
||||||
|
const subject = encodeURIComponent('Dog Calorie Calculator');
|
||||||
|
const body = encodeURIComponent('Check out this useful dog calorie calculator: ' + window.location.href);
|
||||||
|
window.location.href = 'mailto:?subject=' + subject + '&body=' + body;
|
||||||
|
}
|
||||||
|
|
||||||
|
async copyShareLink() {
|
||||||
|
const shareUrl = this.container.querySelector('#shareUrl');
|
||||||
|
const copyBtn = this.container.querySelector('#shareCopy');
|
||||||
|
|
||||||
|
if (shareUrl && copyBtn) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(shareUrl.value);
|
||||||
|
const originalText = copyBtn.textContent;
|
||||||
|
copyBtn.textContent = 'Copied!';
|
||||||
|
copyBtn.classList.add('copied');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
copyBtn.textContent = originalText;
|
||||||
|
copyBtn.classList.remove('copied');
|
||||||
|
}, 2000);
|
||||||
|
} catch (err) {
|
||||||
|
// Fallback for older browsers
|
||||||
|
shareUrl.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async copyEmbedCode(type) {
|
||||||
|
const codeElement = document.getElementById(type === 'widget' ? 'widgetCode' : 'iframeCode');
|
||||||
|
const copyBtn = document.getElementById(type === 'widget' ? 'copyWidget' : 'copyIframe');
|
||||||
|
|
||||||
|
if (codeElement && copyBtn) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(codeElement.textContent);
|
||||||
|
const originalText = copyBtn.textContent;
|
||||||
|
copyBtn.textContent = 'Copied!';
|
||||||
|
copyBtn.classList.add('copied');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
copyBtn.textContent = originalText;
|
||||||
|
copyBtn.classList.remove('copied');
|
||||||
|
}, 2000);
|
||||||
|
} catch (err) {
|
||||||
|
// Fallback for older browsers
|
||||||
|
console.log('Copy fallback needed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize calculator when DOM is ready
|
||||||
|
|
||||||
getThemeFromURL() {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const theme = urlParams.get('theme');
|
|
||||||
return ['light', 'dark', 'system'].includes(theme) ? theme : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getScaleFromURL() {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const scale = parseFloat(urlParams.get('scale'));
|
|
||||||
return (!isNaN(scale) && scale >= 0.5 && scale <= 2.0) ? scale : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyTheme() {
|
|
||||||
if (this.options.theme === 'light' || this.options.theme === 'dark') {
|
|
||||||
this.container.classList.add('theme-' + this.options.theme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyScale() {
|
|
||||||
const scale = Math.max(0.5, Math.min(2.0, this.options.scale));
|
|
||||||
if (scale !== 1.0) {
|
|
||||||
this.container.style.transform = `scale(${scale})`;
|
|
||||||
this.container.style.transformOrigin = 'top left';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-initialize widgets on page load
|
// Auto-initialize widgets on page load
|
||||||
function initializeWidget() {
|
function initializeWidget() {
|
||||||
|
|||||||
@ -24,12 +24,12 @@
|
|||||||
|
|
||||||
<div class="test-container">
|
<div class="test-container">
|
||||||
<h2>Test 1: Basic Widget</h2>
|
<h2>Test 1: Basic Widget</h2>
|
||||||
<div id="dog-calorie-calculator"></div>
|
<div class="dog-calorie-calculator" data-theme="light" data-scale="0.9"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="test-container">
|
<div class="test-container">
|
||||||
<h2>Test 2: Dark Theme Widget</h2>
|
<h2>Test 2: Dark Theme Widget</h2>
|
||||||
<div id="dog-calorie-calculator" data-theme="dark" data-scale="0.5"></div>
|
<div class="dog-calorie-calculator" data-theme="dark" data-scale="0.5"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="sundog-dog-food-calculator.js"></script>
|
<script src="sundog-dog-food-calculator.js"></script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user