PageSourceSearch

https://www.der-rollende-shop.de/templates/NOVA/js/jtl.article.js?v=5.2.0

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