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