1Shopify.MegaMenu = {}; 2 3Shopify.LoadMegaMenu = function(){ 4 this.navigation = []; 5 this.layouts = []; 6 this.templates = {}; 7 this.products = []; 8 9 this.selectors = { 10 menuLayoutsData: '[data-mega-menu]', 11 navigationData: '#Navigation > p', 12 htmlTemplates: '[data-template-for]' 13 }; 14 15 Shopify.loadElements(this,this.selectors); 16 17 this.menuLayoutsData = this.menuLayoutsData.innerHTML; 18 var navData = [] 19 document.querySelectorAll("#Navigation > p").forEach(el => { 20 navData = [ 21 ...navData, 22 ...el.children 23 ] 24 }) 25 this.navigationData = navData; 26 this.navigationLinks = Array.from(this.navigationData); 27 28 Shopify.MegaMenu = { 29 layouts: [] 30 } 31 32 this.parseNavLinks(); 33 this.parseMegaMenu(); 34 this.parseHtmlTemplates(); 35 this.buildMenuLayouts(); 36 this.loadMenuProducts(); 37 38}; 39 40Shopify.LoadMegaMenu.prototype = { 41 42 parseNavLinks: function(){ 43 const self = this; 44 45 self.navigationLinks.forEach((item) => { 46 47 var group = { 48 title: item.dataset.title, 49 level: parseInt(item.dataset.level), 50 url: item.dataset.ref, 51 hash: item.dataset.ref == '#' ? '#' : '', 52 handle: '', 53 parentHandle: '', 54 childLinks: [] 55 }; 56 57 group.handle = item.dataset && item.dataset.handle ? item.dataset.handle : item.innerText.toLowerCase().replaceAll(' ','-').replaceAll('#',''); 58 59 let index = parseInt(item.dataset.level); 60 let lastNavItemIndex = self.navigation.length-1; 61 switch(index) { 62 case 1: 63 self.navigation.push(group); 64 break; 65 case 2: 66 group.parentHandle = self.navigation[(lastNavItemIndex)].handle; 67 self.navigation[(lastNavItemIndex)].childLinks.push(group); 68 break; 69 case 3: 70 group.parentHandle = self.navigation[(lastNavItemIndex)].childLinks[(self.navigation[(lastNavItemIndex)].childLinks.length-1)].handle; 71 self.navigation[(lastNavItemIndex)].childLinks[(self.navigation[(lastNavItemIndex)].childLinks.length-1)].childLinks.push(group); 72 break; 73 default: 74 // code block 75 } 76 }); 77 78 }, 79 80 parseMegaMenu: function(){ 81 const self = this; 82 83 const menu = JSON.parse(self.menuLayoutsData); 84 menu.forEach((item)=>{ 85 if (item.menu_handle.length > 0){ 86 Shopify.MegaMenu.layouts.push(item); 87 } 88 }); 89 }, 90 91 parseHtmlTemplates: function(){ 92 const self = this; 93
94 self.htmlTemplates.forEach((item) => { 95 if (item.dataset.templateFor.includes('mega-menu')){ 96 self.templates[item.dataset.templateName] = { 97 html: item.innerHTML, 98 blocks: item.dataset.blocks.split(',') 99 }; 100 } 101 }); 102 103 }, 104 105 loadMenuProducts: async function(){ 106 const self = this; 107 let batchArray = []; 108 109 Shopify.MegaMenu.layouts.forEach(async (menu) => { 110 111 if (menu && menu.images){ 112 menu.images.map( async (blocks) => { 113 if (blocks.product.length > 0){ 114 batchArray.push(self.loadProductData(blocks.product)); 115 } 116 }); 117 } 118 119 }); 120 121 if (batchArray.length > 0){ 122 await Promise.all(batchArray) 123 .then(function() { 124 125 self.buildProductLayouts(); 126 127 }).catch(function(error) { 128 console.log(error); 129 }); 130 } 131 132 }, 133 134 buildGridItem: function(data){ 135 const self = this; 136 let template = {}; 137 138 template = Object.assign({},self.templates['product-item']); 139 140 return self.setTemplateVariables(template,data.product); 141 142 }, 143 144 buildMenuLayouts: function(){ 145 const self = this; 146 Shopify.MegaMenu.layouts.forEach((item)=>{ 147 148 item.template = Object.assign({}, self.templates[item.layout]); 149 150 if (!item.navList){ 151 item.navList = self.navigation.find((list)=>{ 152 if (list.handle == item.menu_handle){ 153 return list; 154 } 155 }); 156 } 157 158 item.template.blocks.forEach((block)=>{ 159 switch(block) { 160 case 'navlist': 161 item.template.html = item.template.html.replaceAll(`{${block}}`,self.buildNavlist(item)); 162 break; 163 case 'images': 164 item.template.html = item.template.html.replaceAll(`{${block}}`,self.buildImages(item)); 165 break; 166 default: 167 item.template.html = item.template.html.replaceAll(`{${block}}`,item[block]); 168 break; 169 } 170 }); 171 172 }); 173 174 175 }, 176 177 buildProductLayouts: function(){ 178 const self = this; 179 180 Shopify.MegaMenu.layouts.forEach((menu) => { 181 182 if (menu && menu.images){ 183 menu.images.map((block) => { 184 if (block.product.length > 0){ 185 let product = Object.assign(block, self.products.find((item)=> item.handle == block.product)); 186 187 console.log("product", product); 188 console.log("menu", menu); 189 console.log("block", block); 190 if (product){ 191 if (menu.icon) { 192 product.title = `${menu.icon} ${product.title}`; 193 } 194 let productGridItem = self.buildProductGridItem(product); 195 196 console.log("productGridItem", productGridItem); 197 menu.template.html = menu.template.html.replace('<div data-menu-product></div>',productGridItem); 198 199 const updateMegaMenu = new CustomEvent('update:mega-menu', { 200 bubbles: true, 201 detail: { handle: menu.menu_handle } 202 }); 203 document.dispatchEvent(updateMegaMenu); 204 } 205 206 } 207 }); 208 } 209 210 }); 211 }, 212 213 buildProductGridItem: function(product){ 214 const self = this; 215 let template = Object.assign({}, self.templates['product-item']); 216 217 template.blocks.forEach((block)=>{ 218 switch(block) { 219 case 'navlist': 220 template.html = template.html.replaceAll(`{${block}}`,self.buildNavlist(product)); 221 break; 222 case 'images': 223 template.html = template.html.replaceAll(`{${block}}`,self.buildImages(product)); 224 break; 225 case 'price': 226 template.html = template.html.replaceAll(`{${block}}`,Shopify.formatMoney(product[block])); 227 break; 228 default: 229 template.html = template.html.replaceAll(`{${block}}`,product[block]); 230 break; 231 } 232 }); 233 234 return template.html; 235 }, 236 237 loadProductData: async function(handle){ 238 const self = this; 239 240 return fetch(`${routes.product_url.replace('[handle]',handle)}`) 241 .then((response) => { 242 return response.text(); 243 }) 244 .then((data) => { 245 self.products.push(JSON.parse(data)); 246 return; 247 }).catch(()=>{ 248 return ''; 249 });
250 251 }, 252 253 buildImages: function(item){ 254 const self = this; 255 let imageGrid = ''; 256 257 const imageItemTemplate = Object.assign({}, self.templates['image-item']); 258 const emptyProductTemplate = '<div data-menu-product></div>' 259 260 item.images.forEach((imageItem)=>{ 261 262 if (imageItem.product.length == 0){ 263 imageGrid = imageGrid + self.setTemplateVariables(imageItemTemplate,imageItem); 264 } 265 else { 266 imageGrid = imageGrid + emptyProductTemplate; 267 } 268 269 }); 270 271 return imageGrid; 272 }, 273 274 setTemplateVariables: function(template,item){ 275 let output = template.html; 276 277 template.blocks.forEach((block)=>{ 278 output = output.replaceAll(`{${block}}`,item[block]); 279 }); 280 281 return output; 282 }, 283 284 buildNavlist: function(item){ 285 const self = this; 286 let output = ''; 287 let navlistTemplate = Object.assign({},self.templates['navlist']); 288 let navItemTemplate = Object.assign({},self.templates['navItem']); 289 290 if (item.navList && item.navList.childLinks.length > 0){ 291 item.navList.childLinks.forEach((listItem)=>{ 292 let navItemsOutput = ''; 293 let navlistOutput = navlistTemplate.html; 294 295 navItemsOutput = navItemsOutput + self.setTemplateVariables(navItemTemplate,listItem); 296 297 listItem.childLinks.forEach((linkItem)=>{ 298 if (item.icon) { 299 linkItem.title = `${item.icon} ${linkItem.title}`; 300 } 301 navItemsOutput = navItemsOutput + self.setTemplateVariables(navItemTemplate,linkItem); 302 }); 303 304 navlistOutput = navlistOutput.replace('{items}',navItemsOutput); 305 output = output + navlistOutput; 306 }); 307 } 308 output = output.replaceAll(`{handle}`,item.handle); 309 return output; 310 }, 311 312}; 313 314 315class MegaMenu extends HTMLElement { 316 constructor() { 317 super(); 318 this.menus = []; 319 320 this.selectors = { 321 menuItems: '.mega-menu-btn, .mega-menu-link', 322 menuDropdowns: '.mega-menu-dropdown', 323 menuToggles: '[aria-expanded]' 324 }; 325 326 Shopify.loadElements(this,this.selectors,this); 327 this.menuDropdowns = Array.from(this.menuDropdowns); 328 329 this.bindElements(); 330 this.buildMenus(); 331 this.bindEvents(); 332 } 333 334 bindEvents(){ 335 const self = this; 336 337 document.addEventListener('update:mega-menu',(evt)=>{ 338 let handle = evt.detail.handle; 339 console.log('self.menus', self.menus); 340 console.log('handle', handle); 341 let menu = self.menus.find((menu)=> menu.handle == handle); 342 console.log('menu2', menu); 343 if(menu){ 344 self.updateMenu(menu); 345 } 346 else { 347 console.log('Unable to match menu handle, please check all menuItem selectors are set') 348 } 349 350 }) 351 352 this.addEventListener('keydown', (event)=>{this.triggerKeydownHandler(event)}); 353 } 354 355 triggerKeydownHandler(event){ 356 self = this; 357 if (event.keyCode === 13) { 358 if (event.target.hasAttribute('aria-expanded')){ 359 self.closeOtherMenus(event.target); 360 event.target.setAttribute('aria-expanded',!(event.target.getAttribute('aria-expanded') == 'true')); 361 } 362 } 363 if(event.key == 'Escape') { 364 self.closeOtherMenus(); 365 event.target.closest('.mega-menu-item').nextElementSibling.focus(); 366 } 367 } 368 369 closeOtherMenus(current){ 370 for (var i = this.menuToggles.length - 1; i >= 0; i--) { 371 let toggle = this.menuToggles[i]; 372 if (current && current == toggle){ 373 374 } 375 else { 376 toggle.setAttribute('aria-expanded',false); 377 } 378 379 } 380 } 381 382 bindElements(){ 383 const self = this; 384 let handleCounts = {}; 385 386 this.menuItems.forEach((item)=>{ 387 let key = item.dataset.menu; 388 let title = item.dataset.item; 389 let scope = item.closest('.mega-menu-item'); 390 let dropdown = scope ? scope.querySelector('.mega-menu-dropdown') : undefined; 391 if (dropdown && dropdown.dataset.menu !== key){ 392 console.log('Mismatched mega-menu dropdown for item data-menu='+key); 393 dropdown = undefined; 394 } 395 let handleIndex = handleCounts[title] || 0; 396 handleCounts[title] = handleIndex + 1; 397 this.menus.push( { 398 handle: title, 399 handleIndex: handleIndex, 400 button: this.menuItems, 401 dropdown: dropdown 402 }); 403 });
404 } 405 406 buildMenus(handle){ 407 const self = this; 408 409 self.menus.forEach((menu)=>{ 410 self.updateMenu(menu); 411 }); 412 413 } 414 415 updateMenu(menu){ 416 const self = this; 417 418 let matches = Shopify.MegaMenu.layouts.filter((item)=> { 419 return item.menu_handle == menu.handle; 420 }); 421 let layout = matches[menu.handleIndex] || matches[0]; 422 if (layout && layout.template){ 423 if (menu.dropdown){ 424 menu.dropdown.innerHTML = layout.template.html; 425 setTimeout(function(){ 426 menu.dropdown.classList.remove('hidden'); 427 }, 500); 428 } 429 else { 430 console.log('unable to find menu dropdown + data-menu='+menu.handle); 431 } 432 433 } 434 } 435 436} 437 438new Shopify.LoadMegaMenu(); 439 440customElements.define('mega-menu', MegaMenu); 441 442
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.