PageSourceSearch

https://rechtsanwalt-richard-schmitt-stiftung.de/_/scripts/rgElements/src/Elements/rgNav/rg-nav.js

js rechtsanwalt-richard-schmitt-stiftung.de collected 2026-10-08 08:23:14 UTC 20,682 bytes, 459 lines download raw bytes

1import {rgElement} from '../rgElement/rg-element.js';
2
3const setup = function(nav) {
4        if(!this.offcanvasNav) {
5            let config = this.defaultConfig;
6            for(const [key, value] of Object.entries(this.defaultConfig)) {
7                let attribute = this.getAttribute('data-' + key);
8                if(attribute) {
9                    config[key] = this.parseAttribute(attribute);
10                }
11            }
12            this.toggleButton.addEventListener('click', function() {
13                let evt = new KeyboardEvent('keydown', {
14                    keyCode: 13
15                });
16                this.toggleButton.dispatchEvent(evt);
17            }.bind(this));
18            config.customToggle = this.toggleButton;
19            if(this.mode === 'megamenu') {
20                config.closeOnClick = false;
21            }
22            this.offcanvasNav = new hcOffcanvasNav(this.mode === 'dropdown' ? this.firstElementChild : this.wrapper.firstElementChild, config);
23            if(this.mode === 'offcanvas') {
24                this.offcanvasNav.setAttribute('inert', '');
25            }
26            this.templates.forEach((tpl) => {
27                let target = document.querySelector(tpl.dataset.target),
28                    dynamic = this.parseAttribute(tpl.dataset.dynamic);
29                if(target) {
30                    const tag = document.createElement('div'),
31                        operation = tpl.dataset.operation || 'prepend';
32                    tpl.dataset.wrapperclass && tag.classList.add(tpl.dataset.wrapperclass);
33                    tag.append(tpl.content);
34                    switch(operation) {
35                        case 'append':
36                            target.append(tag);
37                            break;
38                        case 'prepend':
39                        default:
40                            target.prepend(tag);
41                            break;
42                    }
43                    if(dynamic) {
44                        this.offcanvasNav.on('open.level', moveNode.bind(tpl, target, tag));
45                        this.offcanvasNav.on('close.level', moveNode.bind(tpl, target, tag));
46                    }
47                }
48            });
49            if(config.closeOpenLevels === false) {
50                try {
51                    let currentLink = this.offcanvasNav.querySelector('a[href^="' + document.location.pathname.split('/').filter(p => {
52                            return !p.match('.html')
53                        }).join('/') + '"]')?.closest('*[data-index]'),
54                        dataLevel = Number(currentLink?.getAttribute('data-level')),
55                        dataIndex = Number(currentLink?.getAttribute('data-index'));
56                    if(dataLevel) {
57                        //some external stylesheets may be animating things... give them some time
58                        window.setTimeout(function() {
59                            //if found... open it
60                            this.offcanvasNav.style.display = 'none';
61                            this.offcanvasNav.open(dataLevel, dataIndex);
62                            this.offcanvasNav.close();
63                            this.offcanvasNav.style.display = '';
64                        }.bind(this), 100);
65                    }
66                } catch(ex) {
67                }
68            }
69            this.offcanvasNav.on('open', () => {
70                if(this.mode === 'offcanvas') {
71                    this.offcanvasNav.removeAttribute('inert')
72                }
73                if(config.closeOpenLevels === false) {
74                    this.offcanvasNav.querySelector('a[href="' + document.location.pathname + '"], a[href="' + document.location.pathname + 'index.html"]')?.focus()
75                } else {
76                    this.offcanvasNav.querySelector('ul a')?.focus()
77                }
78                document.dispatchEvent(new CustomEvent('rgnav:open', {
79                    detail: {el: this}
80                }));
81            });
82            this.offcanvasNav.on('close', () => {
83                if(this.mode === 'offcanvas') {
84                    this.offcanvasNav.setAttribute('inert', '')
85                }
86            });
87            document.querySelectorAll('a[data-containeronly]').forEach($element => {
88                $element.addEventListener('click', $event => {
89                    $event.preventDefault();
90                    let currentLink = $element.closest('li').querySelector('*[data-index]'),
91                        dataLevel = Number(currentLink?.getAttribute('data-level')),
92                        dataIndex = Number(currentLink?.getAttribute('data-index'));
93                    if(dataLevel) {
94                        //some external stylesheets may be animating things... give them some time
95                        window.setTimeout(function() {
96                            //if found... open it
97                            this.offcanvasNav.open(dataLevel, dataIndex);
98                        }.bind(this), 100);
99                    }
100                })
101            })
102            document.dispatchEvent(new CustomEvent('rgnav:setupComplete', {
103                detail: {el: this}
104            }))
105        }
106    },
107    moveNode = function(navEl, elemtag, ev, n) {
108        let panel = navEl.querySelector('div[data-level="' + ev.data.currentLevel + '"][data-index="' + ev.data.currentIndex + '"]'),
109            newTarget = panel ? panel.querySelector(this.dataset.target) : navEl;
110        switch(this.dataset.operation) {
111            case 'append':
112                newTarget.append(elemtag);
113                break;
114            case 'prepend':
115                newTarget.prepend(elemtag);
116                break;
117        }
118    },
119    buildMegamenu = function() {
120        let $megamenu = document.createElement('nav');
121        $megamenu.classList.add('megamenu');
122        $megamenu.setAttribute('role', 'navigation');
123        const $menuDataUl = this.querySelector('ul');
124        $megamenu.append(buildSubmenu.call(this, $menuDataUl, 0));
125        return $megamenu;
126    },
127    buildSubmenu = function($menuDataUl, level, $overviewLink = null) {
128        const $ul = document.createElement('ul');
129        $ul.classList.add('megamenu--level' + level);
130        if(level > 0) {
131            $ul.classList.add('megamenu__submenu');
132            $ul.setAttribute('role', 'menu');
133            $ul.setAttribute('aria-hidden', 'true');
134        } else {
135            $ul.classList.add('megamenu__mainmenu');
136            $ul.setAttribute('role', 'menubar');
137        }
138        $ul.setAttribute('data-level', level);
139        if($overviewLink) {
140            const $li = document.createElement('li');
141            $li.setAttribute('role', 'menuitem');
142            const $element = $overviewLink.cloneNode(true);
143            $element.classList.add('megamenu__entry');
144            $element.innerHTML = 'Übersicht';
145            $li.append($element);
146            $ul.append($li);
147        }
148        Array.from($menuDataUl.children).forEach($menuDataLi => {
149            let $nextOverviewLink = null;
150            const $li = document.createElement('li');
151            $li.setAttribute('role', 'menuitem');
152            let hasSubmenu = ($menuDataLi.querySelector(':scope > ul') !== null);
153            Array.from($menuDataLi.children).forEach($menuDataElement => {
154                if($menuDataElement.tagName !== 'UL') {
155                    let $element;
156                    if(hasSubmenu) {
157                        if(!$menuDataElement.dataset.containeronly) {
158                            $nextOverviewLink = $menuDataElement;
159                        }
160                        $element = document.createElement('span');
161                        $element.innerHTML = $menuDataElement.innerHTML;
162                    } else {
163                        $element = $menuDataElement.cloneNode(true);
164                    }
165                    $element.setAttribute('tabindex', 0);
166                    $element.classList.add('megamenu__entry');
167                    if($element.tagName === 'SPAN') {
168                        $li.setAttribute('aria-haspopup', 'menu');
169                        $li.setAttribute('aria-expanded', 'false')
170                    }
171                    $li.append($element);
172                } else {
173                    $li.classList.add('megamenu--has_submenu');
174                    hasSubmenu = true;
175                    const $arrow = document.createElement('i');
176                    $arrow.classList.add('megamenu__arrow');
177                    $arrow.classList.add('fas');
178                    $arrow.setAttribute('tabindex', 0);
179                    $li.append($arrow);
180                    const $submenu = buildSubmenu.call(this, $menuDataElement, level + 1, $nextOverviewLink);
181                    if(level === 0 && $menuDataLi.dataset.extracontentid) {
182                        const $extracontent_template = this.querySelector('.megamenu-extracontent-template[data-extracontentid="' + $menuDataLi.dataset.extracontentid + '"]');
183                        if($extracontent_template) {
184                            const $extracontent_li = document.createElement('li');
185                            $extracontent_li.classList.add('megamenu__extracontent-container');
186                            const extracontent_copy = $extracontent_template.cloneNode(true);
187                            extracontent_copy.classList.remove('megamenu-extracontent-template');
188                            $extracontent_li.append(extracontent_copy);
189                            $ul.append($extracontent_li);
190                            $submenu.append($extracontent_li);
191                        }
192                    }
193                    $li.append($submenu)
194                }
195            });
196            if(hasSubmenu) {
197                $li.addEventListener('click', event => megamenuClickhandler.call(this, event));
198                $li.addEventListener('keydown', event => {
199                    if(event.code === 'Enter') {
200                        megamenuClickhandler.call(this, event);
201                    }
202                    if(event.code === 'Escape') {
203                        document.querySelectorAll('.megamenu--level0 > li.megamenu--open').forEach($element => {
204                            $element.dispatchEvent(new PointerEvent('pointerdown', {
205                                view: window, bubbles: false, cancelable: true
206                            }))
207                        })
208                    }
209                });
210            }
211            $ul.append($li);
212        });
213        return $ul;
214    },
215    megamenuClickhandler = function(event) {
216        if(event.type === 'pointerdown' && event.pointerType === 'mouse' && event.buttons !== 1) {
217            return;
218        }
219        if(event.target.tagName === "A") {
220            return;
221        }
222        const $target = event.currentTarget;
223        let mode;
224        const $submenu = $target.querySelector('ul');
225        if($target.classList.contains('megamenu--open')) {
226            mode = 'closing';
227            megamenuCloseSubmenu.call(this, $target);
228            if($target.parentNode.classList.contains('megamenu--level0')) {
229                document.querySelectorAll('.megamenu__overlay').forEach($overlay 
229=> $overlay.remove());
230            }
231        } else {
232            mode = 'opening';
233            const $siblings = $target && $target.parentNode && [...$target.parentNode.children].filter(node => node !== $target && node.classList.contains('megamenu--open'));
234            $siblings.forEach($element => {
235                megamenuCloseSubmenu.call(this, $element);
236            })
237            $target.classList.add('megamenu--open');
238            $target.setAttribute('aria-expanded', 'true');
239            $submenu.setAttribute('aria-hidden', 'false');
240            if($target.parentNode.classList.contains('megamenu--level0')) {
241                document.querySelectorAll('.megamenu__overlay').forEach($overlay => $overlay.remove())
242                const $overlay = document.createElement('div');
243                $overlay.classList.add('megamenu__overlay');
244                $target.append($overlay);
245            }
246        }
247        if(!this.megamenuSubmenuSizes.has($submenu)) {
248            this.megamenuSubmenuSizes.set($submenu, $submenu.offsetHeight);
249            $submenu.classList.add('megamenu--expand-submenu');
250        }
251        const megamenuPadding = parseInt(getComputedStyle($target).getPropertyValue('--megamenu-padding'));
252        const $extracontentContainer = $target.parentNode.classList.contains('megamenu--level0') ? $target.querySelector('.megamenu__extracontent-container') : $target.closest('.megamenu--level1')?.querySelector('.megamenu__extracontent-container');
253        let extracontentContainerHeight = '';
254        if($extracontentContainer) {
255            extracontentContainerHeight = $extracontentContainer.offsetHeight;
256        }
257        if(!$target.parentNode.classList.contains('megamenu--level0')) {
258            if(mode === 'opening') {
259                this.megamenuOpenSubmenus.set($submenu, $submenu);
260            }
261            let maxSize = extracontentContainerHeight;
262            this.megamenuOpenSubmenus.forEach($submenu => {
263                if(this.megamenuSubmenuSizes.get($submenu) > maxSize) {
264                    maxSize = this.megamenuSubmenuSizes.get($submenu);
265                }
266            })
267            if($target.closest('.megamenu--level1').offsetHeight < maxSize + 2 * megamenuPadding) {
268                $target.closest('.megamenu--level1').style.minHeight = (maxSize + 2 * megamenuPadding) + 'px';
269            } else {
270                window.setTimeout(() => {
271                    $target.closest('.megamenu--level1').style.minHeight = (maxSize + 2 * megamenuPadding) + 'px';
272                }, 300)
273            }
274        } else {
275            $target.querySelector('.megamenu--level1').style.minHeight = (extracontentContainerHeight + 2 * megamenuPadding) + 'px';
276        }
277        event.cancelBubble = true;
278    },
279    megamenuCloseSubmenu = function($target) {
280        $target.classList.remove('megamenu--open');
281        $target.querySelector('ul').setAttribute('aria-hidden', 'true');
282        $target.setAttribute('aria-expanded', 'false');
283        this.megamenuOpenSubmenus.delete($target.querySelector('ul'));
284        $target.querySelectorAll('.megamenu--open').forEach($element => {
285            $element.classList.remove('megamenu--open');
286            $element.setAttribute('aria-expanded', 'false')
287            $element.querySelector('ul').setAttribute('aria-hidden', 'true');
288            this.megamenuOpenSubmenus.delete($element.querySelector('ul'));
289        })
290    },
291    connected = function() {
292        if(!this.querySelectorAll('ul').length) {
293            return;
294        }
295        let adoptedCssCode = this.getStyles('.rg-nav', '.rg-nav-wrapper');
296        const fontColor = adoptedCssCode.match(/color:(.*?);/)
297        if(fontColor) {
298            adoptedCssCode = adoptedCssCode + ' .hc-nav-trigger {--nav-trigger-color: ' + fontColor[1] + '}';
299        }
300        const adoptedCSS = document.createElement('style');
301        adoptedCSS.innerHTML = adoptedCssCode;
302        /**
303         * search for custom templates to append or prepend to navigation-html (see "setup")
304         */
305        this.templates = this.querySelectorAll('template');
306        /**
307         * create elements and load required files
308         */
309        const offcanvasCss = document.createElement('link');
310        offcanvasCss.setAttribute('rel', 'stylesheet');
311        offcanvasCss.setAttribute('href', 'https://cdn.regiogate.net/hc-offcanvas-nav/hc-offcanvas-nav.css');
312        let customCss = '';
313        if(this.dataset.customcss) {
314            customCss = document.createElement('link');
315            customCss.setAttribute('rel', 'stylesheet');
316            customCss.setAttribute('href', this.dataset.customcss);
317        }
318        if(this.dataset.mode) {
319            this.mode = this.dataset.mode;
320        } else {
321            this.mode = 'offcanvas';
322        }
323        const linkElem = document.createElement('link');
324        linkElem.setAttribute('rel', 'stylesheet');
325        linkElem.setAttribute('href', this.rootpath + '/css/rg-nav.css');
326        const offCanvasScript = document.createElement('script');
327        this.wrapper = document.createElement('div');
328        const $slot = document.createElement('slot')
329        this.wrapper.append($slot);
330        this.wrapper.setAttribute('class', 'rg-nav-wrapper');
331        this.classList.forEach(classname => {
332            if(classname !== 'rg-nav') {
333                this.wrapper.classList.add(classname);
334            }
335        })
336        if(this.dataset.customtoggle !== undefined) {
337            this.toggleButton = document.querySelector(this.dataset.customtoggle)
338        } else {
339            this.toggleButton = document.createElement('a');
340            this.toggleButton.setAttribute('href', '#');
341            this.toggleButton.setAttribute('class', 'hc-nav-trigger fas fa-bars');
342            this.toggleButton.append(document.createElement('span'));
343        }
344        document.head.append(offcanvasCss, customCss);
345        switch(this.mode) {
346            case 'dropdown':
347                //nav remains in light dom, slotted in. Should become default, but needs css adaptations...
348                break;
349            case 'megamenu':
350                this.wrapper.innerHTML = this.innerHTML + this.wrapper.innerHTML;
351                const $megamenu = buildMegamenu.call(this);
352                this.innerHTML = '';
353                this.append($megamenu);
354                break;
355            default:
356                this.wrapper.innerHTML = this.innerHTML + this.wrapper.innerHTML;
357                this.innerHTML = '';
358        }
359        if(this.dataset.customtoggle === undefined) {
360            this.wrapper.append(this.toggleButton);
361        }
362        this.shadowRoot.append(linkElem, adoptedCSS, offCanvasScript, this.wrapper);
363        /**
364         * as offcanvas-script is loaded run setup
365         */
366        offCanvasScript.onload = function() {
367            setup.call(this);
368            this.style.display = '';
369            if(this.toggleButton.querySelector('.menu-button__icon')) {
370                this.toggleButton.classList.remove('hc-nav-trigger');
371            }
372        }.bind(this);
373        offCanvasScript.src = 'https://cdn.regiogate.net/hc-offcanvas-nav/hc-offcanvas-nav.js';
374        this.setupComplete();
375    }
376
377/*
378 * exports
379 */
380export class rgNav extends rgElement {
381    defaultConfig = {
382        width: 280,
383        height: 'auto',
384        disableAt: false,
385        pushContent: null,
386        swipeGestures: true,
387        expanded: false,
388        position: 'right', // left, right, top, botto,
389        levelOpen: 'overlap', // overlap, expand, none/fals,
390        levelSpacing: 40,
391        levelTitles: true,
392        closeOpenLevels: false,
393        closeActiveLevel: false,
394        navTitle: null,
395        navClass: '',
396        disableBody: true,
397        closeOnClick: true,
398        closeOnEsc: true,
399        activeToggleClass: null,
400        bodyInsert: 'prepend', // prepend/appen,
401        keepClasses: true,
402        removeOriginalNav: false,
403        rtl: false,
404        insertClose: true,
405        insertBack: true,
406        levelTitleAsBack: true,
407        labelClose: '',
408        labelBack: 'Back',
409    };
410    toggleButton = null;
411    wrapper = null;
412    templates = [];
413    mode = "default";
414    megamenuSubmenuSizes = new Map();
415    megamenuOpenSubmenus = new Map();
416
417    constructor() {
418        super();
419    }
420
421    connectedCallback() {
422        this.setUpElement();
423        this.offcanvasNav = null;
424        !this.shadowRoot && this.attachShadow({mode: 'open'});
425        this.style.display = 'none';
426        if(document.readyState === 'complete') {
427            connected.call(this);
428        } else {
429            window.addEventListener('load', connected.bind(this))
430        }
431        document.addEventListener('rgsearch:show', this.hideNav.bind(this));
432    }
433
434    showNav() {
435        this.offcanvasNav && this.offcanvasNav.open();
436        document.dispatchEvent(new CustomEvent('rgnav:open', {
437            detail: {el: this}
438        }));
439    }
440
441    hideNav() {
442        this.offcanvasNav && this.offcanvasNav.close();
443    }
444
445    toggleNav() {
446        if(this.offcanvasNav) {
447            this.offcanvasNav.toggle();
448            if(this.offcanvasNav.isOpen()) {
449                document.dispatchEvent(new CustomEvent('rgnav:open', {
450                    detail: {el: this}
451                }));
452            }
453        }
454    }
455
456    isOpen() {
457        return this.offcanvasNav ? this.offcanvasNav.isOpen() : false;
458    }
459}

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.