PageSourceSearch

https://sunpersunglasses.com/skin/frontend/rwd/default/js/app.js

js sunpersunglasses.com collected 2026-09-27 06:03:21 UTC 45,759 bytes, 1,278 lines download raw bytes

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.