PageSourceSearch

https://fangoorn.de/plugins/knm_offcanvas/frontend/js/knmOffcanvas531.min.js?v=1.7.1

js fangoorn.de collected 2026-10-01 14:24:49 UTC 19,131 bytes, 522 lines download raw bytes

1var closeTimer = false;
2var lastFocusedTrigger = null;
3
4/**
5 * Gibt alle sichtbaren, fokussierbaren Elemente innerhalb eines Containers zurück
6 *
7 * @param {Element|jQuery} container
8 * @returns {jQuery}
9 */
10function knmGetFocusableElements(container) {
11    return $(container).find(
12        'a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), ' +
13        'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
14    ).filter(':visible');
15}
16
17/**
18 * Öffnet ein Offcanvas-Panel und verschiebt den Fokus hinein
19 *
20 * @param {Element} trigger  Auslösendes Element
21 * @param {boolean} autoClose  Automatisch schließen nach Verzögerung
22 * @returns {boolean}
23 */
24function knmOpenCanvas(trigger, autoClose = false) {
25    lastFocusedTrigger = document.activeElement;
26
27    let bsOverlay = $('.bs-canvas-overlay');
28    let ctrl = $(trigger);
29    let elm = ctrl.is('button') ? ctrl.data('target') : ctrl.attr('href');
30    let $elm = $(elm);
31
32    $('body').addClass('knm-canvas-openend');
33    $elm.addClass('show');
34    $(elm + ' .bs-canvas-close').attr('aria-expanded', 'true');
35    $('[data-target="' + elm + '"], a[href="' + elm + '"]').attr('aria-expanded', 'true');
36    if (bsOverlay.length) {
37        bsOverlay.addClass('show');
38    }
39
40    // Fokus auf ersten fokussierbaren Inhalt verschieben (nach Transition-Start)
41    setTimeout(function () {
42        let $focusable = knmGetFocusableElements($elm);
43        if ($focusable.length) {
44            $focusable.first().focus();
45        }
46    }, 50);
47
48    if (autoClose) {
49        $elm.on('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function (event) {
50            if (event.target.id === 'cart-offcanvas' && closeTimer === false) {
51                if (isTouchDevice() && closeDelayMobile > 0) {
52                    closeTimer = setTimeout(function () {
53                        knmCloseCanvas(null);
54                    }, closeDelayMobile * 1000);
55                } else if (closeDelayDesktop > 0) {
56                    closeTimer = setTimeout(function () {
57                        knmCloseCanvas(null);
58                    }, closeDelayDesktop * 1000);
59                }
60            }
61        });
62    }
63
64    return false;
65}
66
67/**
68 * Schließt ein Offcanvas-Panel und gibt den Fokus zurück
69 *
70 * @param {Element|null} targetElement  Auslösendes Element (Close-Button) oder null für alle schließen
71 * @returns {boolean}
72 */
73function knmCloseCanvas(targetElement) {
74    clearTimeout(closeTimer);
75    closeTimer = false;
76
77    let elm;
78    let bsOverlay = $('.bs-canvas-overlay');
79    $('body').removeClass('knm-canvas-openend');
80
81    if (targetElement && $(targetElement).hasClass('bs-canvas-close')) {
82        // Gezieltes Schließen über den Close-Button
83        elm = $(targetElement).closest('.bs-canvas');
84        let elmId = elm.attr('id');
85        $('[data-target="#' + elmId + '"], a[href="#' + elmId + '"]').attr('aria-expanded', 'false');
86    } else {
87        // Alle Offcanvas schließen (Overlay-Klick, Timer oder Escape)
88        elm = $('.bs-canvas');
89        $('[data-toggle="canvas"]').attr('aria-expanded', 'false');
90    }
91
92    elm.removeClass('show');
93    $('.bs-canvas-close', elm).attr('aria-expanded', 'false');
94    if (bsOverlay.length) {
95        bsOverlay.removeClass('show');
96    }
97
98    // Fokus zurück auf auslösendes Element
99    if (lastFocusedTrigger && typeof lastFocusedTrigger.focus === 'function') {
100        lastFocusedTrigger.focus();
101        lastFocusedTrigger = null;
102    }
103
104    return false;
105}
106
107/**
108 * Gibt true zurück wenn das Gerät Touch-Eingabe unterstützt
109 *
110 * @returns {boolean}
111 */
112function isTouchDevice() {
113    return window.ontouchstart !== undefined;
114}
115
116$(function () {
117
118    let $body = $('body');
119    let $cartOffcanvas = $('#cart-offcanvas');
120    let accountDropdown = $('body.offcanvas-account .account-icon-dropdown');
121    let wishlistDropdown = $('body.offcanvas-wishlist #shop-nav-wish');
122    let compareDropdown = $('body.offcanvas-compare #shop-nav-compare');
123
124    // Offcanvas öffnen per Klick
125    $('[data-toggle="canvas"]').on('click', function (e) {
126        e.preventDefault();
127        knmOpenCanvas(this);
128    });
129
130    // Offcanvas schließen per Klick auf Close-Button oder Overlay
131    $(document).on('click', '.bs-canvas-close, .bs-canvas-overlay', function (e) {
132        e.preventDefault();
133        knmCloseCanvas(this);
134    });
135
136    // Escape-Taste schließt das aktive Offcanvas
137    $(document).on('keydown', function (e) {
138        if (e.key === 'Escape' && $body.hasClass('knm-canvas-openend')) {
139            e.preventDefault();
140            let $closeBtn = $('.bs-canvas.show .bs-canvas-close').first();
141            knmCloseCanvas($closeBtn[0] || null);
142        }
143    });
144
145    // Fokus-Trap: Tab-Navigation bleibt innerhalb des geöffneten Offcanvas
146    $(document).on('keydown', '.bs-canvas.show', function (e) {
147        if (e.key !== 'Tab') {
148            return;
149        }
150        let $focusable = knmGetFocusableElements(this);
151        if (!$focusable.length) {
152            return;
153        }
154        let first = $focusable.first()[0];
155        let last = $focusable.last()[0];
156        if (e.shiftKey) {
157            if (document.activeElement === first) {
158                e.preventDefault();
159                last.focus();
160            }
161        } else {
162            if (document.activeElement === last) {
163                e.preventDefault();
164                first.focus();
165            }
166        }
167    });
168
169    // Tastatur-Handler für klickbare Tabellenzeilen (Wunschliste)
170    $(document).on('keydown', '#offcanvas-wrapper .clickable-row', function (e) {
171        if (e.key === 'Enter' || e.key === ' ') {
172            e.preventDefault();
173            let href = $(this).data('href');
174            if (href) {
175                window.location.href = href;
176            }
177        }
178    });
179
180    // override jtl js class
181    let basketClassOverride = $.evo.basket();
182    basketClassOverride.updateCart = function (type, cartNotices, cartIndex, couponNotices, autoClose = true) {
183        let that = this;
184        let t = parseInt(type);
185
186        if (type === undefined) {
187            t = 0;
188        }
189
190        let paymenBtnsWrapper = $('#ppc-paypal-button-custom-miniCart-wrapper');
191        let paymenBtnsContainer = $('#paypal-button-miniCart-container');
192        let paymentBanner = $('#ppc-miniCart-instalment-banner');
193        let paymentBannerAmazon = $('#cart-offcanvas .lpa-button');
194
195        $.evo.io().call('knmGetBasketItemsOffcanvas', [t], this, function (error, data) {
196
197            if (error) {
198                return;
199            }
200
201            let tpl = data.response.cTemplate;
202            let triggerSelector = 'a[href="#cart-offcanvas"]';
203            let $trigger = $(triggerSelector);
204            let $content = $(tpl);
205            let highlightDuration = 7000;
206
207            // Warenkorbinhalt ersetzen und Mengenfelder initialisieren
208            $cartOffcanvas.html($content.find('#cart-offcanvas').html());
209            $trigger.html($content.find(triggerSelector).html());
210
211            $cartOffcanvas.html($content.find('#cart-offcanvas').html());
212            $cartOffcanvas.find('.cart-dropdown-buttons').append(paymenBtnsWrapper);
213            $cartOffcanvas.find('.cart-dropdown-buttons').append(paymenBtnsContainer);
214            $cartOffcanvas.find('.cart-dropdown-buttons').after(paymentBanner);
215            $cartOffcanvas.find('.cart-dropdown-buttons').after(paymentBannerAmazon);
216
217            if (typeof knm_unregister_qty_input_events == 'function') {
218                knm_unregister_qty_input_events();
219            }
220
221            window.initNumberInput();
222
223            // Warenkorbhinweise anzeigen
224            if (cartNotices) {
225                for (let i = 0; i < cartNotices.length; i++) {
226                    let Warenkorbhinweis = cartNotices[i];
227                    let listChildItem = $('.cart-dropdown .item:nth-child(' + (1 + parseInt(cartIndex)) + ')');
228                    let alert = $('<div/>').addClass('alert alert-warning');
229                    alert.text(Warenkorbhinweis);
230                    listChildItem.find('.alert-wrapper').html(alert);
231                }
232
233                setTimeout(function () {
234                    $('.alert-wrapper').fadeOut();
235                }, highlightDuration);
236            }
237
238            if (couponNotices) {
239                let $drodownbody = $('.cart-dropdown .dropdown-body');
240                $cartOffcanvas.removeClass('loading');
241                $drodownbody.find('.alert').remove();
242                $drodownbody.prepend($alerts);
243            }
244
245            // Screenreader über Warenkorb-Aktualisierung informieren
246            let $statusLive = $('#knm-cart-status');
247            let statusMsg = $statusLive.data('msg-updated') || '';
248            $statusLive.text('');
249            setTimeout(function () {
250                $statusLive.text(statusMsg);
251            }, 100);
252
253            // Offcanvas öffnen wenn entsprechende Einstellung aktiv ist
254            if (isTouchDevice() && $body.hasClass('offcanvas-cart-hidepushedsuccess-mobile') || !isTouchDevice() && $body.hasClass('offcanvas-cart-hidepushedsuccess-desktop')) {
255                knmOpenCanvas($trigger, autoClose);
256            }
257
258            // Aktualisierte Position ins Sichtfeld scrollen
259            let $firstItem = $('.cart-dropdown .item.updated').first();
260            if ($firstItem.length) {
261                var targetElm = $firstItem[0];
262                targetElm.scrollIntoView();
263                setTimeout(function () {
264                    $('.cart-dropdown .item.updated').removeClass('updated');
265                }, highlightDuration);
266            }
267
268            $.evo.trigger('loaded.evo.content', {});
269            $cartOffcanvas.removeClass('loading');
270
271            if ($body.attr('data-page') === '3') {
272                window.location = window.location.href;
273            }
274
275        });
276    };
277
278    basketClassOverride.pushToBasket = function ($main, productId, quantity, data) {
279        let that = this;
280
281        that.toggleState($main, true);
282
283        $.evo.io().call('pushToBasket', [productId, quantity, data], that, function (error, data) {
284
285            that.toggleState($main, false);
286
287            if (error) {
288                return;
289            }
290
291            let response = data.response;
292
293            if (response) {
294                switch (response.nType) {
295                    case 0:
296                        that.error(response);
297                        break;
298                    case 1:
299                        that.redirectTo(response);
300                        break;
301                    case 2:
302                        that.updateCart();
303                        if ((isTouchDevice() === true && !$body.hasClass('offcanvas-cart-hidepushedsuccess-mobile')) || (isTouchDevice() === false && !$body.hasClass('offcanvas-cart-hidepushedsuccess-desktop'))) {
304                            that.pushedToBasket(response);
305                        } else {
306                            $('.product-wrapper.loading').removeClass('loading');
307                        }
308                        break;
309                }
310            }
311        });
312    };
313
314    $.evo.basket = function () {
315        return basketClassOverride;
316    };
317
318    // Artikel aus dem Warenkorb entfernen
319    function knmRemoveFromCart(index) {
320        let context = {};
321        $cartOffcanvas.addClass('loading');
322
323        $.evo.io().call('knmRemoveFromCart', {'index': index}, context, function (error, data) {
324            if (data.state === 'success') {
325                $.evo.basket().updateCart(null, null, null, null, false);
326            }
327        });
328    }
329
330    // Klick-Events im Canvas nicht an übergeordnete Dropdowns weitergeben
331    $(document).on('click', '.bs-canvas .dropdown-body *:not(.btn)', function (event) {
332        event.stopPropagation();
333    });
334
335    // Artikel-Entfernen-Button
336    $(document).on('click', '#cart-offcanvas .btn-cart-remove', function (e) {
337        e.preventDefault();
338        let $item = $(this).closest('.item');
339        let index = $item.find('input[name="index"]').val();
340        $item.fadeOut();
341        knmRemoveFromCart(index);
342    });
343
344    // Mengenänderung im Warenkorb
345    $(document).on('change', '#cart-offcanvas .quantity', function (event) {
346        $cartOffcanvas.addClass('loading');
347        let index = $(this).closest('.item').find('input[name="index"]').val();
348        let qty = $(this).val();
349        let kArtikel = $(this).closest('.item').find('input[name="kArtikel"]').val();
350        let context = {};
351        $.evo.io().call('knmChangeCartQuantity', {
352            'index': index,
353            'qty': qty,
354            'kArtikel': kArtikel
355        }, context, function (error, data) {
356            $.evo.basket().updateCart(2, data.Warenkorbhinweise, data.index, null, false);
357        });
358    });
359
360    // Kupon-Formular
361    $(document).on('submit', '#offcanvas-wrapper #basket-coupon-form', function (e) {
362        e.preventDefault();
363        let $form = $(this);
364        let $cartOffcanvasLocal = $('#offcanvas-wrapper .cart-dropdown');
365        $cartOffcanvasLocal.addClass('loading');
366
367        $.ajax({
368            type: 'POST',
369            url: $form.attr('action'),
370            data: $form.serialize(),
371            async: true,
372            success: function (data) {
373                $dataContent = $(data);
374                $alerts = $dataContent.find('.alert[data-key="couponError"],.alert[data-key="couponSuccess"]');
375                $.evo.basket().updateCart(null, null, null, $alerts, false);
376            }
377        });
378    });
379
380    $('#coupon-form-offcanvas').on('shown.bs.collapse', function (event) {
381        $('input#couponCode').focus();
382    });
383
384    // Pushed-Success auf Detailseite entfernen
385    if (isTouchDevice() && !$body.hasClass('offcanvas-cart-hidepushedsuccess-mobile') || !isTouchDevice() && !$body.hasClass('offcanvas-cart-hidepushedsuccess-desktop')) {
386        if ($body.attr('data-page') == '1') {
387            let $pushSuccessAlert = $('#pushed-success');
388
389            if (isTouchDevice() && closeDelayMobile > 0) {
390                closeTimer = setTimeout(function () {
391                    $pushSuccessAlert.remove();
392                }, closeDelayMobile * 1000);
393
394                $pushSuccessAlert.on('touchstart', function () {
395                    clearTimeout(closeTimer);
396                });
397            } else if (closeDelayDesktop > 0) {
398                closeTimer = setTimeout(function () {
399                    $pushSuccessAlert.remove();
400                }, closeDelayDesktop * 1000);
401
402                $pushSuccessAlert.on('mouseover', function () {
403                    clearTimeout(closeTimer);
404                });
405            }
406        }
407    }
408
409    // Pushed-Success-Modal auf Listenseite entfernen
410    let evoClassOverride = $.evo;
411    $.evo.showNotify = function (options) {
412        eModal.alert({
413            size: 'xl',
414            buttons: false,
415            title: options.title,
416            message: options.text,
417            keyboard: true,
418            tabindex: -1
419        })
420            .then(
421                function () {
422                    $.evo.generateSlickSlider();
423
424                    let $pushSuccessModal = $('#pushed-success').closest('.modal');
425                    if (isTouchDevice() && closeDelayMobile > 0) {
426                        closeTimer = setTimeout(function () {
427                            $pushSuccessModal.modal('hide');
428                        }, closeDelayMobile * 1000);
429
430                        $pushSuccessModal.on('touchstart', function () {
431                            clearTimeout(closeTimer);
432                        });
433                    } else if (closeDelayDesktop > 0) {
434                        closeTimer = setTimeout(function () {
435                            $pushSuccessModal.modal('hide');
436                        }, closeDelayDesktop * 1000);
437
438                        $pushSuccessModal.on('mouseover', function () {
439                            clearTimeout(closeTimer);
440                        });
441                    }
442                }
443            );
444    };
445
446    // Konto-Offcanvas: Fokus-Tracking und Fokus-Verschiebung
447    accountDropdown.on('show.bs.dropdown', function (event) {
448        lastFocusedTrigger = document.activeElement;
449        $body.addClass('show-offcanvas-dropdown');
450        $('#offcanvas-wrapper .account-dropdown').addClass('show');
451        $('#quick-login input').on('keyup mouseup', function (e) {
452            if (e.which == 9) {
453                return false;
454            }
455        });
456    });
457
458    accountDropdown.on('shown.bs.dropdown', function (event) {
459        $('#quick-login input[name="email"]').focus();
460    });
461
462    accountDropdown.on('hide.bs.dropdown', function (event) {
463        $body.removeClass('show-offcanvas-dropdown');
464        $('#offcanvas-wrapper .account-dropdown').removeClass('show');
465        // Fokus zurück auf auslösendes Element
466        if (lastFocusedTrigger && typeof lastFocusedTrigger.focus === 'function') {
467            lastFocusedTrigger.focus();
468            lastFocusedTrigger = null;
469        }
470    });
471
472    // Wunschliste-Offcanvas: Fokus-Tracking und Fokus-Verschiebung
473    wishlistDropdown.on('show.bs.dropdown', function (event) {
474        lastFocusedTrigger = document.activeElement;
475        $body.addClass('show-offcanvas-dropdown');
476        $('#offcanvas-wrapper .wishlist-dropdown').addClass('show');
477    });
478
479    wishlistDropdown.on('shown.bs.dropdown', function (event) {
480        let $focusable = knmGetFocusableElements($('#offcanvas-wrapper .wishlist-dropdown'));
481        if ($focusable.length) {
482            $focusable.first().focus();
483        }
484    });
485
486    wishlistDropdown.on('hide.bs.dropdown', function (event) {
487        $body.removeClass('show-offcanvas-dropdown');
488        $('#offcanvas-wrapper .wishlist-dropdown').removeClass('show');
489        if (lastFocusedTrigger && typeof lastFocusedTrigger.focus === 'function') {
490            lastFocusedTrigger.focus();
491            lastFocusedTrigger = null;
492        }
493    });
494
495    // Vergleichsliste-Offcanvas: Fokus-Tracking und Fokus-Verschiebung
496    compareDropdown.on('show.bs.dropdown', function (event) {
497        lastFocusedTrigger = document.activeElement;
498        $body.addClass('show-offcanvas-dropdown');
499        $('#offcanvas-wrapper .compare-dropdown ').addClass('show');
500    });
501
502    compareDropdown.on('shown.bs.dropdown', function (event) {
503        let $focusable = knmGetFocusableElements($('#offcanvas-wrapper .compare-dropdown'));
504        if ($focusable.length) {
505            $focusable.first().focus();
506        }
507    });
508
509    compareDropdown.on('hide.bs.dropdown', function (event) {
510        $body.removeClass('show-offcanvas-dropdown');
511        $('#offcanvas-wrapper .compare-dropdown').removeClass('show');
512        if (lastFocusedTrigger && typeof lastFocusedTrigger.focus === 'function') {
513            lastFocusedTrigger.focus();
514            lastFocusedTrigger = null;
515        }
516    });
517
518    $cartOffcanvas.on('touchstart mouseover mousemove', function () {
519        clearTimeout(closeTimer);
520    });
521
522});

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.