PageSourceSearch

https://mhaw.nz/scripts/mhf-quantity-selector.js?v=LDA627xAGiVo10vuADwPgMZULO4WmtQkPPK6BY2QNvo

js mhaw.nz collected 2026-09-28 22:47:35 UTC 7,812 bytes, 161 lines download raw bytes

1(function () {
2    'use strict';
3
4    // AB#24083: the pre-payment webhook remains authoritative for existing carts.
5    // This guard gives immediate feedback on product pages and listing cards.
6    if (window.mhfStockGuard) window.mhfStockGuard.dispose();
7    var states = new Map();
8    var pending = new Map();
9    var disposed = false;
10    var store = (window.FC && window.FC.settings && window.FC.settings.storedomain) || 'mhf.foxycart.com';
11
12    function lookup(sku) {
13        if (!pending.has(sku)) {
14            var controller = new AbortController();
15            var timer = setTimeout(function () { controller.abort(); }, 7000);
16            var request = fetch('/umbraco/api/inventorystock?sku=' + encodeURIComponent(sku), {
17                cache: 'no-store', signal: controller.signal
18            }).then(function (response) {
19                if (!response.ok) throw new Error('Stock lookup failed');
20                return response.json();
21            }).finally(function () { clearTimeout(timer); pending.delete(sku); });
22            pending.set(sku, request);
23        }
24        return pending.get(sku);
25    }
26
27    function disable(state, message) {
28        state.available = false;
29        state.link.removeAttribute('href');
30        state.link.setAttribute('aria-disabled', 'true');
31        state.link.setAttribute('role', 'link');
32        state.link.setAttribute('tabindex', '0');
33        state.message.textContent = message;
34        if (state.selector) state.selector.style.display = 'none';
35    }
36
37    function quantitySelector(state, max) {
38        var container = state.selector;
39        if (!container) return;
40        var select = container.querySelector('.mhf-qty-select');
41        var note = container.querySelector('.mhf-quantity-max-note');
42        if (!select) return;
43        var minimum = Math.max(1, Number(state.url.searchParams.get('quantity_min')) || 1);
44        var configured = state.url.searchParams.get('quantity_options');
45        var previous = Number(new URL(state.link.href).searchParams.get('quantity')) || Number(select.value);
46        var choices = configured ? configured.split('|').map(Number)
47            : Array.from(select.options).map(function (option) { return Number(option.value); });
48        if (!configured && !choices.includes(previous)) choices.push(previous);
49        var options = choices.filter(function (n) {
50            return Number.isInteger(n) && n >= minimum && (max === null || n <= max);
51        });
52        if (!options.length) return;
53        select.replaceChildren();
54        options.forEach(function (quantity) {
55            var option = document.createElement('option');
56            option.value = quantity;
57            option.textContent = quantity;
58            select.appendChild(option);
59        });
60        select.value = options.includes(previous) ? previous : options[0];
61        select.onchange = function () {
62            if (!state.available) return;
63            var url = new URL(state.link.href);
64            url.searchParams.set('quantity', select.value);
65            state.link.href = url.toString();
66        };
67        select.onchange();
68        if (note) {
69            note.textContent = max === null ? '' : 'Maximum order quantity: ' + max + '. For larger orders, please contact us.';
70            note.style.display = max === null ? 'none' : '';
71        }
72        container.style.display = '';
73    }
74
75    function check(state) {
76        disable(state, 'Checking availability...');
77        return lookup(state.sku).then(function (data) {
78            if (disposed || !state.link.isConnected) return;
79            if (!data || typeof data.isAvailable !== 'boolean'
80                || String(data.sku).toLowerCase() !== state.sku.toLowerCase()
81                || !(data.quantityMax === null || Number.isInteger(data.quantityMax) && data.quantityMax >= 0)) {
82                throw new Error('Invalid stock response');
83            }
84            if (!data.isAvailable || data.quantityMax === 0) {
85                disable(state, data.outOfStockMessage || 'Currently unavailable');
86                return;
87            }
88            var url = new URL(state.url);
89            var originalMax = Number(url.searchParams.get('quantity_max'));
90            var max = data.quantityMax;
91            if (originalMax > 0) max = max === null ? originalMax : Math.min(originalMax, max);
92            var minimum = Math.max(1, Number(url.searchParams.get('quantity_min')) || 1);
93            if (max !== null && max < minimum) { disable(state, 'Currently unavailable'); return; }
94            if (max !== null) url.searchParams.set('quantity_max', max);
95            var quantity = Math.max(minimum, Number(url.searchParams.get('quantity')) || minimum);
96            url.searchParams.set('quantity', max === null ? quantity : Math.min(quantity, max));
97            state.link.href = url.toString();
98            state.link.removeAttribute('aria-disabled');
99            ['role', 'tabindex'].forEach(function (attribute) {
100                if (state.attributes[attribute] === null) state.link.removeAttribute(attribute);
101                else state.link.setAttribute(attribute, state.attributes[attribute]);
102            });
103            state.available = true;
104            state.message.textContent = '';
105            quantitySelector(state, max);
106        }).catch(function () {
107            if (!disposed) disable(state, 'Stock could not be checked. Please refresh to try again.');
108        });
109    }
110
111    function scan() {
112        if (disposed) return;
113        document.querySelectorAll('a[href]').forEach(function (link) {
114            if (states.has(link)) return;
115            var url;
116            try { url = new URL(link.href); } catch (_) { return; }
117            if (url.hostname !== store || !/^\/cart\/?$/.test(url.pathname)
118                || !url.searchParams.has('name')) return;
119            var sku = (url.searchParams.get('code') || '').trim();
120            var info = link.closest('.shop_product-info');
121            var message = document.createElement('span');
122            message.className = 'mhf-stock-status';
123            message.setAttribute('role', 'status');
124            message.style.display = 'block';
125            link.insertAdjacentElement('afterend', message);
126            var state = { link: link, url: url, sku: sku, message: message, available: false,
127                selector: info && info.querySelector('.mhf-quantity-selector'),
128                attributes: { role: link.getAttribute('role'), tabindex: link.getAttribute('tabindex') } };
129            states.set(link, state);
130            if (sku) check(state);
131            else disable(state, 'Stock could not be checked. Please contact us.');
132        });
133    }
134
135    function guard(event) {
136        var link = event.target.closest && event.target.closest('a');
137        var state = states.get(link);
138        if (state && !state.available) {
139            event.preventDefault();
140            event.stopImmediatePropagation();
141        }
142    }
143    function refresh() {
144        states.forEach(function (state, link) {
145            if (!link.isConnected) states.delete(link);
146            else if (state.sku) check(state);
147        });
148        scan();
149    }
150    document.addEventListener('click', guard, true);
151    window.addEventListener('pageshow', refresh);
152    var observer = new MutationObserver(scan);
153    observer.observe(document.body, { childList: true, subtree: true });
154    window.mhfStockGuard = { dispose: function () {
155        disposed = true;
156        observer.disconnect();
157        document.removeEventListener('click', guard, true);
158        window.removeEventListener('pageshow', refresh);
159    } };
160    scan();
161}());

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.