1// Mobile Navbar Dropdown Fix for UK and PK Layouts 2// Ensures Bootstrap collapse and accordion work properly on mobile 3 4(function() { 5 'use strict'; 6 7 // Mobile browsers can restore DOM state (bfcache / pageshow) and/or some code may 8 // initialize Bootstrap Collapse with default toggle:true, which can auto-open. 9 // This helper force-closes the mobile nav safely (without blocking user clicks). 10 let userToggledMobileNav = false; 11 let isAutoClosing = false; 12 13 function forceCloseMobileNavbar() { 14 const mobileNavbar = document.getElementById('mobileNavbar'); 15 if (!mobileNavbar) return; 16 17 const menuIcon = document.getElementById('menuIcon'); 18 const closeIcon = document.getElementById('closeIcon'); 19 const toggleButton = document.querySelector('[data-bs-target="#mobileNavbar"]'); 20 21 // If the user already interacted, don't fight them. 22 if (userToggledMobileNav) return; 23 if (isAutoClosing) return; 24 isAutoClosing = true; 25 26 // Prefer Bootstrap API when available. 27 if (window.bootstrap && window.bootstrap.Collapse) { 28 try { 29 const instance = window.bootstrap.Collapse.getOrCreateInstance(mobileNavbar, { toggle: false }); 30 instance.hide(); 31 } catch (e) { 32 // Fall back to class cleanup below. 33 } 34 } 35 36 // Hard cleanup (covers cases where Bootstrap isn't available yet or state is stale). 37 mobileNavbar.classList.remove('show', 'collapsing'); 38 mobileNavbar.classList.add('collapse'); 39 mobileNavbar.style.removeProperty('height'); 40 41 if (toggleButton) { 42 toggleButton.setAttribute('aria-expanded', 'false'); 43 toggleButton.classList.add('collapsed'); 44 } 45 46 if (menuIcon) menuIcon.style.display = 'block'; 47 if (closeIcon) closeIcon.style.display = 'none'; 48 49 // Allow future auto-closes (if another script re-opens during load) 50 setTimeout(() => { isAutoClosing = false; }, 50); 51 } 52 53 function scheduleForceClose() { 54 // Run a few times to beat any late initializers. 55 [0, 50, 150, 350].forEach(ms => setTimeout(forceCloseMobileNavbar, ms)); 56 } 57 58 document.addEventListener('DOMContentLoaded', function() { 59 const mobileNavbar = document.getElementById('mobileNavbar'); 60 const menuIcon = document.getElementById('menuIcon'); 61 const closeIcon = document.getElementById('closeIcon'); 62 const toggleButton = document.querySelector('[data-bs-target="#mobileNavbar"]'); 63 64 if (!mobileNavbar) return; 65 66 // CRITICAL: Ensure mobile navbar is closed by default (and stays closed on refresh) 67 scheduleForceClose(); 68 69 // Initialize icon states 70 if (menuIcon && closeIcon) { 71 menuIcon.style.display = 'block'; 72 closeIcon.style.display = 'none'; 73 } 74 75 // Mark when the user actually toggles the menu (so we don't auto-close afterwards) 76 if (toggleButton) { 77 toggleButton.addEventListener('click', function() { 78 userToggledMobileNav = true; 79 }, { passive: true }); 80 } 81 82 // If ANY script adds `.show` during initial load/refresh, close it immediately. 83 // This is the robust fix for the "menu opens on refresh" issue. 84 try { 85 const observer = new MutationObserver(function(mutations) { 86 if (userToggledMobileNav) return; 87 88 for (const m of mutations) { 89 if (m.type === 'attributes' && m.attributeName === 'class') { 90 if (mobileNavbar.classList.contains('show')) { 91 forceCloseMobileNavbar(); 92 } 93 } 94 } 95 }); 96 97 observer.observe(mobileNavbar, { attributes: true, attributeFilter: ['class'] }); 98 99 // Stop watching after load settles (prevents overhead) 100 setTimeout(() => observer.disconnect(), 2500); 101 } catch (e) { 102 // Ignore if MutationObserver not supported (very old browsers) 103 } 104 105 // Handle Bootstrap collapse events for main menu 106 if (window.bootstrap && window.bootstrap.Collapse) { 107 // Create collapse instance with toggle:false to prevent auto-opening 108 const collapseInstance = window.bootstrap.Collapse.getOrCreateInstance(mobileNavbar, { 109 toggle: false 110 }); 111 112 // Listen for show event 113 mobileNavbar.addEventListener('show.bs.collapse', function() { 114 if (menuIcon) menuIcon.style.display = 'none'; 115 if (closeIcon) closeIcon.style.display = 'block'; 116 if (toggleButton) toggleButton.setAttribute('aria-expanded', 'true'); 117 }); 118 119 // Listen for hide event 120 mobileNavbar.addEventListener('hide.bs.collapse', function() { 121 if (menuIcon) menuIcon.style.display = 'block'; 122 if (closeIcon) closeIcon.style.display = 'none'; 123 if (toggleButton) toggleButton.setAttribute('aria-expanded', 'false'); 124 }); 125 126 // Listen for shown event (after animation completes) 127 mobileNavbar.addEventListener('shown.bs.collapse', function() { 128 // Ensure all accordion items are properly initialized 129 const accordionItems = mobileNavbar.querySelectorAll('.accordion-collapse');
130 accordionItems.forEach(item => { 131 if (window.bootstrap && window.bootstrap.Collapse) { 132 // toggle:false prevents accordion items from auto-opening on init 133 window.bootstrap.Collapse.getOrCreateInstance(item, { toggle: false }); 134 } 135 }); 136 }); 137 } else { 138 // Fallback if Bootstrap is not loaded yet 139 if (toggleButton) { 140 toggleButton.addEventListener('click', function() { 141 userToggledMobileNav = true; 142 setTimeout(function() { 143 const isExpanded = mobileNavbar.classList.contains('show'); 144 if (isExpanded) { 145 if (menuIcon) menuIcon.style.display = 'none'; 146 if (closeIcon) closeIcon.style.display = 'block'; 147 } else { 148 if (menuIcon) menuIcon.style.display = 'block'; 149 if (closeIcon) closeIcon.style.display = 'none'; 150 } 151 }, 10); 152 }); 153 } 154 } 155 156 // Handle nested POS Solutions dropdown 157 const posSolutionsToggle = mobileNavbar.querySelector('.pos-solutions-toggle'); 158 const posSolutionsCollapse = mobileNavbar.querySelector('#mobileCollapsePOS'); 159 if (posSolutionsToggle && posSolutionsCollapse) { 160 if (window.bootstrap && window.bootstrap.Collapse) { 161 window.bootstrap.Collapse.getOrCreateInstance(posSolutionsCollapse, { 162 toggle: false 163 }); 164 } 165 166 posSolutionsToggle.addEventListener('click', function(e) { 167 e.preventDefault(); 168 e.stopPropagation(); 169 170 if (window.bootstrap && window.bootstrap.Collapse) { 171 const collapseInstance = window.bootstrap.Collapse.getInstance(posSolutionsCollapse); 172 if (collapseInstance) { 173 collapseInstance.toggle(); 174 } 175 } else { 176 // Fallback 177 const isExpanded = posSolutionsCollapse.classList.contains('show'); 178 if (isExpanded) { 179 posSolutionsCollapse.classList.remove('show'); 180 posSolutionsToggle.setAttribute('aria-expanded', 'false'); 181 } else { 182 posSolutionsCollapse.classList.add('show'); 183 posSolutionsToggle.setAttribute('aria-expanded', 'true'); 184 } 185 } 186 }); 187 } 188 189 // Ensure accordion buttons work properly 190 const accordionButtons = mobileNavbar.querySelectorAll('.accordion-button[data-bs-toggle="collapse"]'); 191 accordionButtons.forEach(button => { 192 button.addEventListener('click', function(e) { 193 // Prevent default only if Bootstrap is handling it 194 if (!window.bootstrap || !window.bootstrap.Collapse) { 195 e.preventDefault(); 196 const targetId = button.getAttribute('data-bs-target'); 197 if (targetId) { 198 const target = document.querySelector(targetId); 199 if (target) { 200 const isExpanded = target.classList.contains('show'); 201 if (isExpanded) { 202 target.classList.remove('show'); 203 button.classList.add('collapsed'); 204 button.setAttribute('aria-expanded', 'false'); 205 } else { 206 // Close other accordion items in the same parent 207 const parent = button.closest('.accordion'); 208 if (parent) { 209 const otherItems = parent.querySelectorAll('.accordion-collapse.show');
210 otherItems.forEach(item => { 211 if (item !== target) { 212 item.classList.remove('show'); 213 const otherButton = parent.querySelector(`[data-bs-target="#${item.id}"]`); 214 if (otherButton) { 215 otherButton.classList.add('collapsed'); 216 otherButton.setAttribute('aria-expanded', 'false'); 217 } 218 } 219 }); 220 } 221 target.classList.add('show'); 222 button.classList.remove('collapsed'); 223 button.setAttribute('aria-expanded', 'true'); 224 } 225 } 226 } 227 } 228 }); 229 }); 230 231 // Close mobile menu when clicking on links with data-close-mobile-nav 232 const closeLinks = mobileNavbar.querySelectorAll('[data-close-mobile-nav="true"]'); 233 closeLinks.forEach(link => { 234 link.addEventListener('click', function(e) { 235 const href = link.getAttribute('href'); 236 // Don't close for placeholder links 237 if (!href || href === '#' || href === '#!') { 238 return; 239 } 240 241 // Close the mobile menu 242 if (window.bootstrap && window.bootstrap.Collapse) { 243 const collapseInstance = window.bootstrap.Collapse.getInstance(mobileNavbar); 244 if (collapseInstance) { 245 collapseInstance.hide(); 246 } 247 } else { 248 mobileNavbar.classList.remove('show'); 249 if (menuIcon) menuIcon.style.display = 'block'; 250 if (closeIcon) closeIcon.style.display = 'none'; 251 if (toggleButton) toggleButton.setAttribute('aria-expanded', 'false'); 252 } 253 }); 254 }); 255 256 // Close menu when clicking outside 257 document.addEventListener('click', function(event) { 258 if (!mobileNavbar.contains(event.target) && 259 (!toggleButton || !toggleButton.contains(event.target))) { 260 if (mobileNavbar.classList.contains('show')) { 261 if (window.bootstrap && window.bootstrap.Collapse) { 262 const collapseInstance = window.bootstrap.Collapse.getInstance(mobileNavbar); 263 if (collapseInstance) { 264 collapseInstance.hide(); 265 } 266 } else { 267 mobileNavbar.classList.remove('show'); 268 if (menuIcon) menuIcon.style.display = 'block'; 269 if (closeIcon) closeIcon.style.display = 'none'; 270 if (toggleButton) toggleButton.setAttribute('aria-expanded', 'false'); 271 } 272 } 273 } 274 }); 275 276 // Ensure Bootstrap Collapse is initialized for all accordion items 277 function initAccordionCollapses() { 278 if (window.bootstrap && window.bootstrap.Collapse) { 279 const accordionCollapses = mobileNavbar.querySelectorAll('.accordion-collapse'); 280 accordionCollapses.forEach(collapse => { 281 try { 282 window.bootstrap.Collapse.getOrCreateInstance(collapse, { 283 toggle: false 284 }); 285 } catch (e) { 286 console.warn('Could not initialize collapse for:', collapse, e); 287 } 288 }); 289 290 // Also initialize nested POS Solutions collapse 291 const posSolutionsCollapse = mobileNavbar.querySelector('#mobileCollapsePOS'); 292 if (posSolutionsCollapse) { 293 try { 294 window.bootstrap.Collapse.getOrCreateInstance(posSolutionsCollapse, { 295 toggle: false 296 }); 297 } catch (e) { 298 console.warn('Could not initialize POS Solutions collapse:', e); 299 } 300 } 301 } 302 } 303 304 // Initialize accordion collapses after a short delay to ensure Bootstrap is loaded 305 setTimeout(initAccordionCollapses, 100); 306 307 // Re-initialize on window resize (in case Bootstrap loads late) 308 window.addEventListener('load', function() { 309 initAccordionCollapses(); 310 }); 311 });
312 313 // Important for mobile emulation + Safari/iOS bfcache: DOM may be restored without DOMContentLoaded. 314 window.addEventListener('pageshow', function() { 315 userToggledMobileNav = false; 316 scheduleForceClose(); 317 }); 318})(); 319
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.