1/** 2 * off-canvas nav 3 * @name ruffnav 4 * @author Dominik Kressler 5 * @version 2.3.0 6 * @thanks Jasmin Reiff 7 * @edit by Malte Zoudlik 8 * @edit by Dario Rieke 9 * @lastedit 2023-09-04 10 * @copyright (c) 2018 11 */ 12 function Ruffnav(options) { 13 this.options = { 14 toggler: '#navi-toggled', 15 container: '#navigation', 16 navList: '.navi-main', 17 subParent: '.has-sub', 18 subClass: '.sub', 19 backText: false, 20 mobileBreak: 'large', 21 closeOnScroll: true, 22 desktopHover: false, 23 navSplit: false, 24 navAppend: false 25 } 26 this.options = {...this.options, ...options} 27 28 this.depth = 0; 29 this.toggler = document.querySelector(this.options.toggler); 30 this.container = document.querySelector(this.options.container); 31 this.navList = this.container.querySelector(this.options.navList) 32 this.sub = this.container.querySelectorAll(this.options.subClass); 33 this.subParent = this.container.querySelectorAll(this.options.subParent); 34 this.html = document.documentElement; 35 this.device = breakpoint(this.options.mobileBreak) ? 'desktop' : 'mobile'; 36 this.isOpen = false; 37 this.subToggler = null; 38 this.windowSize = window.innerWidth; 39 this.navSize = 0; 40 this.activePath = ''; 41 this.path = window.location.pathname.split('/').filter(function(el) {return el != ''}); 42 43 // set back button text by language, or by overwrite 44 if(this.options.backText === false) 45 { 46 var html = document.querySelector('html'); 47 this.options.backText = (html.lang == 'de') ? 'Zurück' : 'back'; 48 } 49 50 this.backButton = create('li', { 51 'class': 'nav-back', 52 'tabindex': '0' 53 }, '<span>'+this.options.backText+'</span>'); 54 55 var self = this; 56 this.init = function() { 57 if(!self.navList) return console.warn(`No element found with class "${self.options.navList}". Ruffnav could't be initialized. Please add the class to your main navigation UL-Element`); 58 // add class for desktop hover 59 self.container.classList.add('first-level'); 60 if(self.options.desktopHover) self.container.classList.add('hover-navigation'); 61 62 // append custom Content to Navigation 63 if(self.options.navAppend) { 64 var itemToAppend = create('li', {'class': 'nav-item appended-item'}, self.options.navAppend); 65 self.navList.appendChild(itemToAppend); 66 } 67 68 self.navSize = self.navList.childNodes.length; 69 // split navigation in half 70 71 // transform Navigation markup 72 [].forEach.call(self.subParent, function(parentItem) { 73 var childNode = parentItem.firstElementChild, 74 categoryLabel = (childNode.dataset && childNode.dataset.categoryLabel) ? childNode.dataset.categoryLabel : childNode.innerHTML; 75 76 // parent-name attribute for UL's 77 childNode.nextElementSibling.setAttribute('data-parent-name', childNode.innerText.trim()); 78 79 // only clone when Element is a link 80 if(childNode.nodeName !== 'SPAN') { 81 // var cActive = (window.location.origin+window.location.pathname === childNode.href) ? 'active' : ''; 82 83 // copy link inside UL's 84 childNode.nextElementSibling.insertBefore(create('li', { 85 'class': 'nav-item '+childNode.className.trim() 86 }, '<a href="'+childNode.href+'" title="'+childNode.title+'">'+categoryLabel+'</a>'), parentItem.children[1].children[0]); 87 if(self.path.length) { 88 self.activeClassFallback(childNode); 89 } 90 } 91 // replace link's with span's 92 parentItem.replaceChild(create('span', { 93 'class': 'sub-toggler '+childNode.className.trim(), 94 'tabindex': 0, 95 'data-href': childNode.href 96 }, childNode.innerHTML), childNode) 97 }); 98 [].forEach.call(self.sub, function(subItem){ 99 // add backbuttons to UL's 100 subItem.insertBefore(self.backButton.cloneNode(true), subItem.childNodes[0]); 101 }); 102 103 if(self.options.navSplit) { 104 var elm; 105 var splitList = create('ul', {class: "navi split-list"}); 106 for (var i = parseInt(self.navSize / 2); i != splitList.childNodes.length; i) { 107 elm = self.navList.childNodes[i].cloneNode(true);
108 splitList.appendChild(elm) 109 self.navList.childNodes[i].remove(); 110 } 111 self.navList.parentElement.appendChild(splitList); 112 } 113 114 // close on mobile toggle 115 self.toggler.addEventListener('click', self._closeAll); 116 117 // add Listeners for different events 118 self.addListener(); 119 120 // add global resize listener 121 self.listenResize(); 122 123 return self; 124 } 125 this._open = function(e) { 126 // open event 127 var target, sameLevelCurrents; 128 129 if(e.type === 'mouseenter' && self.device === "mobile") return; 130 if(e.type === 'mouseenter') { 131 target = e.target; 132 target.childNodes[1].classList.add('current'); 133 if(self.container.querySelector('.open') != null) self.container.querySelector('.open').classList.remove('open'); 134 target.childNodes[1].classList.add('open'); 135 } else { 136 target = e.currentTarget; 137 138 //close other on same level when already opened 139 sameLevelCurrents = target.parentElement.parentElement.querySelector('.current'); 140 if(sameLevelCurrents !== null) self.closeRecusive(sameLevelCurrents, false) 141 142 target.nextElementSibling.classList.add('current'); 143 if(self.container.querySelector('.open') != null) self.container.querySelector('.open').classList.remove('open'); 144 target.nextElementSibling.classList.add('open'); 145 } 146 if(!self.isOpen && self.device !== 'mobile') { 147 if(self.options.closeOnScroll) window.addEventListener('scroll', self._closeAll); 148 document.addEventListener('click', self._closeAll); 149 document.addEventListener('keydown', self._closeAll); 150 self.isOpen = true; 151 } 152 self.container.classList.remove('first-level'); 153 self.scrollTop(); 154 } 155 this._close = function(e) { 156 // close event 157 var currentListTree; 158 159 if(e.type === 'mouseleave' && self.device === "mobile") return; 160 if(e.type === 'mouseleave') currentListTree = e.currentTarget; 161 else currentListTree = e.currentTarget.parentElement; 162 163 self.closeRecusive(currentListTree, false); 164 if(self.container.querySelectorAll('ul.current').length === 0) self.isOpen = false; 165 if(self.container.querySelectorAll('ul.current').length === 0) self.container.classList.add('first-level'); 166 167 self.scrollTop(); 168 } 169 this._closeAll = function(e) { 170 (self.toggler.checked) ? self.html.classList.add('hidden-scroll') : self.html.classList.remove('hidden-scroll'); 171 // close all 172 if(e.type === 'scroll') { 173 self.closeRecusive(null, true); 174 } else if(e.type === 'click') { 175 // check if click was not inside navigation 176 if(e.target === self.container || self.container.contains(e.target)) return; 177 else self.closeRecusive(null, true) 178 } else if(e.type === 'keydown') { 179 // check if "enter" was pressed 180 if(e.keyCode === 27) self.closeRecusive(null, true); 181 } else { 182 self.closeRecusive(null, true); 183 } 184 self.removeListeners(); 185 self.isOpen = false 186 } 187 this.closeRecusive = function(firstList, closeAll) { 188 // close all open items 189 if(closeAll) { 190 [].forEach.call(self.container.querySelectorAll('.current'), function(openSubs) {openSubs.classList.remove('current')}); 191 if(self.container.querySelector('.open') != null) self.container.querySelector('.open').classList.remove('open'); 192 self.container.classList.add('first-level'); 193 } else { 194 if(self.container.querySelector('.open') != null) self.container.querySelector('.open').classList.remove('open'); 195 [].forEach.call(firstList.querySelectorAll('.current'), function(openSubs){ 196 openSubs.classList.remove('current'); 197 }); 198 firstList.parentNode.parentNode.classList.add('open'); 199 firstList.classList.remove('current'); 200 } 201 } 202 this.removeListeners = function() { 203 // remove eventListener for global close 204 window.removeEventListener('scroll', self._closeAll); 205 document.removeEventListener('keydown', self._closeAll); 206 document.removeEventListener('click', self._closeAll); 207 } 208 this.addListener = function() { 209 // add eventListener to subToggle and back 210 self.subToggler = self.container.querySelectorAll('.sub-toggler'); 211 self.backButtons = self.container.querySelectorAll('.nav-back'); 212 [].forEach.call(self.subToggler, function(subTogglerItem){ 213 if(self.options.desktopHover) { 214 subTogglerItem.parentNode.addEventListener('mouseenter', self._open); 215 subTogglerItem.parentNode.addEventListener('mouseleave', self._close); 216 } 217 subTogglerItem.addEventListener('click', self._open); 218 }); 219 [].forEach.call(self.backButtons, function(backButtonsItem){ 220 backButtonsItem.addEventListener('click', self._close, false); 221 }); 222 } 223 this.listenResize = function() { 224 // global resize Event 225 window.onresize = function(){ 226 self.device = breakpoint(self.options.mobileBreak) ? 'desktop' : 'mobile'; 227 if(self.device === 'mobile') self.removeListeners(); 228 if(self.windowSize != window.innerWidth && self.device === 'desktop') { 229 self.closeRecusive(null, true); 230 self.isOpen = false;
231 } 232 self.windowSize = window.innerWidth; 233 } 234 } 235 236 this.scrollTop = function() { 237 [].forEach.call(self.sub, function(item) {item.scrollTop = 0} ) 238 self.container.childNodes[0].scrollTop = 0; 239 } 240 241 function create(type, attr, content) { 242 // create Node element 243 var elm = document.createElement(type); 244 [].forEach.call(Object.entries(attr), function(name){ 245 elm.setAttribute(name[0], name[1]); 246 }); 247 if(content) elm.innerHTML = content; 248 return elm 249 } 250 251 this.activeClassFallback = function(elem) { 252 if(elem.href === window.location.origin + '/' + this.activePath + this.path[0]+'/') { 253 elem.classList.add('active'); 254 if(elem.parentNode.classList.contains(self.options.subParent.substring(1))) elem.parentNode.classList.add('active') 255 if(this.path.length === 1) elem.parentNode.querySelector('[href="'+window.location.origin+'/' + this.activePath + this.path[0]+'/"]').classList.add('active'); 256 this.activePath += this.path[0] + '/'; 257 this.path.splice(0,1); 258 } 259 } 260 261 this.init(); 262} 263if (!Object.entries) 264Object.entries = function( obj ){ 265 var ownProps = Object.keys( obj ), 266 i = ownProps.length, 267 resArray = new Array(i); // preallocate the Array 268 while (i--) 269 resArray[i] = [ownProps[i], obj[ownProps[i]]]; 270 271 return resArray; 272}; 273if (!('remove' in Element.prototype)) { 274 Element.prototype.remove = function() { 275 if (this.parentNode) this.parentNode.removeChild(this); 276 }; 277}
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.