PageSourceSearch

https://www.alinma.com/-/media/Themes/AlinmaNew/Portal/AlinmaNew/S…nternational-transaction-fees-calculator.js

js alinma.com collected 2026-09-24 10:08:48 UTC 12,729 bytes, 298 lines download raw bytes

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.