PageSourceSearch

https://www.dustbane.ca/assets/js/category-nav.js

js dustbane.ca collected 2026-10-04 11:00:29 UTC 6,358 bytes, 128 lines download raw bytes

1(function () {
2    function setExpanded(btn, el, expanded) {
3        if (btn) btn.setAttribute('aria-expanded', String(expanded));
4        if (el) el.setAttribute('aria-hidden', String(!expanded));
5        if (el) { if (expanded) el.classList.remove('is-hidden'); else el.classList.add('is-hidden'); }
6    }
7
8    function openMenuById(id) {
9        var el = document.getElementById(id);
10        if (!el) return false;
11        var btn = document.querySelector('.submenu-toggle[data-target="' + id + '"]');
12        setExpanded(btn, el, true);
13        return true;
14    }
15
16    document.addEventListener('DOMContentLoaded', function () {
17        document.querySelectorAll('.submenu-toggle').forEach(function (btn) {
18            btn.addEventListener('click', function (e) {
19                e.preventDefault();
20                e.stopPropagation();
21                var id = this.dataset.target;
22                var el = document.getElementById(id);
23                var expanded = this.getAttribute('aria-expanded') === 'true';
24                setExpanded(this, el, !expanded);
25            });
26
27            // keyboard activation for Enter / Space
28            btn.addEventListener('keydown', function (e) {
29                if (e.key === 'Enter' || e.key === ' ') {
30                    e.preventDefault();
31                    this.click();
32                }
33            });
34        });
35
36        // Close all submenus on Escape
37        document.addEventListener('keydown', function (e) {
38            if (e.key === 'Escape') {
39                document.querySelectorAll('.submenu-toggle').forEach(function (b) {
40                    var id = b.dataset.target;
41                    var el = document.getElementById(id);
42                    setExpanded(b, el, false);
43                });
44            }
45        });
46
47        try {
48            var path = window.location.pathname || '';
49            var parts = path.split('/').filter(function (p) { return p.length > 0; });
50            // Expecting URIs like /products/* — skip 'products' and the first nesting segment
51            if (parts.length && parts[0] === 'products') {
52                var slugs = parts.slice(2);
53                // build cumulative ids and open ancestor menus only (do not auto-open final slug)
54                var cumulative = '';
55                if (slugs.length > 1) {
56                    for (var i = 0; i < slugs.length - 1; i++) {
57                        var raw = slugs[i];
58                        if (!raw) continue;
59                        var slug = decodeURIComponent(raw).replace(/[^a-zA-Z0-9_-]/g, '');
60                        if (!slug) continue;
61                        cumulative = cumulative ? cumulative + '-' + slug : slug;
62                        openMenuById('cat-' + cumulative);
63                    }
64                }
65
66                // mark active link for the deepest slug and highlight ancestors — match by full cumulative path suffix
67                if (slugs.length) {
68                    // build the path suffix to match e.g. /floor-machines/accessories
69                    var pathSuffix = '/' + slugs.join('/');
70                    var anchors = document.querySelectorAll('nav[data-scoped-menu] a[href]');
71                    var matched = null;
72                    anchors.forEach(function (a) {
73                        try {
74                            var u = new URL(a.href, window.location.origin);
75                            var p = u.pathname.replace(/\/$/, '');
76                            if (p.endsWith(pathSuffix)) {
77                                matched = a;
78                            }
79                        } catch (e) { }
80                    });
81                    if (matched) {
82                        matched.classList.add('is-active');
83                        matched.setAttribute('aria-current', 'page');
84                        var li = matched.closest('li');
85                        if (li) li.classList.add('is-active');
86
87                        // ensure the matched item's own submenu (if any) remains closed
88                        if (li) {
89                            var childToggle = li.querySelector('.submenu-toggle');
90                            if (childToggle) {
91                                var childId = childToggle.dataset.target;
92                                var childEl = document.getElementById(childId);
93                                setExpanded(childToggle, childEl, false);
94                            }
95                        }
96
97                        // mark ancestor slugs by walking the cumulative path and marking the corresponding toggles/li
98                        var cum = '';
99                        for (var i = 0; i < slugs.length - 1; i++) {
100                            var anc = decodeURIComponent(slugs[i]).replace(/[^a-zA-Z0-9_-]/g, '');
101                            if (!anc) continue;
102                            cum = cum ? cum + '-' + anc : anc;
103                            var ancBtn = document.querySelector('.submenu-toggle[data-target="cat-' + cum + '"]');
104                            if (ancBtn) {
105                                ancBtn.classList.add('is-active');
106                                var ancLi = ancBtn.closest('li');
107                                if (ancLi) ancLi.classList.add('is-active');
108                                // also mark the ancestor link (if present) as active so it inherits the same text styling
109                                var ancLink = ancLi ? ancLi.querySelector('a[href]') : null;
110                                if (ancLink) ancLink.classList.add('is-active');
111                            } else {
112                                // fallback: find an anchor that contains the ancestor slug and mark it active
113                                var ancA = document.querySelector('nav[data-scoped-menu] a[href*="/' + anc + '"]');
114                                if (ancA) {
115                                    ancA.classList.add('is-active');
116                                    var ancLi2 = ancA.closest('li');
117                                    if (ancLi2) ancLi2.classList.add('is-active');
118                                }
119                            }
120                        }
121                    }
122                }
123            }
124        } catch (err) {
125            console.error('menu expand error', err);
126        }
127    });
128})();

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.