PageSourceSearch

https://www.kirchberg.ch/public/templates/3/514-BsDropdownController.js

js kirchberg.ch collected 2026-10-02 20:23:57 UTC 8,931 bytes, 207 lines download raw bytes

1function BsDropdownController(dropdownItems, config) {
2    this.config = {
3        toolbarBackground: '.mobilenav__bg',
4        typeConfig: [
5            {type: 'mobilenav', hasBackground: true, skiplinkToModify: 'a[href="#mainnav"]'},
6            {type: 'search', hasBackground: true, skiplinkToModify: 'a[href="#search-input"]'},
7            {type: 'dropdown', hasBackground: false }
8        ]
9    };
10    this.dropdownItems = [];
11    this.backgroundVisible = false;
12    this.backgroundAdded = false;
13
14    // überschreibt Config, wenn mitgegeben
15    // initialisiert BsDropdownController-Instanz
16    this.init = function (dropdownItems, config) {
17        if (config) {
18            for (let prop in config) {
19                this.config[prop] = config[prop];
20            }
21        }
22        if (dropdownItems) {
23            // pusht mitgegebene items in dropdownitems
24            for (let i = 0; i < dropdownItems.length; i++) {
25                const item = dropdownItems[i];
26                // fügt typeConfig dem Item hinzu
27                for (let i = 0; i < this.config.typeConfig.length; i++) {
28                    if (this.config.typeConfig[i].type === item.type) {
29                        item.typeConfig = this.config.typeConfig[i];
30                    }
31                }
32                this.dropdownItems.push(item);
33            }
34            this.addDOMElements();
35            this.addEventHandler();
36            this.modifySkiplinks();
37        }
38    };
39    // fügt Background hinzu, falls gewünscht
40    // fügt click-handler an background
41    this.addDOMElements = function () {
42        for (let i = 0; i < this.dropdownItems.length; i++) {
43            const item = this.dropdownItems[i];
44            if (item.typeConfig.hasBackground && !this.backgroundAdded) {
45                $('body').append('<div class="' + this.config.toolbarBackground.slice(1) + '"></div>');
46                this.backgroundAdded = true;
47                $(this.config.toolbarBackground).on('click', function () {
48                    this.hideAllDropdowns();
49                }.bind(this));
50            }
51        }
52    };
53    // fügt event-handler den Buttons hinzu
54    this.addEventHandler = function () {
55        for (let i = 0; i < this.dropdownItems.length; i++) {
56            const item = this.dropdownItems[i];
57            const index = i;
58            $(item.btn).on('click', function () {
59                this.toggleContent(index);
60            }.bind(this));
61        }
62    };
63    // Skiplink für Navigation modifizieren, wenn Mobilenav sichtbar ist
64    // Skiplink für Suche modifizieren, wenn die Suche ausgeblendet wird
65    this.modifySkiplinks = function () {
66        let mobileNavItem;
67        let searchItem;
68        for (let i = 0; i < this.dropdownItems.length; i++) {
69            const type = this.dropdownItems[i].type;
70            if (type === 'mobilenav') {
71                mobileNavItem = this.dropdownItems[i];
72            }
73            if (type === 'search') {
74                searchItem = this.dropdownItems[i];
75            }
76        }
77        if (mobileNavItem) {
78            $(mobileNavItem.typeConfig.skiplinkToModify).on('click', function (e) {
79                if (this.isElementVisible(mobileNavItem.btn)) {
80                    e.preventDefault();
81                    $(mobileNavItem.btn).click();
82                    $(mobileNavItem.content).find('li:first-child>a').focus();
83                    return false;
84                }
85            }.bind(this));
86        }
87        if (searchItem) {
88            $(searchItem.typeConfig.skiplinkToModify).on('click', function (e) {
89                if (!this.isElementVisible(searchItem.content)) {
90                    $(searchItem.btn).click();
91                }
92            }.bind(this));
93        }
94    };
95    // aktualisiert view gemäss state der items
96    // setzt event-handlers für Tab (beim letzten Element wieder auf den Button)
97    // setzt event-handlers für Esc (schliessen des Contents)
98    // setzt event-handler für focusout, wenn kein Background da ist (schliessen des Contents)
99    this.updateView = function () {
100        for (let i = 0; i < this.dropdownItems.length; i++) {
101            const item = this.dropdownItems[i];
102            const index = i;
103            if (item.visible) {
104                $(item.content).slideDown(200);
105                $(item.btn).addClass('active').attr('aria-expanded', 'true');
106                $(item.btn + ', ' + item.content).on('keydown', function (e) {
107                    if (e.key === 'Escape') {
108                        this.hideAllDropdowns();
109                        $(this.dropdownItems[index].btn).focus();
110                        return false;
111                    }
112                    if (!e.shiftKey && e.key === 'Tab') {
113                        if (e.target === this.getLastFocusableElement(this.dropdownItems[index].content)) {
114                            e.preventDefault();
115                            $(this.dropdownItems[index].btn).focus();
116                            return false;
117                        }
118                    }
119                    if (e.shiftKey && e.key === 'Tab') {
120                        if ($(e.target).hasClass(this.dropdownItems[index].btn.substring(1)) && $(this.dropdownItems[index].content).is(':visible')) {
121                            e.preventDefault();
122                            this.getLastFocusableElement(this.dropdownItems[index].content).focus();
123                            return false;
124                        }
125                    }
126                }.bind(this));
127                if (!item.typeConfig.hasBackground) {
128                    $(item.btn + ', ' + item.content).focusout(function (e) {
129                        const content = document.querySelector(item.content);
130                        const btn = document.querySelector(item.btn);
131                        if ($.contains(content, e.relatedTarget) || (btn === e.relatedTarget)) {
132                            return;
133                        }
134                        this.hideAllDropdowns();
135                    }.bind(this));
136                }
137                if (item.typeConfig.type === 'search') {
138                    $(item.content).find('input[type=search]').focus();
139                }
140            } else {
141                $(item.content).slideUp(200);
142                $(item.btn).removeClass('active').attr('aria-expanded', 'false');
143            }
144        }
145    };
146    // toggles den Content
147    this.toggleContent = function (index) {
148        const notClickedItems = this.getIndexArrayOfNotClickedItems(index);
149        for (let i = 0; i < notClickedItems.length; i++) {
150            this.setVisibleState(notClickedItems[i], false);
151        }
152        this.toggleVisibleState(index);
153        this.toggleBackground();
154        this.updateView();
155    };
156    // gibt ein array aller Indexe, ausser dem Index des geklickten Items zurück
157    this.getIndexArrayOfNotClickedItems = function (clickedItemIndex) {
158        let notClickedItems = [];
159        for (let i = 0; i < this.dropdownItems.length; i++) {
160            if (i !== clickedItemIndex) {
161                notClickedItems.push(i);
162            }
163        }
164        return notClickedItems;
165    };
166    // setzt alle states auf visible: false, view wird aktualisiert
167    this.hideAllDropdowns = function () {
168        for (let i = 0; i < this.dropdownItems.length; i++) {
169            this.setVisibleState(i, false);
170        }
171        this.updateView();
172        this.backgroundVisible = false;
173        this.toggleBackground();
174    };
175    // gibt true/false zurück, ob das Element sichtbar ist
176    this.isElementVisible = function (element) {
177        return $(element).is(':visible');
178    };
179    // setzt State für Item
180    this.setVisibleState = function (index, state) {
181        this.dropdownItems[index].visible = state;
182    };
183    // toggles State und Background-State
184    this.toggleVisibleState = function (index) {
185        this.dropdownItems[index].visible = !this.dropdownItems[index].visible;
186        if (this.dropdownItems[index].typeConfig.hasBackground) {
187            this.backgroundVisible = this.dropdownItems[index].visible;
188        }
189    };
190    // zeigt/versteckt den Hintergrund, je nachdem was in der Config der Stand ist
191    this.toggleBackground = function () {
192        if (this.backgroundVisible) {
193            $(this.config.toolbarBackground).fadeIn(200);
194        } else {
195            $(this.config.toolbarBackground).fadeOut(200);
196        }
197    };
198    // gibt das letzte fokusierbare Element zurück
199    this.getLastFocusableElement = function (containerClass) {
200        const containerElement = document.querySelector(containerClass);
201        const focusableElements = containerElement.querySelectorAll(
202            'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'
203        );
204        return focusableElements[focusableElements.length - 1];
205    };
206    this.init(dropdownItems, config);
207}

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.