PageSourceSearch

https://busse-yachtshop.de/s/templates/NOVA/js/jtl.article.js?v=1.8.0

js busse-yachtshop.de collected 2026-09-25 06:30:49 UTC 81,915 bytes, 1,912 lines download raw bytes

1(function($, document, window, viewport){
2    'use strict';
3
4    var _stock_info = ['out-of-stock', 'in-short-supply', 'in-stock'],
5        $v,
6        ArticleClass = function () {
7            this.init();
8        };
9
10    ArticleClass.DEFAULTS = {
11        input: {
12            id: 'a',
13            childId: 'VariKindArtikel',
14            quantity: 'anzahl'
15        },
16        action: {
17            compareList: 'Vergleichsliste',
18            compareListRemove: 'Vergleichsliste.remove',
19            wishList: 'Wunschliste',
20            wishListRemove: 'Wunschliste.remove'
21        },
22        selector: {
23            navUpdateCompare: '#comparelist-dropdown-content',
24            navBadgeUpdateCompare: '#comparelist-badge',
25            navCompare: '#shop-nav-compare',
26            navContainerWish: '#wishlist-dropdown-container',
27            navBadgeWish: '#badge-wl-count',
28            navBadgeAppend: '#shop-nav li.cart-menu',
29            boxContainer: '#sidebox',
30            boxContainerWish: '#sidebox',
31            quantity: 'input.quantity'
32        },
33        modal: {
34            id: 'modal-article-dialog',
35            wrapper: '#result-wrapper',
36            wrapper_modal: '#result-wrapper-modal'
37        }
38    };
39
40    ArticleClass.prototype = {
41        modalShown: false,
42        modalView: null,
43
44        constructor: ArticleClass,
45
46        init: function () {
47            this.options = ArticleClass.DEFAULTS;
48            this.gallery = null;
49        },
50
51        onLoad: function() {
52            if (this.isSingleArticle()) {
53                var that = this;
54                var form = $.evo.io().getFormValues('buy_form');
55
56                if (typeof history.replaceState === 'function') {
57                    history.replaceState({
58                        a: form.a,
59                        a2: form.VariKindArtikel || form.a,
60                        url: document.location.href,
61                        variations: {}
62                    }, document.title, document.location.href);
63                }
64
65                window.addEventListener('popstate', function (event) {
66                    if (event.state) {
67                        that.setArticleContent(event.state.a, event.state.a2, event.state.url, event.state.variations);
68                    }
69                }, false);
70            }
71        },
72
73        isSingleArticle: function() {
74            return $('#buy_form').length > 0;
75        },
76
77        getWrapper: function(wrapper) {
78            return typeof wrapper === 'undefined' ? $(this.options.modal.wrapper) : $(wrapper);
79        },
80
81        getCurrent: function($item) {
82            if ($item.hasClass('variation')) {
83                return $item;
84            }
85            let $current = $item.closest('.variation');
86            let $checked = $item.find('input:checked');
87            if ($checked.parent().hasClass('variation')) {
88                //swatch and textbox
89                $current = $checked.parent();
90            } else if ($checked.next().hasClass('variation')) {
91                //radio
92                $current = $checked.next();
93            } else if ($item.find('option:selected').hasClass('variation')) {
94                //select
95                $current = $item.find('option:selected');
96            } else if ($current.length === 0) {
97                $current = $item.next('.variation');
98            }
99
100            return $current;
101        },
102
103        incrementProductVisitCount: function() {
104            console.log('This function is deprecated since JTL Shop 5.4.0');
105        },
106
107        navigateBackToList: function() {
108            console.log('This function is deprecated since JTL Shop 5.4.0');
109        },
110
111        register: function(wrapper) {
112            var $wrapper = this.getWrapper(wrapper);
113
114            if (this.isSingleArticle()) {
115                this.registerGallery($wrapper);
116                this.registerConfig();
117                this.registerHoverVariations($wrapper);
118            }
119
120            this.registerSimpleVariations($wrapper);
121            this.registerSwitchVariations($wrapper);
122            this.registerBulkPrices($wrapper);
123            this.registerAccordion();
124            // this.registerImageSwitch($wrapper);
125            this.registerArticleOverlay($wrapper);
126            this.registerImageHover($wrapper);
127            this.registerFinish($wrapper);
128            window.initNumberInput();
129            this.initAbnahmeIntervallError();
130            this.setClickableProductbox();
131        },
132
133        setClickableProductbox: function() {
134            $('.productbox[tabindex="0"]').on('keydown', function(e) {
135                // on Enter or Space
136                if (e.keyCode === 13 || e.keyCode === 32) {
137                    e.preventDefault();
138                    $(this).find('a')[0].click();
139                }
140            });
141        },
142
143        registerAccordion: function() {
144            $('.is-mobile .accordion [id^="tab-"]').on('shown.bs.collapse', function () {
145                let self = $(this);
146                $.evo.destroyScrollSearchEvent();
147                setTimeout(function() {
148                    $('html').animate(
149                        {scrollTop: self.offset().top-40},
150                        100,
151                        'linear',
152                        function () {
153                            setTimeout(function() {
154                                $.evo.initScrollSearchEvent();
155                            }, 500);
156                        });
157                }, 400);
158            });
159        },
160
161        registerArticleOverlay: function($wrapper) {
162            var that         = this;
163
164            $('.configpreview', $wrapper)
165                .each(function(i, item) {
166                    let $item   = $(item);
167                    let wrapper = that.options.modal.wrapper_modal + '_modal';
168
169                    $item.on('click', function (event) {
170                        event.preventDefault();
171                        that.modalArticleDetail(this, wrapper);
172                    });
173                });
174            $wrapper.hover(null, function() {
175                $(this).removeClass('active');
176            })
177        },
178
179        registerGallery: function(wrapper) {
180            /*
181             * product slider and zoom (details)
182             */
183            function slickinit()
184            {
185                $('.initial-slick-dots').on('click', function () {
186                    let mainNode = $('#gallery');
187                    mainNode.removeClass('slick-lazy');
188                    $.evo.extended().initSlick(mainNode, mainNode.data('slick-type'));
189                    if(mainNode.slick('getSlick').slideCount > mainNode.slick('slickGetOption', 'slidesToShow')) {
190                        mainNode.slick('slickGoTo', 1);
191                    }
192                });
193            }
194
195            function toggleFullscreen(fullscreen = false)
196            {
197                let $imgWrapper = $('#image_wrapper'),
198                    $gallery    = $('#gallery');
199                if (!$gallery.hasClass('slick-initialized') || (fullscreen && $imgWrapper.hasClass('fullscreen'))){
200                    return;
201                }
202
203                let maxHeight       = Math.max(document.documentElement.clientHeight, window.innerHeight || 0
203),
204                    otherElemHeight = 0,
205                    current         = ($('#gallery .slick-current').data('slick-index')),
206                    $galleryImages  = $('#gallery img, #gallery picture source'),
207                    hidePreview     = maxHeight < 700,
208                    previewHeight   = $('#gallery_preview_wrapper').length > 0 && !hidePreview ? 170 : 30,
209                    $previewBar     = $('.product-detail-image-preview-bar');
210
211                if (fullscreen) {
212                    $imgWrapper.addClass('fullscreen');
213                    let $galleryTopbar = $('#image_wrapper .product-detail-image-topbar');
214
215                    otherElemHeight = $galleryTopbar.outerHeight()
216                        + 2*parseInt($imgWrapper.css('paddingTop'))
217                        + previewHeight;
218
219                    $galleryImages.removeAttr('sizes');
220                    // lazySizes.autoSizer.updateElem($galleryImages);
221
222                    if (hidePreview) {
223                        $previewBar.addClass('d-none');
224                    }
225
226                    $galleryImages.css('max-height', maxHeight-otherElemHeight);
227                    $gallery.css('max-height', maxHeight-otherElemHeight);
228
229                    $('body').off('click.toggleFullscreen').on('click.toggleFullscreen', function (event) {
230                        if (!($(event.target).hasClass('product-image') || $(event.target).hasClass('slick-arrow'))) {
231                            toggleFullscreen(false);
232                            $('body').off('click.toggleFullscreen');
233                        }
234                    });
235                } else {
236                    $imgWrapper.removeClass('fullscreen');
237                    $galleryImages.css('max-height', '100%');
238                    $gallery.css('max-height', '100%');
239                    $previewBar.removeClass('d-none');
240                }
241
242                $gallery.slick('slickSetOption','initialSlide', current, true);
243                $('#gallery_preview').slick('slickGoTo', current, true);
244
245                //fix firefox height bug
246                $('.slick-slide, .slick-arrow').css({'display': 'none'});
247                setTimeout(function(){
248                    $('.slick-slide, .slick-arrow').css({'display': 'block'});
249                }, 50);
250            }
251
252            function addClickListener() {
253                $('#gallery img').off('click').on('click', e => {
254                    if (window.innerWidth > globals.breakpoints.lg) {
255                        toggleFullscreen(true);
256                    }
257                });
258            }
259
260            slickinit();
261
262            if (wrapper[0].id.indexOf(this.options.modal.wrapper_modal.slice(1)) === -1) {
263                addClickListener();
264
265                $(document).on('keyup', e => {
266                    if (e.key === "Escape" && $('#image_wrapper').hasClass('fullscreen')) {
267                        toggleFullscreen();
268                        addClickListener();
269                    }
270                });
271            }
272        },
273
274        registerConfig: function() {
275            var that   = this,
276                config = $('#product-configurator')
277                    .closest('form')
278                    .find('input[type="radio"], input[type="text"], input[type="checkbox"], input[type="number"], select'),
279                dropdown = $('#product-configurator')
280                    .closest('form')
281                    .find('select');
282            let offset = $('#jtl-nav-wrapper').height();
283
284            if (dropdown.length > 0) {
285                dropdown.on('change', function () {
286                    var item = $(this).val();
287                    $(this).parents('.cfg-group').find('.cfg-drpdwn-item.collapse.show').collapse('hide');
288                    $('#drpdwn_qnt_' + item).collapse('show');
289                })
290            }
291
292            if (config.length > 0) {
293                config.on('change', function() {
294                    that.configurator();
295                })
296                    .on('keypress', function (e) {
297                        if (e.key === 'Enter') {
298                            return false;
299                        }
300                    });
301                // timeout fixes problem with loading order of bootstrap dropdowns
302                setTimeout(function(){
303                    that.configurator(true);
304                },0);
305            }
306
307            function scroll (selector) {
308                $(selector).bind('click', function(event) {
309                    event.preventDefault();
310                    let anchor = $(this).attr('href');
311                    $('html, body').stop().animate({
312                        scrollTop: $(anchor).offset().top - offset - 15
313                    });
314                });
315            }
316            scroll('a[href="#cfg-container"]');
317
318
319            $('#cfg-nav nav a').on('click', function() {
320                $('html, body').animate({
321                    scrollTop: $('#cfg-container').offset().top - offset - 15
322                });
323                let anchor = $(this).attr('href'),
324                    elOffset = $(anchor)[0].offsetTop;
325                $('#cfg-container .cfg-options').stop().animate({
326                    scrollTop: elOffset
327                });
328            });
329
330            $(document).on('scroll', function (e) {
331                let headerHeight = $('#jtl-nav-wrapper').outerHeight() + 10;
332                $('.cfg-position-details.cfg-layout-list #product-configuration-sidebar, ' +
333                    '.cfg-position-details.cfg-layout-list .cfg-group .cfg-group-info').css('top', headerHeight + 'px');
334            });
335        },
336
337        registerSimpleVariations: function($wrapper) {
338            var that = this;
339
340            $('.variations select', $wrapper).selectpicker({
341                iconBase: 'fa',
342                tickIcon: 'fa-check',
343                hideDisabled: true,
344                showTick: true
345            });
346
347            $('.simple-variations input[type="radio"]', $wrapper)
348                .on('change', function() {
349                    var val = $(this).val(),
350                        key = $(this).parent().data('key');
351                    $('.simple-variations [data-key="' + key + '"]').removeClass('active');
352                    $('.simple-variations [data-value="' + val + '"]').addClass('active');
353                    $(this).closest(".swatches").addClass("radio-selected");
354                });
355
356            $('.simple-variations input[type="radio"], .simple-variations select', $wrapper)
357                .each(function(i, item) {
358                    var $item   = $(item),
359                        wrapper = '#' + $item.closest('form').closest('div[data-wrapper="true"]').attr('id');
360
361                    $item.on('change', function () {
362                        that.variationPrice($(this), true, wrapper);
363                    });
364                });
365            $('.simple-variations input[type="text"]', $wrapper)
366                .each(function(i, item) {
367                    let $item   = $(item),
368                        wrapper = '#' + $item.closest('form').closest('div[data-wrapper="true"]').attr('id'),
369                        timeout = null;
370                    $item.on('keyup', function (e) {
371                        clearTimeout(timeout);
372                        let self = $(this);
373
374                        timeout = setTimeout(function () {
375                            that.variationPrice(self, true, wrapper);
376                        }, 500);
377                    });
378                });
379        },
380
381        registerBulkPrices: function($wrapper) {
382            var $bulkPrice = $('.bulk-price', $wrapper),
383                that       = this,
384                $config    = $('#product-configurator');
385
386            if (($bulkPrice.length > 0 && $config.length === 0) || $('#product-list').length > 0) {
387                $('#quantity, [data-bulk="1"] .quantity', $wrapper)
388                    .each(function(i, item) {
389                        let $item   = $(item),
390                            wrapper = '#' + $item.closest('form').closest('div[data-wrapper="true"]').attr('id');
391
392                        $item.on('change', function () {
393                            that.variationPrice($(this), true, wrapper);
394                        });
395                        that.variationPrice($(this), true, wrapper);
396                    });
397            }
398        },
399
400        registerSwitchVariations: function($wrapper) {
401            var that = this;
402
403            $('.switch-variations input[type="radio"], .switch-variations select', $wrapper)
404                .each(function(i, item) {
405                    var $item   = $(item),
406                        wrapper = '#' + $item.closest('form').closest('div[id]').attr('id');
407
408                    $item.on('change', function () {
409                        that.variationSwitch($(this), false, wrapper);
410                    });
411                });
412
413            if (isTouchCapable()) {
414                $('.variations .swatches .variation', $wrapper)
415                    .on('mouseover', function() {
416                        $(this).trigger('click');
417                    });
418            }
419
420            // ie11 fallback
421            if (typeof document.body.style.msTransform === 'string') {
422                $('.variations label.variation', $wrapper)
423                    .on('click', function (e) {
424                        if (e.target.tagName === 'IMG') {
425                            $(this).trigger('click');
426                        }
427                    });
428            }
429        },
430
431        registerHoverVariations: function ($wrapper) {
432            let delay=300, setTimeoutConst;
433            $('.variations label.variation', $wrapper)
434                .on('mouseenter', function (e) {
435                    setTimeoutConst = setTimeout(function () {
436                        let mainImageHeight = $('.js-gallery-images').innerHeight();
437                        $('.variation-image-preview.vt' + $(e.currentTarget).data('value')).addClass('show d-md-block')
438                            .css('top', $(e.currentTarget).offset().top - $(e.currentTarget).closest('#content').position().top - mainImageHeight / 2 - 12);
439                    }, delay)
440                })
441                .on('mouseleave', function (e) {
442                    clearTimeout(setTimeoutConst);
443                    $('.variation-image-preview.vt' + $(this).data('value')).removeClass('show d-md-block');
444                });
445
446            $('.variations .selectpicker')
447                .on('show.bs.select', function () {
448                    let selectPicker = $(this);
449                    $(this).parent().find('li .variation')
450                        .on('mouseenter', function (e) {
451                            setTimeoutConst = setTimeout(function () {
452                                let mainImageHeight = $('.js-gallery-images').innerHeight();
453                                let selectOptionValue = selectPicker.find('option').eq(
454                                    e.currentTarget.ariaPosInSet
455                                ).data("value");
456                                $('.variation-image-preview.vt' + selectOptionValue)
457                                    .addClass('show d-md-block')
458                                    .css('top', $(e.currentTarget).offset().top - $(e.currentTarget).closest('#content').position().top - mainImageHeight / 2 - 12);
459                            }, delay)
460                        })
461                        .on('mouseleave', function (e) {
462                            clearTimeout(setTimeoutConst);
463                            let selectOptionValue = selectPicker.find('option').eq(
464                                e.currentTarget.ariaPosInSet
465                            ).data("value");
466                            $('.variation-image-preview.vt' + selectOptionValue)
467                                .removeClass('show d-md-block');
468                    });
469                })
470                .on('hide.bs.select', function () {
471                    $(this).parent().find('li .variation').off('mouseenter mouseleave');
472                    $('.variation-image-preview').removeClass('show');
473                });
474        },
475
476        registerImageSwitch: function($wrapper) {
477            var that     = this,
478                imgSwitch,
479                gallery  = this.gallery;
480
481            if (gallery !== null) {
482                imgSwitch = function (context, temporary, force) {
483                    var $context = $(context),
484                        id       = $context.attr('data-key'),
485                        value    = $context.attr('data-value'),
486                        data     = $context.data('list'),
487                        title    = $context.attr('data-title');
488
489                    if (typeof temporary === 'undefined') {
490                        temporary = true;
491                    }
492
493                    if ((!$context.hasClass('active') || force) && !!data) {
494                        gallery.setItems([data], value);
495
496                        if (!temporary) {
497                            var items  = [data],
498                                stacks = gallery.getStacks();
499                            for (var s in stacks) {
500                                if (stacks.hasOwnProperty(s) && s.match(/^_[0-9a-zA-Z]*$/) && s !== '_' + id) {
501                                    items = $.merge(items, stacks[s]);
502                                }
503                            }
504
505                            gallery.setItems([data], '_' + id);
506                            gallery.setItems(items, '__');
507                            gallery.render('__');
508
509                            that.galleryIndex     = gallery.index;
510                            that.galleryLastIdent = gallery.ident;
511                        } else {
512                            gallery.render(value);
513                        }
514                    }
515                }
516            } else {
517                imgSwitch = function (context, temporary) {
518                    var $context = $(context),
519                        value    = $context.attr('data-value'),
520                        data     = $context.data('list'),
521                        title    = $context.attr('data-title');
522
523                    if (typeof temporary === 'undefined') {
524                        temporary = true;
525                    }
526
527                    if (!!data) {
528                        var $wrapper = $(context).closest('.product-wrapper'),
529                            $img     = $('.image-box img', $wrapper);
530                        if ($img.length === 1) {
531                            $img.attr('src', data.md.src);
532                            if (!temporary) {
533                                $img.data('src', data.md.src);
534                            }
535                        }
536                    }
537                };
538            }
539
540            $('.variations .bootstrap-select select', $wrapper)
541                .on('change', function() {
542                    var sel  = $(this).find('[value=' + this.value + ']'),
543                        cont = $(this).closest('.variations');
544
545                    if (cont.hasClass('simple-variations')) {
546                        imgSwitch(sel, false, false);
547                    } else {
548                        imgSwitch(sel, true, false);
549                    }
550                });
551
552            if (!isTouchCapable() || ResponsiveBootstrapToolkit.current() !== 'xs') {
553                $('.variations .bootstrap-select .dropdown-menu li', $wrapper)
554                    .on('hover', function () {
555                        var tmp_idx = parseInt($(this).attr('data-original-index')) + 1,
556                            rule    = 'select option:nth-child(' + tmp_idx + ')',
557                            sel     = $(this).closest('.bootstrap-select').find(rule);
558                        imgSwitch(sel);
559                    }, function () {
560                        var tmp_idx = parseInt($(this).attr('data-original-index')) + 1,
561                            rule    = 'select option:nth-child(' + tmp_idx + ')',
562                            sel     = $(this).closest('.bootstrap-select').find(rule),
563                            gallery = that.gallery,
564                            active;
565
566                        if (gallery !== null) {
567                            active = $(sel).find('.variation.active');
568                            gallery.render(that.galleryLastIdent);
569                            gallery.activate(that.galleryIndex);
570                        } else {
571                            var $wrapper = $(sel).closest('.product-wrapper'),
572                                $img     = $('.image-box img', $wrapper);
573                            if ($img.length === 1) {
574                                $img.attr('src', $img.data('src'));
575                            }
576                        }
577                    });
578            }
579
580            $('.variations.simple-variations .variation', $wrapper)
581                .on('click', function () {
582                    imgSwitch(this, false);
583                });
584
585            if (!isTouchCapable() || ResponsiveBootstrapToolkit.current() !== 'xs') {
586                $('.variations .variation', $wrapper)
587                    .on('hover', function () {
588                        imgSwitch(this);
589                    }, function () {
590                        var sel     = $(this).closest('.variation'),
591                            gallery = that.gallery;
592
593                        if (gallery !== null) {
594                            gallery.render(that.galleryLastIdent);
595                            gallery.activate(that.galleryIndex);
596                        } else {
597                            var $wrapper = $(sel).closest('.product-wrapper'),
598                                $img     = $('.image-box img', $wrapper);
599                            if ($img.length === 1) {
600                                $img.attr('src', $img.data('src'));
601                            }
602                        }
603                    });
604            }
605        },
606
607        registerImageHover: function($wrapper) {
608            const findCollapse = function(container) {
609                return container.find('.productbox-variations > .collapse, .productbox-variations > .collapsing');
610            };
611
612            const forceCompleteTransition = function(collapse) {
613                if (collapse.hasClass('collapsing')) {
614                    collapse.trigger('bsTransitionEnd');
615                }
616            };
617
618            const reserveSpace = function(box) {
619                let $productWrapper = box.closest('.product-wrapper');
620                let isLastInRow = $productWrapper.is(':last-child')
621                    || $productWrapper.next('.product-wrapper').length === 0;
622                if (!isLastInRow) {
623                    return;
624                }
625                // Only measure while the box is still in the normal flow. Once it is
626                // absolutely positioned (focus/transition) the wrapper collapses and
627                // would yield a near-zero height, so we fall back to the cached value.
628                let reserved = $productWrapper.data('reservedHeight');
629                if (!box.hasClass('focus') && !box.hasClass('transition')) {
630                    reserved = $productWrapper.outerHeight();
631                    $productWrapper.data('reservedHeight', reserved);
632                }
633                if (reserved) {
634                    $productWrapper.css('min-height', reserved + 'px');
635                }
636            };
637
638            const closeProductboxVariations = function(box) {
639                let collapse = findCollapse(box);
640                let $productWrapper = box.closest('.product-wrapper');
641                box.removeClass('focus');
642
643                if (collapse.length === 1) {
644                    forceCompleteTransition(collapse);
645
646                    if (collapse.hasClass('show')) {
647                        box.addClass('transition');
648                        collapse.collapse('hide');
649                        collapse.off('hidden.bs.collapse').one('hidden.bs.collapse', function () {
650                            if (box.hasClass('focus')) {
651                                return;
652                            }
653                            box.removeClass('transition');
654                            $productWrapper.css('min-height', '');
655                        });
656                    } else {
657                        box.removeClass('transition');
658   
658                     $productWrapper.css('min-height', '');
659                    }
660                } else {
661                    $productWrapper.css('min-height', '');
662                }
663            };
664
665            $('.product-list .productbox-hover:not(.focus), .product-list .productbox-show-variations:not(.focus)',
666                $wrapper).on('mouseenter focusin', function () {
667                $(this).closest('.product-list').find('.focus').not(this).removeClass('focus');
668            });
669            $('.productbox-show-variations', $wrapper).on('mouseenter focusin', function () {
670                let $box = $(this);
671                let collapse = findCollapse($box);
672
673                let closeTimer = $box.data('closeTimer');
674                if (closeTimer) {
675                    clearTimeout(closeTimer);
676                    $box.removeData('closeTimer');
677                }
678
679                reserveSpace($box);
680
681                if (!$box.hasClass('focus')) {
682                    $box.addClass('focus');
683                    forceCompleteTransition(collapse);
684                    collapse.collapse('show');
685                }
686            });
687            $('.productlist-page-nav', $wrapper).on('focusin', function () {
688                $('.productbox-show-variations.focus', $wrapper).removeClass('focus');
689            });
690            $('.productbox-show-variations', $wrapper).on('mouseleave', function () {
691                let $box = $(this);
692                let closeTimer = setTimeout(function () {
693                    $box.removeData('closeTimer');
694                    closeProductboxVariations($box);
695                }, 100);
696                $box.data('closeTimer', closeTimer);
697            });
698            if (isTouchCapable()) {
699                $(document).on('touchstart', function (e) {
700                    if (!$(e.target).closest('.productbox-show-variations').length) {
701                        $('.productbox-show-variations.focus', $wrapper).each(function () {
702                            closeProductboxVariations($(this));
703                        });
704                    }
705                });
706            }
707            $('.is-mobile .show-variation-collapse a').on('click', function (e) {
708                if (!$(this).closest('.show-variation-collapse').find('.collapse').hasClass('show')) {
709                    e.preventDefault();
710                }
711            });
712        },
713
714        registerFinish: function($wrapper) {
715            let tabs = ['votes', 'guarantee']
716            $.each(tabs, function(i, tab) {
717                $('#jump-to-' + tab + '-tab', $wrapper).on('click', function () {
718                    let $tabID = $('#content a[href="#tab-' + tab + '"]');
719                    if ($tabID.length > 0) {
720                        $tabID.tab('show');
721                    } else {
722                        $tabID = $('#tab-' + tab);
723                        $tabID.collapse('show');
724                    }
725
726                    $([document.documentElement, document.body]).animate({
727                        scrollTop: $tabID.offset().top
728                    }, 200);
729                });
730            });
731            let $tabID = $('#product-tabs a[href="' + window.location.hash + '"]');
732            if ($tabID.length) {
733                $tabID.tab('show');
734                $([document.documentElement, document.body]).animate({
735                    scrollTop: $tabID.offset().top
736                }, 200);
737            }
738
739            if (this.isSingleArticle()) {
740                if ($('.switch-variations .form-group', $wrapper).length === 1) {
741                    var wrapper = '#' + $($wrapper).attr('id');
742                    this.variationSwitch($('.switch-variations', $wrapper), false, wrapper);
743                }
744            }
745            else {
746                var that = this;
747
748                $('.product-cell.hover-enabled')
749                    .on('click', function (event) {
750                        if (isTouchCapable() && ResponsiveBootstrapToolkit.current() !== 'xs') {
751                            var $this = $(this);
752
753                            if (!$this.hasClass('active')) {
754                                event.preventDefault();
755                                event.stopPropagation();
756                                $('.product-cell').removeClass('active');
757                                $this.addClass('active');
758                            }
759                        }
760                    })
761                    .on('mouseenter', function (event) {
762                        var $this = $(this),
763                            wrapper = '#' + $this.attr('id');
764
765                        if (!$this.data('varLoaded') && $('.switch-variations .form-group', $this).length === 1) {
766                            that.variationSwitch($('.switch-variations', $this), false, wrapper);
767                        }
768                        $this.data('varLoaded', true);
769                    });
770            }
771
772            this.registerProductActions($('#sidepanel_left'));
773            this.registerProductActions($('#footer'));
774            this.registerProductActions($('#shop-nav'));
775            this.registerProductActions($wrapper);
776            this.registerProductActions('#cart-form');
777        },
778
779        registerProductActions: function($wrapper) {
780            var that = this;
781
782            $('*[data-toggle="product-actions"] button', $wrapper)
783                .on('click', function(event) {
784                    var data = $(this.form).serializeObject();
785
786                    if ($wrapper === '#cart-form') {
787                        data.wlPos = $(this).data('wl-pos');
788                        data.a = $(this).data('product-id-wl');
789                    }
790
791                    if (that.handleProductAction(this, data)) {
792                        event.preventDefault();
793                    }
794                });
795            $('a[data-toggle="product-actions"]', $wrapper)
796                .on('click', function(event) {
797                    var data  = $(this).data('value');
798                    this.name = $(this).data('name');
799
800                    if (that.handleProductAction(this, data)) {
801                        event.preventDefault();
802                    }
803                });
804        },
805
806        loadModalArticle: function(url, wrapper, done, fail) {
807            var that       = this,
808                id         = wrapper.slice(1),
809                $modalBody = $('.modal-body', this.modalView);
810
811            $.ajax(url, {data: {'isAjax':1, 'quickView':1}})
812                .done(function(data) {
813                    var $html      = $('<div />').html(data);
814                    var $headerCSS = $html.find('link[type="text/css"]');
815                    var $headerJS  = $html.find('script[src][src!=""]');
816                    var content    = $html.find(that.options.modal.wrapper).html();
817
818                    $headerCSS.each(function (pos, item) {
819                        var $cssLink = $('head link[href="' + item.href + '"]');
820                        if ($cssLink.length === 0) {
821                            $('head').append('<link rel="stylesheet" type="text/css" href="' + item.href + '" >');
822                        }
823                    });
824
825                    $headerJS.each(function (pos, item) {
826                        if (typeof item.src !== 'undefined' && item.src.length > 0) {
827                            var $jsLink = $('head script[src="' + item.src + '"]');
828                            if ($jsLink.length === 0) {
829                                $('head').append('<script defer src="' + item.src + '" >');
830                            }
831                        }
832                    });
833
834                    $modalBody.html($('<div id="' + id + '" />').html(content));
835
836                    var $modal  = $modalBody.closest(".modal-dialog"),
837                        title   = $modal.find('.modal-body h1'),
838                        $config = $('#product-configurator', $modalBody);
839
840                    if ($config.length > 0) {
841                        // Configurator in child article!? Currently not supported!
842                        $config.remove();
843                        $.evo.extended().startSpinner($modalBody);
844                        location.href = url;
845                    }
846                    if (title.length > 0 && title.text().length > 0) {
847                        $modal.find('.modal-title').text(title.text());
848                        title.remove();
849                    }
850
851                    $('form', $modalBody).on('submit', function(event) {
852                        event.preventDefault();
853
854                        var $form = $(this);
855                        var data  = $form.serializeObject();
856                        if (data['VariKindArtikel']) {
857                            data['a'] = data['VariKindArtikel'];
858                        }
859
860                        $.evo.basket().addToBasket($form, data);
861                        that.modalView.modal('hide');
862                    });
863
864                    if (typeof done === 'function') {
865                        done();
866                    }
867                })
868                .fail(function() {
869                    if (typeof fail === 'function') {
870                        fail();
871                    }
872                })
873                .always(function() {
874                    $.evo.extended().stopSpinner();
875                });
876        },
877
878        addToComparelist: function(data, $action) {
879            var productId = parseInt(data[this.options.input.id]);
880            var childId = parseInt(data[this.options.input.childId]);
881            if (childId > 0) {
882                productId = childId;
883            }
884            if (productId > 0) {
885                var that = this;
886                $.evo.io().call('pushToComparelist', [productId], that, function(error, data) {
887                    if (error) {
888                        return;
889                    }
890
891                    var response = data.response;
892
893                    if (response) {
894                        switch (response.nType) {
895                            case 0: // error
896                                var errorlist = '<ul><li>' + response.cHints.join('</li><li>') + '</li></ul>';
897                                eModal.alert({
898                                    title: response.cTitle,
899                                    message: errorlist,
900                                    keyboard: true,
901                                    tabindex: -1,
902                                    buttons: false
903                                });
904                                break;
905                            case 1: // forwarding
906                                window.location.href = response.cLocation;
907                                break;
908                            case 2: // added to comparelist
909                                that.updateComparelist(response);
910                                break;
911                        }
912                    }
913                });
914
915                return true;
916            }
917
918            return false;
919        },
920
921        removeFromCompareList: function(data) {
922            var productId = parseInt(data[this.options.input.id]);
923            if (productId > 0) {
924                var that = this;
925                $.evo.io().call('removeFromComparelist', [productId], that, function(error, data) {
926                    if (error) {
927                        return;
928                    }
929
930                    var response = data.response;
931
932                    if (response) {
933                        switch (response.nType) {
934                            case 0: // error
935                                var errorlist = '<ul><li>' + response.cHints.join('</li><li>') + '</li></ul>';
936                                eModal.alert({
937                                    title: response.cTitle,
938                                    message: errorlist,
939                                    keyboard: true,
940                                    tabindex: -1,
941                                    buttons: false
942                                });
943                                break;
944                            case 1: // forwarding
945                                window.location.href = response.cLocation;
946                                break;
947                            case 2: // removed from comparelist
948                                that.updateComparelist(response);
949                                break;
950                        }
951                    }
952                });
953
954                return true;
955            }
956
957            return false;
958        },
959
960        updateComparelist: function(data) {
961            var $badgeUpd = $(this.options.selector.navUpdateCompare);
962
963            var badge = $(data.navDropdown);
964            $badgeUpd.html(badge);
965            $(this.options.selector.navBadgeUpdateCompare).html(data.nCount);
966
967            if (data.nCount > 0) {
968                $(this.options.selector.navCompare).removeClass('d-none');
969            } else {
970                $(this.options.selector.navCompare).addClass('d-none');
971                $('#nav-comparelist-collapse').removeClass('show');
972            }
973            if (data.nCount > 1) {
974                $('#nav-comparelist-goto').removeClass('d-none');
975            } else {
976                $('#nav-comparelist-goto').addClass('d-none');
977            }
978            this.registerProductActions($('#shop-nav'));
979
980            if (data.productID) {
981                let $action = $('button[data-product-id-cl="' + data.productID + '"]')
982                $action.removeClass("on-list");
983                $action.next().removeClass("press");
984                $('.comparelist [data-product-id-cl="' + data.productID + '"]').remove();
985            }
986
987            for (var ind in data.cBoxContainer) {
988                var $list = $(this.options.selector.boxContainer+ind);
989
990                if ($list.length > 0) {
991                    if (data.cBoxContainer[ind].length) {
992                        var $boxContent = $(data.cBoxContainer[ind]);
993                        this.registerProductActions($boxContent);
994                        $list.replaceWith($boxContent).removeClass('d-none');
995                    } else {
996                        $list.html('').addClass('d-none');
997                    }
998                }
999            }
1000        },
1001
1002        addToWishlist: function(data, $action) {
1003            let productId = parseInt(data[this.options.input.id]),
1004                childId = parseInt(data[this.options.input.childId]),
1005                qty =  parseFloat(data[this.options.input.quantity]);
1006            if (childId > 0) {
1007                productId = childId;
1008            }
1009            if (isNaN(qty)) {
1010                qty = 1;
1011            }
1012            if (productId > 0) {
1013                var that = this;
1014                $.evo.io().call('pushToWishlist', [productId, qty, data], that, function(error, data) {
1015                    if (error) {
1016                        $action.closest('form')[0].reportValidity();
1017                        return;
1018                    }
1019                    if ($action.hasClass('action-tip-animation-b')) {
1020                        $action.addClass("on-list");
1021                        $action.next().addClass("press");
1022                        $action.next().next().removeClass("press");
1023                    }
1024                    var response = data.response;
1025
1026                    if (response) {
1027                        switch (response.nType) {
1028                            case 0: // error
1029                                var errorlist = '<ul><li>' + response.cHints.join('</li><li>') + '</li></ul>';
1030                                eModal.alert({
1031                                    title: response.cTitle,
1032                                    message: errorlist,
1033                                    keyboard: true,
1034                                    tabindex: -1,
1035                                    buttons: false
1036                                });
1037                                break;
1038                            case 1: // forwarding
1039                                window.location.href = response.cLocation;
1040                                break;
1041                            case 2: // added to wishlist
1042                                that.updateWishlist(response);
1043                                break;
1044                        }
1045                    }
1046                });
1047
1048                return true;
1049            }
1050
1051            return false;
1052        },
1053
1054        removeFromWishList: function(data) {
1055            var productId = parseInt(data[this.options.input.id]);
1056            if (productId > 0) {
1057                var that = this;
1058                $.evo.io().call('removeFromWishlist', [productId], that, function(error, data) {
1059                    if (error) {
1060                        return;
1061                    }
1062
1063                    var response = data.response;
1064
1065                    if (response) {
1066                        switch (response.nType) {
1067                            case 0: // error
1068                                var errorlist = '<ul><li>' + response.cHints.join('</li><li>') + '</li></ul>';
1069                                eModal.alert({
1070                                    title: response.cTitle,
1071                                    message: errorlist,
1072                                    keyboard: true,
1073                                    tabindex: -1,
1074                                    buttons: false
1075                                });
1076                                break;
1077                            case 1: // forwarding
1078                                window.location.href = response.cLocation;
1079                                break;
1080                            case 2: // removed from wishlist
1081                                that.updateWishlist(response);
1082                                break;
1083                        }
1084                    }
1085                });
1086
1087                return true;
1088            }
1089
1090            return false;
1091        },
1092
1093        updateWishlist: function(data) {
1094            var $navContainerWish = $(this.options.selector.navContainerWish);
1095            var $navBadgeWish = $(this.options.selector.navBadgeWish);
1096
1097            if (data.wlPosRemove) {
1098                let $action = $('button[data-wl-pos="' + data.wlPosRemove + '"]');
1099                $action.removeClass("on-list");
1100                $action.next().removeClass("press");
1101                $action.find('.wishlist-icon').addClass('far').removeClass('fas');
1102            }
1103            if (data.wlPosAdd) {
1104                let $action = $('button[data-product-id-wl="' + data.productID + '"]');
1105                $action.attr('data-wl-pos', data.wlPosAdd);
1106                $action.data('wl-pos', data.wlPosAdd);
1107                $action.closest('form').find('input[name="wlPos"]').val(data.wlPosAdd)
1108                $action.find('.wishlist-icon').addClass('fas').removeClass('far');
1109            }
1110            $.evo.io().call('updateWishlistDropdown', [$navContainerWish, $navBadgeWish], this, function(error, data) {
1111                if (error) {
1112                    return;
1113                }
1114                if (data.response.currentPosCount > 0) {
1115                    $navBadgeWish.removeClass('d-none');
1116                } else {
1117                    $navBadgeWish.addClass('d-none');
1118                }
1119                $navContainerWish.html(data.response.content);
1120                $navBadgeWish.html(data.response.currentPosCount);
1121                setClickableRow();
1122            });
1123
1124            for (var ind in data.cBoxContainer) {
1125                var $list = $(this.options.selector.boxContainerWish+ind);
1126                if ($list.length > 0) {
1127                    if (data.cBoxContainer[ind].length) {
1128                        var $boxContent = $(data.cBoxContainer[ind]);
1129                        this.registerProductActions($boxContent);
1130                        $list.replaceWith($boxContent).removeClass('d-none');
1131                    } else {
1132                        $list.html('').addClass('d-none');
1133                    }
1134                }
1135            }
1136        },
1137
1138        handleProductAction: function(action, data) {
1139            let $action = $(action);
1140            switch (action.name) {
1141                case this.options.action.compareList:
1142                    if ($action.hasClass('action-tip-animation-b')) {
1143                        if ($action.hasClass('on-list')) {
1144                            $action.removeClass("on-list");
1145                            $action.next().removeClass("press");
1146                            $action.next().next().addClass("press");
1147                            return this.removeFromCompareList(data);
1148                        } else {
1149                            $action.addClass("on-list");
1150                            $action.next().addClass("press");
1151                            $action.next().next().removeClass("press");
1152                            $(this.options.selector.navCompare).removeClass('d-none');
1153
1154                            let $moveTo = isMobileByBodyClass()
1155                                ? $('.wish-compare-animation-mobile #burger-menu')
1156                                : $('.wish-compare-animation-desktop #shop-nav-compare');
1157                            $.evo.article().moveItemAnimation($action, $moveTo);
1158
1159                            return this.addToComparelist(data, $action);
1160                        }
1161                    } else {
1162                        return this.addToComparelist(data, $action);
1163                    }
1164                case this.options.action.compareListRemove:
1165                    return this.removeFromCompareList(data);
1166                case this.options.action.wishList:
1167                    data[this.options.input.quantity] = $('input[data-product-id="' + data.a + '"]').val()
1168                        || $('#buy_form_'+data.a+' '+this.options.selector.quantity).val()
1169                        || $('#quantity').val();
1170                    if ($action.hasClass('on-list')) {
1171                        $action.removeClass("on-list");
1172                        $action.next().removeClass("press");
1173                        $action.next().next().addClass("press");
1174                        data.a = data.wlPos;
1175                        return this.removeFromWishList(data);
1176                    } else {
1177                        $action.addClass("on-list");
1178                        let $moveTo = isMobileByBodyClass()
1179                            ? $('.wish-compare-animation-mobile #burger-menu')
1180                            : $('.wish-compare-animation-desktop #shop-nav-wish');
1181                        $.evo.article().moveItemAnimation($action, $moveTo);
1182                        return this.addToWishlist(data, $action);
1183                    }
1184                case this.options.action.wishListRemove:
1185                    return this.removeFromWishList(data);
1186            }
1187
1188            return false;
1189        },
1190
1191        configurator: function(init) {
1192            if (this.isSingleArticle()) {
1193                var that      = this,
1194                    container = $('#cfg-container'),
1195                    sidebar   = $('#cfg-sticky-sidebar'),
1196                    width,
1197                    form;
1198
1199                if (container.length === 0) {
1200                    return;
1201                }
1202
1203                $.evo.extended().startSpinner(container);
1204
1205                $('#buy_form').find('*[data-selected="true"]')
1206                    .attr('checked', true)
1207                    .attr('selected', true)
1208                    .attr('data-selected', null);
1209
1210                form = $.evo.io().getFormValues('buy_form');
1211
1212                $.evo.io().call('buildConfiguration', [form], that, function (error, data) {
1213                    var result,
1214                        i,
1215                        j,
1216                        item,
1217                        cBeschreibung,
1218                        quantityWrapper,
1219                        grp,
1220                        value,
1221                        enableQuantity,
1222                        nNetto,
1223                        quantityInput;
1224                    $('.js-start-configuration').prop('disabled', !(data.response.variationsSelected && data.response.inStock));
1225                    $('.js-choose-variations-wrapper').toggleClass('d-none', data.response.variationsSelected);
1226                    $('.js-cfg-group').each(function (i, item) {
1227                        let iconChecked     = $('a[href="#'+this.id+'"] .js-group-checked'),
1228                            badgeInfoDanger = 'alert-info';
1229                        if (data.response.invalidGroups && data.response.invalidGroups.includes($(this).data('id'))) {
1230                            iconChecked.addClass('d-none');
1231                            if ($(this).hasClass('visited')) {
1232                                badgeInfoDanger = 'alert-danger';
1233                            }
1234                            $(this).find('.js-group-badge-checked')
1235                                .removeClass('alert-success alert-info')
1236                                .addClass(badgeInfoDanger);
1237                        } else {
1238                            iconChecked.removeClass('d-none');
1239                            $(this).find('.js-group-badge-checked')
1240                                .addClass('alert-success')
1241                                .removeClass('alert-danger alert-info');
1242                        }
1243                    });
1244                    $('.js-cfg-group-error').addClass('d-none').html('');
1245                    $.each(data.response.errorMessages, function (i, item) {
1246                        $('.js-cfg-group-error[data-id="' + item.group + '"]').removeClass('d-none').html(item.message);
1247                    });
1248                    let $summaryFinish = $('#cfg-tab-summary-finish');
1249                    if (data.response.valid) {
1250                        $('.js-cfg-validate').prop('disabled', false);
1251                        $summaryFinish.children().removeClass('disabled');
1252                        $summaryFinish.removeClass('disabled');
1253                    } else {
1254                        $('.js-cfg-validate').prop('disabled', true);
1255                        $summaryFinish.children().addClass('disabled');
1256                        $summaryFinish.addClass('disabled');
1257                    }
1258                    $.evo.extended().stopSpinner();
1259                    if (error) {
1260                        $.evo.error(data);
1261                        return;
1262                    }
1263                    result = data.response;
1264
1265                    if (!result.oKonfig_arr) {
1266                        $.evo.error('Missing configuration groups');
1267                        return;
1268                    }
1269
1270                    // global price
1271                    nNetto = result.nNettoPreise;
1272                    that.setPrice(result.fGesamtpreis[nNetto], result.cPreisLocalized[nNetto], result.cPreisString);
1273                    if (result.fAlterPreis && result.cAlterPreisLocalized) {
1274                        that.setOldPrice(
1275                            result.fAlterPreis[nNetto],
1276                            result.cAlterPreisLocalized[nNetto],
1277                            result.nAlterPreisDiscountPercent || 0
1278                        );
1279                    }
1280                    that.setStockInformation(result.cEstimatedDelivery, undefined, result.Lageranzeige && result.Lageranzeige.nStatus, result.Lageranzeige && (result.Lageranzeige.AmpelText || ''));
1281
1282                    $('#content .summary').html(result.cTemplate);
1283                    $.evo.tooltips();
1284                    $.evo.extended()
1285                        .trigger('priceChanged', result);
1286                });
1287            }
1288        },
1289
1290        initConfigListeners: function () {
1291            $('.js-cfg-group').on('click', function () {
1292                $(this).addClass('visited');
1293            });
1294            $('#cfg-tab-summary-finish').on('click', function () {
1295                if (!$(this).hasClass('disabled')) {
1296                    $('#cfg-modal-tabs').find('.nav-link').removeClass('active');
1297                    $('#cfg-tab-summary').children().addClass('active');
1298                    $(this).children().removeClass('active');
1299                }
1300            });
1301            $('.js-cfg-group-collapse').on('click', function () {
1302                $(this).addClass('visited');
1303            });
1304        },
1305
1306        variationRefreshAll: function($wrapper) {
1307            $('.variations select', $wrapper).selectpicker('refresh');
1308            $.evo.initSliders()
1309        },
1310
1311        getConfigGroupQuantity: function (groupId) {
1312            return $('.cfg-group[data-id="' + groupId + '"] .quantity');
1313        },
1314
1315        getConfigGroupQuantityInput: function (groupId) {
1316            return $('.cfg-group[data-id="' + groupId + '"] .quantity input');
1317        },
1318
1319        getConfigGroupImage: function (groupId) {
1320            return $('.cfg-group[data-id="' + groupId + '"] .group-image img');
1321        },
1322
1323        setConfigItemImage: function (groupId, img) {
1324            $('.cfg-group[data-id="' + groupId + '"] .group-image img').attr('src', img).first();
1325        },
1326
1327        setConfigItemDescription: function (groupId, itemBeschreibung) {
1328            var groupItems                       = $('.cfg-group[data-id="' + groupId + '"] .group-items');
1329            var descriptionDropdownContent       = groupItems.find('#filter-collapsible_dropdown_' + groupId + '');
1330            var descriptionDropdownContentHidden = groupItems.find('.d-none');
1331            var descriptionCheckdioContent       = groupItems.find('div[id^="filter-collapsible_checkdio"]');
1332            var multiselect                      = groupItems.find('select').attr("multiple");
1333
1334            //  Bisher kein Content mit einer Beschreibung vorhanden, aber ein Artikel mit Beschreibung ausgewählt
1335            if (descriptionDropdownContentHidden.length > 0 && descriptionCheckdioContent.length === 0 && itemBeschreibung.length > 0 && multiselect !== "multiple") {
1336                groupItems.find('a[href="#filter-collapsible_dropdown_' + groupId + '"]').removeClass('d-none');
1337                descriptionDropdownContent.replaceWith('<div id="filter-collapsible_dropdown_' + groupId + '" class="collapse top10 panel-body">' + itemBeschreibung + '</div>');
1338                //  Bisher Content mit einer Beschreibung vorhanden, aber ein Artikel ohne Beschreibung ausgewählt
1339            } else if (descriptionDropdownContentHidden.length === 0 && descriptionCheckdioContent.length === 0 && itemBeschreibung.length === 0 && multiselect !== "multiple") {
1340                groupItems.find('a[href="#filter-collapsible_dropdown_' + groupId + '"]').addClass('d-none');
1341                descriptionDropdownContent.addClass('d-none');
1342                //  Bisher Content mit einer Beschreibung vorhanden und ein Artikel mit Beschreibung ausgewählt
1343            } else if (descriptionDropdownContentHidden.length === 0 && descriptionCheckdioContent.length === 0 && itemBeschreibung.length > 0 && multiselect !== "multiple") {
1344                descriptionDropdownContent.replaceWith('<div id="filter-collapsible_dropdown_' + groupId + '" class="collapse top10 panel-body">' + itemBeschreibung + '</div>');
1345            }
1346        },
1347
1348        setPrice: function(price, fmtPrice, priceLabel, wrapper) {
1349            var $wrapper = this.getWrapper(wrapper);
1350
1351            if (this.isSingleArticle()) {
1352                $('#product-offer .price', $wrapper).html(fmtPrice);
1353                $('#product-offer meta[itemprop="price"]', $wrapper).attr('content',price);
1354                if (priceLabel.length > 0) {
1355                    $('#product-offer .price_label', $wrapper).html(priceLabel);
1356                }
1357            } else {
1358                var $price = $('.price_wrapper', $wrapper);
1359
1360                $('.price span:first-child', $price).html(fmtPrice);
1361                if (priceLabel.length > 0) {
1362                    $('.price_label', $price).html(priceLabel);
1363                }
1364            }
1365
1366            $.evo.trigger('changed.article.price', { price: price });
1367        },
1368
1369        setStockInformation: function(cEstimatedDelivery, wrapper, nStatus, ampelText) {
1370            let $wrapper = this.getWrapper(wrapper);
1371            let $span    = $('.delivery-status .estimated-delivery span', $wrapper);
1372
1373            $span.html(cEstimatedDelivery);
1374
1375            if (typeof nStatus !== 'undefined' && nStatus !== false) {
1376                $span.removeClass('a0 a1 a2').addClass('a' + nStatus);
1377
1378                let $statusSpan = $('.delivery-status .status', $wrapper);
1379                $statusSpan.removeClass('status-0 status-1 status-2').addClass('status-' + nStatus);
1380                if (typeof ampelText !== 'undefined' && ampelText !== false && ampelText !== '') {
1381                    let $icon = $statusSpan.find('.status-icon').detach();
1382                    $statusSpan.html(ampelText).prepend($icon);
1383                }
1384            }
1385        },
1386
1387        setOldPrice: function(price, fmtPrice, discountPercent, wrapper) {
1388            let $wrapper   = this.getWrapper(wrapper),
1389                $container = $('#product-offer', $wrapper);
1390
1391            if (this.isSingleArticle()) {
1392                let $oldPriceDetail = $('.old-price-dynamic', $container);
1393                if (price > 0 && fmtPrice.length > 0) {
1394                    if ($oldPriceDetail.length > 0) {
1395                        $oldPriceDetail.find('.old-price-value').html(fmtPrice);
1396                        $oldPriceDetail.removeClass('d-none');
1397                    } else {
1398                        let $staticOldPrice = $('.old-price-dynamic .old-price-value, .old-price .old-price-value', $container);
1399                        if ($staticOldPrice.length > 0) {
1400                            $staticOldPrice.html(fmtPrice);
1401                            $staticOldPrice.closest('.old-price-dynamic, .old-price').removeClass('d-none');
1402                        }
1403                    }
1404                    if (discountPercent > 0) {
1405                        $('.discount-dynamic .discount-percent', $container).html(discountPercent + '%');
1406                        $('.discount-dynamic', $container).removeClass('d-none');
1407                    } else {
1408                        $('.discount-dynamic', $container).addClass('d-none');
1409                    }
1410                } else {
1411                    $oldPriceDetail.addClass('d-none');
1412                    $('.discount-dynamic', $container).addClass('d-none');
1413                    $('.old-price-dynamic, .old-price', $container)
1414                        .not('.old-price-dynamic')
1415                        .addClass('d-none');
1416                }
1417            } else {
1418                let $priceWrapper = $('.price_wrapper', $wrapper);
1419                let $oldPriceList = $('.old-price', $priceWrapper);
1420                if (price > 0 && fmtPrice.length > 0) {
1421                    if ($oldPriceList.length > 0) {
1422                        $oldPriceList.find('.old-price-value, .value').html(fmtPrice);
1423                        $oldPriceList.removeClass('d-none');
1424                    }
1425                } else {
1426                    $oldPriceList.addClass('d-none');
1427                }
1428            }
1429
1430            $.evo.trigger('changed.article.oldprice', { price: price, discountPercent: discountPercent });
1431        },
1432
1433        setStaffelPrice: function(prices, fmtPrices, wrapper) {
1434            var $wrapper   = this.getWrapper(wrapper),
1435                $container = $('#product-offer', $wrapper);
1436
1437            $.each(fmtPrices, function(index, value){
1438                $('.bulk-price-' + index + ' .bulk-price', $container).html(value);
1439            });
1440        },
1441
1442        setVPEPrice: function(fmtVPEPrice, VPEPrices, fmtVPEPrices, wrapper) {
1443            let $wrapper   = this.getWrapper(wrapper);
1444            if ($('#product-offer').length > 0) {
1445                $wrapper = $('#product-offer', $wrapper);
1446            }
1447
1448            $('.base-price .value', $wrapper).html(fmtVPEPrice);
1449            $.each(fmtVPEPrices, function(index, value){
1450                $('.bulk-price-' + index + ' .bulk-base-price', $wrapper).html(value);
1451            });
1452        },
1453
1454        /**
1455         * @deprecated since 4.05 - use setArticleWeight instead
1456         */
1457        setUnitWeight: function(UnitWeight, newUnitWeight) {
1458            $('#article-tabs .product-attributes .weight-unit').html(newUnitWeight);
1459        },
1460
1461        setArticleWeight: function(ArticleWeight, wrapper) {
1462            if (this.isSingleArticle()) {
1463                var $articleTabs = $('#article-tabs');
1464
1465                if ($.isArray(ArticleWeight)) {
1466                    $('.product-attributes .weight-unit', $articleTabs).html(ArticleWeight[0][1]);
1467                    $('.product-attributes .weight-unit-article', $articleTabs).html(ArticleWeight[1][1]);
1468                } else {
1469                    $('.product-attributes .weight-unit', $articleTabs).html(ArticleWeight);
1470                }
1471            } else {
1472                var $wrapper = this.getWrapper(wrapper);
1473
1474                if ($.isArray(ArticleWeight)) {
1475                    $('.attr-weight .value', $wrapper).html(ArticleWeight[0][1]);
1476                    $('.attr-weight.weight-unit-article .value', $wrapper).html(ArticleWeight[1][1]);
1477                } else {
1478                    $('.attr-weight .value', $wrapper).html(ArticleWeight);
1479                }
1480            }
1481
1482        },
1483
1484        setProductNumber: function(productNumber, wrapper) {
1485            var $wrapper = this.getWrapper(wrapper);
1486
1487            $('#product-offer span[itemprop="sku"]', $wrapper).html(productNumber);
1488        },
1489
1490        setArticleContent: function(id, variation, url, variations, wrapper) {
1491            var $wrapper  = this.getWrapper(wrapper),
1492                listStyle = $('#product-list-type').val();
1493
1494                if (listStyle === 'undefined') {
1495                    listStyle = $('#ed_list.active').length > 0 ? 'list' : 'gallery';
1496                }
1497
1498                $.evo.extended().startSpinner($wrapper);
1499
1500            if (this.modalShown) {
1501                this.loadModalArticle(url, wrapper,
1502                    function() {
1503                        var article = new ArticleClass();
1504                        article.register(wrapper);
1505                        $.evo.extended().stopSpinner();
1506                    },
1507                    function() {
1508                        $.evo.extended().stopSpinner();
1509                        $.evo.error('Error loading ' + url);
1510                    }
1511                );
1512            } else if (this.isSingleArticle()) {
1513                if($('#opc-startmenu').length > 0) {
1514                    return this.redirectToArticle(id, variation, url, variations, wrapper);
1515                }
1516
1517                $.evo.extended().loadContent(url, () => {
1518                    $.evo.extended().register();
1519                    $.evo.article().register(wrapper);
1520
1521                    $(variations).each(function (i, item) {
1522                        $.evo.article().variationSetVal(item.key, item.value, wrapper);
1523                    });
1524
1525                    if (document.location.href !== url) {
1526                        history.pushState({a: id, a2: variation, url: url, variations: variations}, "", url);
1527                    }
1528                    $.evo.extended().stopSpinner();
1529                }, function () {
1530                    $.evo.error('Error loading ' + url);
1531                    $.evo.extended().stopSpinner();
1532                }, false, wrapper);
1533            } else {
1534                $.evo.extended().loadContent(url + (url.indexOf('?') >= 0 ? '&' : '?') + 'isListStyle=' + listStyle, function (content) {
1535                    $.evo.article().register(wrapper);
1536
1537                    $('[data-toggle="basket-add"]', $(wrapper)).on('submit', function(event) {
1538                        event.preventDefault();
1539                        event.stopPropagation();
1540
1541                        var $form = $(this);
1542                        var data  = $form.serializeObject();
1543                        data['a'] = variation;
1544
1545                        $.evo.basket().addToBasket($form, data);
1546                    });
1547
1548                    $(variations).each(function (i, item) {
1549                        $.evo.article().variationSetVal(item.key, item.value, wrapper);
1550                    });
1551
1552                    $.evo.extended().stopSpinner();
1553                }, function () {
1554                    $.evo.error('Error loading ' + url);
1555                    $.evo.extended().stopSpinner();
1556                }, false, wrapper);
1557            }
1558        },
1559
1560        redirectToArticle: function(id, variation, url, variations, wrapper) {
1561            var $wrapper  = this.getWrapper(wrapper),
1562                listStyle = $('#product-list-type').val();
1563
1564            $.evo.extended().startSpinner($wrapper);
1565
1566            window.open(url, '_top');
1567
1568            $.evo.extended().stopSpinner($wrapper);
1569        },
1570
1571        variationResetAll: function(wrapper) {
1572            var $wrapper = this.getWrapper(wrapper);
1573
1574            $('.variation[data-value] input:checked', $wrapper).prop('checked', false);
1575            $('.variations select option', $wrapper).prop('selected', false);
1576            $('.variations select', $wrapper).selectpicker('refresh');
1577        },
1578
1579        variationDisableAll: function(wrapper) {
1580            let $wrapper = this.getWrapper(wrapper);
1581
1582            $('.swatches-selected', $wrapper).text('');
1583            $('[data-value].variation', $wrapper).each(function(i, item) {
1584                $(item)
1585                    .removeClass('active loading')
1586                    .addClass('not-available');
1587                $.evo.article()
1588                    .removeStockInfo($(item));
1589            });
1590        },
1591
1592        variationSetVal: function(key, value, wrapper) {
1593            var $wrapper = this.getWrapper(wrapper);
1594
1595            $('[data-key="' + key + '"]', $wrapper).val(value);
1596        },
1597
1598        variationEnable: function(key, value, wrapper) {
1599            var $wrapper = this.getWrapper(wrapper),
1600                $item    = $('[data-value="' + value + '"].variation', $wrapper);
1601
1602            $item.removeClass('not-available swatches-sold-out swatches-not-in-stock');
1603        },
1604
1605        showGalleryVariation: function(key, wrapper) {
1606            var $wrapper = this.getWrapper(wrapper),
1607                $item    = $('.variation-' + key, $wrapper);
1608
1609            $item.collapse();
1610        },
1611
1612        variationActive: function(key, value, def, wrapper) {
1613            var $wrapper = this.getWrapper(wrapper),
1614                $item    = $('[data-value="' + value + '"].variation', $wrapper);
1615            $item.addClass('active')
1616                .find('input')
1617                .prop('checked', true)
1618                .end()
1619                .prop('selected', true);
1620
1621            $('[data-id="'+key+'"].swatches-selected')
1622                .text($item.attr('data-original'));
1623        },
1624
1625        removeStockInfo: function($item) {
1626            var type = $item.attr('data-type'),
1627                elem,
1628                label,
1629                wrapper;
1630
1631            switch (type) {
1632                case 'option':
1633                    label = $item.data('content');
1634                    wrapper = $('<div />').append(label);
1635                    $(wrapper)
1636                        .find('.badge-not-available')
1637                        .remove();
1638                    label = $(wrapper).html();
1639                    $item.data('content', label)
1640                        .attr('data-content', label);
1641                    break;
1642                case 'radio':
1643                    elem = $item.find('.badge-not-available');
1644                    if (elem.length === 1) {
1645                        $(elem).remove();
1646                    }
1647                    break;
1648                case 'swatch':
1649                    $item.tooltip('dispose');
1650                    break;
1651            }
1652            $item.removeAttr('data-stock');
1653        },
1654
1655        variationInfo: function(value, status, note, notExists) {
1656            let $item = $('[data-value="' + value + '"].variation'),
1657                type = $item.attr('data-type'),
1658                text,
1659                content,
1660                $wrapper,
1661                label;
1662
1663            $item.attr('data-stock', _stock_info[status]);
1664
1665            switch (type) {
1666                case 'option':
1667                    text     = ' (' + note + ')';
1668                    content  = $item.data('content');
1669                    $wrapper = $('<div />');
1670
1671                    $wrapper.append(content);
1672                    $wrapper
1673                        .find('.badge-not-available')
1674                        .remove();
1675
1676                    label = $('<span />')
1677                        .addClass('badge badge-danger badge-not-available')
1678                        .text(' '+note);
1679
1680                    $wrapper.append(label);
1681
1682                    $item.data('content', $wrapper.html())
1683                        .attr('data-content', $wrapper.html());
1684
1685                    $item.closest('select')
1686                        .selectpicker('refresh');
1687                    break;
1688                case 'radio':
1689                    $item.find('.badge-not-available')
1690                        .remove();
1691
1692                    label = $('<span />')
1693                        .addClass('badge badge-danger badge-not-available')
1694                        .text(' '+note);
1695
1696                    $item.append(label);
1697                    break;
1698                case 'swatch':
1699                    if (!$item.hasClass('gall-preview')) {
1700                        $item.tooltip({
1701                            title: note,
1702                            trigger: 'hover',
1703                            container: 'body'
1704                        });
1705                    }
1706                    if (notExists) {
1707                        $item.addClass('swatches-not-in-stock');
1708                    } else {
1709                        $item.addClass('swatches-sold-out');
1710                    }
1711                    break;
1712            }
1713        },
1714
1715        variationSwitch: function($item, animation, wrapper) {
1716            if ($item) {
1717                var formID   = $item.closest('form').attr('id'),
1718                    $current = this.getCurrent($item),
1719                    key      = $current.data('key'),
1720                    value    = $current.data('value'),
1721                    io       = $.evo.io(),
1722                    args     = io.getFormValues(formID),
1723                    layout   = $('#product-list-type').val(),
1724                    $wrapper = this.getWrapper(wrapper);
1725
1726                if (animation) {
1727                    $.evo.extended().startSpinner();
1728                } else {
1729                    $('.updatingStockInfo', $wrapper).show();
1730                }
1731
1732                $('.tooltip.show').remove();
1733                args.wrapper = wrapper;
1734                args.layout  = layout;
1735
1736                $.evo.article()
1737                    .variationDispose(wrapper);
1738
1739                io.call('checkVarkombiDependencies', [args, key, value], $item, function (error, data) {
1740                    if (animation) {
1741                        $.evo.extended().stopSpinner();
1742                    }
1743                    $('.updatingStockInfo', $wrapper).hide();
1744                    if (error) {
1745                        $.evo.error('checkVarkombiDependencies');
1746                    }
1747                });
1748            }
1749        },
1750
1751        modalArticleDetail: function(item, wrapper)
1752        {
1753            let $item = $(item);
1754            let title = $item.data('title');
1755            let url   = $item.data('src');
1756
1757            if (typeof this.modalView === 'undefined' || this.modalView === null) {
1758                this.modalView = $(
1759                    '<div id="' + this.options.modal.id + '" class="modal fade" role="dialog" tabindex="-1" >' +
1760                    '   <div class="modal-dialog modal-lg">' +
1761                    '       <div class="modal-content">' +
1762                    '           <div class="modal-header">' +
1763                    '               <button type="button" class="x close" data-dismiss="modal"><i class="fa fa-times"></i></button>' +
1764                    '               <div class="modal-title h4">' + title + '</div>' +
1765                    '           </div>' +
1766                    '           <div class="modal-body">' +
1767                    '               <div id="' + wrapper.slice(1) + '" style="min-height:100px">' +
1768                    '<div class="jtl-spinner"><i class="fa fa-spinner fa-pulse"></i></div>' +
1769                    '               </div>' +
1770                    '           </div>' +
1771                    '       </div>' +
1772                    '   </div>' +
1773                    '</div>');
1774                this.modalView
1775                    .on('hidden.bs.modal', () => {
1776                        $('.modal-body', this.modalView)
1777                            .html('<div id="' + wrapper.slice(1) + '" style="min-height:100px" />');
1778                        $('.modal-title', this.modalView).html('');
1779                        this.modalView
1780                            .off('shown.bs.modal');
1781                        this.modalShown = false;
1782                    });
1783            } else {
1784                $('.modal-title', this.modalView).html(title);
1785                $('.modal-body', this.modalView)
1786                    .html('<div id="' + wrapper.slice(1) + '" style="min-height:100px">' +
1787                        '<div class="jtl-spinner"><i class="fa fa-spinner fa-pulse"></i></div></div>');
1788            }
1789
1790            this.modalView
1791                .on('shown.bs.modal', () => {
1792                    this.modalShown = true;
1793                    this.loadModalArticle(url, wrapper,
1794                        function() {
1795                            // no functionality needed in quickview
1796                            // var article = new ArticleClass();
1797                            // article.register(wrapper);
1798                            $.evo.extended().stopSpinner();
1799                            $('[data-toggle="popover"]', wrapper).popover({ html: true });
1800                            $('.product-image', wrapper).css('cursor', 'default');
1801                        },
1802                        function() {
1803                            $.evo.extended().stopSpinner();
1804                            $.evo.error('Error loading ' + params.url);
1805                        }
1806                    );
1807                })
1808                .modal('show');
1809        },
1810
1811        variationPrice: function($item, animation, wrapper) {
1812            var formID   = $item.closest('form').attr('id'),
1813                $wrapper = this.getWrapper(wrapper),
1814                io       = $.evo.io(),
1815                args     = io.getFormValues(formID);
1816
1817            if (animation) {
1818                $.evo.extended().startSpinner($wrapper);
1819            }
1820
1821            args.wrapper = wrapper;
1822            io.call('checkDependencies', [args], $(this), function (error, data) {
1823                let $action = $('button[data-product-id-wl="' + data.response.itemID + '"]');
1824                if (data.response.check > 0) {
1825                    $action.attr('data-wl-pos', data.response.check);
1826                    $action.data('wl-pos', data.response.check);
1827                    $action.closest('form').find('input[name="wlPos"]').val(data.response.check)
1828                    $action.addClass('on-list');
1829                } else {
1830                    $action.removeClass('on-list');
1831                }
1832
1833                if (animation) {
1834                    $.evo.extended().stopSpinner();
1835                }
1836                if (error) {
1837                    $.evo.error('checkDependencies');
1838                }
1839            });
1840        },
1841
1842        variationDispose: function(wrapper) {
1843            var $wrapper = this.getWrapper(wrapper);
1844
1845            $('[role="tooltip"]', $wrapper).remove();
1846        },
1847
1848        moveItemAnimation: function(item, moveTo) {
1849            if (!item.length || !moveTo.length || $(this).hasClass('on-list')) {
1850                return;
1851            }
1852            setTimeout(function() {
1853                let itemClone = item.clone()
1854                    .offset({
1855                        top: item.offset().top,
1856                        left: item.offset().left
1857                    }).css({
1858                        'opacity': '0.5',
1859                        'position': 'absolute',
1860                        'z-index': '10000'
1861                    })
1862                    .appendTo($('body'))
1863                    .animate({
1864                        'top': moveTo.offset().top + 5,
1865                        'left': moveTo.offset().left + 5,
1866                    }, 700);
1867
1868                itemClone.animate({
1869                    'width': 0,
1870                    'height': 0
1871                }, function () {
1872                    $(this).detach()
1873                });
1874            }, 0);
1875        },
1876
1877        initAbnahmeIntervallError: function() {
1878            let $intervallNotice = $('#intervall-notice');
1879            if ($intervallNotice.length > 0) {
1880                $('#quantity').on('change', function () {
1881                    let $step   = $(this).attr('step'),
1882                        diff    = Math.abs(($(this).val() % $step) - $step),
1883                        epsilon = 0.00000001;
1884                    $('#intervall-notice-danger').remove();
1885                    if (!(diff < epsilon || diff + epsilon > $step)) {
1886                        $('#quantity-grp').after('<div id="intervall-notice-danger" class="alert alert-danger mt-2">'
1887                            + $intervallNotice.html() + '</div>');
1888                    }
1889                });
1890            }
1891        }
1892    };
1893
1894    $v = new ArticleClass();
1895
1896    $(document).ready(function () {
1897        $v.onLoad();
1898        $v.register();
1899    });
1900
1901    $(window).on('resize',
1902        viewport.changed(function(){
1903            $v.configurator();
1904        })
1905    );
1906
1907    // PLUGIN DEFINITION
1908    // =================
1909    $.evo.article = function () {
1910       return $v;
1911    };
1912})(jQuery, document, window, ResponsiveBootstrapToolkit);

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.