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.magento.com for more information. 19 * 20 * @category design 21 * @package rwd_default 22 * @copyright Copyright (c) 2006-2016 X.commerce, Inc. and affiliates (http://www.magento.com) 23 * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) 24 */ 25 26// ============================================= 27// Primary Break Points 28// ============================================= 29 30// These should be used with the bp (max-width, xx) mixin 31// where a min-width is used, remember to +1 to break correctly 32// If these are changed, they must also be updated in _var.scss 33 34var bp = { 35 xsmall: 479, 36 small: 599, 37 medium: 770, 38 large: 979, 39 xlarge: 1199 40}; 41 42// ============================================== 43// Search 44// ============================================== 45 46/** 47 * Implements a custom validation style for the search form. When the form is invalidly submitted, the validation-failed 48 * class gets added to the input, but the "This is a required field." text does not display 49 */ 50Varien.searchForm.prototype.initialize = function (form, field, emptyText) { 51 this.form = $(form); 52 this.field = $(field); 53 this.emptyText = emptyText; 54 55 Event.observe(this.form, 'submit', this.submit.bind(this)); 56 Event.observe(this.field, 'change', this.change.bind(this)); 57 Event.observe(this.field, 'focus', this.focus.bind(this)); 58 Event.observe(this.field, 'blur', this.blur.bind(this)); 59 this.blur(); 60}; 61 62Varien.searchForm.prototype.submit = function (event) { 63 if (this.field.value == this.emptyText || this.field.value == ''){ 64 Event.stop(event); 65 this.field.addClassName('validation-failed'); 66 this.field.focus(); 67 return false; 68 } 69 return true; 70}; 71 72Varien.searchForm.prototype.change = function (event) { 73 if ( 74 this.field.value != this.emptyText 75 && this.field.value != '' 76 && this.field.hasClassName('validation-failed') 77 ) { 78 this.field.removeClassName('validation-failed'); 79 } 80}; 81 82Varien.searchForm.prototype.blur = function (event) { 83 if (this.field.hasClassName('validation-failed')) { 84 this.field.removeClassName('validation-failed'); 85 } 86}; 87 88// ============================================== 89// Pointer abstraction 90// ============================================== 91 92/** 93 * This class provides an easy and abstracted mechanism to determine the 94 * best pointer behavior to use -- that is, is the user currently interacting 95 * with their device in a touch manner, or using a mouse. 96 * 97 * Since devices may use either touch or mouse or both, there is no way to 98 * know the user's preferred pointer type until they interact with the site. 99 * 100 * To accommodate this, this class provides a method and two events 101 * to determine the user's preferred pointer type. 102 * 103 * - getPointer() returns the last used pointer type, or, if the user has 104 * not yet interacted with the site, falls back to a Modernizr test. 105 * 106 * - The mouse-detected event is triggered on the window object when the user 107 * is using a mouse pointer input, or has switched from touch to mouse input. 108 * It can be observed in this manner: $j(window).on('mouse-detected', function(event) { // custom code }); 109 * 110 * - The touch-detected event is triggered on the window object when the user 111 * is using touch pointer input, or has switched from mouse to touch input. 112 * It can be observed in this manner: $j(window).on('touch-detected', function(event) { // custom code }); 113 */ 114var PointerManager = { 115 MOUSE_POINTER_TYPE: 'mouse', 116 TOUCH_POINTER_TYPE: 'touch', 117 POINTER_EVENT_TIMEOUT_MS: 500, 118 standardTouch: false, 119 touchDetectionEvent: null, 120 lastTouchType: null, 121 pointerTimeout: null, 122 pointerEventLock: false, 123 124 getPointerEventsSupported: function() { 125 return this.standardTouch; 126 }, 127 128 getPointerEventsInputTypes: function() { 129 if (window.navigator.pointerEnabled) { //IE 11+ 130 //return string values from http://msdn.microsoft.com/en-us
130/library/windows/apps/hh466130.aspx 131 return { 132 MOUSE: 'mouse', 133 TOUCH: 'touch', 134 PEN: 'pen' 135 }; 136 } else if (window.navigator.msPointerEnabled) { //IE 10 137 //return numeric values from http://msdn.microsoft.com/en-us/library/windows/apps/hh466130.aspx 138 return { 139 MOUSE: 0x00000004, 140 TOUCH: 0x00000002, 141 PEN: 0x00000003 142 }; 143 } else { //other browsers don't support pointer events 144 return {}; //return empty object 145 } 146 }, 147 148 /** 149 * If called before init(), get best guess of input pointer type 150 * using Modernizr test. 151 * If called after init(), get current pointer in use. 152 */ 153 getPointer: function() { 154 // On iOS devices, always default to touch, as this.lastTouchType will intermittently return 'mouse' if 155 // multiple touches are triggered in rapid succession in Safari on iOS 156 if(Modernizr.ios) { 157 return this.TOUCH_POINTER_TYPE; 158 } 159 160 if(this.lastTouchType) { 161 return this.lastTouchType; 162 } 163 164 return Modernizr.touch ? this.TOUCH_POINTER_TYPE : this.MOUSE_POINTER_TYPE; 165 }, 166 167 setPointerEventLock: function() { 168 this.pointerEventLock = true; 169 }, 170 clearPointerEventLock: function() { 171 this.pointerEventLock = false; 172 }, 173 setPointerEventLockTimeout: function() { 174 var that = this; 175 176 if(this.pointerTimeout) { 177 clearTimeout(this.pointerTimeout); 178 } 179 180 this.setPointerEventLock(); 181 this.pointerTimeout = setTimeout(function() { that.clearPointerEventLock(); }, this.POINTER_EVENT_TIMEOUT_MS); 182 }, 183 184 triggerMouseEvent: function(originalEvent) { 185 if(this.lastTouchType == this.MOUSE_POINTER_TYPE) { 186 return; //prevent duplicate events 187 } 188 189 this.lastTouchType = this.MOUSE_POINTER_TYPE; 190 $j(window).trigger('mouse-detected', originalEvent); 191 }, 192 triggerTouchEvent: function(originalEvent) { 193 if(this.lastTouchType == this.TOUCH_POINTER_TYPE) { 194 return; //prevent duplicate events 195 } 196 197 this.lastTouchType = this.TOUCH_POINTER_TYPE; 198 $j(window).trigger('touch-detected', originalEvent); 199 }, 200 201 initEnv: function() { 202 if (window.navigator.pointerEnabled) { 203 this.standardTouch = true; 204 this.touchDetectionEvent = 'pointermove'; 205 } else if (window.navigator.msPointerEnabled) { 206 this.standardTouch = true; 207 this.touchDetectionEvent = 'MSPointerMove'; 208 } else { 209 this.touchDetectionEvent = 'touchstart'; 210 } 211 }, 212 213 wirePointerDetection: function() { 214 var that = this; 215 216 if(this.standardTouch) { //standard-based touch events. Wire only one event. 217 //detect pointer event 218 $j(window).on(this.touchDetectionEvent, function(e) { 219 switch(e.originalEvent.pointerType) { 220 case that.getPointerEventsInputTypes().MOUSE: 221 that.triggerMouseEvent(e); 222 break; 223 case that.getPointerEventsInputTypes().TOUCH: 224 case that.getPointerEventsInputTypes().PEN: 225 // intentionally group pen and touch together 226 that.triggerTouchEvent(e); 227 break; 228 } 229 }); 230 } else { //non-standard touch events. Wire touch and mouse competing events. 231 //detect first touch 232 $j(window).on(this.touchDetectionEvent, function(e) { 233 if(that.pointerEventLock) { 234 return; 235 } 236 237 that.setPointerEventLockTimeout(); 238 that.triggerTouchEvent(e); 239 }); 240 241 //detect mouse usage 242 $j(document).on('mouseover', function(e) { 243 if(that.pointerEventLock) { 244 return; 245 } 246 247 that.setPointerEventLockTimeout(); 248 that.triggerMouseEvent(e); 249 }); 250 } 251 }, 252 253 init: function() { 254 this.initEnv(); 255 this.wirePointerDetection(); 256 } 257}; 258 259/** 260 * This class manages the main navigation and supports infinite nested 261 * menus which support touch, mouse click, and hover correctly. 262 * 263 * The following is the expected behavior: 264 * 265 * - Hover with an actual mouse should expand the menu (at any level of nesting) 266 * - Click with an actual mouse will follow the link, regardless of any children 267 * - Touch will follow links without children, and toggle submenus of links with children 268 * 269 * Caveats: 270 * - According to Mozilla's documentation (https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events), 271 * Firefox has disabled Apple-style touch events on desktop, so desktop devices using Firefox will not support 272 * the desired touch behavior. 273 */ 274var MenuManager = { 275 // These variables are used to detect incorrect touch / mouse event order 276 mouseEnterEventObserved: false, 277 touchEventOrderIncorrect: false, 278 cancelNextTouch: false, 279 280 /** 281 * This class manages touch scroll detection 282 */ 283 TouchScroll: { 284 /** 285 * Touch which moves the screen vertically more than 286 * this many pixels will be considered a scroll. 287 */ 288 TOUCH_SCROLL_THRESHOLD: 20, 289 290 touchStartPosition: null, 291 292 /** 293 * Note scroll position so that scroll action can be detected later. 294 * Should probably be called on touchstart (or similar) event. 295 */ 296 reset: function() { 297 this.touchStartPosition = $j(window).scrollTop(); 298 }, 299 300 /** 301 * Determines if touch was actually a scroll. Should probably be checked 302 * on touchend (or similar) event. 303 * @returns {boolean} 304 */ 305 shouldCancelTouch: function() { 306 if(this.touchStartPosition == null) { 307 return false;
308 } 309 310 var scroll = $j(window).scrollTop() - this.touchStartPosition; 311 return Math.abs(scroll) > this.TOUCH_SCROLL_THRESHOLD; 312 } 313 }, 314 315 /** 316 * Determines if small screen behavior should be used. 317 * 318 * @returns {boolean} 319 */ 320 useSmallScreenBehavior: function() { 321 return Modernizr.mq("screen and (max-width:" + bp.medium + "px)"); 322 }, 323 324 /** 325 * Toggles a given menu item's visibility. 326 * On large screens, also closes sibling and children of sibling menus. 327 * 328 * @param target 329 */ 330 toggleMenuVisibility: function(target) { 331 var link = $j(target); 332 var li = link.closest('li'); 333 334 if(!this.useSmallScreenBehavior()) { 335 // remove menu-active from siblings and children of siblings 336 li.siblings() 337 .removeClass('menu-active') 338 .find('li') 339 .removeClass('menu-active'); 340 //remove menu-active from children 341 li.find('li.menu-active').removeClass('menu-active'); 342 } 343 344 //toggle current item's active state 345 li.toggleClass('menu-active'); 346 }, 347 348 // -------------------------------------------- 349 // Initialization methods 350 // 351 352 /** 353 * Initialize MenuManager and wire all required events. 354 * Should only be called once. 355 * 356 */ 357 init: function() { 358 this.wirePointerEvents(); 359 }, 360 361 /** 362 * This method observes an absurd number of events 363 * depending on the capabilities of the current browser 364 * to implement expected header navigation functionality. 365 * 366 * The goal is to separate interactions into four buckets: 367 * - pointer enter using an actual mouse 368 * - pointer leave using an actual mouse 369 * - pointer down using an actual mouse 370 * - pointer down using touch 371 * 372 * Browsers supporting PointerEvent events will use these 373 * to differentiate pointer types. 374 * 375 * Browsers supporting Apple-style will use those events 376 * along with mouseenter / mouseleave to emulate pointer events. 377 */ 378 wirePointerEvents: function() { 379 var that = this; 380 var pointerTarget = $j('#nav a.has-children'); 381 var hoverTarget = $j('#nav li'); 382 383 if(PointerManager.getPointerEventsSupported()) { 384 // pointer events supported, so observe those type of events 385 386 var enterEvent = window.navigator.pointerEnabled ? 'pointerenter' : 'mouseenter'; 387 var leaveEvent = window.navigator.pointerEnabled ? 'pointerleave' : 'mouseleave'; 388 var fullPointerSupport = window.navigator.pointerEnabled; 389 390 hoverTarget.on(enterEvent, function(e) { 391 if(e.originalEvent.pointerType === undefined // Browsers with partial PointerEvent support don't provide pointer type 392 || e.originalEvent.pointerType == PointerManager.getPointerEventsInputTypes().MOUSE) { 393 394 if(fullPointerSupport) { 395 that.mouseEnterAction(e, this); 396 } else { 397 that.PartialPointerEventsSupport.mouseEnterAction(e, this); 398 } 399 } 400 }).on(leaveEvent, function(e) { 401 if(e.originalEvent.pointerType === undefined // Browsers with partial PointerEvent support don't provide pointer type 402 || e.originalEvent.pointerType == PointerManager.getPointerEventsInputTypes().MOUSE) { 403 404 if(fullPointerSupport) { 405 that.mouseLeaveAction(e, this); 406 } else { 407 that.PartialPointerEventsSupport.mouseLeaveAction(e, this); 408 } 409 } 410 }); 411 412 if(!fullPointerSupport) { 413 //click event doesn't have pointer type on it. 414 //observe MSPointerDown to set pointer type for click to find later 415 416 pointerTarget.on('MSPointerDown', function(e) { 417 $j(this).data('pointer-type', e.originalEvent.pointerType); 418 }); 419 } 420 421 pointerTarget.on('click', function(e) { 422 var pointerType = fullPointerSupport ? e.originalEvent.pointerType : $j(this).data('pointer-type'); 423 424 if(pointerType === undefined || pointerType == PointerManager.getPointerEventsInputTypes().MOUSE) { 425 that.mouseClickAction(e, this); 426 } else { 427 if(fullPointerSupport) { 428 that.touchAction(e, this); 429 } else { 430 that.PartialPointerEventsSupport.touchAction(e, this); 431 } 432 } 433 434 $j(this).removeData('pointer-type'); // clear pointer type hint from target, if any 435 }); 436 } else { 437 //pointer events not supported, use Apple-style events to simulate 438 439 hoverTarget.on('mouseenter', function(e) { 440 // Touch events should cancel this event if a touch pointer is used. 441 // Record that this method has fired so that erroneous following 442 // touch events (if any) can respond accordingly. 443 that.mouseEnterEventObserved = true; 444 that.cancelNextTouch = true; 445 446 that.mouseEnterAction(e, this); 447 }).on('mouseleave', function(e) { 448 that.mouseLeaveAction(e, this); 449 }); 450 451 $j(window).on('touchstart', function(e) { 452 if(that.mouseEnterEventObserved) { 453 // If mouse enter observed before touch, then device touch 454 // event order is incorrect. 455 that.touchEventOrderIncorrect = true; 456 that.mouseEnterEventObserved = false; // Reset test 457 } 458 459 // Reset TouchScroll in order to detect scroll later. 460 that.TouchScroll.reset(); 461 }); 462 463 pointerTarget.on('touchend', function(e) { 464 $j(this).data('was-touch', true); // Note that element was invoked by touch pointer 465 466 e.preventDefault(); // Prevent mouse compatibility events from firing where possible 467 468 if(that.TouchScroll.shouldCancelTouch()) { 469 return; // Touch was a scroll -- don't do anything else 470 } 471 472 if(that.touchEventOrderIncorrect) { 473 that.PartialTouchEventsSupport.touchAction(e, this); 474 } else { 475 that.touchAction(e, this); 476 } 477 }).on('click', function(e) { 478 if($j(this).data('was-touch')) { // Event invoked after touch 479 e.preventDefault(); // Prevent following link 480 return; // Prevent other behavior 481 } 482 483 that.mouseClickAction(e, this); 484 }); 485 } 486 }, 487 488 // -------------------------------------------- 489 // Behavior "buckets" 490 // 491 492 /** 493 * Browsers with incomplete PointerEvent support (such as IE 10) 494 * require special event management. This collection of methods 495 * accommodate such browsers. 496 */
497 PartialPointerEventsSupport: { 498 /** 499 * Without proper pointerenter / pointerleave / click pointerType support, 500 * we have to use mouseenter events. These end up triggering 501 * lots of mouseleave events that can be misleading. 502 * 503 * Each touch mouseenter and click event that ends up triggering 504 * an undesired mouseleave increments this lock variable. 505 * 506 * Mouseleave events are cancelled if this variable is > 0, 507 * and then the variable is decremented regardless. 508 */ 509 mouseleaveLock: 0, 510 511 /** 512 * Handles mouse enter behavior, but if using touch, 513 * toggle menus in the absence of full PointerEvent support. 514 * 515 * @param event 516 * @param target 517 */ 518 mouseEnterAction: function(event, target) { 519 if(MenuManager.useSmallScreenBehavior()) { 520 // fall back to normal method behavior 521 MenuManager.mouseEnterAction(event, target); 522 return; 523 } 524 525 event.stopPropagation(); 526 527 var jtarget = $j(target); 528 if(!jtarget.hasClass('level0')) { 529 this.mouseleaveLock = jtarget.parents('li').length + 1; 530 } 531 532 MenuManager.toggleMenuVisibility(target); 533 }, 534 535 /** 536 * Handles mouse leave behaivor, but obeys the mouseleaveLock 537 * to allow undesired mouseleave events to be cancelled. 538 * 539 * @param event 540 * @param target 541 */ 542 mouseLeaveAction: function(event, target) { 543 if(MenuManager.useSmallScreenBehavior()) { 544 // fall back to normal method behavior 545 MenuManager.mouseLeaveAction(event, target); 546 return; 547 } 548 549 if(this.mouseleaveLock > 0) { 550 this.mouseleaveLock--; 551 return; // suppress duplicate mouseleave event after touch 552 } 553 554 $j(target).removeClass('menu-active'); //hide all menus 555 }, 556 557 /** 558 * Does no work on its own, but increments mouseleaveLock 559 * to prevent following undesireable mouseleave events. 560 * 561 * @param event 562 * @param target 563 */ 564 touchAction: function(event, target) { 565 if(MenuManager.useSmallScreenBehavior()) { 566 // fall back to normal method behavior 567 MenuManager.touchAction(event, target); 568 return; 569 } 570 event.preventDefault(); // prevent following link 571 this.mouseleaveLock++; 572 } 573 }, 574 575 /** 576 * Browsers with incomplete Apple-style touch event support 577 * (such as the legacy Android browser) sometimes fire 578 * touch events out of order. In particular, mouseenter may 579 * fire before the touch events. This collection of methods 580 * accommodate such browsers. 581 */ 582 PartialTouchEventsSupport: { 583 /** 584 * Toggles visibility of menu, unless suppressed by previous 585 * out of order mouseenter event. 586 * 587 * @param event 588 * @param target 589 */ 590 touchAction: function(event, target) { 591 if(MenuManager.cancelNextTouch) { 592 // Mouseenter has already manipulated the menu. 593 // Suppress this undesired touch event. 594 MenuManager.cancelNextTouch = false; 595 return; 596 } 597 598 MenuManager.toggleMenuVisibility(target); 599 } 600 }, 601 602 /** 603 * On large screens, show menu. 604 * On small screens, do nothing. 605 * 606 * @param event 607 * @param target 608 */ 609 mouseEnterAction: function(event, target) { 610 if(this.useSmallScreenBehavior()) { 611 return; // don't do mouse enter functionality on smaller screens 612 } 613 614 $j(target).addClass('menu-active'); //show current menu 615 }, 616 617 /** 618 * On large screens, hide menu. 619 * On small screens, do nothing. 620 * 621 * @param event 622 * @param target 623 */ 624 mouseLeaveAction: function(event, target) { 625 if(this.useSmallScreenBehavior()) { 626 return; // don't do mouse leave functionality on smaller screens 627 } 628 629 $j(target).removeClass('menu-active'); //hide all menus 630 }, 631 632 /** 633 * On large screens, don't interfere so that browser will follow link. 634 * On small screens, toggle menu visibility. 635 * 636 * @param event 637 * @param target 638 */ 639 mouseClickAction: function(event, target) { 640 if(this.useSmallScreenBehavior()) { 641 event.preventDefault(); //don't follow link 642 this.toggleMenuVisibility(target); //instead, toggle visibility 643 } 644 }, 645 646 /** 647 * Toggle menu visibility, and prevent event default to avoid 648 * undesired, duplicate, synthetic mouse events. 649 * 650 * @param event 651 * @param target 652 */ 653 touchAction: function(event, target) { 654 this.toggleMenuVisibility(target); 655 656 event.preventDefault(); 657 } 658}; 659 660// ============================================== 661// jQuery Init 662// ============================================== 663 664// Use $j(document).ready() because Magento executes Prototype inl
664ine 665$j(document).ready(function () { 666 667 // ============================================== 668 // Shared Vars 669 // ============================================== 670 671 // Document 672 var w = $j(window); 673 var d = $j(document); 674 var body = $j('body'); 675 676 Modernizr.addTest('ios', function () { 677 return navigator.userAgent.match(/(iPad|iPhone|iPod)/g); 678 }); 679 680 //initialize pointer abstraction manager 681 PointerManager.init(); 682 683 /* Wishlist Toggle Class */ 684 685 $j(".change").click(function (e) { 686 $j( this ).toggleClass('active'); 687 e.stopPropagation(); 688 }); 689 690 $j(document).click(function (e) { 691 if (! $j(e.target).hasClass('.change')) $j(".change").removeClass('active'); 692 }); 693 694 695 // ============================================= 696 // Skip Links 697 // ============================================= 698 699 var skipContents = $j('.skip-content'); 700 var skipLinks = $j('.skip-link'); 701 702 skipLinks.on('click', function (e) { 703 e.preventDefault(); 704 705 var self = $j(this); 706 // Use the data-target-element attribute, if it exists. Fall back to href. 707 var target = self.attr('data-target-element') ? self.attr('data-target-element') : self.attr('href'); 708 709 // Get target element 710 var elem = $j(target); 711 712 // Check if stub is open 713 var isSkipContentOpen = elem.hasClass('skip-active') ? 1 : 0; 714 715 // Hide all stubs 716 skipLinks.removeClass('skip-active'); 717 skipContents.removeClass('skip-active'); 718 719 // Toggle stubs 720 if (isSkipContentOpen) { 721 self.removeClass('skip-active'); 722 } else { 723 self.addClass('skip-active'); 724 elem.addClass('skip-active'); 725 } 726 }); 727 728 $j('#header-cart').on('click', '.skip-link-close', function(e) { 729 var parent = $j(this).parents('.skip-content'); 730 var link = parent.siblings('.skip-link'); 731 732 parent.removeClass('skip-active'); 733 link.removeClass('skip-active'); 734 735 e.preventDefault(); 736 }); 737 738 739 // ============================================== 740 // Header Menus 741 // ============================================== 742 743 // initialize menu 744 MenuManager.init(); 745 746 // Prevent sub menus from spilling out of the window. 747 function preventMenuSpill() { 748 var windowWidth = $j(window).width(); 749 $j('ul.level0').each(function(){ 750 var ul = $j(this); 751 //Show it long enough to get info, then hide it. 752 ul.addClass('position-test'); 753 ul.removeClass('spill'); 754 var width = ul.outerWidth(); 755 var offset = ul.offset().left; 756 ul.removeClass('position-test'); 757 //Add the spill class if it will spill off the page. 758 if ((offset + width) > windowWidth) { 759 ul.addClass('spill'); 760 } 761 }); 762 } 763 preventMenuSpill(); 764 $j(window).on('delayed-resize', preventMenuSpill); 765 766 767 // ============================================== 768 // Language Switcher 769 // ============================================== 770 771 // In order to display the language switcher next to the logo, we are moving the content at different viewports, 772 // rather than having duplicate markup or changing the design 773 enquire.register('(max-width: ' + bp.medium + 'px)', { 774 match: function () { 775 $j('.page-header-container .store-language-container').prepend($j('.form-language')); 776 }, 777 unmatch: function () { 778 $j('.header-language-container .store-language-container').prepend($j('.form-language')); 779 } 780 }); 781 782 // ============================================== 783 // Enquire JS 784 // ============================================== 785 786 enquire.register('screen and (min-width: ' + (bp.medium + 1) + 'px)', { 787 match: function () { 788 $j('.menu-active').removeClass('menu-active'); 789 $j('.sub-menu-active').removeClass('sub-menu-active'); 790 $j('.skip-active').removeClass('skip-active'); 791 }, 792 unmatch: function () { 793 $j('.menu-active').removeClass('menu-active');
794 $j('.sub-menu-active').removeClass('sub-menu-active'); 795 $j('.skip-active').removeClass('skip-active'); 796 } 797 }); 798 799 // ============================================== 800 // UI Pattern - Media Switcher 801 // ============================================== 802 803 // Used to swap primary product photo from thumbnails. 804 805 var mediaListLinks = $j('.media-list').find('a'); 806 var mediaPrimaryImage = $j('.primary-image').find('img'); 807 808 if (mediaListLinks.length) { 809 mediaListLinks.on('click', function (e) { 810 e.preventDefault(); 811 812 var self = $j(this); 813 814 mediaPrimaryImage.attr('src', self.attr('href')); 815 }); 816 } 817 818 // ============================================== 819 // UI Pattern - ToggleSingle 820 // ============================================== 821 822 // Use this plugin to toggle the visibility of content based on a toggle link/element. 823 // This pattern differs from the accordion functionality in the Toggle pattern in that each toggle group acts 824 // independently of the others. It is named so as not to be confused with the Toggle pattern below 825 // 826 // This plugin requires a specific markup structure. The plugin expects a set of elements that it 827 // will use as the toggle link. It then hides all immediately following siblings and toggles the sibling's 828 // visibility when the toggle link is clicked. 829 // 830 // Example markup: 831 // <div class="block"> 832 // <div class="block-title">Trigger</div> 833 // <div class="block-content">Content that should show when </div> 834 // </div> 835 // 836 // JS: jQuery('.block-title').toggleSingle(); 837 // 838 // Options: 839 // destruct: defaults to false, but if true, the plugin will remove itself, display content, and remove event handlers 840 841 842 jQuery.fn.toggleSingle = function (options) { 843 844 // passing destruct: true allows 845 var settings = $j.extend({ 846 destruct: false 847 }, options); 848 849 return this.each(function () { 850 if (!settings.destruct) { 851 $j(this).on('click', function () { 852 $j(this) 853 .toggleClass('active') 854 .next() 855 .toggleClass('no-display'); 856 }); 857 // Hide the content 858 $j(this).next().addClass('no-display'); 859 } else { 860 // Remove event handler so that the toggle link can no longer be used 861 $j(this).off('click'); 862 // Remove all classes that were added by this plugin 863 $j(this) 864 .removeClass('active') 865 .next() 866 .removeClass('no-display'); 867 } 868 869 }); 870 }; 871 872 // ============================================== 873 // UI Pattern - Toggle Content (tabs and accordions in one setup) 874 // ============================================== 875 876 $j('.toggle-content').each(function () { 877 var wrapper = jQuery(this); 878 879 var hasTabs = wrapper.hasClass('tabs'); 880 var hasAccordion = wrapper.hasClass('accordion'); 881 var startOpen = wrapper.hasClass('open'); 882 883 var dl = wrapper.children('dl:first'); 884 var dts = dl.children('dt'); 885 var panes = dl.children('dd'); 886 var groups = new Array(dts, panes); 887 888 //Create a ul for tabs if necessary. 889 if (hasTabs) { 890 var ul = jQuery('<ul class="toggle-tabs"></ul>'); 891 dts.each(function () { 892 var dt = jQuery(this); 893 var li = jQuery('<li></li>'); 894 li.html(dt.html()); 895 ul.append(li); 896 }); 897 ul.insertBefore(dl); 898 var lis = ul.children(); 899 groups.push(lis); 900 } 901 902 //Add "last" classes. 903 var i; 904 for (i = 0; i < groups.length; i++) { 905 groups[i].filter(':last').addClass('last'); 906 } 907 908 function toggleClasses(clickedItem, group) { 909 var index = group.index(clickedItem); 910 var i; 911 for (i = 0; i < groups.length; i++) { 912 groups[i].removeClass('current'); 913 groups[i].eq(index).addClass('current'); 914 } 915 } 916 917 //Toggle on tab (dt) click. 918 dts.on('click', function (e) { 919 //They clicked the current dt to close it. Restore the wrapper to unclicked state. 920 if (jQuery(this).hasClass('current') && wrapper.hasClass('accordion-open')) { 921 wrapper.removeClass('accordion-open'); 922 } else { 923 //They're clicking something new. Reflect the explicit user interaction. 924 wrapper.addClass('accordion-open'); 925 } 926 toggleClasses(jQuery(this), dts); 927 }); 928 929 //Toggle on tab (li) click. 930 if (hasTabs) { 931 lis.on('click', function (e) { 932 toggleClasses(jQuery(this), lis); 933 }); 934 //Open the first tab. 935 lis.eq(0).trigger('click'); 936 } 937 938 //Open the first accordion if desired. 939 if (startOpen) { 940 dts.eq(0).trigger('click'); 941 } 942 943 }); 944 945 946 // ============================================== 947 // Layered Navigation Block 948 // ============================================== 949 950 // On product list pages, we want to show the layered nav/category menu immediately above the product list. 951 // While it would make more sense to just move the .block-layered-nav block rather than .col-left-first 952 // (since other blocks can be inserted into left_first), it creates simpler code to move the entire 953 // .col-left-first block, so that is the approach we're taking 954 if ($j('.col-left-first > .block').length && $j('div.category-products').length) { 955 enquire.register('screen and (max-width: ' + bp.medium + 'px)', { 956 match: function () { 957 $j('.col-left-first').insertBefore($j('div.category-products')); 958 }, 959 unmatch: function () { 960 // Move layered nav back to left column 961 $j('.col-left-first').insertBefore($j('.col-main')); 962 } 963 }); 964 } 965 966 // ============================================== 967 // 3 column layout 968 // ============================================== 969 970 // On viewports smaller than 1000px, move the right column into the left column 971 if ($j('.main-container.col3-layout').length > 0) { 972 enquire.register('screen and (max-width: 1000px)', { 973 match: function () { 974 var rightColumn = $j('.col-right'); 975 var colWrapper = $j('.col-wrapper'); 976 977 rightColumn.appendTo(colWrapper); 978 }, 979 unmatch: function () { 980 var rightColumn = $j('.col-right'); 981 var main = $j('.main'); 982 983 rightColumn.appendTo(main); 984 } 985 }); 986 } 987 988 989 // ==============================================
990 // Block collapsing (on smaller viewports) 991 // ============================================== 992 993 enquire.register('(max-width: ' + bp.medium + 'px)', { 994 setup: function () { 995 this.toggleElements = $j( 996 // This selects the menu on the My Account and CMS pages 997 '.col-left-first .block:not(.block-layered-nav) .block-title, ' + 998 '.col-left-first .block-layered-nav .block-subtitle--filter, ' + 999 '.sidebar:not(.col-left-first) .block .block-title' 1000 ); 1001 }, 1002 match: function () { 1003 this.toggleElements.toggleSingle(); 1004 }, 1005 unmatch: function () { 1006 this.toggleElements.toggleSingle({destruct: true}); 1007 } 1008 }); 1009 1010 1011 // ============================================== 1012 // OPC - Progress Block 1013 // ============================================== 1014 1015 if ($j('body.checkout-onepage-index').length) { 1016 enquire.register('(max-width: ' + bp.large + 'px)', { 1017 match: function () { 1018 $j('#checkout-step-review').prepend($j('#checkout-progress-wrapper')); 1019 }, 1020 unmatch: function () { 1021 $j('.col-right').prepend($j('#checkout-progress-wrapper')); 1022 } 1023 }); 1024 } 1025 1026 1027 // ============================================== 1028 // Checkout Cart - events 1029 // ============================================== 1030 1031 if ($j('body.checkout-cart-index').length) { 1032 $j('input[name^="cart"]').focus(function () { 1033 $j(this).siblings('button').fadeIn(); 1034 }); 1035 } 1036 1037 1038 // ============================================== 1039 // Gift Registry Styles 1040 // ============================================== 1041 1042 if ($j('.a-left').length) { 1043 enquire.register('(max-width: ' + bp.large + 'px)', { 1044 match: function () { 1045 $j('.gift-info').each(function() { 1046 $j(this).next('td').children('textarea').appendTo(this).children(); 1047 }); 1048 }, 1049 unmatch: function () { 1050 $j('.left-note').each(function() { 1051 $j(this).prev('td').children('textarea').appendTo(this).children(); 1052 }); 1053 } 1054 }); 1055 } 1056 1057 1058 // ============================================== 1059 // Product Listing - Align action buttons/links 1060 // ============================================== 1061 1062 // Since the number of columns per grid will vary based on the viewport size, the only way to align the action 1063 // buttons/links is via JS 1064 1065 if ($j('.products-grid').length) { 1066 1067 var alignProductGridActions = function () { 1068 // Loop through each product grid on the page 1069 $j('.products-grid').each(function(){ 1070 var gridRows = []; // This will store an array per row 1071 var tempRow = []; 1072 productGridElements = $j(this).children('li'); 1073 productGridElements.each(function (index) { 1074 // The JS ought to be agnostic of the specific CSS breakpoints, so we are dynamically checking to find 1075 // each row by grouping all cells (eg, li elements) up until we find an element that is cleared. 1076 // We are ignoring the first cell since it will always be cleared. 1077 if ($j(this).css('clear') != 'none' && index != 0) { 1078 gridRows.push(tempRow); // Add the previous set of rows to the main array 1079 tempRow = []; // Reset the array since we're on a new row 1080 } 1081 tempRow.push(this); 1082 1083 // The last row will not contain any cells that clear that row, so we check to see if this is the last cell 1084 // in the grid, and if so, we add its row to the array 1085 if (productGridElements.length == index + 1) { 1086 gridRows.push(tempRow); 1087 } 1088 }); 1089 1090 $j.each(gridRows, function () { 1091 var tallestProductInfo = 0; 1092 $j.each(this, function () { 1093 // Since this function is called every time the page is resized, we need to remove the min-height 1094 // and bottom-padding so each cell can return to its natural size before being measured. 1095 $j(this).find('.product-info').css({ 1096 'min-height': '', 1097 'padding-bottom': '' 1098 }); 1099 1100 // We are checking the height of .product-info (rather than the entire li), because the images 1101 // will not be loaded when this JS is run. 1102 var productInfoHeight = $j(this).find('.product-info').height(); 1103 // Space above .actions element 1104 var actionSpacing = 10; 1105 // The height of the absolutely positioned .actions element 1106 var actionHeight = $j(this).find('.product-info .actions').height(); 1107 1108 // Add height of two elements. This is necessary since .actions is absolutely positioned and won't 1109 // be included in the height of .product-info 1110 var totalHeight = productInfoHeight + actionSpacing + actionHeight; 1111 if (totalHeight > tallestProductInfo) { 1112 tallestProductInfo = totalHeight; 1113 } 1114 1115 // Set the bottom-padding to accommodate the height of the .actions element. Note: if .actions 1116 // elements are of varying heights, they will not be aligned. 1117 $j(this).find('.product-info').css('padding-bottom', actionHeight + 'px'); 1118 }); 1119 // Set the height of all .product-info elements in a row to the tallest height 1120 $j.each(this, function () { 1121 $j(this).find('.product-info').css('min-height', tallestProductInfo); 1122 }); 1123 }); 1124 }); 1125 }; 1126 alignProductGridActions(); 1127 1128 // Since the height of each cell and the number of columns per page may change when the page is resized, we are 1129 // going to run the alignment function each time the page is resized. 1130 $j(window).on('delayed-resize', function (e, resizeEvent) { 1131 alignProductGridActions(); 1132 }); 1133 } 1134 1135 // ============================================== 1136 // Generic, efficient window resize handler 1137 // ============================================== 1138 1139 // Using setTimeout since Web-Kit and some other browsers call the resize function constantly upon window resizing. 1140 var resizeTimer; 1141 $j(window).resize(function (e) { 1142 clearTimeout(resizeTimer); 1143 resizeTimer = setTimeout(function () { 1144 $j(window).trigger('delayed-resize', e); 1145 }, 250); 1146 }); 1147}); 1148 1149// ============================================== 1150// PDP - image zoom - needs to be available outside document.ready scope 1151// ============================================== 1152 1153var ProductMediaManager = { 1154 IMAGE_ZOOM_THRESHOLD: 20, 1155 imageWrapper: null, 1156 1157 destroyZoom: function() { 1158 $j('.zoomContainer').remove(); 1159 $j('.product-image-gallery .gallery-image').removeData('elevateZoom'); 1160 }, 1161 1162 createZoom: function(image) { 1163 // Destroy since zoom shouldn't be enabled under certain conditions 1164 ProductMediaManager.destroyZoom(); 1165 1166 if( 1167 // Don't use zoom on devices where touch has been used
1168 PointerManager.getPointer() == PointerManager.TOUCH_POINTER_TYPE 1169 // Don't use zoom when screen is small, or else zoom window shows outside body 1170 || Modernizr.mq("screen and (max-width:" + bp.medium + "px)") 1171 ) { 1172 return; // zoom not enabled 1173 } 1174 1175 if(image.length <= 0) { //no image found 1176 return; 1177 } 1178 1179 if(image[0].naturalWidth && image[0].naturalHeight) { 1180 var widthDiff = image[0].naturalWidth - image.width() - ProductMediaManager.IMAGE_ZOOM_THRESHOLD; 1181 var heightDiff = image[0].naturalHeight - image.height() - ProductMediaManager.IMAGE_ZOOM_THRESHOLD; 1182 1183 if(widthDiff < 0 && heightDiff < 0) { 1184 //image not big enough 1185 1186 image.parents('.product-image').removeClass('zoom-available'); 1187 1188 return; 1189 } else { 1190 image.parents('.product-image').addClass('zoom-available'); 1191 } 1192 } 1193 1194 image.elevateZoom(); 1195 }, 1196 1197 swapImage: function(targetImage) { 1198 targetImage = $j(targetImage); 1199 targetImage.addClass('gallery-image'); 1200 1201 ProductMediaManager.destroyZoom(); 1202 1203 var imageGallery = $j('.product-image-gallery'); 1204 1205 if(targetImage[0].complete) { //image already loaded -- swap immediately 1206 1207 imageGallery.find('.gallery-image').removeClass('visible'); 1208 1209 //move target image to correct place, in case it's necessary 1210 imageGallery.append(targetImage); 1211 1212 //reveal new image 1213 targetImage.addClass('visible'); 1214 1215 //wire zoom on new image 1216 ProductMediaManager.createZoom(targetImage); 1217 1218 } else { //need to wait for image to load 1219 1220 //add spinner 1221 imageGallery.addClass('loading'); 1222 1223 //move target image to correct place, in case it's necessary 1224 imageGallery.append(targetImage); 1225 1226 //wait until image is loaded 1227 imagesLoaded(targetImage, function() { 1228 //remove spinner 1229 imageGallery.removeClass('loading'); 1230 1231 //hide old image 1232 imageGallery.find('.gallery-image').removeClass('visible'); 1233 1234 //reveal new image 1235 targetImage.addClass('visible'); 1236 1237 //wire zoom on new image 1238 ProductMediaManager.createZoom(targetImage); 1239 }); 1240 1241 } 1242 }, 1243 1244 wireThumbnails: function() { 1245 //trigger image change event on thumbnail click 1246 $j('.product-image-thumbs .thumb-link').click(function(e) { 1247 e.preventDefault(); 1248 var jlink = $j(this); 1249 var target = $j('#image-' + jlink.data('image-index')); 1250 1251 ProductMediaManager.swapImage(target); 1252 }); 1253 }, 1254 1255 initZoom: function() { 1256 ProductMediaManager.createZoom($j(".gallery-image.visible")); //set zoom on first image 1257 }, 1258 1259 init: function() { 1260 ProductMediaManager.imageWrapper = $j('.product-img-box'); 1261 1262 // Re-initialize zoom on viewport size change since resizing causes problems with zoom and since smaller 1263 // viewport sizes shouldn't have zoom 1264 $j(window).on('delayed-resize', function(e, resizeEvent) { 1265 ProductMediaManager.initZoom(); 1266 }); 1267 1268 ProductMediaManager.initZoom(); 1269 1270 ProductMediaManager.wireThumbnails(); 1271 1272 $j(document).trigger('product-media-loaded', ProductMediaManager); 1273 } 1274}; 1275 1276$j(document).ready(function() { 1277 ProductMediaManager.init(); 1278});
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.