PageSourceSearch

https://www.nsolf.org/_assets/fd3d67d6f622faf52ec5115b5fc51a95/JavaScript/calculate.js?1790885531

js nsolf.org collected 2026-10-02 13:53:19 UTC 5,321 bytes, 136 lines download raw bytes

1(() => {
2    const form = document.currentScript.closest('form');
3    const formId = form.id;
4
5    // polyfill for `Element.closest` from MDN
6    if (!Element.prototype.matches) {
7        Element.prototype.matches = Element.prototype.msMatchesSelector ||
8            Element.prototype.webkitMatchesSelector;
9    }
10
11    if (!Element.prototype.closest) {
12        Element.prototype.closest = function (s) {
13            var el = this;
14
15            do {
16                if (Element.prototype.matches.call(el, s)) return el;
17                el = el.parentElement || el.parentNode;
18            } while (el !== null && el.nodeType === 1);
19            return null;
20        };
21    }
22
23    function makeValidDollar(dollarAmount) {
24        return dollarAmount.toLocaleString('en', {
25            useGrouping: true,
26            minimumFractionDigits: 2,
27            maximumFractionDigits: 2,
28            style: "currency",
29            currency: "USD"
30        });
31    }
32
33    function roundToTwo(num) {
34        return +num.toLocaleString('en', {
35            useGrouping: false,
36            minimumFractionDigits: 2,
37            maximumFractionDigits: 2
38        });
39    }
40
41    /**
42     * Safer evaluation
43     * @param {String} calculation
44     */
45    function saferEval(calculation) {
46        return math.evaluate(calculation) || 0;
47    }
48
49    const config = {
50        attributes: true,
51        childList: true,
52        subtree: true
53    };
54
55    let calculations = document.getElementsByClassName("calculation");
56    for (var calcIndex = 0; calcIndex < calculations.length; calcIndex++) {
57        const calc = calculations[calcIndex];
58        let result = document.getElementsByClassName('calcResult')[calcIndex];
59        let visibleResult = document.getElementsByClassName('visibleCalcResult')[calcIndex];
60
61        let operands = calc.getElementsByClassName('operand');
62        for (var operandIndex = 0; operandIndex < operands.length; operandIndex++) {
63            const operand = operands[operandIndex];
64            let dataRef = operand.getAttribute('data-ref').split(' ');
65            let refId = dataRef[0];
66
67            // Initial value
68            let ref = document.getElementById(refId);
69            let child = ref.querySelector('[name]');
70
71            operand.textContent = (child && form.elements[child.name] && form.elements[child.name].value) || (ref.type === 'checkbox' && ref.checked && ref.value) || ref.type !== 'checkbox' && ref.value || 0;
72
73            ref.addEventListener('input', function (e) {
74                /** @type {string} */
75                let val = e.target.value || 0;
76                /** @type {HTMLInputElement} */
77                const target = e.target;
78                if (e.target.type === "checkbox") {
79                    val = e.target.checked ? val.replace(/[a-zA-Z]/g) : 0;
80                }
81
82                // check if it's an "other" field
83                if (target.type === 'radio' && target.value === '0') {
84                    operand.textContent = '0' + (target.nextElementSibling.value || 0);
85                } else {
86                    operand.textContent = (typeof val === 'number') ? val : parseFloat(val.replace(/[^\d.]/g, ''));
87                }
88            });
89
90            // Prevent unwanted input
91            ref.addEventListener('keydown', function (e) {
92                if (e.target.type === 'text') {
93                    // prevent keys other than digits and decimal point
94                    // (exclude Backspace and Delete and keyboard shortcuts with the Ctrl key)
95                    if (e.key && (e.key.match(/[^0-9.]/) && !e.ctrlKey &&
96                            e.key != 'Backspace' && e.key != 'Delete' && e.key != 'Tab' && !e.key.includes('Arrow') ||
97                            (e.key == '.' && e.target.value.includes('.')))) { // prevent more than 1 decimal point
98                        e.preventDefault();
99                    }
100                }
101            });
102        }
103
104        // Initial value
105        const evalResult = saferEval(calc.textContent.replace(/[a-zA-Z]/g, '0'));
106        result.value = roundToTwo(evalResult);
107        visibleResult.textContent = visibleResult.hasAttribute('data-format-as-dollar') ? makeValidDollar(evalResult) : roundToTwo(evalResult);
108
109        /**
110         * @param {MutationRecord[]} mutationsList
111         */
112        let mutationCallback = function (mutationsList, observer) {
113            for (var i = 0; i < mutationsList.length; i++) {
114                const mutation = mutationsList[i];
115                if (mutation.type === "childList") {
116                    // don't set value if it contains letters
117                    if (calc.textContent.match(/[A-Za-z]/)) return;
118
119                    const evalResult = saferEval(calc.textContent);
120
121                    result.value = roundToTwo(evalResult);
122                    var event = new Event('input', {
123                        'bubbles': true,
124                        'cancelable': true
125                    });
126                    result.dispatchEvent(event);
127
128                    visibleResult.textContent = visibleResult.hasAttribute('data-format-as-dollar') ? makeValidDollar(evalResult) : roundToTwo(evalResult);
129                }
130            }
131        }
132
133        let mut = new MutationObserver(mutationCallback);
134        mut.observe(calc, config);
135    }
136})();

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.