1/** 2 * Magento 3 * 4 * NOTICE OF LICENSE 5 * 6 * This source file is subject to the Academic Free License (AFL 3.0) 7 * that is bundled with this package in the file LICENSE_AFL.txt. 8 * It is also available through the world-wide-web at this URL: 9 * http://opensource.org/licenses/afl-3.0.php 10 * If you did not receive a copy of the license and are unable to 11 * obtain it through the world-wide-web, please send an email 12 * to [email protected] so we can send you a copy immediately. 13 * 14 * DISCLAIMER 15 * 16 * Do not edit or add to this file if you wish to upgrade Magento to newer 17 * versions in the future. If you wish to customize Magento for your 18 * needs please refer to http://www.magentocommerce.com for more information. 19 * 20 * @category design 21 * @package default_default 22 * @copyright Copyright (c) 2014 Magento Inc. (http://www.magentocommerce.com) 23 * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) 24 */ 25 26// TODO refactor this to remove useless code and reduce dependencies 27window.Zoey.require(['Varien', 'jQuery', 'enquire', 'imagesLoaded', 'pix.core', 'pix.globalEvents', 'pix.media'], function(Varien, jQuery, enquire, imagesLoaded, pixCore, globalEvents, media) { 28 29 // ============================================= 30 // Primary Break Points 31 // ============================================= 32 33 // These should be used with the bp (max-width, xx) mixin 34 // where a min-width is used, remember to +1 to break correctly 35 // If these are changed, they must also be updated in _var.scss 36 37 var bp = { 38 xsmall: 479, 39 small: 599, 40 medium: 770, 41 large: 979, 42 xlarge: 1199 43 } 44 45 // ============================================== 46 // jQuery Init 47 // ============================================== 48 49 // Avoid PrototypeJS conflicts, assign jQuery to $j instead of $ 50 var $j = jQuery.noConflict(); 51 52 // Use $j(document).ready() because Magento executes Prototype inline 53 $j(function () { 54 55 // ============================================== 56 // Shared Vars 57 // ============================================== 58 59 // Document 60 var w = $j(window); 61 var d = $j(document); 62 var body = $j('body'); 63 64 // ============================================= 65 // Skip Links 66 // ============================================= 67 68 // var skipContents = $j('.skip-content'); 69 // var skipLinks = $j('.skip-link'); 70 71 // skipLinks.on('click', function (e) { 72 // e.preventDefault(); 73 74 // var self = $j(this); 75 // var target = self.attr('href'); 76 77 // // Get target element 78 // var elem = $j(target); 79 80 // // Check if stub is open 81 // var isSkipContentOpen = elem.hasClass('skip-active') ? 1 : 0; 82 83 // // Hide all stubs 84 // skipLinks.removeClass('skip-active'); 85 // skipContents.removeClass('skip-active'); 86 87 // // Toggle stubs 88 // if (isSkipContentOpen) { 89 // self.removeClass('skip-active'); 90 // } else { 91 // self.addClass('skip-active'); 92 // elem.addClass('skip-active'); 93 // } 94 // }); 95 96 // $j('#header-cart').on('click', '.skip-link-close', function(e) { 97 // var parent = $j(this).parents('.skip-content'); 98 // var link = parent.siblings('.skip-link'); 99 100 // parent.removeClass('skip-active'); 101 // link.removeClass('skip-active'); 102 103 // e.preventDefault(); 104 // }); 105 106 107 // ============================================== 108 // Header Menus 109 // ============================================== 110 111 var nav = $j('#nav'); 112 113 // ---------------------------------------------- 114 // Top Menus 115 116 var MenuManagerState = { 117 TOUCH_SCROLL_THRESHOLD: 20, 118 119 touchStartPosition: null, 120 121 shouldCancelTouch: function() { 122 if(!this.touchStartPosition) { 123 return false;
124 } 125 126 var scroll = $j(window).scrollTop() - this.touchStartPosition; 127 return Math.abs(scroll) > this.TOUCH_SCROLL_THRESHOLD; 128 } 129 }; 130 131 var pointerEvent = 'touchend'; 132 // If device has implemented touch/click agnostic event, use it instead of "click" 133 if (window.navigator.pointerEnabled) { 134 pointerEvent = 'pointerdown'; 135 } else if (window.navigator.msPointerEnabled) { 136 pointerEvent = 'MSPointerDown'; 137 } 138 nav.find('a.has-children.level0').on(pointerEvent,function (event) { 139 //scroll occurred, cancel event 140 if(MenuManagerState.shouldCancelTouch()) { 141 return; 142 } 143 144 // If mouse is being used on large viewport, use native hover state 145 if ( 146 (window.navigator.msPointerEnabled || window.navigator.pointerEnabled) 147 && event.originalEvent.pointerType == 'mouse' 148 && window.matchMedia("screen and (min-width:" + (bp.medium + 1) + "px)") 149 ) { 150 $j(this).data('has-touch', false); 151 return; 152 } 153 $j(this).data('has-touch', true); 154 var elem = $j(this).parent(); 155 156 var alreadyExpanded = elem.hasClass('menu-active'); 157 158 nav.find('li.level0').removeClass('menu-active'); 159 160 // Collapse all active sub-menus 161 nav.find('.sub-menu-active').removeClass('sub-menu-active'); 162 163 if (!alreadyExpanded) { 164 elem.addClass('menu-active'); 165 } 166 167 event.preventDefault(); 168 }).on('click', function (event) { 169 var elem = $j(this); 170 if (elem.data('has-touch')) { 171 elem.data('has-touch', false); 172 event.preventDefault(); 173 return; 174 } 175 176 if(window.matchMedia("screen and (max-width:" + bp.medium + "px)")) { 177 var elem = $j(this).parent(); 178 179 var alreadyExpanded = elem.hasClass('menu-active'); 180 181 nav.find('li.level0').removeClass('menu-active'); 182 183 // Collapse all active sub-menus 184 nav.find('.sub-menu-active').removeClass('sub-menu-active'); 185 186 if (!alreadyExpanded) { 187 elem.addClass('menu-active'); 188 event.preventDefault(); 189 } else { 190 return true; 191 } 192 193 194 } 195 }).on('touchstart', function(event) { 196 $j(this).data('has-touch'); 197 MenuManagerState.touchStartPosition = $j(window).scrollTop(); 198 }); 199 200 // ---------------------------------------------- 201 // Sub Menus 202 203 nav.find('li.level1 a.has-children').on('click', function (e) { 204 //e.preventDefault(); 205 e.stopPropagation(); 206 207 var elem = $j(this).parent(); 208 209 // Check if sub-menu is open 210 var isSubMenuActive = elem.hasClass('sub-menu-active') ? 1 : 0; 211 212 // On smaller screens, allow multiple sibling sub-menus to show at once, 213 // but this is a large touch device, avoid multiple sub-menus showing at once. 214 if (window.matchMedia("screen and (min-width:" + (bp.medium + 1) + "px)")) { 215 elem 216 .siblings('.sub-menu-active') 217 .removeClass('sub-menu-active') 218 .find('.sub-menu-active') 219 .removeClass('sub-menu-active'); 220 } 221 if (isSubMenuActive) { 222 elem.removeClass('sub-menu-active'); 223 } else { 224 elem.addClass('sub-menu-active'); 225 } 226 }); 227 228 // Prevent sub menus from spilling out of the window. 229 function preventMenuSpill() { 230 var windowWidth = $j(window).width(); 231 $j('ul.level0').each(function(){ 232 var ul = $j(this); 233 //Show it long enough to get info, then hide it. 234 ul.addClass('position-test'); 235 ul.removeClass('spill'); 236 var width = ul.outerWidth(); 237 var offset = ul.offset().left; 238 ul.removeClass('position-test'); 239 //Add the spill class if it will spill off the page. 240 if ((offset + width) > windowWidth) { 241 ul.addClass('spill'); 242 } 243 }); 244 } 245 preventMenuSpill(); 246 $j(window).on('delayed-resize', preventMenuSpill); 247 248 // ============================================== 249 // Enquire JS 250 // ============================================== 251 252 enquire.register('screen and (min-width: ' + (bp.medium + 1) + 'px)', { 253 match: function () { 254 $j('.menu-active').removeClass('menu-active');
255 $j('.sub-menu-active').removeClass('sub-menu-active'); 256 $j('.skip-active').removeClass('skip-active'); 257 }, 258 unmatch: function () { 259 $j('.menu-active').removeClass('menu-active'); 260 $j('.sub-menu-active').removeClass('sub-menu-active'); 261 $j('.skip-active').removeClass('skip-active'); 262 } 263 }); 264 265 // ============================================== 266 // UI Pattern - Media Switcher 267 // ============================================== 268 269 // Used to swap primary product photo from thumbnails. 270 271 var mediaListLinks = $j('.media-list').find('a'); 272 var mediaPrimaryImage = $j('.primary-image').find('img'); 273 274 if (mediaListLinks.length) { 275 mediaListLinks.on('click', function (e) { 276 e.preventDefault(); 277 278 var self = $j(this); 279 280 mediaPrimaryImage.attr('src', self.attr('href')); 281 }); 282 } 283 284 // ============================================== 285 // UI Pattern - ToggleSingle 286 // ============================================== 287 288 // Use this plugin to toggle the visibility of content based on a toggle link/element. 289 // This pattern differs from the accordion functionality in the Toggle pattern in that each toggle group acts 290 // independently of the others. It is named so as not to be confused with the Toggle pattern below 291 // 292 // This plugin requires a specific markup structure. The plugin expects a set of elements that it 293 // will use as the toggle link. It then hides all immediately following siblings and toggles the sibling's 294 // visibility when the toggle link is clicked. 295 // 296 // Example markup: 297 // <div class="block"> 298 // <div class="block-title">Trigger</div> 299 // <div class="block-content">Content that should show when </div> 300 // </div> 301 // 302 // JS: jQuery('.block-title').toggleSingle(); 303 // 304 // Options: 305 // destruct: defaults to false, but if true, the plugin will remove itself, display content, and remove event handlers 306 307 308 jQuery.fn.toggleSingle = function (options) { 309 310 // passing destruct: true allows 311 var settings = $j.extend({ 312 destruct: false 313 }, options); 314 315 return this.each(function () { 316 if (!settings.destruct) { 317 $j(this).on('click', function () { 318 $j(this) 319 .toggleClass('active') 320 .next() 321 .toggleClass('no-display'); 322 }); 323 // Hide the content 324 $j(this).next().addClass('no-display'); 325 } else { 326 // Remove event handler so that the toggle link can no longer be used 327 $j(this).off('click'); 328 // Remove all classes that were added by this plugin 329 $j(this) 330 .removeClass('active') 331 .next() 332 .removeClass('no-display'); 333 } 334 335 }); 336 }; 337 338 var toggleElements = { 339 _items: null, 340 init: function(){ 341 this._items = $j( 342 // This selects the menu on the My Account and CMS pages 343 '.col-left-first .block:not(.block-layered-nav) .block-title, ' + 344 '.col-left-first .block-layered-nav .block-subtitle--filter, ' + 345 '.sidebar:not(.col-left-first) .block .block-title' 346 ); 347 return this; 348 }, 349 get: function(){ 350 return this._items; 351 } 352 }; 353 354 // ============================================== 355 // UI Pattern - Toggle Content (tabs and accordions in one setup) 356 // ============================================== 357 358 var initToggleContent = function(root){ 359 root = root || document; 360 $j(root).find('.toggle-content').each(function () { 361 var wrapper = jQuery(this); 362 363 var hasTabs = wrapper.hasClass('tabs'); 364 var hasAccordion = wrapper.hasClass('accordion'); 365 var startOpen = wrapper.hasClass('open'); 366 367 var dl = wrapper.children('dl').first(); 368 var dts = dl.children('dt'); 369 var panes = dl.children('dd'); 370 var groups = [dts, panes]; 371 372 //Create a ul for tabs if necessary. 373 if (hasTabs) { 374 var ul = jQuery('<ul class="toggle-tabs"></ul>'); 375 dts.each(function () { 376 var dt = jQuery(this); 377 var li = jQuery('<li></li>'); 378 li.html(dt.html()); 379 ul.append(li); 380 }); 381 ul.insertBefore(dl); 382 var lis = ul.children(); 383 groups.push(lis); 384 } 385 386 //Add "last" classes. 387 var i; 388 for (i = 0; i < groups.length; i++) {
389 groups[i].last().addClass('last'); 390 } 391 392 function toggleClasses(clickedItem, group) { 393 var index = group.index(clickedItem); 394 var i; 395 for (i = 0; i < groups.length; i++) { 396 groups[i].removeClass('current'); 397 groups[i].eq(index).addClass('current'); 398 } 399 } 400 401 //Toggle on tab (dt) click. 402 dts.on('click', function (e) { 403 //They clicked the current dt to close it. Restore the wrapper to unclicked state. 404 if (jQuery(this).hasClass('current') && wrapper.hasClass('accordion-open')) { 405 wrapper.removeClass('accordion-open'); 406 } else { 407 //They're clicking something new. Reflect the explicit user interaction. 408 wrapper.addClass('accordion-open'); 409 } 410 toggleClasses(jQuery(this), dts); 411 }); 412 413 //Toggle on tab (li) click. 414 if (hasTabs) { 415 lis.on('click', function (e) { 416 toggleClasses(jQuery(this), lis); 417 }); 418 //Open the first tab. 419 lis.eq(0).trigger('click'); 420 } 421 422 //Open the first accordion if desired. 423 if (startOpen) { 424 dts.eq(0).trigger('click'); 425 } 426 427 }); 428 }; 429 initToggleContent(); 430 w.on('ajaxFilterComplete', function(){ 431 var filters = d.find('.block-layered-nav-filters'); 432 433 //toggleSingle for only mobile 434 if(enquire.queries['(max-width: 770px)'].matches()) 435 filters.find('.block-subtitle--filter').toggleSingle(); 436 437 //update toggleElements 438 toggleElements.init(); 439 440 //init toggleContent 441 initToggleContent(filters); 442 }); 443 444 445 // ============================================== 446 // Layered Navigation Block 447 // ============================================== 448 449 // On product list pages, we want to show the layered nav/category menu immediately above the product list. 450 // While it would make more sense to just move the .block-layered-nav block rather than .col-left-first 451 // (since other blocks can be inserted into left_first), it creates simpler code to move the entire 452 // .col-left-first block, so that is the approach we're taking 453 if ($j('.col-left-first > .block').length && $j('.category-products').length) { 454 enquire.register('screen and (max-width: ' + bp.medium + 'px)', { 455 match: function () { 456 $j('.col-left-first').insertBefore($j('.category-products')) 457 }, 458 unmatch: function () { 459 // Move layered nav back to left column 460 $j('.col-left-first').insertBefore($j('.col-main')) 461 } 462 }); 463 } 464 465 // ============================================== 466 // 3 column layout 467 // ============================================== 468 469 // On viewports smaller than 1000px, move the right column into the left column 470 if ($j('.main-container.col3-layout').length > 0) { 471 enquire.register('screen and (max-width: 1000px)', { 472 match: function () { 473 var rightColumn = $j('.col-right'); 474 var colWrapper = $j('.col-wrapper'); 475 476 rightColumn.appendTo(colWrapper); 477 }, 478 unmatch: function () { 479 var rightColumn = $j('.col-right'); 480 var main = $j('.main'); 481 482 rightColumn.appendTo(main); 483 } 484 }); 485 } 486 487 488 // ============================================== 489 // Block collapsing (on smaller viewports) 490 // ============================================== 491 492 enquire.register('(max-width: ' + bp.medium + 'px)', { 493 setup: function () { 494 toggleElements.init(); 495 }, 496 match: function () { 497 toggleElements.get().toggleSingle(); 498 }, 499 unmatch: function () {
500 toggleElements.get().toggleSingle({destruct: true}); 501 } 502 }); 503 504 505 // NOTE: this doesn't do anything. #checkout-progress-wrapper doesn't exist on checkout-onepage-index. remove if not being used 506 // ============================================== 507 // OPC - Progress Block 508 // ============================================== 509 510 if ($j('body.checkout-onepage-index').length) { 511 enquire.register('(max-width: ' + bp.large + 'px)', { 512 match: function () { 513 $j('#checkout-step-review').prepend($j('#checkout-progress-wrapper')); 514 }, 515 unmatch: function () { 516 $j('.col-right').prepend($j('#checkout-progress-wrapper')); 517 } 518 }); 519 } 520 521 522 // ============================================== 523 // Checkout Cart - events 524 // ============================================== 525 526 if ($j('body.checkout-cart-index').length) { 527 // $j('input[name^="cart"]').on('focus change', function () { 528 // $j(this).siblings('button').fadeIn(); 529 // }); 530 531 globalEvents.add('checkout__shoppingCart__updated', function(){ 532 $j('input[name^="cart"]').on('focus change', function () { 533 $j(this).siblings('button').fadeIn(); 534 }); 535 }); 536 } 537 538 // ============================================== 539 // Product Listing - Align action buttons/links 540 // ============================================== 541 542 // Since the number of columns per grid will vary based on the viewport size, the only way to align the action 543 // buttons/links is via JS 544 545 // if ($j('.products-grid').length) { 546 547 // var alignProductGridActions = function () { 548 // var gridRows = []; // This will store an array per row 549 // var tempRow = []; 550 // productGridElements = $j('.products-grid > li'); 551 // productGridElements.each(function (index) { 552 // // The JS ought to be agnostic of the specific CSS breakpoints, so we are dynamically checking to find 553 // // each row by grouping all cells (eg, li elements) up until we find an element that is cleared. 554 // // We are ignoring the first cell since it will always be cleared. 555 // if ($j(this).css('clear') != 'none' && index != 0) { 556 // gridRows.push(tempRow); // Add the previous set of rows to the main array 557 // tempRow = []; // Reset the array since we're on a new row 558 // } 559 // tempRow.push(this); 560 561 // // The last row will not contain any cells that clear that row, so we check to see if this is the last cell 562 // // in the grid, and if so, we add its row to the array 563 // if (productGridElements.length == index + 1) { 564 // gridRows.push(tempRow); 565 // } 566 // }); 567 568 // $j.each(gridRows, function () { 569 // var tallestHeight = 0; 570 // $j.each(this, function () { 571 // // Since this function is called every time the page is resized, we need to remove the min-height 572 // // so each cell can return to its natural size before being measured. 573 // $j(this).find('.product-info').css('min-height', ''); 574 // // We are checking the height of .product-info (rather than the entire li), because the images 575 // // will not be loaded when this JS is run. 576 // elHeight = parseInt($j(this).find('.product-info').css('height')); 577 // if (elHeight > tallestHeight) { 578 // tallestHeight = elHeight; 579 // } 580 // }); 581 // // Set the height of all .product-info elements in a row to the tallest height 582 // $j.each(this, function () { 583 // $j(this).find('.product-info').css('minHeight', tallestHeight); 584 // }); 585 // }); 586 // } 587 // alignProductGridActions(); 588 589 // // Since the height of each cell and the number of columns per page may change when the page is resized, we are 590 // // going to run the alignment function each time the page is resized. 591 // $j(window).on('delayed-resize', function (e, resizeEvent) { 592 // alignProductGridActions(); 593 // }); 594 // } 595 596 // ============================================== 597 // Generic, efficient window resize handler 598 // ============================================== 599 600 // Using setTimeout since Web-Kit and some other browsers call the resize function constantly upon window resizing. 601 var resizeTimer; 602 $j(window).resize(function (e) { 603 clearTimeout(resizeTimer); 604 resizeTimer = setTimeout(function () { 605 $j(window).trigger('delayed-resize', e); 606 }, 250); 607 }); 608 }); 609 610 // ============================================== 611 // PDP - image zoom - needs to be available outside document.ready scope 612 // ============================================== 613 614 var ProductMediaManager = { 615 IMAGE_ZOOM_THRESHOLD: 20, 616 zoomEnabled: window.matchMedia("screen and (min-width:768px)"), 617 // imageWrapper: null, 618 619 _isInit: false, 620 621 isInit: function() { 622 return this._isInit; 623 }, 624 625 destroyZoom: function() { 626 $j('.product-image-gallery .gallery-image').each(function(index, element){ 627 var $img = $j(element); 628 var elevateZoom = $img.data('elevateZoom') 629 if(elevateZoom){ 630 elevateZoom.destroy(); 631 $img.parents('.product-image').removeClass('zoom-available').removeClass('enlarge-available'); 632 } 633 }); 634 635 $j('.zoomContainer').remove(); 636 $j('.product-image-gallery .gallery-image').removeData('elevateZoom'); 637 }, 638 639 createZoom: function(image) { 640 ProductMediaManager.destroyZoom(); 641 var config = image.parents('.product-image').attr('data-zoom-config') ? JSON.parse(image.parents('.product-image').attr('data-zoom-config')) : null; 642 //console.log(config); 643 644 if(!ProductMediaManager.zoomEnabled) { //zoom not enabled 645 return; 646 } 647 648 if(image.length <= 0) { //no image found 649 return; 650 } 651 652 if(!config){ 653 return; 654 } 655 656 // allow click to work even if zoom is disabled. 657 if(config.clickToEnlarge){ 658 // click to enlarge should work even if the naturalWi
658dth and naturalHeight are smaller than the image. 659 // Based on http://www.elevateweb.co.uk/image-zoom/examples#gallery-lightbox 660 // var moreViewsContainer = image.parents('.zoey-product-media').find(".more-views").first(); 661 // var galleryId = moreViewsContainer.attr('id'); 662 // if(!galleryId){ 663 // galleryId = 'zpm_' + Date.now(); 664 // moreViewsContainer.attr('id', galleryId); 665 // } 666 667 // config.gallery = galleryId; 668 // config.galleryActiveClass = 'active'; 669 // config.imageCrossfade = true; 670 671 this.initFancyBox(); 672 673 image.on('click', function(evt){ 674 var startIndex = -1; 675 // capture the src by removing the filestack stuff. 676 var mainSrcToCheck = evt.target.src.split(/https?:\/\//).pop(); 677 var galleryNodeList = evt.target.up('.component-content').querySelectorAll('.thumb-link'); 678 var galleryConfig = []; 679 Array.prototype.slice.call(galleryNodeList).map(function(item) { 680 const dataImageIndex = +item.getAttribute('data-image-index'); 681 const imageConfig = { href: item.getAttribute('data-zoom-image'), title: undefined }; 682 683 // capture the src by removing the filestack stuff. 684 var srcToCheck = imageConfig.href.split(/https?:\/\//).pop(); 685 686 // capture the index of the gallery item it matches the main image. 687 startIndex = mainSrcToCheck === srcToCheck ? dataImageIndex : startIndex; 688 galleryConfig[dataImageIndex] = imageConfig; 689 }); 690 691 if (!~startIndex) { 692 // if image doesn't exist in the gallery then add it first and set the start index. 693 galleryConfig.unshift({ href: image.get(0).src, title: undefined }); 694 startIndex = 0; 695 } 696 697 jQuery.fancyboxPlus(galleryConfig, { 698 index: startIndex, 699 cyclic: galleryConfig.length > 1, 700 padding: 0, 701 }); 702 }); 703 704 image.parents('.product-image').addClass('enlarge-available'); 705 } 706 707 if(!config.enabled){ 708 return; 709 } 710 711 if(image[0].naturalWidth && image[0].naturalHeight) { 712 var widthDiff = image[0].naturalWidth - image.width() - ProductMediaManager.IMAGE_ZOOM_THRESHOLD; 713 var heightDiff = image[0].naturalHeight - image.height() - ProductMediaManager.IMAGE_ZOOM_THRESHOLD; 714 715 if(widthDiff < 0 && heightDiff < 0) { 716 //image not big enough 717 718 image.parents('.product-image').removeClass('zoom-available'); 719 720 return; 721 } else { 722 image.parents('.product-image').addClass('zoom-available'); 723 } 724 } 725 726 if(!config.parentContainer){ 727 config.parentContainer = image.siblings('.zoey-zoom-container-parent').get(0); 728 } 729 730 window.Zoey.require('jQuery.elevateZoom', function() { 731 if(image.elevateZoom !== undefined){ 732 // there are issues with swatches that will call this repeatedly. 733 config.refresh = 1000; 734 image.elevateZoom(config); 735 } 736 }); 737 }, 738 739 initFancyBox: function() { 740 if(!jQuery.fancyboxPlus){ 741 // add fancybox to the page. 742 pixCore.domHelper.get$Body().append('<script type="text/javascript" src="/js/fancybox-plus/dist/jquery.fancybox-plus.min.js"></script>'); 743 pixCore.domHelper.get$Body().append('<link rel="stylesheet" type="text/css" href="/js/fancybox-plus/css/jquery.fancybox-plus.css" />'); 744 } 745 }, 746 747 swapImage: function(targetImage) { 748 targetImage = $j(targetImage); 749 targetImage.addClass('gallery-image'); 750 751 ProductMediaManager.destroyZoom(); 752 753 var imageGallery = $j('.product-image-gallery'); 754 755 if(targetImage[0].complete) { //image already loaded -- swap immediately 756 757 imageGallery.find('.gallery-image').removeClass('visible'); 758 759 //move target image to correct place, in case it's necessary 760 imageGallery.append(targetImage); 761 762 //reveal new image 763 targetImage.addClass('visible'); 764 765 //wire zoom on new image 766 ProductMediaManager.createZoom(targetImage); 767 768 globalEvents.notify('productMediaImageChange', targetImage.get(0)); 769 } else { //need to wait for image to load 770 771 //add spinner 772 imageGallery.addClass('loading'); 773 774 //move target image to correct place, in case it's necessary 775 imageGallery.append(targetImage); 776 777 this.toLoad = this.toLoad || 0; 778 this.toLoad++; 779 var self = this; 780 //wait until image is loaded 781 imagesLoaded(targetImage, function() { 782 if(self.toLoad == this){ 783 //remove spinner 784 imageGallery.removeClass('loading'); 785 786 //hide old image 787 imageGallery.find('.gallery-image').removeClass('visible'); 788 789 //reveal new image 790 targetImage.addClass('visible'); 791 792 //wire zoom on new image 793 ProductMediaManager.createZoom(targetImage); 794 globalEvents.notify('productMediaImageChange', targetImage.get(0)); 795 } 796 }.bind(this.toLoad)); 797 798 } 799 }, 800 801 _galleryUniqueKey: 1, 802 swapGalleryImages: function(images) { 803 const galleryContainer = document.querySelector('.product-image-thumbs'); 804 const mainImageContainer = document.querySelector('.product-image-gallery') 805 806 // empty 807 if (galleryContainer) { 808 galleryContainer.innerHTML = ''; 809 } 810
811 images.forEach((image, index) => { 812 image.__gallery_key = image.__gallery_key || `generated_gallery_key_${this._galleryUniqueKey++}`; 813 814 if (mainImageContainer) { 815 if (!document.getElementById(`image-${image.__gallery_key}`)) { 816 const img = document.createElement('img'); 817 img.setAttribute('id', `image-${image.__gallery_key}`); 818 img.setAttribute('class', 'gallery-image'); 819 img.setAttribute('src', image.main); 820 img.setAttribute('data-zoom-image', image.main); 821 mainImageContainer.appendChild(img); 822 } 823 } 824 825 if (galleryContainer) { 826 const li = document.createElement('li'); 827 const a = document.createElement('a'); 828 a.classList.add('thumb-link'); 829 a.setAttribute('href', '#'); 830 a.setAttribute('title', image.title); 831 a.setAttribute('data-image-index', image.__gallery_key); 832 a.setAttribute('style', `background-image: url(${image.thumbnail});`); 833 a.setAttribute('data-image', image.thumbnail); 834 a.setAttribute('data-zoom-image', image.main); 835 836 li.appendChild(a); 837 galleryContainer.appendChild(li); 838 } 839 }); 840 841 document.dispatchEvent( 842 new CustomEvent('zoey:app:swapGalleryImages', { 843 detail: { 844 items: Array.from(galleryContainer.children) 845 } 846 }) 847 ); 848 849 this.wireThumbnails(); 850 }, 851 852 wireThumbnails: function() { 853 //trigger image change event on thumbnail click 854 $j('.product-image-thumbs .thumb-link').click(function(e) { 855 e.preventDefault(); 856 var jlink = $j(this); 857 var target = $j('#image-' + jlink.data('image-index')); 858 859 ProductMediaManager.swapImage(target); 860 document.dispatchEvent(new CustomEvent('zoey:product-essentials-thumbnail-after-click')); 861 }); 862 }, 863 864 initZoom: function() { 865 ProductMediaManager.createZoom($j(".no-touch .gallery-image.visible")); //set zoom on first image 866 }, 867 868 init: function() { 869 this._isInit = true; 870 // ProductMediaManager.imageWrapper = $j('.product-img-box'); 871 872 enquire.register("screen and (min-width:768px)", { 873 match : function() { 874 ProductMediaManager.zoomEnabled = true; 875 ProductMediaManager.initZoom(); 876 }, 877 unmatch : function() { 878 ProductMediaManager.destroyZoom(); 879 ProductMediaManager.zoomEnabled = false; 880 } 881 }); 882 883 //resizing the window causes problems with zoom -- reinitialize 884 $j(window).on('delayed-resize', function(e, resizeEvent) { 885 ProductMediaManager.destroyZoom(); 886 ProductMediaManager.initZoom(); 887 }); 888 889 ProductMediaManager.wireThumbnails(); 890 891 $j(document).trigger('product-media-loaded', ProductMediaManager); 892 893 // product media download link action 894 $j(document.body).on('click', '.zoey-product-media .download-link', function(e) { 895 // get visible img src 896 var img = $j('.zoey-product-media .product-image-gallery img:visible'); 897 var imgSrc = img.data('dlImage'); 898 899 // set it as href of current link 900 e.target.href = imgSrc; 901 e.target.download = imgSrc; 902 903 setTimeout(function() { 904 // clear href 905 e.target.href = ''; 906 }); 907 }); 908 }, 909 910 getBaseImage: function() { 911 return $j('.product-image-gallery .gallery-image#image-main').eq(0); 912 } 913 }; 914 915 916 globalEvents.add('documentLoaded', function(){ 917 ProductMediaManager.init(); 918 }); 919 920 globalEvents.add('initFancyBox', function(){ 921 ProductMediaManager.initFancyBox(); 922 }); 923 924 // ============================================== 925 // Skip Links actions 926 // ============================================== 927 928 var skipLinks = { 929 COMPONENT_CLASS: 'pix-design-component', 930 HIDDEN_CLASS: 'hidden', 931 CLOSE_CLASS: 'skip-link-close', 932 TOGGLE_ATTR: 'data-toggle', 933 TOGGLE_POSITION_ATTR: 'data-toggle-position', 934 TOGGLE_ALIGN_ATTR: 'data-toggle-align', 935 POSITION_THRESHOLD: 200, 936 937 _isInit: false, 938 939 init: function() { 940 skipLinks.toggleEls = $j('['+skipLinks.TOGGLE_ATTR+']'); 941 942 skipLinks.relocate(); 943 this._mediaChangeListener = function(){ 944 skipLinks.closeAll(); 945 }; 946 this._skipLinkOpenListener = function(evt){ 947 skipLinks.open(this); 948 }; 949 this._skipLinkCloseListener = function() { 950 skipLinks.closeAll(); 951 }; 952 this._skipLinkBodyClickListner = function(evt){ 953 var target = evt.target; 954 955 var canClose = true; 956 if (skipLinks.toggleEls.get().indexOf(target) < 0 && skipLinks.toggleEls.has(target).length === 0) {
957 canClose = skipLinks.toggleEls.get().every(function(el) { 958 var toggle = $j(el).attr(skipLinks.TOGGLE_ATTR); 959 if (toggle) { 960 toggle = toggle.match(/^[.#[]/) ? toggle : '#' + toggle; 961 var $toggleElem = $j(toggle); 962 if ($toggleElem[0] && ($toggleElem[0] === target || $toggleElem.has(target).length)) { 963 return false; 964 } 965 } 966 return true; 967 }); 968 } else { 969 canClose = false; 970 } 971 972 if (canClose) { 973 skipLinks.closeAll(); 974 } 975 }; 976 skipLinks.attachEvents(); 977 this._isInit = true; 978 return this; 979 }, 980 reinit: function() { 981 if (!this._isInit) { 982 return this.init(); 983 } 984 985 skipLinks.dettachEvents(); 986 skipLinks.toggleEls = $j('['+skipLinks.TOGGLE_ATTR+']'); 987 skipLinks.relocate(); 988 skipLinks.attachEvents(); 989 return this; 990 }, 991 attachEvents: function(){ 992 jQuery(window).on('pixMediaChange', this._mediaChangeListener); 993 skipLinks.toggleEls.on('click', this._skipLinkOpenListener); 994 pixCore.domHelper.get$Document().on('click', '.' + skipLinks.CLOSE_CLASS, this._skipLinkCloseListener); 995 globalEvents.add('bodyClick', this._skipLinkBodyClickListner); 996 }, 997 dettachEvents: function() { 998 jQuery(window).off('pixMediaChange', this._mediaChangeListener); 999 skipLinks.toggleEls.off('click', this._skipLinkOpenListener); 1000 pixCore.domHelper.get$Document().off('click', '.' + skipLinks.CLOSE_CLASS, this._skipLinkCloseListener); 1001 globalEvents.remove('bodyClick', this._skipLinkBodyClickListner); 1002 }, 1003 closeAll: function(){ 1004 var self = this; 1005 skipLinks.toggleEls.each(function(index, el){ 1006 var $el = $j('#'+$j(el).attr(skipLinks.TOGGLE_ATTR)); 1007 if ($el.data('skip-link-status') === 'open') 1008 return; 1009 1010 $el.addClass(self.HIDDEN_CLASS).removeAttr('style'); 1011 $el.parents('.'+this.COMPONENT_CLASS).removeAttr('style'); 1012 }); 1013 }, 1014 _setCss: function(el){ 1015 var css = {}; 1016 1017 if(el){ 1018 el.css('position', 'relative'); 1019 1020 var offset = el.offset(); 1021 1022 // css['position'] = 'absolute'; 1023 // css['left'] = '0'; 1024 // css['top'] = '0'; 1025 css['width'] = window.innerWidth+'px'; 1026 css['margin-top'] = -offset.top+'px'; 1027 css['margin-left'] = -offset.left+'px'; 1028 css['min-height'] = '100px'; 1029 css['z-index'] = 999999; 1030 1031 el.css(css); 1032 } 1033 1034 return this; 1035 }, 1036 open: function(el, force){ 1037 var $el = $j(el); 1038 var id = $el.attr(skipLinks.TOGGLE_ATTR); 1039 var togglePosition = $el.attr(skipLinks.TOGGLE_POSITION_ATTR) || 'static'; 1040 var toggleAlign = $el.attr(skipLinks.TOGGLE_ALIGN_ATTR) || 'left'; 1041 var toggleEl = $j('#'+id); 1042 var isHidden = toggleEl.hasClass(this.HIDDEN_CLASS) || force; 1043 1044 skipLinks.closeAll(); 1045 1046 if(toggleEl.length){ 1047 if(isHidden && $el.data('skip-link-status') !== 'close') { 1048 toggleEl.removeClass(this.HIDDEN_CLASS); 1049 1050 if(media.isMobile()){ 1051 1052 if(toggleEl.attr('data-href')){ 1053 window.location = toggleEl.attr('data-href'); 1054 toggleEl.addClass(this.HIDDEN_CLASS); 1055 return; 1056 } 1057 1058 toggleEl.parents('.'+this.COMPONENT_CLASS).css({ 1059 'z-index': 99999999999999, 1060 'position': 'absolute', 1061 'top': '0', 1062 'left': '0' 1063 }); 1064 1065 this._setCss(toggleEl); 1066 } else { 1067 var position = $el.position(); 1068 var offset = $el.offset(); 1069 var bodyWidth = $j(document.body).outerWidth(); 1070 var css = { 1071 zIndex: 99999999999999, 1072 position: togglePosition === 'absolute' ? 'absolute' : 'fixed', 1073 top: offset.top + $el.outerHeight() - (togglePosition === "fixed" ? jQuery(window).scrollTop() : 0) 1074 }; 1075 1076 if(toggleAlign === 'center'){ 1077 css.left = (bodyWidth/2); 1078 } else if(toggleAlign === 'full'){ 1079 css.left = 0; 1080 css.width = '100%'; 1081 } else if(offset.left < bodyWidth/2){ 1082 css.left = offset.left; 1083 } else { 1084 if(toggleAlign === 'left') { 1085 css.left = Math.max(0, offset.left); 1086 } else if(toggleAlign === 'center-toggle'){ 1087 css.left = offset.left + ($el.outerWidth()/2); 1088 css.transform = 'translateX(-50%)'; 1089 } else if( toggleAlign === 'right' ) { 1090 css.right = Math.max(0, bodyWidth - (offset.left + $el.outerWidth())); 1091 } 1092 1093 } 1094 1095 toggleEl.css(css); 1096 1097 if(toggleEl.outerHeight() + toggleEl.position().top > window.innerHeight){ 1098 toggleEl.css({ 1099 top: 'auto', 1100 bottom: '0px' 1101 }); 1102 } 1103 1104 } 1105 1106 } 1107 } 1108 }, 1109 relocate: function(){ 1110 var body = jQuery(document.body); 1111 skipLinks.toggleEls.each(function(){ 1112 var id = $j(this).attr(skipLinks.TOGGLE_ATTR); 1113 body.append(jQuery('#'+id)); 1114 }); 1115 } 1116 }; 1117 1118 globalEvents.add('documentLoaded', function(){ 1119 skipLinks.init(); 1120 }); 1121
1122 window.Zoey.export('skipLinks', skipLinks); 1123 window.Zoey.export('ProductMediaManager', ProductMediaManager); 1124}); 1125 1126window.Zoey.require(['pix.globalEvents'], function(globalEvents) { 1127 // ============================================== 1128 // Qty inputs 1129 // ============================================== 1130 1131 globalEvents.add('documentLoaded', function() { 1132 Array.from(document.querySelectorAll('input[name="qty"]')).forEach(function(el) { 1133 el.addEventListener('blur', function(e) { 1134 let input = e.target; 1135 let value = input.value; 1136 1137 if(value == 0) { 1138 input.placeholder = '0'; 1139 input.value = ''; 1140 } 1141 }) 1142 }) 1143 }); 1144});
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.