2025-07-22 08:50:48 +02:00
|
|
|
/**
|
|
|
|
|
* Input Utilities Module
|
|
|
|
|
* Handles input formatting, validation, and parsing
|
|
|
|
|
*/
|
|
|
|
|
class InputUtils {
|
2025-07-23 14:50:53 +02:00
|
|
|
static formatNumberWithCommas(num, currency = null) {
|
2025-07-22 08:50:48 +02:00
|
|
|
try {
|
2025-07-23 14:50:53 +02:00
|
|
|
// Get current currency if not provided
|
|
|
|
|
if (!currency) {
|
|
|
|
|
const currencyElement = document.getElementById('currency');
|
|
|
|
|
currency = currencyElement ? currencyElement.value : 'CHF';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use appropriate locale for number formatting
|
|
|
|
|
const locale = currency === 'EUR' ? 'de-DE' : 'de-CH';
|
|
|
|
|
return parseInt(num).toLocaleString(locale);
|
2025-07-22 08:50:48 +02:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error formatting number with commas:', error);
|
|
|
|
|
return String(num);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static parseFormattedNumber(str) {
|
|
|
|
|
if (typeof str !== 'string') {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Remove all non-numeric characters except decimal points and commas
|
|
|
|
|
const cleaned = str.replace(/[^\d,.-]/g, '');
|
|
|
|
|
|
|
|
|
|
// Handle empty string
|
|
|
|
|
if (!cleaned) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove commas and parse as float
|
|
|
|
|
const result = parseFloat(cleaned.replace(/,/g, ''));
|
|
|
|
|
|
|
|
|
|
// Return 0 for invalid numbers or NaN
|
|
|
|
|
return isNaN(result) ? 0 : result;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error parsing formatted number:', error);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static handleInvestmentAmountInput(input) {
|
|
|
|
|
if (!input || typeof input.value !== 'string') {
|
|
|
|
|
console.error('Invalid input element provided to handleInvestmentAmountInput');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2025-07-23 14:38:30 +02:00
|
|
|
// Allow only digits, no immediate formatting during typing
|
|
|
|
|
let value = input.value.replace(/[^\d]/g, '');
|
|
|
|
|
|
|
|
|
|
// Handle empty input
|
|
|
|
|
if (!value) {
|
|
|
|
|
input.setAttribute('data-value', '0');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-07-22 08:50:48 +02:00
|
|
|
|
|
|
|
|
// Parse the numeric value
|
2025-07-23 14:38:30 +02:00
|
|
|
let numericValue = parseInt(value) || 0;
|
|
|
|
|
|
|
|
|
|
// Update the data attribute with the raw numeric value (no limits during typing)
|
|
|
|
|
input.setAttribute('data-value', numericValue.toString());
|
|
|
|
|
|
|
|
|
|
// Update the input value (keep it clean, no commas during typing)
|
|
|
|
|
input.value = value;
|
|
|
|
|
|
|
|
|
|
// Update the slider if it exists (with limits)
|
|
|
|
|
const slider = document.getElementById('investment-slider');
|
|
|
|
|
if (slider) {
|
|
|
|
|
const minValue = parseInt(slider.min) || 100000;
|
|
|
|
|
const maxValue = parseInt(slider.max) || 2000000;
|
|
|
|
|
const sliderValue = Math.max(minValue, Math.min(maxValue, numericValue));
|
|
|
|
|
slider.value = sliderValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trigger calculations with debouncing
|
|
|
|
|
if (window.ROICalculatorApp && window.ROICalculatorApp.calculator) {
|
|
|
|
|
clearTimeout(input._calculationTimeout);
|
|
|
|
|
input._calculationTimeout = setTimeout(() => {
|
|
|
|
|
window.ROICalculatorApp.calculator.updateCalculations();
|
|
|
|
|
}, 300); // 300ms delay to avoid excessive calculations during typing
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error handling investment amount input:', error);
|
|
|
|
|
// Set a safe default value
|
|
|
|
|
input.setAttribute('data-value', '500000');
|
|
|
|
|
input.value = '500000';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static handleInvestmentAmountBlur(input) {
|
|
|
|
|
if (!input) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Get the numeric value
|
|
|
|
|
let numericValue = parseInt(input.getAttribute('data-value')) || 500000;
|
2025-07-22 08:50:48 +02:00
|
|
|
|
2025-07-23 14:38:30 +02:00
|
|
|
// Enforce min/max limits on blur
|
2025-07-22 08:50:48 +02:00
|
|
|
const minValue = 100000;
|
|
|
|
|
const maxValue = 2000000;
|
|
|
|
|
|
|
|
|
|
if (numericValue < minValue) {
|
|
|
|
|
numericValue = minValue;
|
|
|
|
|
} else if (numericValue > maxValue) {
|
|
|
|
|
numericValue = maxValue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-23 14:38:30 +02:00
|
|
|
// Update the data attribute with the corrected value
|
2025-07-22 08:50:48 +02:00
|
|
|
input.setAttribute('data-value', numericValue.toString());
|
|
|
|
|
|
2025-07-23 14:38:30 +02:00
|
|
|
// Format and display the value with commas on blur
|
2025-07-22 08:50:48 +02:00
|
|
|
input.value = InputUtils.formatNumberWithCommas(numericValue);
|
|
|
|
|
|
2025-07-23 14:38:30 +02:00
|
|
|
// Update the slider
|
2025-07-22 08:50:48 +02:00
|
|
|
const slider = document.getElementById('investment-slider');
|
|
|
|
|
if (slider) {
|
|
|
|
|
slider.value = numericValue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-23 14:38:30 +02:00
|
|
|
// Trigger immediate calculation on blur
|
2025-07-22 08:50:48 +02:00
|
|
|
if (window.ROICalculatorApp && window.ROICalculatorApp.calculator) {
|
2025-07-23 14:38:30 +02:00
|
|
|
clearTimeout(input._calculationTimeout);
|
2025-07-22 08:50:48 +02:00
|
|
|
window.ROICalculatorApp.calculator.updateCalculations();
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
2025-07-23 14:38:30 +02:00
|
|
|
console.error('Error handling investment amount blur:', error);
|
2025-07-22 08:50:48 +02:00
|
|
|
// Set a safe default value
|
|
|
|
|
input.setAttribute('data-value', '500000');
|
|
|
|
|
input.value = '500,000';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-23 14:38:30 +02:00
|
|
|
static handleInvestmentAmountFocus(input) {
|
|
|
|
|
if (!input) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Remove commas when focusing for easier editing
|
|
|
|
|
const numericValue = input.getAttribute('data-value') || '500000';
|
|
|
|
|
input.value = numericValue;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error handling investment amount focus:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-22 08:50:48 +02:00
|
|
|
static getCSRFToken() {
|
|
|
|
|
try {
|
|
|
|
|
// Try to get CSRF token from meta tag first
|
|
|
|
|
const metaTag = document.querySelector('meta[name="csrf-token"]');
|
|
|
|
|
if (metaTag) {
|
|
|
|
|
return metaTag.getAttribute('content');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback to cookie method
|
|
|
|
|
const cookies = document.cookie.split(';');
|
|
|
|
|
for (let cookie of cookies) {
|
|
|
|
|
const [name, value] = cookie.trim().split('=');
|
|
|
|
|
if (name === 'csrftoken') {
|
|
|
|
|
return decodeURIComponent(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If no CSRF token found, return empty string (will cause server error, but won't break JS)
|
|
|
|
|
console.error('CSRF token not found');
|
|
|
|
|
return '';
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error getting CSRF token:', error);
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|