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.