Files
umrechner/hugo/static/js/converter.js

152 lines
4.1 KiB
JavaScript

'use strict';
/**
* Alpine.js navigation helpers for conversion page.
* Handles navigation via hidden AJAX link.
*/
function navActions() {
return {
navigate(url) {
this.$refs.ajaxLink.href = url;
this.$nextTick(() => this.$refs.ajaxLink.click());
},
navigateWithValue(url) {
const el = document.getElementById('value');
const val = el ? el.value.replace(/,/g, '.') : '1';
this.$refs.ajaxLink.href = url + '?v=' + val;
this.$nextTick(() => this.$refs.ajaxLink.click());
},
navigateWithResult(url) {
const el = document.getElementById('result');
const val = el ? el.value.replace(/,/g, '.') : '';
if (val) {
this.$refs.ajaxLink.href = url + '?v=' + val;
} else {
this.$refs.ajaxLink.href = url;
}
this.$nextTick(() => this.$refs.ajaxLink.click());
}
};
}
/**
* Factory function for Alpine.js converter components.
* @param {string} engine - 'linear', 'intermediate' or 'runtime'
* @param {object} config - Engine-specific configuration
* @returns {object} Alpine component data
*/
function createConverter(engine, config) {
return {
inputValue: '1',
result: '',
rates: {},
ratesError: '',
async init() {
const params = new URLSearchParams(
window.location.search);
const valueParam = params.get('v');
if (valueParam) {
this.inputValue = valueParam.replace(/,/g, '.');
}
this.$watch('inputValue', val => {
if (val && val.indexOf(',') !== -1) {
this.inputValue = val.replace(/,/g, '.');
}
});
if (typeof config.init === 'function') {
await config.init.call(this);
}
this.calculate();
},
calculate() {
try {
const normalized = parseNumber(this.inputValue);
if (!normalized || isNaN(normalized)) {
this.result = '';
return;
}
const value = new Decimal(normalized);
let rawResult;
if (typeof config.convert === 'function') {
rawResult = config.convert.call(this, value);
} else if (engine === 'linear') {
const fromFactor = new Decimal(
config.fromFactor);
const toFactor = new Decimal(
config.toFactor);
rawResult = value.times(fromFactor)
.dividedBy(toFactor);
} else if (engine === 'intermediate') {
let v = value.toNumber();
v = config.toIntermediate(v);
v = config.fromIntermediate(v);
rawResult = new Decimal(v);
} else {
this.result = '';
return;
}
this.result = prettyNumber(rawResult);
const normalizedInputValue = this.inputValue
.replace(/,/g, '.');
if (normalizedInputValue
&& normalizedInputValue !== '0') {
history.replaceState(
null, '', '?v=' + normalizedInputValue);
}
} catch (e) {
this.result = '';
}
}
};
}
/**
* Formats a number as plain digits with dot as decimal separator.
* @param {Decimal|string|number} num
* @param {number} minPrecision - Minimum decimal places
* @param {number} maxPrecision - Maximum decimal places
* @returns {string} Formatted number string
*/
function prettyNumber(num, minPrecision, maxPrecision) {
minPrecision = minPrecision || 4;
maxPrecision = maxPrecision || 10;
const d = new Decimal(num);
const absVal = d.abs();
// Larger numbers need fewer decimal places for readable output;
// smaller numbers get more to stay meaningful.
let precision = maxPrecision;
if (absVal.gte(1000000)) {
precision = 2;
} else if (absVal.gte(1000)) {
precision = 4;
} else if (absVal.gte(1)) {
precision = 6;
} else if (absVal.gte(0.001)) {
precision = 8;
}
precision = Math.max(minPrecision,
Math.min(precision, maxPrecision));
let str = d.toFixed(precision);
if (str.indexOf('.') !== -1) {
str = str.replace(/0+$/, '');
str = str.replace(/\.$/, '');
}
return str;
}
/**
* Parses a German number string.
* Accepts both comma and dot as decimal separator.
* @param {string} input
* @returns {string} Normalized string with dot as decimal
*/
function parseNumber(input) {
if (!input) return '0';
const hasComma = input.indexOf(',') !== -1;
const hasDot = input.indexOf('.') !== -1;
if (hasComma && hasDot) {
return input.replace(/\./g, '').replace(/,/g, '.') || '0';
}
return input.replace(/,/g, '.') || '0';
}