1/** 2 * International Transactions Fees Calculator (scoped to the specific Sitecore form) 3 * - Attaches only to the submit button inside the uniquely identified form. 4 * - Calculates values and writes into td.result-vars.* cells. 5 * - Never shows the loader; it only ensures the loader stops by removing `.active`. 6 * 7 * Drop this script after the DOM (or in your Sitecore JS include that runs after render). 8 */ 9(function () { 10 'use strict'; 11 12 /* ---------- Loader: ONLY HIDE (remove .active), never show ---------- */ 13 function hideLoader() { 14 var el = document.getElementById('alinmaLoader'); 15 if (!el) return; 16 el.classList.remove('active'); 17 } 18 19 /* ---------- Decimal helpers (BigInt for precise math) ---------- */ 20 21 function normalizeNumberString(s) { 22 if (s === null || s === undefined) return '0'; 23 s = String(s).trim(); 24 if (s === '') return '0'; 25 return s.replace(/,/g, ''); 26 } 27 28 function toMantissaAndScale(input) { 29 var s = normalizeNumberString(input); 30 if (s.indexOf('%') >= 0) s = s.replace('%', ''); 31 if (/[eE]/.test(s)) s = Number(s).toString(); 32 var neg = s.startsWith('-'); 33 if (neg) s = s.slice(1); 34 if (s === '') return { m: 0n, s: 0 }; 35 var parts = s.split('.'); 36 var intPart = parts[0] || '0'; 37 var fracPart = parts[1] || ''; 38 intPart = intPart.replace(/^0+(?!$)/, '') || '0'; 39 var mantissaStr = intPart + fracPart; 40 if (mantissaStr === '') mantissaStr = '0'; 41 var m = BigInt(mantissaStr); 42 if (neg) m = -m; 43 return { m: m, s: fracPart.length }; 44 } 45 46 function pow10BigInt(n) { return 10n ** BigInt(n); } 47 48 function multiplyDecimalObjs(a, b) { return { m: a.m * b.m, s: a.s + b.s }; } 49 50 function addDecimalObjs(a, b) { 51 var s = Math.max(a.s, b.s); 52 var m1 = a.m * pow10BigInt(s - a.s); 53 var m2 = b.m * pow10BigInt(s - b.s); 54 return { m: m1 + m2, s: s }; 55 } 56 57 function roundDecimalObjHalfUp(obj, decimals) { 58 var currentScale = obj.s, m = obj.m; 59 if (currentScale === decimals) return { m: m, s: decimals }; 60 if (currentScale < decimals) return { m: m * pow10BigInt(decimals - currentScale), s: decimals }; 61 var diff = currentScale - decimals; 62 var factor = pow10BigInt(diff); 63 var q = m / factor; 64 var r = m % factor; 65 var absRem = r < 0n ? -r : r; 66 var absFactor = factor < 0n ? -factor : factor; 67 if (absRem * 2n >= absFactor) q = m >= 0n ? q + 1n : q - 1n; 68 return { m: q, s: decimals }; 69 } 70 71 function decimalObjToFormattedString(obj, decimals) { 72 var rounded = roundDecimalObjHalfUp(obj, decimals), m = rounded.m, neg = m < 0n; 73 if (neg) m = -m; 74 var intPart = (m / pow10BigInt(decimals)).toString(); 75 var fracPart = (m % pow10BigInt(decimals)).toString().padStart(decimals, '0'); 76 intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',') || '0'; 77 return (neg ? '-' : '') + (decimals > 0 ? intPart + '.' + fracPart : intPart); 78 } 79 80 /* ---------- DOM scoping: find the unique Sitecore form ---------- */ 81 82 var knownCardValues = [ 83 'infinite-credit-card', 84 'signature-credit-card', 85 'platinum-credit-card', 86 'alinma-alfursan-infinite',
87 'alinma-alfursan-signature' 88 ]; 89 90 function findSitecoreForm() { 91 var forms = Array.from(document.querySelectorAll('form')); 92 for (var i = 0; i < forms.length; i++) { 93 var f = forms[i]; 94 var selects = Array.from(f.querySelectorAll('select')); 95 for (var si = 0; si < selects.length; si++) { 96 var sel = selects[si]; 97 var options = Array.from(sel.options).map(function (o) { return (o.value || o.text || '').trim().toLowerCase(); }); 98 for (var k = 0; k < knownCardValues.length; k++) { 99 if (options.indexOf(knownCardValues[k].toLowerCase()) >= 0) { 100 return { form: f, select: sel }; 101 } 102 } 103 } 104 } 105 return null; 106 } 107 108 var target = findSitecoreForm(); 109 if (!target) { 110 console.warn('Intl Fees Calculator: target form NOT found (no matching card select). Script will not attach.'); 111 return; 112 } 113 var form = target.form; 114 var cardSelect = target.select; 115 116 // find submit button inside that form only 117 var submitBtn = form.querySelector('input[type="submit"], button[type="submit"], input[type="button"].calculate, button.calculate'); 118 if (!submitBtn) { 119 console.warn('Intl Fees Calculator: submit button not found inside the identified form. Script will not attach.'); 120 return; 121 } 122 123 /* ---------- find transaction amount input inside the same form ---------- */ 124 function findTransactionAmountInput(form) { 125 var candidates = Array.from(form.querySelectorAll('input,textarea')); 126 var byName = candidates.find(function (el) { 127 var nm = (el.name || el.id || '').toLowerCase(); 128 return (nm.indexOf('transaction') >= 0 && nm.indexOf('amount') >= 0) || nm.indexOf('transaction-amount') >= 0 || nm.indexOf('amount-usd') >= 0; 129 }); 130 if (byName) return byName; 131 var byTypeNumber = candidates.find(function (el) { return el.type === 'number' && (el.name || el.id); }); 132 if (byTypeNumber) return byTypeNumber; 133 return candidates.find(function (el) { 134 var p = (el.placeholder || '').toLowerCase(); 135 return /amount|usd|transaction/.test(p); 136 }) || candidates.find(function (el) { return el.type === 'text' || el.type === 'number'; }); 137 } 138 139 var txInput = findTransactionAmountInput(form); 140 141 /* ---------- helper to build map of hidden / numeric inputs inside form ---------- */ 142 function buildFieldMap(form) { 143 var map = {}; 144 var inputs = Array.from(form.querySelectorAll('input,select,textarea')); 145 inputs.forEach(function (el) { 146 var key = (el.getAttribute('data-sc-field-name') || el.name || el.id || '').trim().toLowerCase(); 147 if (!key) return; 148 if (!map[key]) map[key] = el; 149 }); 150 inputs.forEach(function (el) { 151 var extra = ((el.name || '') + ' ' + (el.id || '')).trim().toLowerCase(); 152 if (extra) { 153 extra.split(/[\s._-]+/).forEach(function (token) { 154 if (token && !map[token]) map[token] = el; 155 }); 156 } 157 }); 158 return map; 159 } 160 161 /* ---------- find exchange-rate & fee inputs for a card value ---------- */ 162 function findExchangeAndFee(cardValue, fieldMap) { 163 var cardKey = (cardValue || '').toLowerCase(); 164 var exch = null, fee = null; 165 Object.keys(fieldMap).forEach(function (k) { 166 if (!exch && k.indexOf(cardKey) >= 0 && (k.indexOf('exchange') >= 0 || k.indexOf('rate') >= 0)) exch = fieldMap[k]; 167 if (!fee && k.indexOf(cardKey) >= 0 && (k.indexOf('fee') >= 0 || k.indexOf('fees') >= 0 || k.indexOf('transaction') >= 0)) fee = fieldMap[k]; 168 }); 169 if (!exch) { 170 ['exchange-rate', 'exchange_rate', 'exchange', 'exchangerate', 'rate'].forEach(function (tok) { 171 if (!exch && fieldMap[tok]) exch = fieldMap[tok]; 172 }); 173 } 174 if (!fee) { 175 ['international-fee', 'international-fees', 'fee', 'fees', 'transaction-fee', 'transaction-fees'].forEach(function (tok) { 176 if (!fee && fieldMap[tok]) fee = fieldMap[tok]; 177 }); 178 } 179 return { exchange: exch, fee: fee }; 180 } 181 182 /* ---------- result cell selectors (global) ---------- */ 183 var outCardTypeCell = document.querySelector('td.result-vars.card-type'); 184 var outTxUsdCell = document.querySelector('td.result-vars.transaction-amount-usd'); 185 var outExchangeCell = document.querySelector('td.result-vars.exch
185ange-rate'); 186 var outIntlFeesCell = document.querySelector('td.result-vars.international-transactions-fees'); 187 var outTotalSarCell = document.querySelector('td.result-vars.total-transaction-amount-sar'); 188 var outTotalIntlFeesCell = document.querySelector('td.result-vars.total-international-transaction-fees'); 189 var outTotalIncludingCell = document.querySelector('td.result-vars.total-amount-including-fees'); 190 191 function showError(msg) { 192 var existing = form.querySelector('.intl-calc-error'); 193 if (!existing) { 194 existing = document.createElement('div'); 195 existing.className = 'intl-calc-error'; 196 existing.style.color = 'crimson'; 197 existing.style.marginTop = '6px'; 198 submitBtn.parentNode && submitBtn.parentNode.appendChild(existing); 199 } 200 existing.textContent = msg; 201 } 202 203 function clearError() { 204 var existing = form.querySelector('.intl-calc-error'); 205 if (existing) existing.textContent = ''; 206 } 207 208 /* ---------- main calculation handler (only hides loader) ---------- */ 209 function calculateAndShow(evt) { 210 if (evt && typeof evt.preventDefault === 'function') evt.preventDefault(); 211 clearError(); 212 213 try { 214 // card 215 var cardText = ''; 216 var cardVal = ''; 217 if (cardSelect) { 218 cardVal = (cardSelect.value || '').trim(); 219 cardText = (cardSelect.options && cardSelect.options[cardSelect.selectedIndex] && cardSelect.options[cardSelect.selectedIndex].text) || cardVal; 220 } 221 222 // transaction amount USD 223 var txRaw = txInput ? txInput.value : ''; 224 txRaw = normalizeNumberString(txRaw); 225 if (txRaw === '' || isNaN(Number(txRaw))) { 226 showError('Enter a valid transaction amount (USD).'); 227 return; 228 } 229 230 // build field map within form 231 var map = buildFieldMap(form); 232 var found = findExchangeAndFee(cardVal, map); 233 if (!found.exchange) { 234 showError('Exchange rate for selected card not found in this form.'); 235 return; 236 } 237 238 // read numeric strings 239 var exchRaw = (found.exchange.value !== undefined ? found.exchange.value : (found.exchange.getAttribute && found.exchange.getAttribute('value')) || '').toString(); 240 exchRaw = normalizeNumberString(exchRaw); 241 if (exchRaw === '' || isNaN(Number(exchRaw))) { 242 showError('Exchange rate value is invalid.'); 243 return; 244 } 245 246 var feeRaw = found.fee ? (found.fee.value !== undefined ? found.fee.value : (found.fee.getAttribute && found.fee.getAttribute('value')) || '') : ''; 247 feeRaw = normalizeNumberString(feeRaw); 248 if (feeRaw === '' || isNaN(Number(feeRaw))) feeRaw = '0'; 249 250 // Interpret fee 251 var feeNum = Number(feeRaw); 252 var feeFraction = Math.abs(feeNum) > 1 ? (feeNum / 100) : feeNum; 253 254 // Compose decimals & compute 255 var txDec = toMantissaAndScale(txRaw); 256 var exchDec = toMantissaAndScale(exchRaw); 257 var totalTxSARDec = multiplyDecimalObjs(txDec, exchDec); 258 var feeFracDec = toMantissaAndScale(String(feeFraction)); 259 var totalIntlFeesDec = multiplyDecimalObjs(totalTxSARDec, feeFracDec); 260 var totalIncludingDec = addDecimalObjs(totalTxSARDec, totalIntlFeesDec); 261 262 // Format outputs 263 var txUsdStr = decimalObjToFormattedString(txDec, 2); 264 var exchStr = decimalObjToFormattedString(exchDec, 2); 265 var feeDisplayStr = (Math.abs(feeNum) >= 1) 266 ? (Number(feeNum).toLocaleString(undefined, { maximumFractionDigits: 4 }) + '%') 267 : (Number(feeFraction * 100).toLocaleString(undefined, { maximumFractionDigits: 4 }) + '%'); 268 var totalTxSarStr = decimalObjToFormattedString(totalTxSARDec, 2); 269 var totalIntlFeesStr = decimalObjToFormattedString(totalIntlFeesDec, 2); 270 var totalIncludingStr = decimalObjToFormattedString(totalIncludingDec, 2); 271 272 // Write outputs 273 if (outCardTypeCell) outCardTypeCell.textContent = cardText || cardVal; 274 if (outTxUsdCell) outTxUsdCell.textContent = txUsdStr; 275 if (outExchangeCell) outExchangeCell.textContent = exchStr; 276 if (outIntlFeesCell) outIntlFeesCell.textContent = feeDisplayStr; 277 if (outTotalSarCell) outTotalSarCell.textContent = totalTxSarStr; 278 if (outTotalIntlFeesCell) outTotalIntlFeesCell.textContent = totalIntlFeesStr; 279 if (outTotalIncludingCell) outTotalIncludingCell.textContent = totalIncludingStr; 280 281 var resultArea = document.querySelector('.result-area'); 282 if (resultArea) resultArea.style.display = ''; 283 } finally { 284 // Always make sure loader stops in our flow⦠285 hideLoader(); 286 287 // â¦and also after the other jQuery scriptâs 500ms timeout (without interfer
287ing with it). 288 setTimeout(hideLoader, 0); // micro next-tick 289 setTimeout(hideLoader, 600); // after their 500ms timer has fired 290 } 291 } 292 293 // Attach without blocking other site handlers (no propagation changes) 294 submitBtn.addEventListener('click', calculateAndShow); 295 form.addEventListener('submit', calculateAndShow); 296 297 console.info('Intl Fees Calculator attached; loader will be deactivated by this script.'); 298})();
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.