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.