diff --git a/build.js b/build.js index f44130c..3b5f19c 100644 --- a/build.js +++ b/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) { - // Use original CSS and HTML without transformation for consistency - const widgetCSS = css; - const widgetHTML = html; + // Transform the extracted JavaScript from iframe.html to work as a widget - const widgetCode = `/** - * Dog Calorie Calculator Widget - * Embeddable JavaScript widget for websites - * - * Usage: - * - *
- * - * Or with options: - *
- * - * By Canine Nutrition and Wellness - * https://caninenutritionandwellness.com - */ - -(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 = {}) { + // Replace the iframe's DOMContentLoaded listener with widget initialization + let transformedJS = js + // Replace the iframe class name with widget class name + .replace(/class DogCalorieCalculator/g, 'class DogCalorieCalculatorWidget') + // Replace document.getElementById with scoped selectors within the widget + .replace(/document\.getElementById\('([^']+)'\)/g, 'this.container.querySelector(\'#$1\')') + // Replace direct document queries in the class with container-scoped queries + .replace(/document\.querySelector\(/g, 'this.container.querySelector(') + .replace(/document\.querySelectorAll\(/g, 'this.container.querySelectorAll(') + // Remove the DOMContentLoaded listener and class instantiation - we'll handle this in the widget wrapper + .replace(/document\.addEventListener\('DOMContentLoaded'.*?\n.*?new DogCalorieCalculator.*?\n.*?\}\);/s, '') + // Add widget initialization methods + .replace(/constructor\(\) \{/, `constructor(container, options = {}) { this.container = container; this.options = { theme: options.theme || this.getThemeFromURL() || 'system', scale: options.scale || this.getScaleFromURL() || 1.0, ...options - }; - this.init(); - } - - init() { - // Insert the transformed calculator HTML - this.container.innerHTML = \`${widgetHTML}\`; + };`) + // Replace the init() method to inject HTML and apply widget settings + .replace(/init\(\) \{/, `init() { + // Inject the calculator HTML into the container + this.container.innerHTML = \`${html}\`; // Apply widget-specific settings this.applyTheme(); this.applyScale(); - // Initialize the calculator with the same functionality as iframe - this.initCalculator(); - } - - 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 = '' + - '' + - '' + - ''; - } - // 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 = '' + - '' + - '' + - ''; - } - // 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; - } - + // Continue with original init logic`); + + // Add widget-specific methods before the class closing brace + transformedJS = transformedJS.replace(/(\s+)(\}\s*$)/, `$1 getThemeFromURL() { const urlParams = new URLSearchParams(window.location.search); const theme = urlParams.get('theme'); @@ -458,9 +161,44 @@ function createWidgetJS(css, html, js) { this.container.style.transform = \`scale(\${scale})\`; 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: + * + *
+ * + * Or with options: + *
+ * + * 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 function initializeWidget() { injectStyles(); diff --git a/sundog-dog-food-calculator.js b/sundog-dog-food-calculator.js index 7f9e8aa..1b78972 100644 --- a/sundog-dog-food-calculator.js +++ b/sundog-dog-food-calculator.js @@ -2,6 +2,9 @@ * 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: * *
@@ -16,7 +19,7 @@ (function() { 'use strict'; - // Inject widget styles with proper namespacing + // Inject widget styles const CSS_STYLES = `/* Sundog Dog Food Calorie Calculator Styles */ body { @@ -928,20 +931,29 @@ document.head.appendChild(style); } - // Main widget class - class DogCalorieCalculatorWidget { - constructor(container, options = {}) { + // ACTUAL JavaScript from iframe.html (transformed for widget use) + /** + * Dog Calorie Calculator - iframe version + * by Canine Nutrition and Wellness + */ + + class DogCalorieCalculatorWidget { + constructor(container, options = {}) { this.container = container; this.options = { theme: options.theme || this.getThemeFromURL() || 'system', scale: options.scale || this.getScaleFromURL() || 1.0, ...options }; - this.init(); - } - - init() { - // Insert the transformed calculator HTML + this.currentMER = 0; + this.isImperial = false; + this.theme = this.getThemeFromURL() || 'system'; + this.scale = this.getScaleFromURL() || 1.0; + this.init(); + } + + init() { + // Inject the calculator HTML into the container this.container.innerHTML = `
@@ -1125,309 +1137,587 @@ this.applyTheme(); this.applyScale(); - // Initialize the calculator with the same functionality as iframe - this.initCalculator(); - } - - 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, + // Continue with original init logic + this.applyTheme(); + this.applyScale(); + this.bindEvents(); + this.updateUnitLabels(); + this.setupIframeResize(); - // Exact same calculation methods from iframe - calculateRER: (weightKg) => 70 * Math.pow(weightKg, 0.75), - calculateMER: (rer, factor) => rer * factor, + // Show the calculator with fade-in + const container = this.container.querySelector('#dogCalculator'); + 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 (decimals === 0) return Math.round(num).toString(); - return num.toFixed(decimals).replace(/\.?0+$/, ''); - }, + if (clampedScale !== 1.0) { + container.style.transform = `scale(${clampedScale})`; + 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) => { - const num = parseFloat(value); - if (isNaN(num) || num < min) return false; - if (isInteger && !Number.isInteger(num)) return false; - return true; - }, + if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations()); - 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; - } - }, + if (foodEnergyInput) { + foodEnergyInput.addEventListener('input', () => this.updateFoodCalculations()); + foodEnergyInput.addEventListener('blur', () => this.validateFoodEnergy()); + } + + const energyUnitSelect = this.container.querySelector('#energyUnit'); + if (energyUnitSelect) energyUnitSelect.addEventListener('change', () => this.updateFoodCalculations()); - 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; - }, + if (daysInput) { + daysInput.addEventListener('input', () => this.updateFoodCalculations()); + daysInput.addEventListener('blur', () => this.validateDays()); + } - 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; - } - }, + if (unitSelect) unitSelect.addEventListener('change', () => this.updateFoodCalculations()); - showError: (elementId, show = true) => { - const errorElement = $('#' + elementId); - if (errorElement) { - if (show) { - errorElement.classList.remove('dog-calculator-hidden'); - } else { - errorElement.classList.add('dog-calculator-hidden'); - } - } - }, + if (unitToggle) unitToggle.addEventListener('change', () => this.toggleUnits()); + + // Modal event listeners + const shareBtn = this.container.querySelector('#shareBtn'); + const embedBtn = this.container.querySelector('#embedBtn'); + const shareModalClose = this.container.querySelector('#shareModalClose'); + 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: () => { - const dogTypeSelect = $('#dogType'); - const calorieResults = $('#calorieResults'); - const rerValue = $('#rerValue'); - const merValue = $('#merValue'); + this.updateUnitLabels(); + this.convertExistingValues(); + this.updateCalorieCalculations(); + } - 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(); - 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 = '' + - '' + - '' + - ''; - } - // 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 = '' + - '' + - '' + - ''; - } - // 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 (metricLabel && imperialLabel) { + metricLabel.classList.toggle('active', !this.isImperial); + imperialLabel.classList.toggle('active', this.isImperial); + } + if (this.isImperial) { + if (weightLabel) weightLabel.textContent = "Dog's Weight (lbs):"; 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 (foodEnergyInput) { - foodEnergyInput.addEventListener('input', () => calc.updateFoodCalculations()); + if (unitSelect) { + unitSelect.innerHTML = '' + + '' + + '' + + ''; } - if (energyUnitSelect) energyUnitSelect.addEventListener('change', () => calc.updateFoodCalculations()); - if (daysInput) { - daysInput.addEventListener('input', () => calc.updateFoodCalculations()); + // Set energy unit to kcal/cup for imperial + if (energyUnitSelect && energyUnitSelect.value === 'kcal100g') { + energyUnitSelect.value = 'kcalcup'; } - 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'); + } 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 = '' + + '' + + '' + + ''; + } + // Set energy unit to kcal/100g for metric + if (energyUnitSelect && energyUnitSelect.value === 'kcalcup') { + energyUnitSelect.value = 'kcal100g'; } } - }; - - calc.init(); - return calc; + } + + convertExistingValues() { + 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 function initializeWidget() { diff --git a/test-widget.html b/test-widget.html index 39a41ae..404397e 100644 --- a/test-widget.html +++ b/test-widget.html @@ -24,12 +24,12 @@

Test 1: Basic Widget

-
+

Test 2: Dark Theme Widget

-
+