PageSourceSearch

https://www.ib-sachsen-anhalt.de/_assets/5bbb5bc2166794318a68f4ddc5f9f10e/Javascript/calculator.js?1790244614

js ib-sachsen-anhalt.de collected 2026-10-02 18:13:25 UTC 6,834 bytes, 192 lines download raw bytes

1document.addEventListener('DOMContentLoaded', function () {
2    /**
3     * Formats a number with German thousands separator (dot) and optional decimals/comma.
4     * @param {number|string} val - value to format
5     * @param {number} decimals - number of decimals (default: 0)
6     * @returns {string}
7     */
8    function formatNumber(val, decimals = 0) {
9        return Number(val)
10            .toLocaleString('de-DE', {
11                minimumFractionDigits: decimals,
12                maximumFractionDigits: decimals
13            });
14    }
15
16    /**
17     * Binds an input[type="range"] slider with a paired <input type="number">
18     * and updates WAI-ARIA attributes for accessibility.
19     * Synchronization works in both directions.
20     *
21     * @param {string} sliderId - id of the slider
22     * @param {string} valueId - id of the number input element
23     * @param {number} decimals - number of decimals
24     */
25    function bindSlider(sliderId, valueId, decimals = 0) {
26        const slider = document.getElementById(sliderId);
27        const valueElem = document.getElementById(valueId);
28        if (!slider || !valueElem) return;
29
30        // Init ARIA
31        slider.setAttribute('role', 'slider');
32        slider.setAttribute('aria-valuemin', slider.min);
33        slider.setAttribute('aria-valuemax', slider.max);
34        slider.setAttribute('aria-describedby', valueId);
35
36        function format(val) {
37            let formatted = formatNumber(val, decimals);
38            if (decimals > 0) formatted = formatted.replace('.', ',');
39            return formatted;
40        }
41
42        // Update value input from slider
43        function updateFromSlider() {
44            valueElem.value = format(slider.value);
45            slider.setAttribute('aria-valuenow', slider.value);
46        }
47
48        // Update slider from number input
49        function updateFromInput() {
50            let raw = valueElem.value.replace(',', '.');
51            let val = parseFloat(raw);
52            if (isNaN(val)) return;
53
54            const min = parseFloat(slider.min);
55            const max = parseFloat(slider.max);
56
57            val = Math.min(Math.max(val, min), max);
58
59            // write to slider
60            slider.value = val;
61
62            // directly format and write back to the input
63            valueElem.value = format(val);
64            slider.setAttribute('aria-valuenow', val);
65
66            // update the track color
67            setTrackColor();
68        }
69
70        // Track fill color
71        function setTrackColor() {
72            const min = +slider.min || 0;
73            const max = +slider.max || 100;
74            const val = +slider.value;
75            const percent = ((val - min) * 100) / (max - min);
76            slider.style.setProperty('--progress', percent);
77        }
78
79        // Events
80        slider.addEventListener('input', () => {
81            updateFromSlider();
82            setTrackColor();
83        });
84
85        valueElem.addEventListener('blur', updateFromInput);
86        valueElem.addEventListener('change', updateFromInput);
87        valueElem.addEventListener('keydown', (e) => {
88            if (e.key === 'Enter') {
89                e.preventDefault();
90                updateFromInput();
91                valueElem.blur();
92            }
93        });
94
95        // Init
96        updateFromSlider();
97        setTrackColor();
98    }
99
100    // Allow only digits and one comma/dot in text inputs for rates
101    document.querySelectorAll('.repayment-value').forEach((field) => {
102        field.addEventListener('keydown', (e) => {
103            // Allow control keys
104            if (
105                e.key === 'Backspace' ||
106                e.key === 'Delete' ||
107                e.key === 'ArrowLeft' ||
108                e.key === 'ArrowRight' ||
109                e.key === 'Tab'
110            ) {
111                return;
112            }
113
114            // Allow digits
115            if (/[0-9]/.test(e.key)) return;
116
117            // Allow comma or dot, but only one
118            if ((e.key === ',' || e.key === '.') && !field.value.includes(',') && !field.value.includes('.')) {
119                return;
120            }
121
122            // Otherwise block
123            e.preventDefault();
124        });
125
126        field.addEventListener('input', () => {
127            // Remove any characters except digits, comma and dot
128            field.value = field.value.replace(/[^0-9,\.]/g, '');
129        });
130    });
131
132    // Bind all sliders with ARIA support
133    bindSlider('years', 'years-value', 0);        // no decimals, e.g. 20
134    bindSlider('repayment', 'repayment-value', 2); // two decimals, e.g. 3,50
135    bindSlider('interest-rate', 'interest-rate-value', 2); // two decimals, e.g. 3,80
136
137    // --- Format thousands with dot in #amount input only ---
138    const amountInput = document.getElementById('amount');
139    if (amountInput) {
140        // Format input as integer with thousands separator (dot) on every input
141        amountInput.addEventListener('input', function () {
142            // Remove all non-digit characters
143            let raw = this.value.replace(/\D/g, '');
144            // Format with thousands separator (dot) if not empty
145            if (raw) {
146                this.value = formatNumber(raw, 0);
147            } else {
148                this.value = '';
149            }
150        });
151
152        // Block non-digit input (like dot, comma, letters) during typing
153        amountInput.addEventListener('keypress', function (e) {
154            if (e.key && !/[0-9]/.test(e.key)) {
155                e.preventDefault();
156            }
157        });
158    }
159
160    // --- Universal ARIA-valuenow update for all range inputs (in case of dynamic generation) ---
161    // This finds all input[type="range"] with aria-valuenow and updates on input
162    document.querySelectorAll('input[type="range"]').forEach(function (slider) {
163        slider.addEventListener('input', function () {
164            slider.setAttribute('aria-valuenow', slider.value);
165        });
166    });
167
168    // AJAX submit for the calculator form
169    const form = document.querySelector('.isa-calculator__form[data-ajax="1"]');
170    if (!form) return;
171
172    form.addEventListener('submit', function (e) {
173        e.preventDefault();
174
175        const formData = new FormData(form);
176
177        fetch(form.action, {
178            method: 'POST',
179            body: formData,
180            headers: { 'X-Requested-With': 'XMLHttpRequest' }
181        })
182            .then(function (response) { return response.text(); })
183            .then(function (html) {
184                const parser = new DOMParser();
185                const doc = parser.parseFromString(html, 'text/html');
186                const newResult = doc.querySelector('.isa-calculator__result');
187                if (newResult) {
188                    document.querySelector('.isa-calculator__result').replaceWith(newResult);
189                }
190            });
191    });
192});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.